changeset 29218:a78a44f0cdcd

merge of '6a184b4a50c778257ab194f0339b91beda1f97c8' and 'd6d5f18ff695ad46c11336cf3301aa955b41d5bb'
author John Bailey <rekkanoryo@rekkanoryo.org>
date Sat, 31 Oct 2009 17:49:11 +0000
parents 7819d3819f4c (diff) fd2afe1b2d3c (current diff)
children e3206d6da6c7
files ChangeLog pidgin/gtkutils.c
diffstat 27 files changed, 378 insertions(+), 56 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Thu Oct 15 10:45:22 2009 +0000
+++ b/ChangeLog	Sat Oct 31 17:49:11 2009 +0000
@@ -13,15 +13,20 @@
 	* Moved the Translator information from the About box to a "Translator
 	  Information" dialog accessible on the Help menu.
 
-version 2.6.3 (??/??/20??):
+version 2.6.4 (??/??/20??):
+	libpurple:
+	* Actually emit the hold signal for media calls.
+
 	General:
 	* New 'plugins' sub-command to 'debug' command (i.e. '/debug plugins')
 	  to announce the list of loaded plugins (in both Finch and Pidgin).
-	* Fix a crash when performing DNS queries on Unixes that use the
-	  blocking DNS lookups.  (Brian Lu)
 	* Fix building the GnuTLS plugin with older versions of GnuTLS.
 	* Fix DNS TXT query resolution.
 
+	MSN:
+	* Don't forget display names for buddies.
+	* Fix a random crash that might occur when idle.
+
 	XMPP:
 	* Users connecting to Google Talk now have an "Initiate Chat" context menu
 	  option for their buddies.  (Eion Robb)
@@ -46,6 +51,17 @@
 	Pidgin:
 	* The userlist in a multiuser chat can be styled via gtkrc by using the
 	  widget name "pidgin_conv_userlist". (Heiko Schmitt)
+	* Add a hold button to the media window.
+
+version 2.6.3 (10/16/2009):
+	General:
+	* Fix a crash when performing DNS queries on Unixes that use the
+	  blocking DNS lookups.  (Brian Lu)
+
+	AIM and ICQ:
+	* Fix a crash when some clients send contacts in a format we don't
+	  understand.
+	* Fix blocking and other privacy lists.  (Thanks to AOL)
 
 version 2.6.2 (09/05/2009):
 	libpurple:
--- a/ChangeLog.API	Thu Oct 15 10:45:22 2009 +0000
+++ b/ChangeLog.API	Sat Oct 31 17:49:11 2009 +0000
@@ -5,7 +5,11 @@
 		Added:
 		* purple_account_get_name_for_display
 		* purple_network_get_all_local_system_ips
+		* purple_prpl_got_media_caps
 		* purple_uuid_random
+		* media_caps to the PurpleBuddy struct
+		* buddy-caps-changed blist signal
+		* ui-caps-changed media manager signal
 	
 	Pidgin:
 		Added:
@@ -13,6 +17,9 @@
 		* pidgin_dialogs_developers (should not be used by anything but Pidgin)
 		* pidgin_dialogs_translators (should not be used by anything but Pidgin)
 
+version 2.6.3 (10/16/2009):
+	No changes
+
 version 2.6.2 (09/05/2009):
 	Perl:
 		Added:
--- a/ChangeLog.win32	Thu Oct 15 10:45:22 2009 +0000
+++ b/ChangeLog.win32	Sat Oct 31 17:49:11 2009 +0000
@@ -2,6 +2,9 @@
 	* Minimum required GTK+ version increased to 2.14.0
 	* Win9x no longer supported.
 
+version 2.6.3 (10/16/2009):
+	* No changes
+
 version 2.6.2 (09/05/2009):
 	* No changes
 
--- a/NEWS	Thu Oct 15 10:45:22 2009 +0000
+++ b/NEWS	Sat Oct 31 17:49:11 2009 +0000
@@ -2,6 +2,11 @@
 
 Our development blog is available at: http://planet.pidgin.im
 
+2.6.3 (10/16/2009):
+	Mark: Someone reported a fairly serious bug in our AIM/ICQ code
+	so we're releasing a special "severe bug fix only" build.  See the
+	ChangeLog for details.  Enjoy!
+
 2.6.2 (09/05/2009):
 	Mark: Woo boy it's been a busy two weeks.  There was a lot of new code
 	in 2.6.0, and with new code comes new bugs.  The cadre of relentless
--- a/libpurple/blist.c	Thu Oct 15 10:45:22 2009 +0000
+++ b/libpurple/blist.c	Sat Oct 31 17:49:11 2009 +0000
@@ -3187,6 +3187,13 @@
 										PURPLE_SUBTYPE_BLIST_NODE),
 						 purple_value_new(PURPLE_TYPE_STRING));
 
