changeset 25021:951b7ff9a888

propagate from branch 'im.pidgin.pidgin' (head 586bc4a7e754d6b26a95c0d69dd5f2967c7a98a1) to branch 'im.pidgin.cpw.darkrain42.oscar.ssl' (head a6ac2fbddf65812a7fe2e01d10451f8b82ab35e5)
author Mark Doliner <mark@kingant.net>
date Tue, 27 Jan 2009 09:36:35 +0000 (2009-01-27)
parents 86ba0d87c04b (diff) 0ba3da7724ff (current diff)
children f6ef3a9534db
files
diffstat 36 files changed, 405 insertions(+), 190 deletions(-) [+]
line wrap: on
line diff
--- a/AUTHORS	Mon Jan 19 18:44:31 2009 +0000
+++ b/AUTHORS	Tue Jan 27 09:36:35 2009 +0000
@@ -36,7 +36,6 @@
 Crazy Patch Writers:
 -------------------
 Paul Aurich
-Felipe 'shx' Contreras
 Marcus 'malu' Lundblad
 Dennis 'EvilDennisR' Ristuccia
 Peter 'Fmoo' Ruibal
@@ -57,6 +56,7 @@
 
 Retired Crazy Patch Writers:
 ---------------------------
+Felipe 'shx' Contreras
 Decklin Foster
 Peter 'Bleeter' Lawler
 Robert 'Robot101' McQueen
--- a/COPYRIGHT	Mon Jan 19 18:44:31 2009 +0000
+++ b/COPYRIGHT	Tue Jan 27 09:36:35 2009 +0000
@@ -63,6 +63,7 @@
 Damien Carbery
 Michael Carlson
 Keegan Carruthers-Smith
+Ludovico Cavedon
 Steve Cavilia
 Julien Cegarra
 Cerulean Studios, LLC
@@ -210,6 +211,7 @@
 Jarom鱈r Karmaz鱈n
 John Kelm
 Jochen Kemnade
+Yann Kerherve
 Akuke Kok
 Kir Kolyshkin
 Konstantin Korikov
@@ -229,6 +231,7 @@
 Steve L叩posi
 Daniel Larsson
 Peter Lawler
+Vadim Lebedev
 Ho-seok Lee
 Jean-Yves Lefort
 Moses Lei
@@ -441,6 +444,7 @@
 Todd Troxell
 Brad Turcotte
 Kyle Turman
+Jon Turney
 Junichi Uekawa
 Igor Vlasenko
 Istv叩n V叩radi
@@ -477,6 +481,7 @@
 Dan Winship
 Michal Witkowski
 Scott Wolchok
+Rogier Wolff
 The Written Word, Inc.
 Kevin Wu Won
 Pui Lam Wong
--- a/ChangeLog	Mon Jan 19 18:44:31 2009 +0000
+++ b/ChangeLog	Tue Jan 27 09:36:35 2009 +0000
@@ -1,6 +1,12 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
 version 2.5.5 (??/??/????):
+	libpurple:
+	* Fix transfer of buddy icons, custom smileys and files from the
+	latest WLM 9 official client. (Thomas Gibson-Robinson)
+	* Fix a crash when removing an account with an unknown protocol id.
+	* Large (multi-part) messages on MSN are now correctly re-combined.
+
 	Finch:
 	* Allow rebinding keys to change the focused widget (details in the
 	  man-page, look for GntBox::binding)
--- a/ChangeLog.API	Mon Jan 19 18:44:31 2009 +0000
+++ b/ChangeLog.API	Tue Jan 27 09:36:35 2009 +0000
@@ -1,5 +1,12 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version 2.5.5 (??/??/2009):
+	libpurple:
+		Changed:
+		* purple_status_type_new now defaults "saveable" to TRUE.
+		  This was necessary in order to maintain the current behavior
+		  while fixing non-saveable statuses not to be saved.
+
 version 2.5.4 (01/12/2009):
 	perl:
 		Changed:
--- a/doc/finch.1.in	Mon Jan 19 18:44:31 2009 +0000
+++ b/doc/finch.1.in	Tue Jan 27 09:36:35 2009 +0000
@@ -543,23 +543,26 @@
 \fIhttp://developer.pidgin.im/query?status=new&status=assigned&status=reopened&component=finch+%28gnt%2Fncurses%29&order=priority\fR
 
 Before sending a bug report, please verify that you have the latest
-version of \fBfinch\fR and \fBlibpurple\fR.  Many bugs (major and minor) are
+version of \fBfinch\fR and libpurple.  Many bugs (major and minor) are
 fixed at each release, and if yours is out of date, the problem may already have
 been solved.
 
-
 .SH PATCHES
 If you fix a bug in \fBfinch\fR (or otherwise enhance it), please submit a
-patch (using \fImtn diff > my.diff\fR against the latest version from the
-Monotone repository) at
-.br
-\fIhttp://developer.pidgin.im/newticket\fR
+patch (using \fBmtn diff > my.diff\fR against the latest version from the
+Monotone repository) at \fIhttp://developer.pidgin.im/simpleticket\fR
 
-You are also encouraged to drop by at \fB#pidgin\fR on \fIirc.freenode.net\fR to
-discuss development.
+You are also encouraged to drop by at \fB#pidgin\fR on \fIirc.freenode.net\fR
+to discuss development.
 
 .SH SEE ALSO
 \fIhttp://pidgin.im/\fR
+.br
+\fIhttp://developer.pidgin.im/\fR
+.br
+\fBpurple-remote\fR(1)
+.br
+\fBpidgin\fR(1)
 
 .SH LICENSE
 This program is free software; you can redistribute it and/or modify
--- a/doc/pidgin.1.in	Mon Jan 19 18:44:31 2009 +0000
+++ b/doc/pidgin.1.in	Tue Jan 27 09:36:35 2009 +0000
@@ -34,6 +34,9 @@
 many common features found in other clients, as well as many unique features.
 Pidgin is not endorsed by or affiliated with America Online, ICQ, Microsoft, or
 Yahoo.
+.PP
+Pidgin can be extended by plugins written in multiple programming languages and
+controlled through DBus or \fBpurple-remote\fR.
 
 .SH OPTIONS
 The following options are provided by Pidgin using the standard GNU
@@ -556,20 +559,28 @@
 .SH BUGS
 The bug tracker can be reached by visiting \fIhttp://developer.pidgin.im/query\fR
 
-.SH PATCHES
-If you fix a bug in Pidgin (or otherwise enhance it), please submit a
-patch (using \fImtn diff > my.diff\fR against the latest version from the
-Monotone repository) at \fIhttp://developer.pidgin.im/simpleticket\fR
-
 Before sending a bug report, please verify that you have the latest
 version of Pidgin.  Many bugs (major and minor) are fixed
 at each release, and if yours is out of date, the problem may already
 have been solved.
 
+.SH PATCHES
+If you fix a bug in Pidgin (or otherwise enhance it), please submit a
+patch (using \fBmtn diff > my.diff\fR against the latest version from the
+Monotone repository) at \fIhttp://developer.pidgin.im/simpleticket\fR
+
+You are also encouraged to drop by at \fB#pidgin\fR on \fIirc.freenode.net\fR
+to discuss development.
+
+
 .SH SEE ALSO
 \fIhttp://pidgin.im/\fR
 .br
 \fIhttp://developer.pidgin.im/\fR
+.br
+\fBpurple-remote\fR(1)
+.br
+\fBfinch\fR(1)
 
 .SH LICENSE
 This program is free software; you can redistribute it and/or modify
