changeset 31732:7c320e4c8d8c

propagate from branch 'im.pidgin.pidgin' (head 240af8944798738f4310d3fcc8f2bc9ff33c2797) to branch 'im.pidgin.pidgin.mxit' (head d4d924811e4a96c620a632e02edaaaddd8b60820)
author andrew.victor@mxit.com
date Tue, 07 Dec 2010 16:58:46 +0000
parents 0bbe0d78db27 (diff) c7f0decf419b (current diff)
children ed057c543e9e
files ChangeLog
diffstat 24 files changed, 489 insertions(+), 195 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Tue Nov 30 07:07:42 2010 +0000
+++ b/ChangeLog	Tue Dec 07 16:58:46 2010 +0000
@@ -10,11 +10,17 @@
 	  of smileys, display the text representation of the smiley properly
 	  when it contains HTML-escapable characters (e.g. "<3" was previously
 	  displayed as "&lt;3").
+	* Drop dependency on GdkGC and use Cairo instead.
+	* New UI hack to assist in first-time setup of Facebook accounts.
 
 	libpurple:
 	* Fix multipart parsing when '=' is included in the boundary for
 	  purple_mime_document_parse. (Jakub Adam) (#11598)
 
+	AIM and ICQ:
+	* Buddies who unset their status message will now be correctly shown
+	  without a message in your buddy list. (#12988)
+
 	Gadu-Gadu:
 	* Updated our bundled libgadu and minimum requirement for external
 	  libgadu to 1.9.0. (#12789)
@@ -24,6 +30,13 @@
 	  disconnected.
 	* Allow full-size display names, by not escaping (most) non-English
 	  characters. (#8508)
+	* Fix receiving messages from users on Yahoo and other federated
+	  services. (#13022)
+	* Correctly remove old endpoints from the list when they sign out.
+	* Add option to disable connections from multiple locations. (#13017)
+
+	XMPP:
+	* Terminate Jingle sessions with unsupported content types. (#13048)
 
 version 2.7.7 (11/23/2010):
 	General:
--- a/libpurple/protocols/jabber/jingle/jingle.c	Tue Nov 30 07:07:42 2010 +0000
+++ b/libpurple/protocols/jabber/jingle/jingle.c	Tue Dec 07 16:58:46 2010 +0000
@@ -98,7 +98,8 @@
 		if (pending_content == NULL) {
 			purple_debug_error("jingle",
 					"Error parsing \"content-add\" content.\n");
-			/* XXX: send error here */
+			jabber_iq_send(jingle_session_terminate_packet(session,
+				"unsupported-applications"));
 		} else {
 			jingle_session_add_pending_content(session,
 					pending_content);
@@ -127,7 +128,8 @@
 			g_free(local_senders);
 		} else {
 			purple_debug_error("jingle", "content_modify: unknown content\n");
-			/* XXX: send error */
+			jabber_iq_send(jingle_session_terminate_packet(session,
+				"unknown-applications"));
 		}
 	}
 }
@@ -176,7 +178,8 @@
 				jingle_session_find_content(session, name, creator);
 		if (parsed_content == NULL) {
 			purple_debug_error("jingle", "Error parsing content\n");
-			/* XXX: send error */
+			jabber_iq_send(jingle_session_terminate_packet(session,
+				"unsupported-applications"));
 		} else {
 			jingle_content_handle_action(parsed_content, content,
 					JINGLE_DESCRIPTION_INFO);
@@ -206,7 +209,8 @@
 				jingle_session_find_content(session, name, creator);
 		if (parsed_content == NULL) {
 			purple_debug_error("jingle", "Error parsing content\n");
-			/* XXX: send error */
+			jabber_iq_send(jingle_session_terminate_packet(session,
+				"unsupported-applications"));
 		} else {
 			jingle_content_handle_action(parsed_content, content,
 					JINGLE_SESSION_ACCEPT);
@@ -230,7 +234,8 @@
 		JingleContent *parsed_content = jingle_content_parse(content);
 		if (parsed_content == NULL) {
 			purple_debug_error("jingle", "Error parsing content\n");
-			/* XXX: send error */
+			jabber_iq_send(jingle_session_terminate_packet(session,
+				"unsupported-applications"));
 		} else {
 			jingle_session_add_content(session, parsed_content);
 			jingle_content_handle_action(parsed_content, content,
@@ -281,7 +286,8 @@
 				jingle_session_find_content(session, name, creator);
 		if (parsed_content == NULL) {
 			purple_debug_error("jingle", "Error parsing content\n");
-			/* XXX: send error */
+			jabber_iq_send(jingle_session_terminate_packet(session,
+				"unsupported-applications"));
 		} else {
 			jingle_content_handle_action(parsed_content, content,
 					JINGLE_TRANSPORT_INFO);
--- a/libpurple/protocols/msn/contact.c	Tue Nov 30 07:07:42 2010 +0000
+++ b/libpurple/protocols/msn/contact.c	Tue Dec 07 16:58:46 2010 +0000
@@ -41,7 +41,8 @@
 	"ContactSave",
 	"MessengerPendingList",
 	"ContactMsgrAPI",
-	"BlockUnblock"
+	"BlockUnblock",
+	"Timer"
 };
 
 const char *MsnMemberRole[] =
@@ -189,6 +190,8 @@
 		strcat(buf, "Renaming Group,");
 	if (action & MSN_UPDATE_INFO)
 		strcat(buf, "Updating Contact Info,");
+	if (action & MSN_ANNOTATE_USER)
+		strcat(buf, "Annotating Contact,");
 
 	return buf;
 }
@@ -709,8 +712,9 @@
 		uid = xmlnode_get_data(contactId);
 		type = xmlnode_get_data(contactType);
 
-		/*setup the Display Name*/
+		/* Find out our settings */
 		if (type && !strcmp(type, "Me")) {
+			/* setup the Display Name */
 			if (purple_connection_get_display_name(pc) == NULL) {
 				char *friendly = NULL;
 				if ((displayName = xmlnode_get_child(contactInfo, "displayName")))
@@ -719,6 +723,23 @@
 					friendly ? purple_url_decode(friendly) : NULL);
 				g_free(friendly);
 			}
+
+			for (annotation = xmlnode_get_child(contactInfo, "annotations/Annotation");
+			     annotation;
+			     annotation = xmlnode_get_next_twin(annotation)) {
+				char *name, *value;
+				name = xmlnode_get_data(xmlnode_get_child(annotation, "Name"));
+				value = xmlnode_get_data(xmlnode_get_child(annotation, "Value"));
+				if (!strcmp(name, "MSN.IM.MPOP")) {
+					if (!value || atoi(value) != 0)
+						session->enable_mpop = TRUE;
+					else
+						session->enable_mpop = FALSE;
+				}
+				g_free(name);
+				g_free(value);
+			}
+
 			continue; /* Not adding own account as buddy to buddylist */
 		}
 
@@ -1497,6 +1518,101 @@
 	xmlnode_insert_child(contact, contact_info);
 	xmlnode_insert_child(contact, changes);
 
+	xmlnode_insert_data(xmlnode_get_child(state->body,
+	                                      "Header/ABApplicationHeader/PartnerScenario"),
+	                    MsnSoapPartnerScenarioText[MSN_PS_SAVE_CONTACT], -1);
+
+	if (user) {
+		xmlnode *contactId = xmlnode_new_child(contact, "contactId");
+		msn_callback_state_set_uid(state, user->uid);
+		xmlnode_insert_data(contactId, state->uid, -1);
+	} else {
+		xmlnode *contactType = xmlnode_new_child(contact_info, "contactType");
+		xmlnode_insert_data(contactType, "Me", -1);
+	}
+
+	msn_contact_request(state);
+}
+
+static void
+msn_annotate_contact_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp,
+	gpointer data)
+{
+	MsnCallbackState *state = (MsnCallbackState *)data;
+	xmlnode *fault;
+
+	/* We don't know how to respond to this faultcode, so log it */
+	fault = xmlnode_get_child(resp->xml, "Body/Fault");
+	if (fault != NULL) {
+		char *fault_str = xmlnode_to_str(fault, NULL);
+		purple_debug_error("msn", "Operation {%s} Failed, SOAP Fault was: %s\n",
+		                   msn_contact_operation_str(state->action), fault_str);
+		g_free(fault_str);
+		return;
+	}
+
+	purple_debug_info("msn", "Contact annotated successfully\n");
+}
+
+/* Update a contact's annotations */
+void
+msn_annotate_contact(MsnSession *session, const char *passport, ...)
+{
+	va_list params;
+	MsnCallbackState *state;
+	xmlnode *contact;
+	xmlnode *contact_info;
+	xmlnode *annotations;
+	MsnUser *user = NULL;
+
+	g_return_if_fail(passport != NULL);
+
+	if (strcmp(passport, "Me") != 0) {
+		user = msn_userlist_find_user(session->userlist, passport);
+		if (!user)
+			return;
+	}
+
+	contact_info = xmlnode_new("contactInfo");
+	annotations = xmlnode_new_child(contact_info, "annotations");
+
+	va_start(params, passport);
+	while (TRUE) {
+		const char *name;
+		const char *value;
+		xmlnode *a, *n, *v;
+
+		name = va_arg(params, const char *);
+		if (!name)
+			break;
+
+		value = va_arg(params, const char *);
+		if (!value)
+			break;
+
+		a = xmlnode_new_child(annotations, "Annotation");
+		n = xmlnode_new_child(a, "Name");
+		xmlnode_insert_data(n, name, -1);
+		v = xmlnode_new_child(a, "Value");
+		xmlnode_insert_data(v, value, -1);
+	}
+	va_end(params);
+
+	state = msn_callback_state_new(session);
+
+	state->body = xmlnode_from_str(MSN_CONTACT_ANNOTATE_TEMPLATE, -1);
+	state->action = MSN_ANNOTATE_USER;
+	state->post_action = MSN_CONTACT_ANNOTATE_SOAP_ACTION;
+	state->post_url = MSN_ADDRESS_BOOK_POST_URL;
+	state->cb = msn_annotate_contact_read_cb;
+
+	xmlnode_insert_data(xmlnode_get_child(state->body,
+	                                      "Header/ABApplicationHeader/PartnerScenario"),
+	                    MsnSoapPartnerScenarioText[MSN_PS_SAVE_CONTACT], -1);
+
+	contact = xmlnode_get_child(state->body, "Body/ABContactUpdate/contacts/Contact");
+	xmlnode_insert_child(contact, contact_info);
+
 	if (user) {
 		xmlnode *contactId = xmlnode_new_child(contact, "contactId");
 		msn_callback_state_set_uid(state, user->uid);
--- a/libpurple/protocols/msn/contact.h	Tue Nov 30 07:07:42 2010 +0000
+++ b/libpurple/protocols/msn/contact.h	Tue Dec 07 16:58:46 2010 +0000
@@ -36,7 +36,8 @@
 	MSN_ADD_GROUP       = 0x10,
 	MSN_DEL_GROUP       = 0x20,
 	MSN_RENAME_GROUP    = 0x40,
-	MSN_UPDATE_INFO     = 0x80
+	MSN_UPDATE_INFO     = 0x80,
+	MSN_ANNOTATE_USER   = 0x100
 } MsnCallbackAction;
 
 typedef enum
@@ -52,7 +53,8 @@
 	MSN_PS_SAVE_CONTACT,
 	MSN_PS_PENDING_LIST,
 	MSN_PS_CONTACT_API,
-	MSN_PS_BLOCK_UNBLOCK
+	MSN_PS_BLOCK_UNBLOCK,
+	MSN_PS_TIMER
 } MsnSoapPartnerScenario;
 
 #include "session.h"
@@ -408,7 +410,7 @@
 		"<ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
 			"<ApplicationId>" MSN_APPLICATION_ID "</ApplicationId>"\
 			"<IsMigration>false</IsMigration>"\
-			"<PartnerScenario>Timer</PartnerScenario>"\
+			"<PartnerScenario></PartnerScenario>"\
 		"</ABApplicationHeader>"\
 		"<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
 			"<ManagedGroupRequest>false</ManagedGroupRequest>"\
@@ -427,6 +429,37 @@
 	"</soap:Body>"\
 "</soap:Envelope>"
 
+/* Update Contact Annotations */
+#define MSN_CONTACT_ANNOTATE_SOAP_ACTION	"http://www.msn.com/webservices/AddressBook/ABContactUpdate"
+#define MSN_CONTACT_ANNOTATE_TEMPLATE	"<?xml version=\"1.0\" encoding=\"utf-8\"?>"\
+"<soap:Envelope"\
+	" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\
+	" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\
+	" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\
+	" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\
+	"<soap:Header>"\
+		"<ABApplicationHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<ApplicationId>" MSN_APPLICATION_ID "</ApplicationId>"\
+			"<IsMigration>false</IsMigration>"\
+			"<PartnerScenario></PartnerScenario>"\
+		"</ABApplicationHeader>"\
+		"<ABAuthHeader xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<ManagedGroupRequest>false</ManagedGroupRequest>"\
+			"<TicketToken>EMPTY</TicketToken>"\
+		"</ABAuthHeader>"\
+	"</soap:Header>"\
+	"<soap:Body>"\
+		"<ABContactUpdate xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+			"<abId>00000000-0000-0000-0000-000000000000</abId>"\
+			"<contacts>"\
+				"<Contact xmlns=\"http://www.msn.com/webservices/AddressBook\">"\
+					"<propertiesChanged>Annotation</propertiesChanged>"\
+				"</Contact>"\
+			"</contacts>"\
+		"</ABContactUpdate>"\
+	"</soap:Body>"\
+"</soap:Envelope>"
+
 /*******************************************************
  * Add/Delete contact from lists SOAP actions
  *******************************************************/
@@ -686,6 +719,8 @@
 /* contact SOAP operations */
 void msn_update_contact(MsnSession *session, const char *passport, MsnContactUpdateType type, const char* value);
 
+void msn_annotate_contact(MsnSession *session, const char *passport, ...) G_GNUC_NULL_TERMINATED;
+
 void msn_add_contact(MsnSession *session, MsnCallbackState *state,
 		     const char *passport);
 void msn_delete_contact(MsnSession *session, MsnUser *user);
--- a/libpurple/protocols/msn/msn.c	Tue Nov 30 07:07:42 2010 +0000
+++ b/libpurple/protocols/msn/msn.c	Tue Dec 07 16:58:46 2010 +0000
@@ -250,7 +250,7 @@
 	MsnSession *session;
 	MsnTransaction *trans;
 	PurpleAccount *account;
-	char real_alias[BUDDY_ALIAS_MAXLEN+1];
+	char real_alias[BUDDY_ALIAS_MAXLEN + 1];
 	struct public_alias_closure *closure;
 
 	session = purple_connection_get_protocol_data(pc);
@@ -258,53 +258,25 @@
 	account = purple_connection_get_account(pc);
 
 	if (alias && *alias) {
-		int i = 0;
-		while (isspace(*alias))
-			alias++;
-
-		for (; *alias && i < BUDDY_ALIAS_MAXLEN; alias++) {
-			if (*alias == '%') {
-				if (i > BUF_LEN - 4)
-					break;
-				real_alias[i++] = '%';
-				real_alias[i++] = '2';
-				real_alias[i++] = '5';
-			} else if (*alias == ' ') {
-				if (i > BUF_LEN - 4)
-					break;
-				real_alias[i++] = '%';
-				real_alias[i++] = '2';
-				real_alias[i++] = '0';
+		if (!msn_encode_spaces(alias, real_alias, BUDDY_ALIAS_MAXLEN + 1)) {
+			if (failure_cb) {
+				struct public_alias_closure *closure =
+					g_new0(struct public_alias_closure, 1);
+				closure->account = account;
+				closure->failure_cb = failure_cb;
+				purple_timeout_add(0, set_public_alias_length_error, closure);
 			} else {
-				real_alias[i++] = *alias;
+				purple_notify_error(pc, NULL,
+				                    _("Your new MSN friendly name is too long."),
+				                    NULL);
 			}
+			return;
 		}
 
-		while (i && isspace(real_alias[i - 1]))
-			i--;
-
-		real_alias[i] = '\0';
+		if (real_alias[0] == '\0')
+			strcpy(real_alias, purple_account_get_username(account));
 	} else
-		real_alias[0] = '\0';
-
-	if (*alias) {
-		if (failure_cb) {
-			struct public_alias_closure *closure =
-				g_new0(struct public_alias_closure, 1);
-			closure->account = account;
-			closure->failure_cb = failure_cb;
-			purple_timeout_add(0, set_public_alias_length_error, closure);
-		} else {
-			purple_notify_error(pc, NULL,
-			                    _("Your new MSN friendly name is too long."),
-			                    NULL);
-		}
-		return;
-	}
-
-	if (real_alias[0] == '\0') {
 		strcpy(real_alias, purple_account_get_username(account));
-	}
 
 	closure = g_new0(struct public_alias_closure, 1);
 	closure->account = account;
@@ -617,6 +589,67 @@
 }
 
 static void
+enable_mpop_cb(PurpleConnection *pc)
+{
+	MsnSession *session = purple_connection_get_protocol_data(pc);
+
+	purple_debug_info("msn", "Enabling MPOP\n");
+
+	session->enable_mpop = TRUE;
+	msn_annotate_contact(session, "Me", "MSN.IM.MPOP", "1", NULL);
+
+	purple_prpl_got_account_actions(purple_connection_get_account(pc));
+}
+
+static void
+disable_mpop_cb(PurpleConnection *pc)
+{
+	PurpleAccount *account = purple_connection_get_account(pc);
+	MsnSession *session = purple_connection_get_protocol_data(pc);
+	GSList *l;
+
+	purple_debug_info("msn", "Disabling MPOP\n");
+
+	session->enable_mpop = FALSE;
+	msn_annotate_contact(session, "Me", "MSN.IM.MPOP", "0", NULL);
+
+	for (l = session->user->endpoints; l; l = l->next) {
+		MsnUserEndpoint *ep = l->data;
+		char *user;
+
+		if (ep->id[0] != '\0' && strncasecmp(ep->id + 1, session->guid, 36) == 0)
+			/* Don't kick myself */
+			continue;
+
+		purple_debug_info("msn", "Disconnecting Endpoint %s\n", ep->id);
+
+		user = g_strdup_printf("%s;%s", purple_account_get_username(account), ep->id);
+		msn_notification_send_uun(session, user, MSN_UNIFIED_NOTIFICATION_MPOP, "goawyplzthxbye");
+		g_free(user);
+	}
+
+	purple_prpl_got_account_actions(account);
+}
+
+static void
+msn_show_set_mpop(PurplePluginAction *action)
+{
+	PurpleConnection *pc;
+
+	pc = (PurpleConnection *)action->context;
+
+	purple_request_action(pc, NULL, _("Allow multiple logins?"),
+						_("Do you want to allow or disallow connecting from "
+						  "multiple locations simultaneously?"),
+						PURPLE_DEFAULT_ACTION_NONE,
+						purple_connection_get_account(pc), NULL, NULL,
+						pc, 3,
+						_("Allow"), G_CALLBACK(enable_mpop_cb),
+						_("Disallow"), G_CALLBACK(disable_mpop_cb),
+						_("Cancel"), NULL);
+}
+
+static void
 msn_show_set_home_phone(PurplePluginAction *action)
 {
 	PurpleConnection *gc;
@@ -1201,7 +1234,7 @@
 	m = g_list_append(m, act);
 	m = g_list_append(m, NULL);
 
-	if (session->protocol_ver >= 16)
+	if (session->enable_mpop && session->protocol_ver >= 16)
 	{
 		act = purple_plugin_action_new(_("View Locations..."),
 		                               msn_show_locations);
@@ -1228,6 +1261,10 @@
 	m = g_list_append(m, act);
 #endif
 
+	act = purple_plugin_action_new(_("Allow/Disallow Multiple Logins..."),
+			msn_show_set_mpop);
+	m = g_list_append(m, act);
+
 	act = purple_plugin_action_new(_("Allow/Disallow Mobile Pages..."),
 			msn_show_set_mobile_pages);
 	m = g_list_append(m, act);
@@ -3085,6 +3122,11 @@
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
 											   option);
 
+	option = purple_account_option_bool_new(_("Allow connecting from multiple locations"),
+										  "mpop", TRUE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
+											   option);
+
 	purple_cmd_register("nudge", "", PURPLE_CMD_P_PRPL,
 	                  PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY,
 	                 "prpl-msn", msn_cmd_nudge,
--- a/libpurple/protocols/msn/msnutils.c	Tue Nov 30 07:07:42 2010 +0000
+++ b/libpurple/protocols/msn/msnutils.c	Tue Dec 07 16:58:46 2010 +0000
@@ -182,29 +182,40 @@
  * We need this because we're only supposed to encode spaces in the font
  * names. purple_url_encode() isn't acceptable.
  */
-static const char *
-encode_spaces(const char *str)
+gboolean
+msn_encode_spaces(const char *str, char *buf, size_t len)
 {
-	static char buf[BUF_LEN];
-	const char *c;
-	char *d;
+	char *nonspace = buf;
 
-	g_return_val_if_fail(str != NULL, NULL);
+	while (isspace(*str))
+		str++;
 
-	for (c = str, d = buf; *c != '\0'; c++)
-	{
-		if (*c == ' ')
-		{
-			*d++ = '%';
-			*d++ = '2';
-			*d++ = '0';
+	for (; *str && len > 1; str++) {
+		if (*str == '%') {
+			if (len < 4)
+				break;
+			*buf++ = '%';
+			*buf++ = '2';
+			*buf++ = '5';
+			len -= 3;
+			nonspace = buf;
+		} else if (*str == ' ') {
+			if (len < 4)
+				break;
+			*buf++ = '%';
+			*buf++ = '2';
+			*buf++ = '0';
+			len -= 3;
+		} else {
+			*buf++ = *str;
+			len--;
+			nonspace = buf;
 		}
-		else
-			*d++ = *c;
 	}
-	*d = '\0';
 
-	return buf;
+	*nonspace = '\0';
+
+	return (*str == '\0');
 }
 
 /*
@@ -223,6 +234,7 @@
 	const char *c;
 	char *msg;
 	char *fontface = NULL;
+	char fontface_encoded[BUF_LEN];
 	char fonteffect[5];
 	char fontcolor[7];
 	char direction = '0';
@@ -449,8 +461,9 @@
 	if (fontface == NULL)
 		fontface = g_strdup("Segoe UI");
 
+	msn_encode_spaces(fontface, fontface_encoded, BUF_LEN);
 	*attributes = g_strdup_printf("FN=%s; EF=%s; CO=%s; PF=0; RL=%c",
-								  encode_spaces(fontface),
+								  fontface_encoded,
 								  fonteffect, fontcolor, direction);
 	*message = msg;
 
--- a/libpurple/protocols/msn/msnutils.h	Tue Nov 30 07:07:42 2010 +0000
+++ b/libpurple/protocols/msn/msnutils.h	Tue Dec 07 16:58:46 2010 +0000
@@ -33,6 +33,18 @@
 char *rand_guid(void);
 
 /**
+ * Encodes the spaces in a string
+ *
+ * @param str The string to be encoded.
+ * @param buf The buffer to hold the encoded string.
+ * @param len The maximum length (including NUL) to put in @buf.
+ *
+ * @return Whether @str was able to fit in @buf.
+ */
+gboolean
+msn_encode_spaces(const char *str, char *buf, size_t len);
+
+/**
  * Parses the MSN message formatting into a format compatible with Purple.
  *
  * @param mime     The mime header with the formatting.
--- a/libpurple/protocols/msn/notification.c	Tue Nov 30 07:07:42 2010 +0000
+++ b/libpurple/protocols/msn/notification.c	Tue Dec 07 16:58:46 2010 +0000
@@ -387,7 +387,10 @@
 	 * command and we are processing it */
 	if (cmd->payload == NULL) {
 		cmdproc->last_cmd->payload_cb = msg_cmd_post;
-		cmd->payload_len = atoi(cmd->params[3]);
+		if (cmdproc->session->protocol_ver >= 16)
+			cmd->payload_len = atoi(cmd->params[5]);
+		else
+			cmd->payload_len = atoi(cmd->params[3]);
 	} else {
 		g_return_if_fail(cmd->payload_cb != NULL);
 
@@ -1548,40 +1551,55 @@
 static void
 parse_user_endpoints(MsnUser *user, xmlnode *payloadNode)
 {
+	MsnSession *session;
 	xmlnode *epNode, *capsNode;
 	MsnUserEndpoint data;
 	const char *id;
 	char *caps, *tmp;
+	gboolean is_me;
 
 	purple_debug_info("msn", "Get EndpointData\n");
 
+	session = user->userlist->session;
+	is_me = (user == session->user);
+
+	msn_user_clear_endpoints(user);
 	for (epNode = xmlnode_get_child(payloadNode, "EndpointData");
 	     epNode;
 	     epNode = xmlnode_get_next_twin(epNode)) {
 		id = xmlnode_get_attrib(epNode, "id");
 		capsNode = xmlnode_get_child(epNode, "Capabilities");
 
-		if (capsNode != NULL) {
-			caps = xmlnode_get_data(capsNode);
-
-			data.clientid = strtoul(caps, &tmp, 10);
-			if (tmp && *tmp)
-				data.extcaps = strtoul(tmp + 1, NULL, 10);
-			else
+		/* Disconnect others, if MPOP is disabled */
+		if (is_me
+		 && !session->enable_mpop
+		 && strncasecmp(id + 1, session->guid, 36) != 0) {
+			purple_debug_info("msn", "Disconnecting Endpoint %s\n", id);
+
+			tmp = g_strdup_printf("%s;%s", user->passport, id);
+			msn_notification_send_uun(session, tmp, MSN_UNIFIED_NOTIFICATION_MPOP, "goawyplzthxbye");
+			g_free(tmp);
+		} else {
+			if (capsNode != NULL) {
+				caps = xmlnode_get_data(capsNode);
+
+				data.clientid = strtoul(caps, &tmp, 10);
+				if (tmp && *tmp)
+					data.extcaps = strtoul(tmp + 1, NULL, 10);
+				else
+					data.extcaps = 0;
+
+				g_free(caps);
+			} else {
+				data.clientid = 0;
 				data.extcaps = 0;
-
-			g_free(caps);
-
-		} else {
-			data.clientid = 0;
-			data.extcaps = 0;
+			}
+
+			msn_user_set_endpoint_data(user, id, &data);
 		}
-
-		msn_user_set_endpoint_data(user, id, &data);
 	}
 
-	/* Need to shortcut this check, probably... */
-	if (user == user->userlist->session->user) {
+	if (is_me && session->enable_mpop) {
 		for (epNode = xmlnode_get_child(payloadNode, "PrivateEndpointData");
 		     epNode;
 		     epNode = xmlnode_get_next_twin(epNode)) {
@@ -1939,12 +1957,6 @@
 		/* This isn't an official message. */
 		return;
 
-	if ((value = msn_message_get_header_value(msg, "kv")) != NULL)
-	{
-		g_free(session->passport_info.kv);
-		session->passport_info.kv = g_strdup(value);
-	}
-
 	if ((value = msn_message_get_header_value(msg, "sid")) != NULL)
 	{
 		g_free(session->passport_info.sid);
--- a/libpurple/protocols/msn/session.c	Tue Nov 30 07:07:42 2010 +0000
+++ b/libpurple/protocols/msn/session.c	Tue Dec 07 16:58:46 2010 +0000
@@ -49,6 +49,7 @@
 	session->oim = msn_oim_new(session);
 
 	session->protocol_ver = 0;
+	session->enable_mpop = TRUE; /* Default only */
 
 	session->guid = rand_guid();
 
@@ -103,7 +104,6 @@
 	g_free(session->blocked_text);
 #endif
 
-	g_free(session->passport_info.kv);
 	g_free(session->passport_info.sid);
 	g_free(session->passport_info.mspauth);
 	g_free(session->passport_info.client_ip);
--- a/libpurple/protocols/msn/session.h	Tue Nov 30 07:07:42 2010 +0000
+++ b/libpurple/protocols/msn/session.h	Tue Dec 07 16:58:46 2010 +0000
@@ -79,12 +79,13 @@
 
 	MsnLoginStep login_step; /**< The current step in the login process. */
 
-	gboolean connected;
-	gboolean logged_in; /**< A temporal flag to ignore local buddy list adds. */
+	gboolean connected:1;
+	gboolean logged_in:1; /**< A temporal flag to ignore local buddy list adds. */
+	gboolean destroying:1; /**< A flag that states if the session is being destroyed. */
+	gboolean http_method:1;
+	gboolean enable_mpop:1; /**< Use Multiple Points of Presence? */
 	int      adl_fqy; /**< A count of ADL/FQY so status is only changed once. */
 	guint    login_timeout; /**< Timeout to force status change if ADL/FQY fail. */
-	gboolean destroying; /**< A flag that states if the session is being destroyed. */
-	gboolean http_method;
 
 	MsnNotification *notification;
 	MsnNexus        *nexus;
@@ -105,7 +106,6 @@
 
 	struct
 	{
-		char *kv;
 		char *sid;
 		char *mspauth;
 		unsigned long sl;
--- a/libpurple/protocols/msn/switchboard.c	Tue Nov 30 07:07:42 2010 +0000
+++ b/libpurple/protocols/msn/switchboard.c	Tue Dec 07 16:58:46 2010 +0000
@@ -743,7 +743,10 @@
 ubm_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd)
 {
 	purple_debug_misc("msn", "get UBM...\n");
-	cmd->payload_len = atoi(cmd->params[3]);
+	if (cmdproc->session->protocol_ver >= 16)
+		cmd->payload_len = atoi(cmd->params[5]);
+	else
+		cmd->payload_len = atoi(cmd->params[3]);
 	cmdproc->last_cmd->payload_cb = msg_cmd_post;
 }
 
--- a/libpurple/protocols/msn/user.c	Tue Nov 30 07:07:42 2010 +0000
+++ b/libpurple/protocols/msn/user.c	Tue Dec 07 16:58:46 2010 +0000
@@ -305,6 +305,22 @@
 }
 
 void
+msn_user_clear_endpoints(MsnUser *user)
+{
+	MsnUserEndpoint *ep;
+	GSList *l;
+
+	g_return_if_fail(user != NULL);
+
+	for (l = user->endpoints; l; l = g_slist_delete_link(l, l)) {
+		ep = l->data;
+		free_user_endpoint(ep);
+	}
+
+	user->endpoints = NULL;
+}
+
+void
 msn_user_set_op(MsnUser *user, MsnListOp list_op)
 {
 	g_return_if_fail(user != NULL);
--- a/libpurple/protocols/msn/user.h	Tue Nov 30 07:07:42 2010 +0000
+++ b/libpurple/protocols/msn/user.h	Tue Dec 07 16:58:46 2010 +0000
@@ -286,6 +286,14 @@
 msn_user_set_endpoint_data(MsnUser *user, const char *endpoint, MsnUserEndpoint *data);
 
 /**
+ * Clears all endpoint data for a user.
+ *
+ * @param user     The user.
+ */
+void
+msn_user_clear_endpoints(MsnUser *user);
+
+/**
  * Sets the client id for a user.
  *
  * @param user     The user.
--- a/libpurple/protocols/oscar/family_locate.c	Tue Nov 30 07:07:42 2010 +0000
+++ b/libpurple/protocols/oscar/family_locate.c	Tue Dec 07 16:58:46 2010 +0000
@@ -1043,7 +1043,7 @@
 						} else {
 							byte_stream_advance(bs, length2);
 							outinfo->status_len = 0;
-							outinfo->status = g_strdup("");
+							outinfo->status = NULL;
 							outinfo->status_encoding = NULL;
 						}
 					} break;
--- a/libpurple/protocols/oscar/oscar.c	Tue Nov 30 07:07:42 2010 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Tue Dec 07 16:58:46 2010 +0000
@@ -1366,7 +1366,7 @@
 	const char *status_id;
 	va_list ap;
 	aim_userinfo_t *info;
-	char *message = NULL;
+	char *message;
 	char *itmsurl = NULL;
 
 	gc = od->gc;
@@ -1453,16 +1453,13 @@
 		purple_prpl_got_user_status_deactive(account, info->bn, OSCAR_STATUS_ID_MOBILE);
 	}
 
-	/* Empty status means we should unset the status message. NULL status means we should keep it from the previous active status.
-	 * Same goes for itmsurl (which is available only for the "available" status).
-	 */
-	if (info->status != NULL) {
-		message = (info->status_len > 0) ? oscar_encoding_to_utf8(info->status_encoding, info->status, info->status_len) : NULL;
-	} else if (previous_status != NULL) {
-		message = g_strdup(purple_status_get_attr_string(previous_status, "message"));
-	}
+	message = (info->status && info->status_len > 0)
+			? oscar_encoding_to_utf8(info->status_encoding, info->status, info->status_len)
+			: NULL;
 
 	if (strcmp(status_id, OSCAR_STATUS_ID_AVAILABLE) == 0) {
+		/* TODO: If itmsurl is NULL, does that mean the URL has been
+		   cleared?  Or does it mean the URL should remain unchanged? */
 		if (info->itmsurl != NULL) {
 			itmsurl = (info->itmsurl_len > 0) ? oscar_encoding_to_utf8(info->itmsurl_encoding, info->itmsurl, info->itmsurl_len) : NULL;
 		} else if (previous_status != NULL && purple_status_is_available(previous_status)) {
--- a/pidgin/gtkaccount.c	Tue Nov 30 07:07:42 2010 +0000
+++ b/pidgin/gtkaccount.c	Tue Dec 07 16:58:46 2010 +0000
@@ -559,10 +559,14 @@
 		/* Google Talk default domain hackery! */
 		menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(dialog->protocol_menu));
 		item = gtk_menu_get_active(GTK_MENU(menu));
-		if (value == NULL && g_object_get_data(G_OBJECT(item), "fake") &&
+		if (value == NULL && g_object_get_data(G_OBJECT(item), "fakegoogle") &&
 			!strcmp(purple_account_user_split_get_text(split), _("Domain")))
 			value = "gmail.com";
 
+		if (value == NULL && g_object_get_data(G_OBJECT(item), "fakefacebook") &&
+			!strcmp(purple_account_user_split_get_text(split), _("Domain")))
+			value = "chat.facebook.com";
+
 		if (value != NULL)
 			gtk_entry_set_text(GTK_ENTRY(entry), value);
 	}
@@ -758,7 +762,7 @@
 {
 	PurpleAccountOption *option;
 	PurpleAccount *account;
-	GtkWidget *vbox, *check, *entry, *combo;
+	GtkWidget *vbox, *check, *entry, *combo, *menu, *item;
 	GList *list, *node;
 	gint i, idx, int_value;
 	GtkListStore *model;
@@ -797,6 +801,9 @@
 			gtk_label_new_with_mnemonic(_("Ad_vanced")), 1);
 	gtk_widget_show(vbox);
 
+	menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(dialog->protocol_menu));
+	item = gtk_menu_get_active(GTK_MENU(menu));
+
 	for (l = dialog->prpl_info->protocol_options; l != NULL; l = l->next)
 	{
 		option = (PurpleAccountOption *)l->data;
@@ -911,6 +918,10 @@
 				model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
 				opt_entry->widget = combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
 
+				if (g_object_get_data(G_OBJECT(item), "fakefacebook") &&
+					!strcmp(opt_entry->setting, "connection_security"))
+					str_value = "opportunistic_tls";
+
 				/* Loop through list of PurpleKeyValuePair items */
 				for (node = list; node != NULL; node = node->next) {
 					if (node->data != NULL) {
--- a/pidgin/gtkimhtml.c	Tue Nov 30 07:07:42 2010 +0000
+++ b/pidgin/gtkimhtml.c	Tue Dec 07 16:58:46 2010 +0000
@@ -756,7 +756,7 @@
 	GtkTextIter start, end, cur;
 	int buf_x, buf_y;
 	GdkRectangle visible_rect;
-	GdkGC *gc = gdk_gc_new(GDK_DRAWABLE(event->window));
+	cairo_t *cr = gdk_cairo_create(GDK_DRAWABLE(event->window));
 	GdkColor gcolor;
 
 	gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(widget), &visible_rect);
@@ -774,16 +774,16 @@
 
 		if (GTK_IMHTML(widget)->edit.background) {
 			gdk_color_parse(GTK_IMHTML(widget)->edit.background, &gcolor);
-			gdk_gc_set_rgb_fg_color(gc, &gcolor);
+			gdk_cairo_set_source_color(cr, &gcolor);
 		} else {
-			gdk_gc_set_rgb_fg_color(gc, &(widget->style->base[GTK_WIDGET_STATE(widget)]));
+			gdk_cairo_set_source_color(cr, &(widget->style->base[GTK_WIDGET_STATE(widget)]));
 		}
 
-		gdk_draw_rectangle(event->window,
-				   gc,
-				   TRUE,
-				   visible_rect.x, visible_rect.y, visible_rect.width, visible_rect.height);
-		g_object_unref(G_OBJECT(gc));
+		cairo_rectangle(cr,
+		                visible_rect.x, visible_rect.y,
+		                visible_rect.width, visible_rect.height);
+		cairo_fill(cr);
+		cairo_destroy(cr);
 
 		if (GTK_WIDGET_CLASS (parent_class)->expose_event)
 			return (* GTK_WIDGET_CLASS (parent_class)->expose_event)
@@ -854,12 +854,12 @@
 				if (!gdk_color_parse(tmp, &gcolor))
 					gdk_color_parse("white", &gcolor);
 			}
-			gdk_gc_set_rgb_fg_color(gc, &gcolor);
-
-			gdk_draw_rectangle(event->window,
-			                   gc,
-			                   TRUE,
-			                   rect.x, rect.y, rect.width, rect.height);
+			gdk_cairo_set_source_color(cr, &gcolor);
+
+			cairo_rectangle(cr,
+			                rect.x, rect.y,
+			                rect.width, rect.height);
+			cairo_fill(cr);
 			gtk_text_iter_backward_char(&cur); /* go back one, in case the end is the begining is the end
 			                                    * note that above, we always moved cur ahead by at least
 			                                    * one character */
@@ -874,7 +874,7 @@
 		       !gtk_text_iter_begins_tag(&cur, NULL));
 	}
 
-	g_object_unref(G_OBJECT(gc));
+	cairo_destroy(cr);
 
 	if (GTK_WIDGET_CLASS (parent_class)->expose_event)
 		return (* GTK_WIDGET_CLASS (parent_class)->expose_event)
--- a/pidgin/gtkutils.c	Tue Nov 30 07:07:42 2010 +0000
+++ b/pidgin/gtkutils.c	Tue Dec 07 16:58:46 2010 +0000
@@ -684,7 +684,7 @@
 	GdkPixbuf *pixbuf = NULL;
 	GtkSizeGroup *sg;
 	GList *p;
-	const char *gtalk_name = NULL;
+	const char *gtalk_name = NULL, *facebook_name = NULL;
 	int i;
 
 	aop_menu = g_malloc0(sizeof(AopMenu));
@@ -693,8 +693,10 @@
 	gtk_widget_show(aop_menu->menu);
 	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
 
-	if (purple_find_prpl("prpl-jabber"))
+	if (purple_find_prpl("prpl-jabber")) {
 		gtalk_name = _("Google Talk");
+		facebook_name = _("Facebook (XMPP)");
+	}
 
 	for (p = purple_plugins_get_protocols(), i = 0;
 		 p != NULL;
@@ -712,7 +714,7 @@
 
 			gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
 				item = aop_menu_item_new(sg, pixbuf, gtalk_name, "prpl-jabber", "protocol"));
-			g_object_set_data(G_OBJECT(item), "fake", GINT_TO_POINTER(1));
+			g_object_set_data(G_OBJECT(item), "fakegoogle", GINT_TO_POINTER(1));
 
 			if (pixbuf)
 				g_object_unref(pixbuf);
@@ -721,6 +723,25 @@
 			i++;
 		}
 
+		if (facebook_name && strcmp(facebook_name, plugin->info->name) < 0) {
+			char *filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols",
+			                                  "16", "facebook.png", NULL);
+			GtkWidget *item;
+
+			pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
+			g_free(filename);
+
+			gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
+				item = aop_menu_item_new(sg, pixbuf, facebook_name, "prpl-jabber", "protocol"));
+			g_object_set_data(G_OBJECT(item), "fakefacebook", GINT_TO_POINTER(1));
+
+			if (pixbuf)
+				g_object_unref(pixbuf);
+
+			facebook_name = NULL;
+			i++;
+		}
+
 		pixbuf = pidgin_create_prpl_icon_from_prpl(plugin, PIDGIN_PRPL_ICON_SMALL, NULL);
 
 		gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
--- a/pidgin/gtkwhiteboard.c	Tue Nov 30 07:07:42 2010 +0000
+++ b/pidgin/gtkwhiteboard.c	Tue Dec 07 16:58:46 2010 +0000
@@ -282,6 +282,9 @@
 	/* Clear graphical memory */
 	if(gtkwb->pixmap)
 	{
+		cairo_t *cr = g_object_get_data(G_OBJECT(gtkwb->pixmap), "cairo-context");
+		if (cr)
+			cairo_destroy(cr);
 		g_object_unref(gtkwb->pixmap);
 		gtkwb->pixmap = NULL;
 	}
@@ -353,25 +356,29 @@
 static gboolean pidgin_whiteboard_configure_event(GtkWidget *widget, GdkEventConfigure *event, gpointer data)
 {
 	PidginWhiteboard *gtkwb = (PidginWhiteboard*)data;
-
 	GdkPixmap *pixmap = gtkwb->pixmap;
+	cairo_t *cr;
 
-	if(pixmap)
+	if (pixmap) {
+		cr = g_object_get_data(G_OBJECT(pixmap), "cairo-context");
+		if (cr)
+			cairo_destroy(cr);
 		g_object_unref(pixmap);
+	}
 
 	pixmap = gdk_pixmap_new(widget->window,
 							widget->allocation.width,
 							widget->allocation.height,
 							-1);
-
 	gtkwb->pixmap = pixmap;
 
-	gdk_draw_rectangle(pixmap,
-					   widget->style->white_gc,
-					   TRUE,
-					   0, 0,
-					   widget->allocation.width,
-					   widget->allocation.height);
+	cr = gdk_cairo_create(GDK_DRAWABLE(pixmap));
+	g_object_set_data(G_OBJECT(pixmap), "cairo-context", cr);
+	gdk_cairo_set_source_color(cr, &widget->style->white);
+	cairo_rectangle(cr,
+	                0, 0,
+	                widget->allocation.width, widget->allocation.height);
+	cairo_fill(cr);
 
 	return TRUE;
 }
@@ -380,13 +387,15 @@
 {
 	PidginWhiteboard *gtkwb = (PidginWhiteboard*)(data);
 	GdkPixmap *pixmap = gtkwb->pixmap;
+	cairo_t *cr;
 
-	gdk_draw_drawable(widget->window,
-					  widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
-					  pixmap,
-					  event->area.x, event->area.y,
-					  event->area.x, event->area.y,
-					  event->area.width, event->area.height);
+	cr = gdk_cairo_create(GDK_DRAWABLE(widget->window));
+	gdk_cairo_set_source_pixmap(cr, pixmap, 0, 0);
+	cairo_rectangle(cr,
+	                event->area.x, event->area.y,
+	                event->area.width, event->area.height);
+	cairo_fill(cr);
+	cairo_destroy(cr);
 
 	return FALSE;
 }
@@ -586,50 +595,24 @@
 	GtkWidget *widget = gtkwb->drawing_area;
 	GdkPixmap *pixmap = gtkwb->pixmap;
 
-	GdkRectangle update_rect;
-
-	GdkGC *gfx_con = gdk_gc_new(pixmap);
+	cairo_t *gfx_con = g_object_get_data(G_OBJECT(pixmap), "cairo-context");
 	GdkColor col;
 
-	update_rect.x      = x - size / 2;
-	update_rect.y      = y - size / 2;
-	update_rect.width  = size;
-	update_rect.height = size;
-
 	/* Interpret and convert color */
 	pidgin_whiteboard_rgb24_to_rgb48(color, &col);
 
-	gdk_gc_set_rgb_fg_color(gfx_con, &col);
-	/* gdk_gc_set_rgb_bg_color(gfx_con, &col); */
+	gdk_cairo_set_source_color(gfx_con, &col);
 
-	/* NOTE 5 is a size constant for now... this is because of how poorly the
-	 * gdk_draw_arc draws small circles
-	 */
-	if(size < 5)
-	{
-		/* Draw a rectangle/square */
-		gdk_draw_rectangle(pixmap,
-						   gfx_con,
-						   TRUE,
-						   update_rect.x, update_rect.y,
-						   update_rect.width, update_rect.height);
-	}
-	else
-	{
-		/* Draw a circle */
-		gdk_draw_arc(pixmap,
-					 gfx_con,
-					 TRUE,
-					 update_rect.x, update_rect.y,
-					 update_rect.width, update_rect.height,
-					 0, FULL_CIRCLE_DEGREES);
-	}
+	/* Draw a circle */
+	cairo_arc(gfx_con,
+	          x, y,
+	          size / 2.0,
+	          0.0, 2.0 * M_PI);
+	cairo_fill(gfx_con);
 
 	gtk_widget_queue_draw_area(widget,
-							   update_rect.x, update_rect.y,
-							   update_rect.width, update_rect.height);
-
-	g_object_unref(G_OBJECT(gfx_con));
+							   x - size / 2, y - size / 2,
+							   size, size);
 }
 
 /* Uses Bresenham's algorithm (as provided by Wikipedia) */
@@ -720,13 +703,14 @@
 	PidginWhiteboard *gtkwb = wb->ui_data;
 	GdkPixmap *pixmap = gtkwb->pixmap;
 	GtkWidget *drawing_area = gtkwb->drawing_area;
+	cairo_t *cr = g_object_get_data(G_OBJECT(pixmap), "cairo-context");
 
-	gdk_draw_rectangle(pixmap,
-					   drawing_area->style->white_gc,
-					   TRUE,
-					   0, 0,
-					   drawing_area->allocation.width,
-					   drawing_area->allocation.height);
+	gdk_cairo_set_source_color(cr, &drawing_area->style->white);
+	cairo_rectangle(cr,
+	                0, 0,
+	                drawing_area->allocation.width,
+	                drawing_area->allocation.height);
+	cairo_fill(cr);
 
 	gtk_widget_queue_draw_area(drawing_area,
 							   0, 0,
--- a/pidgin/pixmaps/Makefile.am	Tue Nov 30 07:07:42 2010 +0000
+++ b/pidgin/pixmaps/Makefile.am	Tue Dec 07 16:58:46 2010 +0000
@@ -225,6 +225,7 @@
 PROTOCOLS_16 = \
 		protocols/16/aim.png \
 		protocols/16/bonjour.png \
+		protocols/16/facebook.png \
 		protocols/16/gadu-gadu.png \
 		protocols/16/google-talk.png \
 		protocols/16/novell.png \
@@ -281,6 +282,7 @@
 PROTOCOLS_22 = \
 		protocols/22/aim.png \
 		protocols/22/bonjour.png \
+		protocols/22/facebook.png \
 		protocols/22/gadu-gadu.png \
 		protocols/22/google-talk.png \
 		protocols/22/novell.png \
@@ -299,6 +301,7 @@
 PROTOCOLS_48 = \
 		protocols/48/aim.png \
 		protocols/48/bonjour.png \
+		protocols/48/facebook.png \
 		protocols/48/gadu-gadu.png \
 		protocols/48/novell.png \
 		protocols/48/icq.png \
Binary file pidgin/pixmaps/protocols/16/facebook.png has changed
Binary file pidgin/pixmaps/protocols/22/facebook.png has changed
Binary file pidgin/pixmaps/protocols/48/facebook.png has changed
--- a/pidgin/plugins/markerline.c	Tue Nov 30 07:07:42 2010 +0000
+++ b/pidgin/plugins/markerline.c	Tue Dec 07 16:58:46 2010 +0000
@@ -79,12 +79,14 @@
 	if (y >= event->area.y)
 	{
 		GdkColor red = {0, 0xffff, 0, 0};
-		GdkGC *gc = gdk_gc_new(GDK_DRAWABLE(event->window));
+		cairo_t *cr = gdk_cairo_create(GDK_DRAWABLE(event->window));
 
-		gdk_gc_set_rgb_fg_color(gc, &red);
-		gdk_draw_line(event->window, gc,
-					0, y, visible_rect.width, y);
-		g_object_unref(G_OBJECT(gc));
+		gdk_cairo_set_source_color(cr, &red);
+		cairo_move_to(cr, 0.0, y + 0.5);
+		cairo_rel_line_to(cr, visible_rect.width, 0.0);
+		cairo_set_line_width(cr, 1.0);
+		cairo_stroke(cr);
+		cairo_destroy(cr);
 	}
 	return FALSE;
 }