+	purple_signal_register(handle, "buddy-caps-changed",
+			purple_marshal_VOID__POINTER_INT_INT, NULL,
+			3, purple_value_new(PURPLE_TYPE_SUBTYPE,
+				PURPLE_SUBTYPE_BLIST_BUDDY),
+			purple_value_new(PURPLE_TYPE_INT),
+			purple_value_new(PURPLE_TYPE_INT));
+
 	purple_signal_connect(purple_accounts_get_handle(), "account-created",
 			handle,
 			PURPLE_CALLBACK(purple_blist_buddies_cache_add_account),
--- a/libpurple/blist.h	Thu Oct 15 10:45:22 2009 +0000
+++ b/libpurple/blist.h	Sat Oct 31 17:49:11 2009 +0000
@@ -108,6 +108,7 @@
 
 #include "account.h"
 #include "buddyicon.h"
+#include "media.h"
 #include "status.h"
 
 /**************************************************************************/
@@ -143,6 +144,7 @@
 	PurpleBuddyIcon *icon;                    /**< The buddy icon. */
 	PurpleAccount *account;					/**< the account this buddy belongs to */
 	PurplePresence *presence;
+	PurpleMediaCaps media_caps;		/**< The media capabilities of the buddy. */
 };
 
 /**
--- a/libpurple/certificate.c	Thu Oct 15 10:45:22 2009 +0000
+++ b/libpurple/certificate.c	Sat Oct 31 17:49:11 2009 +0000
@@ -1402,13 +1402,15 @@
 		if (flags & PURPLE_CERTIFICATE_NAME_MISMATCH) {
 			gchar *sn = purple_certificate_get_subject_name(peer_crt);
 
-			g_string_append_printf(errors, _("The certificate claims to be "
-						"from \"%s\" instead. This could mean that you are "
-						"not connecting to the service you believe you are."),
-						sn);
-			g_free(sn);
+			if (sn) {
+				g_string_append_printf(errors, _("The certificate claims to be "
+							"from \"%s\" instead. This could mean that you are "
+							"not connecting to the service you believe you are."),
+							sn);
+				g_free(sn);
 
-			flags &= ~PURPLE_CERTIFICATE_NAME_MISMATCH;
+				flags &= ~PURPLE_CERTIFICATE_NAME_MISMATCH;
+			}
 		}
 
 		while (i != PURPLE_CERTIFICATE_LAST) {
--- a/libpurple/ft.h	Thu Oct 15 10:45:22 2009 +0000
+++ b/libpurple/ft.h	Sat Oct 31 17:49:11 2009 +0000
@@ -674,7 +674,7 @@
 void purple_xfer_ui_ready(PurpleXfer *xfer);
 
 /**
- * Allows the prpl to signal it's readh to send/receive data (depending on
+ * Allows the prpl to signal it's ready to send/receive data (depending on
  * the direction of the file transfer. Used when the prpl provides read/write
  * ops and cannot/does not provide a raw fd to the core.
  *
--- a/libpurple/marshallers.list	Thu Oct 15 10:45:22 2009 +0000
+++ b/libpurple/marshallers.list	Sat Oct 31 17:49:11 2009 +0000
@@ -4,3 +4,4 @@
 VOID:STRING,STRING,DOUBLE
 VOID:ENUM,STRING,STRING
 VOID:ENUM,STRING,STRING,BOOLEAN
+VOID:FLAGS,FLAGS
--- a/libpurple/media.c	Thu Oct 15 10:45:22 2009 +0000
+++ b/libpurple/media.c	Sat Oct 31 17:49:11 2009 +0000
@@ -103,6 +103,8 @@
 	gboolean initiator;
 	gboolean accepted;
 	gboolean candidates_prepared;
+	gboolean held;
+	gboolean paused;
 
 	GList *active_local_candidates;
 	GList *active_remote_candidates;
@@ -281,7 +283,7 @@
 			{ PURPLE_MEDIA_INFO_HOLD,
 					"PURPLE_MEDIA_INFO_HOLD", "hold" },
 			{ PURPLE_MEDIA_INFO_UNHOLD,
-					"PURPLE_MEDIA_INFO_HOLD", "unhold" },
+					"PURPLE_MEDIA_INFO_UNHOLD", "unhold" },
 			{ 0, NULL, NULL }
 		};
 		type = g_enum_register_static("PurpleMediaInfoType", values);
@@ -289,6 +291,40 @@
 	return type;
 }
 
+GType
+purple_media_caps_get_type()
+{
+	static GType type = 0;
+	if (type == 0) {
+		static const GEnumValue values[] = {
+			{ PURPLE_MEDIA_CAPS_NONE,
+					"PURPLE_MEDIA_CAPS_NONE", "none" },
+			{ PURPLE_MEDIA_CAPS_AUDIO,
+					"PURPLE_MEDIA_CAPS_AUDIO", "audio" },
+			{ PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION,
+					"PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION",
+					"audio-single-direction" },
+			{ PURPLE_MEDIA_CAPS_VIDEO,
+					"PURPLE_MEDIA_CAPS_VIDEO", "video" },
+			{ PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION,
+					"PURPLE_MEDIA_CAPS_VIDEO_SINGLE_DIRECTION",
+					"video-single-direction" },
+			{ PURPLE_MEDIA_CAPS_AUDIO_VIDEO,
+					"PURPLE_MEDIA_CAPS_AUDIO_VIDEO",
+					"audio-video" },
+			{ PURPLE_MEDIA_CAPS_MODIFY_SESSION,
+					"PURPLE_MEDIA_CAPS_MODIFY_SESSION",
+					"modify-session" },
+			{ PURPLE_MEDIA_CAPS_CHANGE_DIRECTION,
+					"PURPLE_MEDIA_CAPS_CHANGE_DIRECTION",
+					"change-direction" },
+			{ 0, NULL, NULL }
+		};
+		type = g_enum_register_static("PurpleMediaCaps", values);
+	}
+	return type;
+}
+
 #ifdef USE_VV
 static void
 purple_media_class_init (PurpleMediaClass *klass)
@@ -2330,11 +2366,46 @@
 		for (; streams; streams = g_list_delete_link(streams, streams)) {
 			PurpleMediaStream *stream = streams->data;
 			if (stream->session->type & PURPLE_MEDIA_SEND_VIDEO) {
+				stream->paused = active;
+
+				if (!stream->held)
+					g_object_set(stream->stream, "direction",
+							purple_media_to_fs_stream_direction(
+							stream->session->type & ((active) ?
+							~PURPLE_MEDIA_SEND_VIDEO :
+							PURPLE_MEDIA_VIDEO)), NULL);
+			}
+		}
+	} else if (local == TRUE && (type == PURPLE_MEDIA_INFO_HOLD ||
+			type == PURPLE_MEDIA_INFO_UNHOLD)) {
+		GList *streams;
+		gboolean active = (type == PURPLE_MEDIA_INFO_HOLD);
+
+		g_return_if_fail(PURPLE_IS_MEDIA(media));
+
+		streams = purple_media_get_streams(media,
+				session_id, participant);
+		for (; streams; streams = g_list_delete_link(streams, streams)) {
+			PurpleMediaStream *stream = streams->data;
+			stream->held = active;
+			if (stream->session->type & PURPLE_MEDIA_VIDEO) {
+				FsStreamDirection direction;
+
+				direction = ((active) ?
+						~PURPLE_MEDIA_VIDEO :
+						PURPLE_MEDIA_VIDEO);
+				if (!active && stream->paused)
+					direction &= ~PURPLE_MEDIA_SEND_VIDEO;
+
+				g_object_set(stream->stream, "direction",
+						purple_media_to_fs_stream_direction(
+						stream->session->type & direction), NULL);
+			} else if (stream->session->type & PURPLE_MEDIA_AUDIO) {
 				g_object_set(stream->stream, "direction",
 						purple_media_to_fs_stream_direction(
 						stream->session->type & ((active) ?
-						~PURPLE_MEDIA_SEND_VIDEO :
-						PURPLE_MEDIA_VIDEO)), NULL);
+						~PURPLE_MEDIA_AUDIO :
+						PURPLE_MEDIA_AUDIO)), NULL);
 			}
 		}
 	}
--- a/libpurple/media.h	Thu Oct 15 10:45:22 2009 +0000
+++ b/libpurple/media.h	Sat Oct 31 17:49:11 2009 +0000
@@ -58,6 +58,7 @@
 #define PURPLE_TYPE_MEDIA_NETWORK_PROTOCOL (purple_media_network_protocol_get_type())
 #define PURPLE_MEDIA_TYPE_STATE      (purple_media_state_changed_get_type())
 #define PURPLE_MEDIA_TYPE_INFO_TYPE	(purple_media_info_type_get_type())
+#define PURPLE_MEDIA_TYPE_CAPS	     (purple_media_caps_get_type())
 
 /** An opaque structure representing a media call. */
 typedef struct _PurpleMedia PurpleMedia;