@@ -643,8 +654,6 @@
 .br
   Paul Aurich
 .br
-  Felipe 'shx' Contreras
-.br
   Marcus 'malu' Lundblad
 .br
   Dennis 'EvilDennisR' Ristuccia
@@ -687,6 +696,8 @@
 
 Our retired crazy patch writers include:
 .br
+  Felipe 'shx' Contreras
+.br
   Decklin Foster
 .br
   Peter 'Bleeter' Lawler
--- a/libpurple/account.c	Mon Jan 19 18:44:31 2009 +0000
+++ b/libpurple/account.c	Tue Jan 27 09:36:35 2009 +0000
@@ -257,15 +257,20 @@
 statuses_to_xmlnode(const PurplePresence *presence)
 {
 	xmlnode *node, *child;
-	GList *statuses, *status;
+	GList *statuses;
+	PurpleStatus *status;
 
 	node = xmlnode_new("statuses");
 
 	statuses = purple_presence_get_statuses(presence);
-	for (status = statuses; status != NULL; status = status->next)
+	for (; statuses != NULL; statuses = statuses->next)
 	{
-		child = status_to_xmlnode((PurpleStatus *)status->data);
-		xmlnode_insert_child(node, child);
+		status = statuses->data;
+		if (purple_status_type_is_saveable(purple_status_get_type(status)))
+		{
+			child = status_to_xmlnode(status);
+			xmlnode_insert_child(node, child);
+		}
 	}
 
 	return node;
--- a/libpurple/blist.c	Mon Jan 19 18:44:31 2009 +0000
+++ b/libpurple/blist.c	Tue Jan 27 09:36:35 2009 +0000
@@ -1811,7 +1811,7 @@
 	PurpleGroup *group;
 	struct _purple_hbuddy hb;
 	PurplePlugin *prpl;
-	PurplePluginProtocolInfo *prpl_info;
+	PurplePluginProtocolInfo *prpl_info = NULL;
 
 	g_return_if_fail(buddy != NULL);
 
@@ -1872,7 +1872,8 @@
 	 * can free proto_data
 	 */
 	prpl = purple_find_prpl(purple_account_get_protocol_id(buddy->account));
-	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
+	if (prpl)
+		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
 	if (prpl_info && prpl_info->buddy_free)
 		prpl_info->buddy_free(buddy);
 
--- a/libpurple/certificate.c	Mon Jan 19 18:44:31 2009 +0000
+++ b/libpurple/certificate.c	Tue Jan 27 09:36:35 2009 +0000
@@ -1546,31 +1546,11 @@
 void
 purple_certificate_uninit(void)
 {
-	GList *full_list, *l;
-
-	/* Unregister all Schemes */
-	full_list = g_list_copy(cert_schemes); /* Make a working copy */
-	for (l = full_list; l; l = l->next) {
-		purple_certificate_unregister_scheme(
-			(PurpleCertificateScheme *) l->data );
-	}
-	g_list_free(full_list);
-
 	/* Unregister all Verifiers */
-	full_list = g_list_copy(cert_verifiers); /* Make a working copy */
-	for (l = full_list; l; l = l->next) {
-		purple_certificate_unregister_verifier(
-			(PurpleCertificateVerifier *) l->data );
-	}
-	g_list_free(full_list);
+	g_list_foreach(cert_verifiers, (GFunc)purple_certificate_unregister_verifier, NULL);
 
 	/* Unregister all Pools */
-	full_list = g_list_copy(cert_pools); /* Make a working copy */
-	for (l = full_list; l; l = l->next) {
-		purple_certificate_unregister_pool(
-			(PurpleCertificatePool *) l->data );
-	}
-	g_list_free(full_list);
+	g_list_foreach(cert_pools, (GFunc)purple_certificate_unregister_pool, NULL);
 }
 
 gpointer
--- a/libpurple/connection.c	Mon Jan 19 18:44:31 2009 +0000
+++ b/libpurple/connection.c	Tue Jan 27 09:36:35 2009 +0000
@@ -545,7 +545,7 @@
 	}
 
 	if (description == NULL) {
-		purple_debug_error("connection", "purple_connection_error_reason: check `description != NULL' failed\n");
+		purple_debug_error("connection", "purple_connection_error_reason called with NULL description\n");
 		description = _("Unknown error");
 	}
 
--- a/libpurple/core.c	Mon Jan 19 18:44:31 2009 +0000
+++ b/libpurple/core.c	Tue Jan 27 09:36:35 2009 +0000
@@ -137,7 +137,7 @@
 	 * subsystem right away too.
 	 */
 	purple_plugins_init();
-	
+
 	/* Initialize all static protocols. */
 	static_proto_init();
 
@@ -198,22 +198,40 @@
 	/* Transmission ends */
 	purple_connections_disconnect_all();
 
+	/*
+	 * Certificates must be destroyed before the SSL plugins, because
+	 * PurpleCertificates contain pointers to PurpleCertificateSchemes,
+	 * and the PurpleCertificateSchemes will be unregistered when the
+	 * SSL plugin is uninit.
+	 */
+	purple_certificate_uninit();
+
+	/* The SSL plugins must be uninit before they're unloaded */
+	purple_ssl_uninit();
+
+	/* Unload all plugins before the UI because UI plugins might call
+	 * UI-specific functions */
+	purple_debug_info("main", "Unloading all plugins\n");
+	purple_plugins_destroy_all();
+
+	/* Shut down the UI before all the subsystems */
+	ops = purple_core_get_ui_ops();
+	if (ops != NULL && ops->quit != NULL)
+		ops->quit();
+
 	/* Save .xml files, remove signals, etc. */
 	purple_smileys_uninit();
 	purple_idle_uninit();
-	purple_ssl_uninit();
 	purple_pounces_uninit();
 	purple_blist_uninit();
 	purple_ciphers_uninit();
 	purple_notify_uninit();
 	purple_conversations_uninit();
 	purple_connections_uninit();
-	purple_certificate_uninit();
 	purple_buddy_icons_uninit();
 	purple_accounts_uninit();
 	purple_savedstatuses_uninit();
 	purple_status_uninit();
-	purple_prefs_uninit();
 	purple_sound_uninit();
 	purple_xfers_uninit();
 	purple_proxy_uninit();
@@ -221,19 +239,15 @@
 	purple_imgstore_uninit();
 	purple_network_uninit();
 
-	purple_debug_info("main", "Unloading all plugins\n");
-	purple_plugins_destroy_all();
-
-	ops = purple_core_get_ui_ops();
-	if (ops != NULL && ops->quit != NULL)
-		ops->quit();
-
+	/* Everything after this must not try to read any prefs */
+	purple_prefs_uninit();
 	purple_plugins_uninit();
 #ifdef HAVE_DBUS
 	purple_dbus_uninit();
 #endif
 
 	purple_cmds_uninit();
+	/* Everything after this cannot try to write things to the confdir */
 	purple_util_uninit();
 
 	purple_signals_uninit();
--- a/libpurple/dbus-analyze-functions.py	Mon Jan 19 18:44:31 2009 +0000
+++ b/libpurple/dbus-analyze-functions.py	Tue Jan 27 09:36:35 2009 +0000
@@ -117,7 +117,7 @@
             self.params.append(Parameter.fromtokens(paramtexts[i].split(), i))
 
         self.call = "%s(%s)" % (self.function.name,
-                                ", ".join([param.name for param in self.params]))
+                                ", ".join(param.name for param in self.params))
         
     
     def process(self):
