changeset 31463:62f6e6796e48

merged from im.pidgin.pidgin
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Wed, 08 Dec 2010 23:34:06 +0900
parents 4635d84e3292 (current diff) a78e05f6fe25 (diff)
children c92bc01056ea
files libpurple/protocols/msn/msn.c pidgin/gtkimhtml.c pidgin/gtkutils.c
diffstat 21 files changed, 367 insertions(+), 114 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Thu Dec 02 20:17:37 2010 +0900
+++ b/ChangeLog	Wed Dec 08 23:34:06 2010 +0900
@@ -10,6 +10,8 @@
 	  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
@@ -30,7 +32,11 @@
 	  characters. (#8508)
 	* Fix receiving messages from users on Yahoo and other federated
 	  services. (#13022)
-	* Correctly remove old endpoints when they sign out.
+	* 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	Thu Dec 02 20:17:37 2010 +0900
+++ b/libpurple/protocols/jabber/jingle/jingle.c	Wed Dec 08 23:34:06 2010 +0900
@@ -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	Thu Dec 02 20:17:37 2010 +0900
+++ b/libpurple/protocols/msn/contact.c	Wed Dec 08 23:34:06 2010 +0900
@@ -190,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;
 }
@@ -730,9 +732,9 @@
 				value = xmlnode_get_data(xmlnode_get_child(annotation, "Value"));
 				if (!strcmp(name, "MSN.IM.MPOP")) {
 					if (!value || atoi(value) != 0)
-						purple_account_set_bool(session->account, "mpop", TRUE);
+						session->enable_mpop = TRUE;
 					else
-						purple_account_set_bool(session->account, "mpop", FALSE);
+						session->enable_mpop = FALSE;
 				}
 				g_free(name);
 				g_free(value);
@@ -1533,6 +1535,97 @@
 }
 
 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);
+		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_del_contact_from_list_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp,
 	gpointer data)
 {
--- a/libpurple/protocols/msn/contact.h	Thu Dec 02 20:17:37 2010 +0900
+++ b/libpurple/protocols/msn/contact.h	Wed Dec 08 23:34:06 2010 +0900
@@ -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
@@ -428,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
  *******************************************************/
@@ -687,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	Thu Dec 02 20:17:37 2010 +0900
+++ b/libpurple/protocols/msn/msn.c	Wed Dec 08 23:34:06 2010 +0900
@@ -616,6 +616,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;
@@ -1200,8 +1261,7 @@
 	m = g_list_append(m, act);
 	m = g_list_append(m, NULL);
 
-	if (purple_account_get_bool(session->account, "mpop", TRUE)
-	 && session->protocol_ver >= 16)
+	if (session->enable_mpop && session->protocol_ver >= 16)
 	{
 		act = purple_plugin_action_new(_("View Locations..."),
 		                               msn_show_locations);
@@ -1228,6 +1288,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);
--- a/libpurple/protocols/msn/msnutils.c	Thu Dec 02 20:17:37 2010 +0900
+++ b/libpurple/protocols/msn/msnutils.c	Wed Dec 08 23:34:06 2010 +0900
@@ -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	Thu Dec 02 20:17:37 2010 +0900
+++ b/libpurple/protocols/msn/msnutils.h	Wed Dec 08 23:34:06 2010 +0900
@@ -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	Thu Dec 02 20:17:37 2010 +0900
+++ b/libpurple/protocols/msn/notification.c	Wed Dec 08 23:34:06 2010 +0900
@@ -1572,7 +1572,7 @@
 
 		/* Disconnect others, if MPOP is disabled */
 		if (is_me
-		 && !purple_account_get_bool(session->account, "mpop", TRUE)
+		 && !session->enable_mpop
 		 && strncasecmp(id + 1, session->guid, 36) != 0) {
 			purple_debug_info("msn", "Disconnecting Endpoint %s\n", id);
 
@@ -1599,7 +1599,7 @@
 		}
 	}
 
-	if (is_me && purple_account_get_bool(session->account, "mpop", TRUE)) {
+	if (is_me && session->enable_mpop) {
 		for (epNode = xmlnode_get_child(payloadNode, "PrivateEndpointData");
 		     epNode;
 		     epNode = xmlnode_get_next_twin(epNode)) {
--- a/libpurple/protocols/msn/session.c	Thu Dec 02 20:17:37 2010 +0900
+++ b/libpurple/protocols/msn/session.c	Wed Dec 08 23:34:06 2010 +0900
@@ -49,6 +49,7 @@
 	session->oim = msn_oim_new(session);
 
 	session->protocol_ver = 0;
+	session->enable_mpop = TRUE; /* Default only */
 
 	session->guid = rand_guid();
 
--- a/libpurple/protocols/msn/session.h	Thu Dec 02 20:17:37 2010 +0900
+++ b/libpurple/protocols/msn/session.h	Wed Dec 08 23:34:06 2010 +0900
@@ -83,6 +83,7 @@
 	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. */
 
--- a/pidgin/Makefile.mingw	Thu Dec 02 20:17:37 2010 +0900
+++ b/pidgin/Makefile.mingw	Wed Dec 08 23:34:06 2010 +0900
@@ -121,6 +121,7 @@
 		-lz \
 		-lgtk-win32-2.0 \
 		-latk-1.0 \
+		-lcairo \
 		-lpango-1.0 \
 		-lgdk-win32-2.0 \
 		-lgdk_pixbuf-2.0 \
--- a/pidgin/gtkaccount.c	Thu Dec 02 20:17:37 2010 +0900
+++ b/pidgin/gtkaccount.c	Wed Dec 08 23:34:06 2010 +0900
@@ -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	Thu Dec 02 20:17:37 2010 +0900
+++ b/pidgin/gtkimhtml.c	Wed Dec 08 23:34:06 2010 +0900
@@ -762,7 +762,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);
@@ -780,16 +780,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)
@@ -860,12 +860,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 */
@@ -880,7 +880,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	Thu Dec 02 20:17:37 2010 +0900
+++ b/pidgin/gtkutils.c	Wed Dec 08 23:34:06 2010 +0900
@@ -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	Thu Dec 02 20:17:37 2010 +0900
+++ b/pidgin/gtkwhiteboard.c	Wed Dec 08 23:34:06 2010 +0900
@@ -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	Thu Dec 02 20:17:37 2010 +0900
+++ b/pidgin/pixmaps/Makefile.am	Wed Dec 08 23:34:06 2010 +0900
@@ -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/Makefile.mingw	Thu Dec 02 20:17:37 2010 +0900
+++ b/pidgin/plugins/Makefile.mingw	Wed Dec 08 23:34:06 2010 +0900
@@ -48,6 +48,7 @@
 			-lgmodule-2.0 \
 			-lgdk_pixbuf-2.0 \
 			-lpango-1.0 \
+			-lcairo \
 			-lintl \
 			-lws2_32 \
 			-lpurple \
--- a/pidgin/plugins/markerline.c	Thu Dec 02 20:17:37 2010 +0900
+++ b/pidgin/plugins/markerline.c	Wed Dec 08 23:34:06 2010 +0900
@@ -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;
 }