@@ -190,6 +191,15 @@
 GType purple_media_info_type_get_type(void);
 
 /**
+ * Gets the type of the media caps flags
+ *
+ * @return The media caps flags' GType
+ *
+ * @since 2.7.0
+ */
+GType purple_media_caps_get_type(void);
+
+/**
  * Gets the type of the media candidate structure.
  *
  * @return The media canditate's GType
--- a/libpurple/mediamanager.c	Thu Oct 15 10:45:22 2009 +0000
+++ b/libpurple/mediamanager.c	Sat Oct 31 17:49:11 2009 +0000
@@ -100,6 +100,7 @@
 
 enum {
 	INIT_MEDIA,
+	UI_CAPS_CHANGED,
 	LAST_SIGNAL
 };
 static guint purple_media_manager_signals[LAST_SIGNAL] = {0};
@@ -148,6 +149,15 @@
 		purple_smarshal_BOOLEAN__OBJECT_POINTER_STRING,
 		G_TYPE_BOOLEAN, 3, PURPLE_TYPE_MEDIA,
 		G_TYPE_POINTER, G_TYPE_STRING);
+
+	purple_media_manager_signals[UI_CAPS_CHANGED] = g_signal_new ("ui-caps-changed",
+		G_TYPE_FROM_CLASS (klass),
+		G_SIGNAL_RUN_LAST,
+		0, NULL, NULL,
+		purple_smarshal_VOID__FLAGS_FLAGS,
+		G_TYPE_NONE, 2, PURPLE_MEDIA_TYPE_CAPS,
+		PURPLE_MEDIA_TYPE_CAPS);
+
 	g_type_class_add_private(klass, sizeof(PurpleMediaManagerPrivate));
 }
 
@@ -894,8 +904,17 @@
 		PurpleMediaCaps caps)
 {
 #ifdef USE_VV
+	PurpleMediaCaps oldcaps;
+
 	g_return_if_fail(PURPLE_IS_MEDIA_MANAGER(manager));
+
+	oldcaps = manager->priv->ui_caps;
 	manager->priv->ui_caps = caps;
+
+	if (caps != oldcaps)
+		g_signal_emit(manager,
+				purple_media_manager_signals[UI_CAPS_CHANGED],
+				0, caps, oldcaps);
 #endif
 }
 
--- a/libpurple/protocols/jabber/chat.c	Thu Oct 15 10:45:22 2009 +0000
+++ b/libpurple/protocols/jabber/chat.c	Sat Oct 31 17:49:11 2009 +0000
@@ -106,7 +106,7 @@
 	{
 		char *room_jid = g_strdup_printf("%s@%s", room, server);
 
-		chat = g_hash_table_lookup(js->chats, jabber_normalize(NULL, room_jid));
+		chat = g_hash_table_lookup(js->chats, room_jid);
 		g_free(room_jid);
 	}
 
@@ -177,10 +177,21 @@
 		xmlnode_insert_data(body, msg, -1);
 	} else {
 		xmlnode_set_attrib(message, "to", name);
+		/*
+		 * Putting the reason into the body was an 'undocumented protocol,
+		 * ...not part of "groupchat 1.0"'.
+		 * http://xmpp.org/extensions/attic/jep-0045-1.16.html#invite
+		 *
+		 * Left here for compatibility.
+		 */
 		body = xmlnode_new_child(message, "body");
 		xmlnode_insert_data(body, msg, -1);