@@ -160,6 +160,10 @@
             elif type[0].startswith("Purple") or type[0] == "xmlnode":
                 return self.inputpurplestructure(type, name)
 
+            # special case for *_get_data functions, be careful here...
+            elif (type[0] == "size_t") and (name == "len"):
+                return self.inputgetdata(type, name)
+            
             # unknown pointers are always replaced with NULL
             else:
                 return self.inputpointer(type, name)
@@ -196,6 +200,10 @@
             if type[0] in ["GList", "GSList"]:
                 return self.outputlist(type, name)
 
+        # Special case for *_get_data functions
+        if type[0] == "gconstpointer":
+            return self.outputgetdata(type, name)
+
         raise myexception
     
 
@@ -309,7 +317,13 @@
         self.returncode.append("return garray_int_to_%s(%s);" %
                                (type[0].lower(), name));
 
- 
+    # Special case for *_get_data functions, don't need client bindings,
+    #  but do need the name so it doesn't crash
+    def inputgetdata(self, type, name):
+        raise myexception
+    def outputgetdata(self, type, name):
+        raise myexception
+
 class ServerBinding (Binding):
     def __init__(self, functiontext, paramtexts):
         Binding.__init__(self, functiontext, paramtexts)
@@ -475,6 +489,21 @@
                               % (name, name))
             self.addouttype("ai", name)
 
+    # Special case for *_get_data functions
+    def inputgetdata(self, type, name):
+        self.cdecls.append("\tsize_t %s = 0;" % name)
+        return True
+    def outputgetdata(self, type, name):
+        # This is a total hack, but self.call is set up before the parameters
+        #  are processed, so we can't tell it to pass a parameter by reference.
+        self.call = "%s(%s)" % (self.function.name,
+                                ", ".join(param.name if param.name != "len" else "&len" for param in self.params))
+
+        self.cdecls.append("\tgconstpointer %s;" % name)
+        self.ccode.append("\t%s = %s;" % (name, self.call))
+        self.cparamsout.append("DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &%s, %s" \
+                               % (name, "len"))
+        self.addouttype("ay", name)
 
 class BindingSet:
     regexp = r"^(\w[^()]*)\(([^()]*)\)\s*;\s*$";
--- a/libpurple/prefs.c	Mon Jan 19 18:44:31 2009 +0000
+++ b/libpurple/prefs.c	Tue Jan 27 09:36:35 2009 +0000
@@ -693,12 +693,15 @@
 	char *name;
 	GSList *l;
 
-	if(!pref || pref == &prefs)
+	if(!pref)
 		return;
 
 	while(pref->first_child)
 		remove_pref(pref->first_child);
 
+	if(pref == &prefs)
+		return;
+
 	if(pref->parent->first_child == pref) {
 		pref->parent->first_child = pref->sibling;
 	} else {
@@ -711,7 +714,8 @@
 
 	name = pref_full_name(pref);
 
-	purple_debug_info("prefs", "removing pref %s\n", name);
+	if (prefs_loaded)
+		purple_debug_info("prefs", "removing pref %s\n", name);
 
 	g_hash_table_remove(prefs_hash, name);
 	g_free(name);
@@ -1451,6 +1455,9 @@
 		sync_prefs();
 	}
 
+	prefs_loaded = FALSE;
+	purple_prefs_destroy();
+	g_hash_table_destroy(prefs_hash);
+	prefs_hash = NULL;
 
-	prefs_loaded = FALSE;
 }
--- a/libpurple/protocols/jabber/auth.c	Mon Jan 19 18:44:31 2009 +0000
+++ b/libpurple/protocols/jabber/auth.c	Tue Jan 27 09:36:35 2009 +0000
@@ -749,8 +749,8 @@
 
 				val_end = cur;
 				while (val_end != val_start && (*val_end == ' ' || *val_end == ',' || *val_end == '\t'
-						|| *val_end == '\r' || *val_start == '\n'
-						|| *val_end == '"'))
+						|| *val_end == '\r' || *val_end == '\n'
+						|| *val_end == '"'  || *val_end == '\0'))
 					val_end--;
 
 				if (val_start != val_end)
--- a/libpurple/protocols/jabber/buddy.c	Mon Jan 19 18:44:31 2009 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Tue Jan 27 09:36:35 2009 +0000
@@ -2518,7 +2518,7 @@
 	JabberBuddyResource *jbr = jabber_buddy_find_resource((JabberBuddy*)jb, NULL);
 
 	if (!jbr) {
-		purple_debug_error("jabber",
+		purple_debug_info("jabber",
 			"Unable to find caps: buddy might be offline\n");
 		return FALSE;
 	}
--- a/libpurple/protocols/msn/cmdproc.c	Mon Jan 19 18:44:31 2009 +0000
+++ b/libpurple/protocols/msn/cmdproc.c	Tue Jan 27 09:36:35 2009 +0000
@@ -35,6 +35,9 @@
 	cmdproc->txqueue = g_queue_new();
 	cmdproc->history = msn_history_new();
 
+	cmdproc->multiparts = g_hash_table_new_full(g_str_hash, g_str_equal,
+	                                            NULL, (GDestroyNotify)msn_message_unref);
+
 	return cmdproc;
 }
 
@@ -53,6 +56,8 @@
 	if (cmdproc->last_cmd != NULL)
 		msn_command_destroy(cmdproc->last_cmd);
 
+	g_hash_table_destroy(cmdproc->multiparts);
+
 	g_free(cmdproc);
 }
 