+
 		x = xmlnode_new_child(message, "x");
 		xmlnode_set_attrib(x, "jid", room_jid);
+
+		/* The better place for it! XEP-0249 style. */
+		xmlnode_set_attrib(x, "reason", msg);
 		xmlnode_set_namespace(x, "jabber:x:conference");
 	}
 
@@ -216,7 +227,8 @@
 	JabberChat *chat;
 	char *jid;
 
-	g_return_val_if_fail(jabber_chat_find(js, room, server) == NULL, NULL);
+	if (jabber_chat_find(js, room, server) != NULL)
+		return NULL;
 
 	chat = g_new0(JabberChat, 1);
 	chat->js = js;
@@ -264,7 +276,8 @@
 	char *jid;
 
 	chat = jabber_chat_new(js, room, server, handle, password, data);
-	g_return_val_if_fail(chat != NULL, NULL);
+	if (chat == NULL)
+		return NULL;
 
 	gc = js->gc;
 	account = purple_connection_get_account(gc);
@@ -371,7 +384,7 @@
 	JabberStream *js = chat->js;
 	char *room_jid = g_strdup_printf("%s@%s", chat->room, chat->server);
 
-	g_hash_table_remove(js->chats, jabber_normalize(NULL, room_jid));
+	g_hash_table_remove(js->chats, room_jid);
 	g_free(room_jid);
 }
 
--- a/libpurple/protocols/jabber/chat.h	Thu Oct 15 10:45:22 2009 +0000
+++ b/libpurple/protocols/jabber/chat.h	Sat Oct 31 17:49:11 2009 +0000
@@ -62,6 +62,8 @@
  * in-prpl function for joining a chat room. Doesn't require sticking goop
  * into a hash table.
  *
+ * @param room     The room to join. This MUST be normalized already.
+ * @param server   The server the room is on. This MUST be normalized already.
  * @param password The password (if required) to join the room. May be NULL.
  * @param data     The chat hash table.  May be NULL (it will be generated
  *                 for current core<>prpl API interface.)
--- a/libpurple/protocols/jabber/jabber.c	Thu Oct 15 10:45:22 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Sat Oct 31 17:49:11 2009 +0000
@@ -3239,7 +3239,7 @@
 	id = purple_cmd_register("part", "s", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
 	                  PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-jabber",
-	                  jabber_cmd_chat_part, _("part [room]:  Leave the room."),
+	                  jabber_cmd_chat_part, _("part [message]:  Leave the room."),
 	                  NULL);
 	jabber_cmds = g_slist_prepend(jabber_cmds, GUINT_TO_POINTER(id));
 
@@ -3453,6 +3453,9 @@
 	jabber_add_feature(JINGLE_APP_RTP_SUPPORT_VIDEO, jabber_video_enabled);
 	jabber_add_feature(JINGLE_TRANSPORT_RAWUDP, 0);
 	jabber_add_feature(JINGLE_TRANSPORT_ICEUDP, 0);
+
+	g_signal_connect(G_OBJECT(purple_media_manager_get()), "ui-caps-changed",
+			G_CALLBACK(jabber_caps_broadcast_change), NULL);
 #endif
 
 	/* IPC functions */
--- a/libpurple/protocols/jabber/message.c	Thu Oct 15 10:45:22 2009 +0000
+++ b/libpurple/protocols/jabber/message.c	Sat Oct 31 17:49:11 2009 +0000
@@ -758,9 +758,22 @@
 					jm->type != JABBER_MESSAGE_ERROR) {
 				const char *jid = xmlnode_get_attrib(child, "jid");
 				if(jid) {
+					const char *reason = xmlnode_get_attrib(child, "reason");
+					const char *password = xmlnode_get_attrib(child, "password");
+
 					jm->type = JABBER_MESSAGE_GROUPCHAT_INVITE;
 					g_free(jm->to);
 					jm->to = g_strdup(jid);
+
+					if (reason) {
+						g_free(jm->body);
+						jm->body = g_strdup(reason);
+					}
+
+					if (password) {
+						g_free(jm->password);
+						jm->password = g_strdup(password);
+					}
 				}
 			} else if(!strcmp(xmlns, "http://jabber.org/protocol/muc#user") &&
 					jm->type != JABBER_MESSAGE_ERROR) {
@@ -775,8 +788,10 @@
 						g_free(jm->body);
 						jm->body = xmlnode_get_data(reason);
 					}
-					if((password = xmlnode_get_child(child, "password")))
+					if((password = xmlnode_get_child(child, "password"))) {
+						g_free(jm->password);
 						jm->password = xmlnode_get_data(password);
+					}
 
 					jm->type = JABBER_MESSAGE_GROUPCHAT_INVITE;
 				}
--- a/libpurple/protocols/jabber/presence.c	Thu Oct 15 10:45:22 2009 +0000
+++ b/libpurple/protocols/jabber/presence.c	Sat Oct 31 17:49:11 2009 +0000
@@ -458,6 +458,10 @@
 	jbr->caps.info = info;
 	jbr->caps.exts = exts;
 
+	purple_prpl_got_media_caps(
+			purple_connection_get_account(userdata->js->gc),
+			userdata->from);
+
 	if (info == NULL)
 		goto out;
 
@@ -476,7 +480,7 @@
 	/*
 	 * Versions of libpurple before 2.6.0 didn't advertise this capability, so
 	 * we can't yet use Entity Capabilities to determine whether or not the
-	 * other client supports Entity Capabilities.
+	 * other client supports Chat States.
 	 */
 	if (jabber_resource_has_capability(jbr, "http://jabber.org/protocol/chatstates"))
 		jbr->chat_states = JABBER_CHAT_STATES_SUPPORTED;