@@ -235,6 +240,61 @@
 msn_cmdproc_process_msg(MsnCmdProc *cmdproc, MsnMessage *msg)
 {
 	MsnMsgTypeCb cb;
+	const char *messageId = NULL;
+
+	/* Multi-part messages */
+	if ((messageId = msn_message_get_attr(msg, "Message-ID")) != NULL) {
+		const char *chunk_text = msn_message_get_attr(msg, "Chunks");
+		guint chunk;
+		if (chunk_text != NULL) {
+			chunk = strtol(chunk_text, NULL, 10);
+			/* 1024 chunks of ~1300 bytes is ~1MB, which seems OK to prevent 
+			   some random client causing pidgin to hog a ton of memory.
+			   Probably should figure out the maximum that the official client
+			   actually supports, though. */
+			if (chunk > 0 && chunk < 1024) {
+				msg->total_chunks = chunk;
+				msg->received_chunks = 1;
+				g_hash_table_insert(cmdproc->multiparts, (gpointer)messageId, msn_message_ref(msg));
+				purple_debug_info("msn", "Received chunked message, messageId: '%s', total chunks: %d\n",
+				                  messageId, chunk);
+			} else {
+				purple_debug_error("msn", "MessageId '%s' has too many chunks: %d\n", messageId, chunk);
+			}
+			return;
+		} else {
+			chunk_text = msn_message_get_attr(msg, "Chunk");
+			if (chunk_text != NULL) {
+				MsnMessage *first = g_hash_table_lookup(cmdproc->multiparts, messageId);
+				chunk = strtol(chunk_text, NULL, 10);
+				if (first == NULL) {
+					purple_debug_error("msn",
+					                   "Unable to find first chunk of messageId '%s' to correspond with chunk %d.\n",
+					                   messageId, chunk+1);
+				} else if (first->received_chunks == chunk) {
+					/* Chunk is from 1 to total-1 (doesn't count first one) */
+					purple_debug_info("msn", "Received chunk %d of %d, messageId: '%s'\n",
+					                  chunk+1, first->total_chunks, messageId);
+					first->body = g_realloc(first->body, first->body_len + msg->body_len);
+					memcpy(first->body + first->body_len, msg->body, msg->body_len);
+					first->body_len += msg->body_len;
+					first->received_chunks++;
+					if (first->received_chunks != first->total_chunks)
+						return;
+					else
+						/* We're done! Send it along... The caller takes care of
+						   freeing the old one. */
+						msg = first;
+				} else {
+					/* TODO: Can you legitimately receive chunks out of order? */
+					g_hash_table_remove(cmdproc->multiparts, messageId);
+					return;
+				}
+			} else {
+				purple_debug_error("msn", "Received MessageId '%s' with no chunk number!\n", messageId);
+			}
+		}
+	}
 
 	if (msn_message_get_content_type(msg) == NULL)
 	{
@@ -245,15 +305,14 @@
 	cb = g_hash_table_lookup(cmdproc->cbs_table->msgs,
 							 msn_message_get_content_type(msg));
 
-	if (cb == NULL)
-	{
+	if (cb != NULL)
+		cb(cmdproc, msg);
+	else
 		purple_debug_warning("msn", "Unhandled content-type '%s'\n",
 						   msn_message_get_content_type(msg));
 
-		return;
-	}
-
-	cb(cmdproc, msg);
+	if (messageId != NULL)
+		g_hash_table_remove(cmdproc->multiparts, messageId);
 }
 
 void
--- a/libpurple/protocols/msn/cmdproc.h	Mon Jan 19 18:44:31 2009 +0000
+++ b/libpurple/protocols/msn/cmdproc.h	Tue Jan 27 09:36:35 2009 +0000
@@ -46,6 +46,8 @@
 
 	MsnHistory *history;
 
+	GHashTable *multiparts; /**< Multi-part message ID's */
+
 	void *data; /**< Extra data, like the switchboard. */
 };
 
--- a/libpurple/protocols/msn/msg.h	Mon Jan 19 18:44:31 2009 +0000
+++ b/libpurple/protocols/msn/msg.h	Tue Jan 27 09:36:35 2009 +0000
@@ -109,6 +109,8 @@
 	char *charset;
 	char *body;
 	gsize body_len;
+	guint total_chunks;   /**< How many chunks in this multi-part message */
+	guint received_chunks; /**< How many chunks we've received so far */
 
 	MsnSlpHeader msnslp_header;
 	MsnSlpFooter msnslp_footer;
--- a/libpurple/protocols/msn/session.h	Mon Jan 19 18:44:31 2009 +0000
+++ b/libpurple/protocols/msn/session.h	Tue Jan 27 09:36:35 2009 +0000
@@ -123,7 +123,7 @@
 	} passport_info;
 
 	GHashTable *soap_table;
-	int soap_cleanup_handle;
+	guint soap_cleanup_handle;
 };
 
 /**
--- a/libpurple/protocols/msn/slpcall.c	Mon Jan 19 18:44:31 2009 +0000
+++ b/libpurple/protocols/msn/slpcall.c	Tue Jan 27 09:36:35 2009 +0000
@@ -206,7 +206,7 @@
 	body = slpmsg->buffer;
 	body_len = slpmsg->size;
 
-	if (slpmsg->flags == 0x0)
+	if (slpmsg->flags == 0x0 || slpmsg->flags == 0x1000000)
 	{
 		char *body_str;
 
@@ -214,7 +214,9 @@
 		slpcall = msn_slp_sip_recv(slplink, body_str);
 		g_free(body_str);
 	}
-	else if (slpmsg->flags == 0x20 || slpmsg->flags == 0x1000030)
+	else if (slpmsg->flags == 0x20 ||
+	         slpmsg->flags == 0x1000020 ||
+	         slpmsg->flags == 0x1000030)
 	{
 		slpcall = msn_slplink_find_slp_call_with_session_id(slplink, slpmsg->session_id);
 
@@ -237,6 +239,13 @@
 			msn_slpcall_session_init(slpcall);
 	}
 #endif
+	else if (slpmsg->flags == 0x2)
+	{
+		/* Acknowledgement of previous message. Don't do anything currently. */
+	}
+	else
+		purple_debug_warning("msn", "Unprocessed SLP message with flags 0x%08lx\n",
+		                     slpmsg->flags);
 
 	return slpcall;
 }
--- a/libpurple/protocols/msn/slplink.c	Mon Jan 19 18:44:31 2009 +0000
+++ b/libpurple/protocols/msn/slplink.c	Tue Jan 27 09:36:35 2009 +0000
@@ -281,7 +281,8 @@
 		g_list_append(slpmsg->msgs, msg);
 	msn_slplink_send_msg(slplink, msg);
 