--- a/libpurple/protocols/oscar/oscar.c	Thu Oct 15 10:45:22 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Sat Oct 31 17:49:11 2009 +0000
@@ -2874,25 +2874,46 @@
 			gchar **text;
 			text = g_strsplit(args->msg, "\376", 0);
 			if (text) {
-				num = 0;
-				for (i=0; i<strlen(text[0]); i++)
-					num = num*10 + text[0][i]-48;
-				for (i=0; i<num; i++) {
-					struct name_data *data = g_new(struct name_data, 1);
-					gchar *message = g_strdup_printf(_("ICQ user %u has sent you a buddy: %s (%s)"), args->uin, text[i*2+2], text[i*2+1]);
-					data->gc = gc;
-					data->name = g_strdup(text[i*2+1]);
-					data->nick = g_strdup(text[i*2+2]);
-
-					purple_request_action(gc, NULL, message,
-										_("Do you want to add this buddy "
-										  "to your buddy list?"),
-										PURPLE_DEFAULT_ACTION_NONE,
-										purple_connection_get_account(gc), data->name, NULL,
-										data, 2,
-										_("_Add"), G_CALLBACK(purple_icq_buddyadd),
-										_("_Decline"), G_CALLBACK(oscar_free_name_data));
-					g_free(message);
+				/* Read the number of contacts that we were sent */
+				errno = 0;
+				num = strtoul(text[0], NULL, 10);
+
+				if (num > 0 && errno == 0) {
+					for (i=0; i<num; i++) {
+						struct name_data *data;
+						gchar *message;
+
+						if (!text[i*2 + 1] || !text[i*2 + 2]) {
+							/* We're missing the contact name or nickname.  Bail out. */
+							gchar *tmp = g_strescape(args->msg, NULL);
+							purple_debug_error("oscar", "Unknown syntax parsing "
+									"ICQ buddies.  args->msg=%s\n", tmp);
+							g_free(tmp);
+							break;
+						}
+
+						message = g_strdup_printf(_("ICQ user %u has sent you a buddy: %s (%s)"), args->uin, text[i*2+2], text[i*2+1]);
+
+						data = g_new(struct name_data, 1);
+						data->gc = gc;
+						data->name = g_strdup(text[i*2+1]);
+						data->nick = g_strdup(text[i*2+2]);
+
+						purple_request_action(gc, NULL, message,
+								_("Do you want to add this buddy "
+								  "to your buddy list?"),
+								PURPLE_DEFAULT_ACTION_NONE,
+								purple_connection_get_account(gc), data->name, NULL,
+								data, 2,
+								_("_Add"), G_CALLBACK(purple_icq_buddyadd),
+								_("_Decline"), G_CALLBACK(oscar_free_name_data));
+						g_free(message);
+					}
+				} else {
+					gchar *tmp = g_strescape(args->msg, NULL);
+					purple_debug_error("oscar", "Unknown syntax parsing "
+							"ICQ buddies.  args->msg=%s\n", tmp);
+					g_free(tmp);
 				}
 				g_strfreev(text);
 			}
@@ -3909,12 +3930,8 @@
 	od->rights.maxpermits = (guint)maxpermits;
 	od->rights.maxdenies = (guint)maxdenies;
 
-	purple_connection_set_state(gc, PURPLE_CONNECTED);
-
 	purple_debug_info("oscar", "buddy list loaded\n");
 
-	aim_srv_clientready(od, conn);
-
 	if (purple_account_get_user_info(account) != NULL)
 		serv_set_info(gc, purple_account_get_user_info(account));
 
@@ -3957,6 +3974,22 @@
 	aim_srv_requestnew(od, SNAC_FAMILY_ALERT);
 	aim_srv_requestnew(od, SNAC_FAMILY_CHATNAV);
 
+	od->bos.have_rights = TRUE;
+
+	/*
+	 * If we've already received our feedbag data then we're not waiting on
+	 * anything else, so send the server clientready.
+	 *
+	 * Normally we get bos rights before we get our feedbag data, so this
+	 * rarely (never?) happens.  And I'm not sure it actually matters if we
+	 * wait for bos rights before calling clientready.  But it seems safer
+	 * to do it this way.
+	 */
+	if (od->ssi.received_data) {
+		aim_srv_clientready(od, conn);
+		purple_connection_set_state(gc, PURPLE_CONNECTED);
+	}
+
 	return 1;
 }
 
@@ -5396,6 +5429,15 @@
 	oscar_set_icon(gc, img);
 	purple_imgstore_unref(img);
 
+	/*
+	 * If we've already received our bos rights then we're not waiting on
+	 * anything else, so send the server clientready.
+	 */
+	if (od->bos.have_rights) {
+		aim_srv_clientready(od, conn);
+		purple_connection_set_state(gc, PURPLE_CONNECTED);
+	}
+
 	return 1;
 }
 
--- a/libpurple/protocols/oscar/oscar.h	Thu Oct 15 10:45:22 2009 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Sat Oct 31 17:49:11 2009 +0000
@@ -535,6 +535,10 @@
 		struct aim_userinfo_s *userinfo;
 	} locate;
 
+	struct {
+		gboolean have_rights;
+	} bos;
+
 	/* Server-stored information (ssi) */
 	struct {
 		gboolean received_data;
--- a/libpurple/prpl.c	Thu Oct 15 10:45:22 2009 +0000
+++ b/libpurple/prpl.c	Sat Oct 31 17:49:11 2009 +0000
@@ -288,8 +288,10 @@
 
 	/* The buddy is no longer online, they are therefore by definition not
 	 * still typing to us. */
-	if (!purple_status_is_online(status))
+	if (!purple_status_is_online(status)) {
 		serv_got_typing_stopped(purple_account_get_connection(account), name);
+		purple_prpl_got_media_caps(account, name);
+	}
 }
 
 void purple_prpl_got_user_status_deactive(PurpleAccount *account, const char *name,
@@ -560,6 +562,35 @@
 	return PURPLE_MEDIA_CAPS_NONE;
 }
 
+void
+purple_prpl_got_media_caps(PurpleAccount *account, const char *name)
+{
+#ifdef USE_VV
+	GSList *list;
+
+	g_return_if_fail(account != NULL);
+	g_return_if_fail(name    != NULL);
+
+	if ((list = purple_find_buddies(account, name)) == NULL)
+		return;
+
+	while (list) {
+		PurpleBuddy *buddy = list->data;
+		PurpleMediaCaps oldcaps = buddy->media_caps;
+		const gchar *bname = purple_buddy_get_name(buddy);
+		list = g_slist_delete_link(list, list);
+		buddy->media_caps = purple_prpl_get_media_caps(account, bname);
+
+		if (oldcaps == buddy->media_caps)
+			continue;
+
+		purple_signal_emit(purple_blist_get_handle(),
+				"buddy-caps-changed", buddy,
+				buddy->media_caps, oldcaps);
+	}
+#endif
+}
+
 /**************************************************************************
  * Protocol Plugin Subsystem API
  **************************************************************************/
--- a/libpurple/prpl.h	Thu Oct 15 10:45:22 2009 +0000
+++ b/libpurple/prpl.h	Sat Oct 31 17:49:11 2009 +0000
@@ -815,6 +815,17 @@
 					const char *who,
 					PurpleMediaSessionType type);
 
+/**
+ * Signals that the prpl received capabilities for the given contact.
+ *
+ * This function is intended to be used only by prpls.
+ *
+ * @param account The account the user is on.
+ * @param who The name of the contact for which capabilities have been received.
+ * @since 2.7.0
+ */
+void purple_prpl_got_media_caps(PurpleAccount *account, const char *who);
+
 /*@}*/
 
 /**************************************************************************/
--- a/libpurple/tests/test_jabber_jutil.c	Thu Oct 15 10:45:22 2009 +0000
+++ b/libpurple/tests/test_jabber_jutil.c	Sat Oct 31 17:49:11 2009 +0000
@@ -134,6 +134,14 @@
 	assert_invalid_jid("paul@2[::1]124/as");
 	assert_invalid_jid("paul@まつ.おおかみ/\x01");
 
+	/*
+	 * RFC 3454 Section 6 reads, in part,
+	 * "If a string contains any RandALCat character, the
+	 *  string MUST NOT contain any LCat character."
+	 * The character is U+066D (ARABIC FIVE POINTED STAR).
+	 */
+	assert_invalid_jid("foo@example.com/٭simplexe٭");
+
 	/* Ensure that jabber_id_new is properly lowercasing node and domains */
 	assert_jid_parts("paul", "darkrain42.org", "PaUL@darkrain42.org");
 	assert_jid_parts("paul", "darkrain42.org", "paul@DaRkRaIn42.org");
--- a/pidgin/gtkmedia.c	Thu Oct 15 10:45:22 2009 +0000
+++ b/pidgin/gtkmedia.c	Sat Oct 31 17:49:11 2009 +0000
@@ -89,6 +89,7 @@
 	GtkWidget *menubar;
 	GtkWidget *statusbar;
 
+	GtkWidget *hold;
 	GtkWidget *mute;
 	GtkWidget *pause;
 
@@ -187,6 +188,15 @@
 }
 
 static void
+pidgin_media_hold_toggled(GtkToggleButton *toggle, PidginMedia *media)
+{
+	purple_media_stream_info(media->priv->media,
+			gtk_toggle_button_get_active(toggle) ?
+			PURPLE_MEDIA_INFO_HOLD : PURPLE_MEDIA_INFO_UNHOLD,
+			NULL, NULL, TRUE);
+}
+
+static void
 pidgin_media_mute_toggled(GtkToggleButton *toggle, PidginMedia *media)
 {
 	purple_media_stream_info(media->priv->media,
@@ -633,6 +643,16 @@
 				FALSE, FALSE, 0);
 		gtk_widget_show(GTK_WIDGET(button_widget));
 		gtk_widget_show(send_widget);
+
+		/* Hold button */
+		gtkmedia->priv->hold =
+				gtk_toggle_button_new_with_mnemonic("_Hold");
+		g_signal_connect(gtkmedia->priv->hold, "toggled",
+				G_CALLBACK(pidgin_media_hold_toggled),
+				gtkmedia);
+		gtk_box_pack_end(GTK_BOX(button_widget), gtkmedia->priv->hold,
+				FALSE, FALSE, 0);
+		gtk_widget_show(gtkmedia->priv->hold);
 	} else {
 		send_widget = gtkmedia->priv->send_widget;
 		button_widget = gtkmedia->priv->button_widget;
--- a/pidgin/gtkroomlist.c	Thu Oct 15 10:45:22 2009 +0000
+++ b/pidgin/gtkroomlist.c	Sat Oct 31 17:49:11 2009 +0000
@@ -111,7 +111,18 @@
 static void dialog_select_account_cb(GObject *w, PurpleAccount *account,
 				     PidginRoomlistDialog *dialog)
 {
+	gboolean change = (account != dialog->account);
 	dialog->account = account;
+
+	if (change && dialog->roomlist) {
+		PidginRoomlist *rl = dialog->roomlist->ui_data;
+		if (rl->tree) {
+			gtk_widget_destroy(rl->tree);
+			rl->tree = NULL;
+		}
+		purple_roomlist_unref(dialog->roomlist);
+		dialog->roomlist = NULL;
+	}
 }
 
 static void list_button_cb(GtkButton *button, PidginRoomlistDialog *dialog)
--- a/pidgin/gtkutils.c	Thu Oct 15 10:45:22 2009 +0000
+++ b/pidgin/gtkutils.c	Sat Oct 31 17:49:11 2009 +0000
@@ -75,7 +75,7 @@
 } AopMenu;
 
 static guint accels_save_timer = 0;