-	if ((slpmsg->flags == 0x20 || slpmsg->flags == 0x1000030) &&
+	if ((slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 ||
+	     slpmsg->flags == 0x1000030) &&
 		(slpmsg->slpcall != NULL))
 	{
 		slpmsg->slpcall->progress = TRUE;
@@ -316,7 +317,8 @@
 	else
 	{
 		/* The whole message has been sent */
-		if (slpmsg->flags == 0x20 || slpmsg->flags == 0x1000030)
+		if (slpmsg->flags == 0x20 ||
+		    slpmsg->flags == 0x1000020 || slpmsg->flags == 0x1000030)
 		{
 			if (slpmsg->slpcall != NULL)
 			{
@@ -362,7 +364,8 @@
 		msg->msnslp_header.ack_size = slpmsg->ack_size;
 		msg->msnslp_header.ack_sub_id = slpmsg->ack_sub_id;
 	}
-	else if (slpmsg->flags == 0x20 || slpmsg->flags == 0x1000030)
+	else if (slpmsg->flags == 0x20 ||
+	         slpmsg->flags == 0x1000020 || slpmsg->flags == 0x1000030)
 	{
 		MsnSlpCall *slpcall;
 		slpcall = slpmsg->slpcall;
@@ -532,7 +535,8 @@
 
 			if (slpmsg->slpcall != NULL)
 			{
-				if (slpmsg->flags == 0x20 || slpmsg->flags == 0x1000030)
+				if (slpmsg->flags == 0x20 ||
+				    slpmsg->flags == 0x1000020 || slpmsg->flags == 0x1000030)
 				{
 					PurpleXfer *xfer;
 
@@ -595,7 +599,8 @@
 			memcpy(slpmsg->buffer + offset, data, len);
 	}
 
-	if ((slpmsg->flags == 0x20 || slpmsg->flags == 0x1000030) &&
+	if ((slpmsg->flags == 0x20 ||
+	     slpmsg->flags == 0x1000020 || slpmsg->flags == 0x1000030) &&
 		(slpmsg->slpcall != NULL))
 	{
 		slpmsg->slpcall->progress = TRUE;
@@ -630,8 +635,9 @@
 				msn_directconn_send_handshake(directconn);
 #endif
 		}
-		else if (slpmsg->flags == 0x0 || slpmsg->flags == 0x20 ||
-				 slpmsg->flags == 0x1000030)
+		else if (slpmsg->flags == 0x00 || slpmsg->flags == 0x1000000 ||  
+		         slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 ||  
+		         slpmsg->flags == 0x1000030)
 		{
 			/* Release all the messages and send the ACK */
 
--- a/libpurple/protocols/msn/switchboard.c	Mon Jan 19 18:44:31 2009 +0000
+++ b/libpurple/protocols/msn/switchboard.c	Tue Jan 27 09:36:35 2009 +0000
@@ -799,7 +799,7 @@
 
 	msn_cmdproc_process_msg(cmdproc, msg);
 
-	msn_message_destroy(msg);
+	msn_message_unref(msg);
 }
 
 static void
--- a/libpurple/purple-url-handler	Mon Jan 19 18:44:31 2009 +0000
+++ b/libpurple/purple-url-handler	Tue Jan 27 09:36:35 2009 +0000
@@ -207,7 +207,11 @@
 
     def correct_server(account):
         username = cpurple.PurpleAccountGetUsername(account)
-        return (server == (username.split("@"))[1])
+        user_split = (username.split("@"))
+        # Not all accounts have a split, so append an empty string so the
+        # [1] doesn't throw an IndexError.
+        user_split.append("")
+        return (server == user_split[1])
 
     account = findaccount(protocol, matcher=correct_server)
 
--- a/libpurple/status.c	Mon Jan 19 18:44:31 2009 +0000
+++ b/libpurple/status.c	Tue Jan 27 09:36:35 2009 +0000
@@ -250,7 +250,7 @@
 {
 	g_return_val_if_fail(primitive != PURPLE_STATUS_UNSET, NULL);
 
-	return purple_status_type_new_full(primitive, id, name, FALSE,
+	return purple_status_type_new_full(primitive, id, name, TRUE,
 			user_settable, FALSE);
 }
 
@@ -816,28 +816,42 @@
 	/* Reset any unspecified attributes to their default value */
 	status_type = purple_status_get_type(status);
 	l = purple_status_type_get_attrs(status_type);
-	while (l != NULL)
-	{
+	while (l != NULL) {
 		PurpleStatusAttr *attr;
 
 		attr = l->data;
-		if (!g_list_find_custom(specified_attr_ids, attr->id, (GCompareFunc)strcmp))
-		{
+		l = l->next;
+
+		if (!g_list_find_custom(specified_attr_ids, attr->id, (GCompareFunc)strcmp)) {
 			PurpleValue *default_value;
 			default_value = purple_status_attr_get_value(attr);
-			if (default_value->type == PURPLE_TYPE_STRING)
-				purple_status_set_attr_string(status, attr->id,
-						purple_value_get_string(default_value));
-			else if (default_value->type == PURPLE_TYPE_INT)
-				purple_status_set_attr_int(status, attr->id,
-						purple_value_get_int(default_value));
-			else if (default_value->type == PURPLE_TYPE_BOOLEAN)
-				purple_status_set_attr_boolean(status, attr->id,
-						purple_value_get_boolean(default_value));
+			if (default_value->type == PURPLE_TYPE_STRING) {
+				const char *cur = purple_status_get_attr_string(status, attr->id);
+				const char *def = purple_value_get_string(default_value);
+				if ((cur == NULL && def == NULL)
+				    || (cur != NULL && def != NULL
+					&& !strcmp(cur, def))) {
+					continue;
+				}
+
+				purple_status_set_attr_string(status, attr->id, def);
+			} else if (default_value->type == PURPLE_TYPE_INT) {
+				int cur = purple_status_get_attr_int(status, attr->id);
+				int def = purple_value_get_int(default_value);
+				if (cur == def)
+					continue;
+
+				purple_status_set_attr_int(status, attr->id, def);
+			} else if (default_value->type == PURPLE_TYPE_BOOLEAN) {
+				gboolean cur = purple_status_get_attr_boolean(status, attr->id);
+				gboolean def = purple_value_get_boolean(default_value);
+				if (cur == def)
+					continue;
+
+				purple_status_set_attr_boolean(status, attr->id, def);
+			}
 			changed = TRUE;
 		}
-
-		l = l->next;
 	}
 	g_list_free(specified_attr_ids);
 
--- a/libpurple/status.h	Mon Jan 19 18:44:31 2009 +0000
+++ b/libpurple/status.h	Tue Jan 27 09:36:35 2009 +0000
@@ -199,8 +199,8 @@
 										  gboolean independent);
 
 /**
- * Creates a new status type with some default values (not
- * savable and not independent).
+ * Creates a new status type with some default values (
+ * saveable and not independent).
  *
  * @param primitive     The primitive status type.
  * @param id            The ID of the status type, or @c NULL to use the id of
--- a/pidgin/gtkaccount.c	Mon Jan 19 18:44:31 2009 +0000
+++ b/pidgin/gtkaccount.c	Tue Jan 27 09:36:35 2009 +0000
@@ -84,6 +84,13 @@
 
 typedef struct
 {
+	GtkWidget *widget;
+	gchar *setting;
+	PurplePrefType type;
+} ProtocolOptEntry;
+
+typedef struct
+{
 	PidginAccountDialogType type;
 
 	PurpleAccount *account;
@@ -548,8 +555,10 @@
 	/* Password */
 	dialog->password_entry = gtk_entry_new();
 	gtk_entry_set_visibility(GTK_ENTRY(dialog->password_entry), FALSE);
+#if !GTK_CHECK_VERSION(2,16,0)
 	if (gtk_entry_get_invisible_char(GTK_ENTRY(dialog->password_entry)) == '*')
 		gtk_entry_set_invisible_char(GTK_ENTRY(dialog->password_entry), PIDGIN_INVISIBLE_CHAR);
+#endif /* Less than GTK+ 2.16 */
 	dialog->password_box = add_pref_box(dialog, vbox, _("_Password:"),
 										  dialog->password_entry);
 
@@ -740,21 +749,22 @@
 	char *title, *tmp;
 	const char *str_value;
 	gboolean bool_value;
+	ProtocolOptEntry *opt_entry;
 
 	if (dialog->protocol_frame != NULL) {
 		gtk_widget_destroy(dialog->protocol_frame);
 		dialog->protocol_frame = NULL;
 	}
 
-	if (dialog->protocol_opt_entries != NULL) {
-		g_list_free(dialog->protocol_opt_entries);
-		dialog->protocol_opt_entries = NULL;
-	}
-
 	if (dialog->prpl_info == NULL ||
-		dialog->prpl_info->protocol_options == NULL) {
-
+			dialog->prpl_info->protocol_options == NULL)
 		return;
+
+	while (dialog->protocol_opt_entries != NULL) {
+		ProtocolOptEntry *opt_entry = dialog->protocol_opt_entries->data;
+		g_free(opt_entry->setting);
+		g_free(opt_entry);
+		dialog->protocol_opt_entries = g_list_delete_link(dialog->protocol_opt_entries, dialog->protocol_opt_entries);
 	}
 
 	account = dialog->account;
@@ -778,7 +788,11 @@
 	{
 		option = (PurpleAccountOption *)l->data;
 
-		switch (purple_account_option_get_type(option))
+		opt_entry = g_new0(ProtocolOptEntry, 1);
+		opt_entry->type = purple_account_option_get_type(option);
+		opt_entry->setting = g_strdup(purple_account_option_get_setting(option));
+
+		switch (opt_entry->type)
 		{
 			case PURPLE_PREF_BOOLEAN:
 				if (account == NULL ||
@@ -795,7 +809,7 @@
 				}
 
 				tmp = g_strconcat("_", purple_account_option_get_text(option), NULL);
-				check = gtk_check_button_new_with_mnemonic(tmp);
+				opt_entry->widget = check = gtk_check_button_new_with_mnemonic(tmp);
 				g_free(tmp);
 
 				gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check),
@@ -803,10 +817,6 @@
 
 				gtk_box_pack_start(GTK_BOX(vbox), check, FALSE, FALSE, 0);
 				gtk_widget_show(check);
-
-				dialog->protocol_opt_entries =
-					g_list_append(dialog->protocol_opt_entries, check);
-
 				break;
 
 			case PURPLE_PREF_INT:
@@ -825,19 +835,13 @@
 
 				g_snprintf(buf, sizeof(buf), "%d", int_value);
 
-				entry = gtk_entry_new();
+				opt_entry->widget = entry = gtk_entry_new();
 				gtk_entry_set_text(GTK_ENTRY(entry), buf);
 
 				title = g_strdup_printf("_%s:",
 						purple_account_option_get_text(option));
-
 				add_pref_box(dialog, vbox, title, entry);
-
 				g_free(title);
-
-				dialog->protocol_opt_entries =
-					g_list_append(dialog->protocol_opt_entries, entry);
-
 				break;
 
 			case PURPLE_PREF_STRING:
@@ -854,12 +858,14 @@
 						purple_account_option_get_default_string(option));
 				}
 
-				entry = gtk_entry_new();
+				opt_entry->widget = entry = gtk_entry_new();
 				if (purple_account_option_get_masked(option))
 				{
 					gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
+#if !GTK_CHECK_VERSION(2,16,0)
 					if (gtk_entry_get_invisible_char(GTK_ENTRY(entry)) == '*')
 						gtk_entry_set_invisible_char(GTK_ENTRY(entry), PIDGIN_INVISIBLE_CHAR);
+#endif /* Less than GTK+ 2.16 */
 				}
 
 				if (str_value != NULL)
@@ -867,14 +873,8 @@
 
 				title = g_strdup_printf("_%s:",
 						purple_account_option_get_text(option));
-
 				add_pref_box(dialog, vbox, title, entry);
-
 				g_free(title);
-
-				dialog->protocol_opt_entries =
-					g_list_append(dialog->protocol_opt_entries, entry);
-
 				break;
 
 			case PURPLE_PREF_STRING_LIST:
@@ -896,7 +896,7 @@
 
 				list = purple_account_option_get_list(option);
 				model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
-				combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
+				opt_entry->widget = combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model));
 
 				/* Loop through list of PurpleKeyValuePair items */
 				for (node = list; node != NULL; node = node->next) {
@@ -928,20 +928,21 @@
 
 				title = g_strdup_printf("_%s:",
 						purple_account_option_get_text(option));
-
 				add_pref_box(dialog, vbox, title, combo);
-
 				g_free(title);
-
-				dialog->protocol_opt_entries =
-					g_list_append(dialog->protocol_opt_entries, combo);
-
 				break;
 
-
 			default:
-				break;
+				purple_debug_error("gtkaccount", "Invalid Account Option pref type (%d)\n",
+						   opt_entry->type);
+				g_free(opt_entry->setting);
+				g_free(opt_entry);
+				continue;
 		}
+
+		dialog->protocol_opt_entries =
+			g_list_append(dialog->protocol_opt_entries, opt_entry);
+
 	}
 }
 
@@ -1092,8 +1093,10 @@
 	/* Password */
 	dialog->proxy_pass_entry = gtk_entry_new();
 	gtk_entry_set_visibility(GTK_ENTRY(dialog->proxy_pass_entry), FALSE);
+#if !GTK_CHECK_VERSION(2,16,0)
 	if (gtk_entry_get_invisible_char(GTK_ENTRY(dialog->proxy_pass_entry)) == '*')
 		gtk_entry_set_invisible_char(GTK_ENTRY(dialog->proxy_pass_entry), PIDGIN_INVISIBLE_CHAR);
+#endif /* Less than GTK+ 2.16 */
 	add_pref_box(dialog, vbox2, _("Pa_ssword:"), dialog->proxy_pass_entry);
 
 	if (dialog->account != NULL &&
@@ -1152,7 +1155,12 @@
 	gtk_widget_destroy(dialog->window);
 
 	g_list_free(dialog->user_split_entries);
-	g_list_free(dialog->protocol_opt_entries);
+	while (dialog->protocol_opt_entries != NULL) {
+		ProtocolOptEntry *opt_entry = dialog->protocol_opt_entries->data;
+		g_free(opt_entry->setting);
+		g_free(opt_entry);
+		dialog->protocol_opt_entries = g_list_delete_link(dialog->protocol_opt_entries, dialog->protocol_opt_entries);
+	}
 	g_free(dialog->protocol_id);
 	g_object_unref(dialog->sg);
 
@@ -1317,46 +1325,38 @@
 
 	/* Add the protocol settings */
 	if (dialog->prpl_info) {
-		for (l = dialog->prpl_info->protocol_options,
-				l2 = dialog->protocol_opt_entries;
-				l != NULL && l2 != NULL;
-				l = l->next, l2 = l2->next) {
-
-			PurplePrefType type;
-			PurpleAccountOption *option = l->data;
-			GtkWidget *widget = l2->data;
-			GtkTreeIter iter;
-			const char *setting;
-			char *value2;
-			int int_value;
-			gboolean bool_value;
-
-			type = purple_account_option_get_type(option);
-
-			setting = purple_account_option_get_setting(option);
-
-			switch (type) {
+		ProtocolOptEntry *opt_entry;
+		GtkTreeIter iter;
+		char *value2;
+		int int_value;
+		gboolean bool_value;
+
+		for (l2 = dialog->protocol_opt_entries; l2; l2 = l2->next) {
+
+			opt_entry = l2->data;
+
+			switch (opt_entry->type) {
 				case PURPLE_PREF_STRING:
-					value = gtk_entry_get_text(GTK_ENTRY(widget));
-					purple_account_set_string(account, setting, value);
+					value = gtk_entry_get_text(GTK_ENTRY(opt_entry->widget));
+					purple_account_set_string(account, opt_entry->setting, value);
 					break;
 
 				case PURPLE_PREF_INT:
-					int_value = atoi(gtk_entry_get_text(GTK_ENTRY(widget)));
-					purple_account_set_int(account, setting, int_value);
+					int_value = atoi(gtk_entry_get_text(GTK_ENTRY(opt_entry->widget)));
+					purple_account_set_int(account, opt_entry->setting, int_value);
 					break;
 
 				case PURPLE_PREF_BOOLEAN:
 					bool_value =
-						gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
-					purple_account_set_bool(account, setting, bool_value);
+						gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(opt_entry->widget));
+					purple_account_set_bool(account, opt_entry->setting, bool_value);
 					break;
 
 				case PURPLE_PREF_STRING_LIST:
 					value2 = NULL;
-					if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(widget), &iter))
-						gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(widget)), &iter, 1, &value2, -1);
-					purple_account_set_string(account, setting, value2);
+					if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(opt_entry->widget), &iter))
+						gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(opt_entry->widget)), &iter, 1, &value2, -1);
+					purple_account_set_string(account, opt_entry->setting, value2);
 					break;
 
 				default:
--- a/pidgin/gtkblist.c	Mon Jan 19 18:44:31 2009 +0000
+++ b/pidgin/gtkblist.c	Tue Jan 27 09:36:35 2009 +0000
@@ -955,8 +955,10 @@
 			if (pce->secret)
 			{
 				gtk_entry_set_visibility(GTK_ENTRY(input), FALSE);
+#if !GTK_CHECK_VERSION(2,16,0)
 				if (gtk_entry_get_invisible_char(GTK_ENTRY(input)) == '*')
 					gtk_entry_set_invisible_char(GTK_ENTRY(input), PIDGIN_INVISIBLE_CHAR);
+#endif /* Less than GTK+ 2.16 */
 			}
 			pidgin_add_widget_to_vbox(GTK_BOX(data->entries_box), pce->label, data->sg, input, TRUE, NULL);
 			g_signal_connect(G_OBJECT(input), "changed",
@@ -6826,8 +6828,10 @@
 			if (pce->secret)
 			{
 				gtk_entry_set_visibility(GTK_ENTRY(input), FALSE);
+#if !GTK_CHECK_VERSION(2,16,0)
 				if (gtk_entry_get_invisible_char(GTK_ENTRY(input)) == '*')
 					gtk_entry_set_invisible_char(GTK_ENTRY(input), PIDGIN_INVISIBLE_CHAR);
+#endif /* Less than GTK+ 2.16 */
 			}
 			pidgin_add_widget_to_vbox(GTK_BOX(data->entries_box), pce->label, data->sg, input, TRUE, NULL);
 			g_signal_connect(G_OBJECT(input), "changed",
--- a/pidgin/gtkgaim-compat.h	Mon Jan 19 18:44:31 2009 +0000
+++ b/pidgin/gtkgaim-compat.h	Tue Jan 27 09:36:35 2009 +0000
@@ -297,7 +297,9 @@
 #define GAIM_HIG_BORDER PIDGIN_HIG_BORDER
 #define GAIM_HIG_BOX_SPACE PIDGIN_HIG_BOX_SPACE
 #define GAIM_HIG_CAT_SPACE PIDGIN_HIG_CAT_SPACE
+#if !GTK_CHECK_VERSION(2,16,0)
 #define GAIM_INVISIBLE_CHAR PIDGIN_INVISIBLE_CHAR
+#endif /* Less than GTK+ 2.16 */
 #define GAIM_IS_GTK_CONVERSATION PIDGIN_IS_PIDGIN_CONVERSATION
 #define GAIM_IS_GTK_PLUGIN PIDGIN_IS_PIDGIN_PLUGIN
 #define gaim_new_check_item pidgin_new_check_item
--- a/pidgin/gtkimhtml.c	Mon Jan 19 18:44:31 2009 +0000
+++ b/pidgin/gtkimhtml.c	Tue Jan 27 09:36:35 2009 +0000
@@ -5378,12 +5378,14 @@
 						text_tag_data_destroy(tmp);
 				}
 
-				if (tmp == NULL)
-					purple_debug_warning("gtkimhtml", "empty queue, more closing tags than open tags!\n");
-				else {
+				if (tmp != NULL) {
 					g_string_append(str, tmp->end);
 					text_tag_data_destroy(tmp);
 				}
+#if 0 /* This can't be allowed to happen because it causes the iters to be invalidated in the debug window imhtml during text copying */
+				else
+					purple_debug_warning("gtkimhtml", "empty queue, more closing tags than open tags!\n");
+#endif
 
 				while ((tmp = g_queue_pop_head(r))) {
 					g_string_append(str, tmp->start);
--- a/pidgin/gtkmain.c	Mon Jan 19 18:44:31 2009 +0000
+++ b/pidgin/gtkmain.c	Tue Jan 27 09:36:35 2009 +0000
@@ -322,9 +322,6 @@
 	pidgin_session_end();
 #endif
 
-	/* Save the plugins we have loaded for next time. */
-	pidgin_plugins_save();
-
 	/* Uninit */
 	pidgin_smileys_uninit();
 	pidgin_conversations_uninit();
@@ -787,8 +784,8 @@
 		dbus_connection_send_with_reply_and_block(conn, message, -1, NULL);
 		dbus_message_unref(message);
 #endif
-		purple_debug_info("main", "exiting because another libpurple client is already running\n");
 		purple_core_quit();
+		g_printerr(_("Exiting because another libpurple client is already running.\n"));
 #ifdef HAVE_SIGNAL_H
 		g_free(segfault_message);
 #endif
--- a/pidgin/gtkpluginpref.c	Mon Jan 19 18:44:31 2009 +0000
+++ b/pidgin/gtkpluginpref.c	Tue Jan 27 09:36:35 2009 +0000
@@ -101,8 +101,10 @@
 				if (purple_plugin_pref_get_masked(pref))
 				{
 					gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
+#if !GTK_CHECK_VERSION(2,16,0)
 					if (gtk_entry_get_invisible_char(GTK_ENTRY(entry)) == '*')
 						gtk_entry_set_invisible_char(GTK_ENTRY(entry), PIDGIN_INVISIBLE_CHAR);
+#endif /* Less than GTK+ 2.16 */
 				}
 				g_signal_connect(G_OBJECT(entry), "changed",
 								 G_CALLBACK(entry_cb),
--- a/pidgin/gtkprefs.c	Mon Jan 19 18:44:31 2009 +0000
+++ b/pidgin/gtkprefs.c	Tue Jan 27 09:36:35 2009 +0000
@@ -1412,8 +1412,10 @@
 		gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
 		gtk_table_attach(GTK_TABLE(table), entry, 3, 4, 1, 2, GTK_FILL , 0, 0, 0);
 		gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
+#if !GTK_CHECK_VERSION(2,16,0)
 		if (gtk_entry_get_invisible_char(GTK_ENTRY(entry)) == '*')
 			gtk_entry_set_invisible_char(GTK_ENTRY(entry), PIDGIN_INVISIBLE_CHAR);
+#endif /* Less than GTK+ 2.16 */
 		g_signal_connect(G_OBJECT(entry), "changed",
 				 G_CALLBACK(proxy_print_option), (void *)PROXYPASS);
 
--- a/pidgin/gtkrequest.c	Mon Jan 19 18:44:31 2009 +0000
+++ b/pidgin/gtkrequest.c	Tue Jan 27 09:36:35 2009 +0000
@@ -430,8 +430,10 @@
 			if (masked)
 			{
 				gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
+#if !GTK_CHECK_VERSION(2,16,0)
 				if (gtk_entry_get_invisible_char(GTK_ENTRY(entry)) == '*')
 					gtk_entry_set_invisible_char(GTK_ENTRY(entry), PIDGIN_INVISIBLE_CHAR);
+#endif /* Less than GTK+ 2.16 */
 			}
 		}
 		gtk_widget_show_all(vbox);
@@ -791,8 +793,10 @@
 		if (purple_request_field_string_is_masked(field))
 		{
 			gtk_entry_set_visibility(GTK_ENTRY(widget), FALSE);
+#if !GTK_CHECK_VERSION(2,16,0)
 			if (gtk_entry_get_invisible_char(GTK_ENTRY(widget)) == '*')
 				gtk_entry_set_invisible_char(GTK_ENTRY(widget),	PIDGIN_INVISIBLE_CHAR);
+#endif /* Less than GTK+ 2.16 */
 		}
 
 		gtk_editable_set_editable(GTK_EDITABLE(widget),
--- a/pidgin/pidgin.h	Mon Jan 19 18:44:31 2009 +0000
+++ b/pidgin/pidgin.h	Tue Jan 27 09:36:35 2009 +0000
@@ -91,11 +91,22 @@
 #define PIDGIN_HIG_BORDER        12
 #define PIDGIN_HIG_BOX_SPACE      6
 
+#if !GTK_CHECK_VERSION(2,16,0) || !defined(PIDGIN_DISABLE_DEPRECATED)
 /*
- * See GNOME bug #307304 for some discussion about the invisible
- * character.  0x25cf is a good choice, too.
+ * Older versions of GNOME defaulted to using an asterisk as the invisible
+ * character.  But this is ugly and we want to use something nicer.
+ *
+ * The default invisible character was changed in GNOME revision 21446
+ * (GTK+ 2.16) from an asterisk to the first available character out of
+ * 0x25cf, 0x2022, 0x2731, 0x273a.  See GNOME bugs 83935 and 307304 for
+ * discussion leading up to the change.
+ *
+ * Here's the change:
+ * http://svn.gnome.org/viewvc/gtk%2B?view=revision&revision=21446
+ *
  */
-#define PIDGIN_INVISIBLE_CHAR (gunichar)0x2022
+#define PIDGIN_INVISIBLE_CHAR (gunichar)0x25cf
+#endif /* Less than GTK+ 2.16 */
 
 #endif /* _PIDGIN_H_ */
 
--- a/po/fi.po	Mon Jan 19 18:44:31 2009 +0000
+++ b/po/fi.po	Tue Jan 27 09:36:35 2009 +0000
@@ -11,7 +11,7 @@
 "Project-Id-Version: Pidgin\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2009-01-07 13:30+0200\n"
-"PO-Revision-Date: 2009-01-07 13:30+0200\n"
+"PO-Revision-Date: 2009-01-23 19:58+0200\n"
 "Last-Translator: Timo Jyrinki <timo.jyrinki@iki.fi>\n"
 "Language-Team: \n"
 "MIME-Version: 1.0\n"
@@ -1169,7 +1169,7 @@
 msgstr "Keskustelut"
 
 msgid "Logging"
-msgstr "Kirjataan lokiin"
+msgstr "Lokiinkirjaus"
 
 msgid "You must fill all the required fields."
 msgstr "T辰yt辰 kaikki vaaditut kent辰t."
@@ -15466,7 +15466,7 @@
 #~ "keskustelun uuden keskustelun aluksi.\n"
 #~ "\n"
 #~ "Historia-liit辰nn辰inen vaatii lokiinkirjauksen k辰ytt旦辰. Loki voidaan ottaa "
-#~ "k辰ytt旦旦n menem辰ll辰 Ty旦kalut -> Asetukset -> Kirjataan lokiin. Lokien "
+#~ "k辰ytt旦旦n menem辰ll辰 Ty旦kalut -> Asetukset -> Lokiinkirjaus. Lokien "
 #~ "k辰ytt旦旦notto pikaviesteille ja/tai ryhm辰keskusteluille ottaa k辰ytt旦旦n "
 #~ "historiatoiminnon vastaaville keskustelutyypeille."
 
--- a/po/l10n.xsl	Mon Jan 19 18:44:31 2009 +0000
+++ b/po/l10n.xsl	Tue Jan 27 09:36:35 2009 +0000
@@ -1,22 +1,47 @@
 <?xml version='1.0' ?>
 <xsl:stylesheet version='2.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
+		<xsl:output
+		method="html"
+		omit-xml-declaration="yes"
+		doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
+		doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
+		indent="yes"
+		/>
 	<xsl:template match='/project'>
-		<html>
+		<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
 			<head>
 				<title><xsl:value-of select='@name'/> translation statistics</title>
+				<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 				<!-- <link rel="Stylesheet" href="/gaim.css" type="text/css" media="screen" /> -->
-				<style>
+				<style type="text/css">
 					.bargraph {
 						width: 200px;
 						height: 20px;
-						background: black;
+						background-color: red;
 						border-collapse: collapse;
 						border-spacing: 0px;
 						margin: 0px;
 						border: 0px;
 						padding: 0px;
 					}
-
+					.translated { 
+						background-color: green; 
+						padding: 0px;
+					}
+					.fuzzy { 
+						background-color: blue; 
+						padding: 0px;
+					}
+					.untranslated { 
+						background-color: red; 
+						padding: 0px;
+					}
+					td.sep {
+						padding-right: 10px;
+					}
+					th {
+						text-align: left;
+					}
 				</style>
 			</head>
 			<body>
@@ -28,21 +53,23 @@
 						<xsl:sort select='@code' />
 						<tr>
 							<td><a><xsl:attribute name='href'><xsl:value-of select='@code'/>.po</xsl:attribute><xsl:value-of select='@name'/> (<xsl:value-of select='@code'/>)</a></td>
-							<td><xsl:value-of select='@translated'/></td><td><xsl:value-of select="format-number(@translated div ../@strings * 100,'#.##')"/> %</td>
-							<td><xsl:value-of select='@fuzzy'/></td><td><xsl:value-of select="format-number(@fuzzy div ../@strings * 100,'#.##')"/> %</td>
-							<td><xsl:value-of select='../@strings - (@translated + @fuzzy)'/></td><td><xsl:value-of select="format-number((../@strings - (@translated + @fuzzy)) div ../@strings * 100,'#.##')"/> %</td>
+							<td><xsl:value-of select='@translated'/></td>
+							<td class='sep'><xsl:value-of select="format-number(@translated div ../@strings * 100,'#.##')"/> %</td>
+							<td><xsl:value-of select='@fuzzy'/></td>
+							<td class='sep'><xsl:value-of select="format-number(@fuzzy div ../@strings * 100,'#.##')"/> %</td>
+							<td><xsl:value-of select='../@strings - (@translated + @fuzzy)'/></td>
+							<td><xsl:value-of select="format-number((../@strings - (@translated + @fuzzy)) div ../@strings * 100,'#.##')"/> %</td>
 						<td>
 							<table class='bargraph'><tr>
-									<td bgcolor='green'><xsl:attribute name='width'><xsl:value-of select='round(@translated div ../@strings * 200)'/>px;</xsl:attribute></td>
-									<td bgcolor='blue'><xsl:attribute name='width'><xsl:value-of select='round(@fuzzy div ../@strings * 200)'/>px;</xsl:attribute></td>
-									<!-- <td bgcolor='red'><xsl:attribute name='width'><xsl:value-of select='200 - round((@translated + @fuzzy) div ../@strings * 200)'/>px;</xsl:attribute></td> -->
-									<td bgcolor='red'></td>
+									<td class="translated"><xsl:attribute name='style'>width:<xsl:value-of select='round(@translated div ../@strings * 200)'/>px;</xsl:attribute></td>
+									<td class="fuzzy"><xsl:attribute name='style'>width:<xsl:value-of select='round(@fuzzy div ../@strings * 200)'/>px;</xsl:attribute></td>
+									<td class="untranslated"><xsl:attribute name='style'>width:<xsl:value-of select='round((../@strings - @translated - @fuzzy) div ../@strings * 200)'/>px;</xsl:attribute></td>
 							</tr></table>
 						</td>
 						</tr>
 					</xsl:for-each>
 				</table>
-				<a><xsl:attribute name='href'><xsl:value-of select='@pofile'/></xsl:attribute><xsl:value-of select='@pofile'/></a> generated on <xsl:value-of select='@generated'/>
+				<p><a><xsl:attribute name='href'><xsl:value-of select='@pofile'/></xsl:attribute><xsl:value-of select='@pofile'/></a> generated on <xsl:value-of select='@generated'/></p>
 				<!-- </div> -->
 			</body>
 		</html>