-static GList *gnome_url_handlers = NULL;
+static GSList *registered_url_handlers = NULL;
 
 static gboolean
 url_clicked_idle_cb(gpointer data)
@@ -3395,7 +3395,7 @@
 				start += sizeof("/desktop/gnome/url-handlers/") - 1;
 
 				protocol = g_strdup_printf("%s:", start);
-				gnome_url_handlers = g_list_prepend(gnome_url_handlers, protocol);
+				registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol);
 				gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu);
 			}
 			start = c + 1;
@@ -3403,7 +3403,7 @@
 	}
 	g_free(tmp);
 
-	return (gnome_url_handlers != NULL);
+	return (registered_url_handlers != NULL);
 }
 
 #ifdef _WIN32
@@ -3415,16 +3415,18 @@
 
 	do {
 		DWORD nameSize = 256;
-		char protocol[256];
+		char start[256];
 		/* I don't think we need to worry about non-ASCII protocol names */
-		ret = RegEnumKeyExA(HKEY_CLASSES_ROOT, idx++, protocol, &nameSize,
+		ret = RegEnumKeyExA(HKEY_CLASSES_ROOT, idx++, start, &nameSize,
 							NULL, NULL, NULL, NULL);
 		if (ret == ERROR_SUCCESS) {
 			HKEY reg_key = NULL;
-			ret = RegOpenKeyExA(HKEY_CLASSES_ROOT, protocol, 0, KEY_READ, &reg_key);
+			ret = RegOpenKeyExA(HKEY_CLASSES_ROOT, start, 0, KEY_READ, &reg_key);
 			if (ret == ERROR_SUCCESS) {
 				ret = RegQueryValueExA(reg_key, "URL Protocol", NULL, NULL, NULL, NULL);
 				if (ret == ERROR_SUCCESS) {
+					gchar *protocol = g_strdup_printf("%s:", start);
+					registered_url_handlers = g_slist_prepend(registered_url_handlers, protocol);
 					/* We still pass everything to the "http" "open" handler for security reasons */
 					gtk_imhtml_class_register_protocol(protocol, url_clicked_cb, link_context_menu);
 				}
@@ -3469,16 +3471,16 @@
 	gtk_imhtml_class_register_protocol("open://", NULL, NULL);
 
 	/* If we have GNOME handlers registered, unregister them. */
-	if (gnome_url_handlers)
+	if (registered_url_handlers)
 	{
-		GList *l;
-		for (l = gnome_url_handlers ; l ; l = l->next)
+		GSList *l;
+		for (l = registered_url_handlers; l; l = l->next)
 		{
 			gtk_imhtml_class_register_protocol((char *)l->data, NULL, NULL);
 			g_free(l->data);
 		}
-		g_list_free(gnome_url_handlers);
-		gnome_url_handlers = NULL;
+		g_slist_free(registered_url_handlers);
+		registered_url_handlers = NULL;
 		return;
 	}
 
--- a/pidgin/plugins/disco/gtkdisco.c	Thu Oct 15 10:45:22 2009 +0000
+++ b/pidgin/plugins/disco/gtkdisco.c	Sat Oct 31 17:49:11 2009 +0000
@@ -141,8 +141,18 @@
 static void dialog_select_account_cb(GObject *w, PurpleAccount *account,
                                      PidginDiscoDialog *dialog)
 {
+	gboolean change = (account != dialog->account);
 	dialog->account = account;
 	gtk_widget_set_sensitive(dialog->browse_button, account != NULL);
+
+	if (change && dialog->discolist) {
+		if (dialog->discolist->tree) {
+			gtk_widget_destroy(dialog->discolist->tree);
+			dialog->discolist->tree = NULL;
+		}
+		pidgin_disco_list_unref(dialog->discolist);
+		dialog->discolist = NULL;
+	}
 }
 
 static void register_button_cb(GtkWidget *unused, PidginDiscoDialog *dialog)
--- a/po/ChangeLog	Thu Oct 15 10:45:22 2009 +0000
+++ b/po/ChangeLog	Sat Oct 31 17:49:11 2009 +0000
@@ -2,8 +2,11 @@
 
 version 2.7.0
 
+version 2.6.4
+	* Vietnamese translation updated (Clytie Siddall)
+
 version 2.6.3
-	* Vietnamese translation updated (Clytie Siddall)
+	* No changes
 
 version 2.6.2
 	* Afrikaans translation updated (Friedel Wolff)