changeset 27842:acef4202e147

propagate from branch 'im.pidgin.pidgin' (head 217103c3e954ff2d295a6590ad3477d357894f9c) to branch 'im.pidgin.pidgin.yaz' (head 0a5a324e3974fba2b40dcd1bb82fb1cc8b8fcabf)
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Sun, 25 May 2008 04:30:41 +0000
parents 5fc417883a5d (current diff) e7f345a980d6 (diff)
children 30eaeb7cc076
files configure.ac libpurple/conversation.c libpurple/protocols/irc/irc.c libpurple/protocols/jabber/message.c libpurple/protocols/msn/msg.c libpurple/protocols/msn/msn.c libpurple/protocols/msnp9/msg.c libpurple/protocols/msnp9/msn.c libpurple/protocols/yahoo/util.c libpurple/protocols/yahoo/yahoo.c libpurple/protocols/yahoo/yahoo_profile.c libpurple/protocols/yahoo/yahoochat.c libpurple/protocols/zephyr/zephyr.h libpurple/server.c libpurple/util.c libpurple/util.h pidgin/gtkblist.c pidgin/gtkconv.c pidgin/gtkimhtml.c pidgin/gtkimhtmltoolbar.c pidgin/gtkprefs.c pidgin/gtkutils.c pidgin/gtkutils.h
diffstat 114 files changed, 4942 insertions(+), 1413 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Mon May 19 16:18:32 2008 +0000
+++ b/COPYRIGHT	Sun May 25 04:30:41 2008 +0000
@@ -233,6 +233,7 @@
 Shlomi Loubaton
 Uli Luckas
 Matthew Luckie
+Marcus Lundblad
 Mike Lundy
 Jason Lynch
 Iain MacDonnell
@@ -289,6 +290,7 @@
 John Oyler
 Matt Pandina
 Laszlo Pandy
+Giulio 'Twain28' Pascali
 Ricardo Fernandez Pascual
 Riley Patterson
 Havoc Pennington
--- a/ChangeLog	Mon May 19 16:18:32 2008 +0000
+++ b/ChangeLog	Sun May 25 04:30:41 2008 +0000
@@ -1,6 +1,22 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
-version 2.4.3 (??/??/2008):
+version 2.5.0 (??/??/2008):
+	libpurple:
+	* Ability to create custom smileys (currently only the MSN protocol
+	  utilizes the feature). (Thanks to Mauro Sérgio Ferreira Brasil,
+	  Marcus Lundblad, Jorge Villaseñor and other contributors)
+	* Yahoo! Japan now uses UTF-8, matching the behavior of official clients
+	  and restoring compatibility with the web messenger (Yusuke Odate)
+
+	Pidgin:
+	* Custom buddy icons can now be added and removed to buddy list
+	  entries via the buddy list entry right-click menu.
+	* Resize large incoming custom smileys to a maximum of 96px on either
+	  side.
+
+	General:
+	* Group and Chat buddy list entries can now be given custom buddy
+	  icons.
 
 version 2.4.2 (05/17/2008):
 	libpurple:
--- a/ChangeLog.API	Mon May 19 16:18:32 2008 +0000
+++ b/ChangeLog.API	Sun May 25 04:30:41 2008 +0000
@@ -1,6 +1,40 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
-version 2.4.2 (05/17/2008):
+version 2.5.0 (??/??/2008):
+	libpurple:
+		Added:
+		* Connection flag PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY to indicate
+		  that the connection supports sending and receiving custom smileys.
+		* PurpleSmiley and the Smiley API.
+		* purple_serv_got_join_chat_failed
+		* chat-join-failed signal (see conversation-signals.dox)
+		* chat-invite-blocked and blocked-im-msg signals (see
+		  converation-signals.dox) (Thanks to Stefan Ott)
+		* purple_blist_update_node_icon
+		* purple_buddy_icons_node_has_custom_icon
+		* purple_buddy_icons_node_find_custom_icon
+		* purple_buddy_icons_node_set_custom_icon
+		* purple_buddy_icons_node_set_custom_icon_from_file
+
+		Deprecated:
+		* purple_blist_update_buddy_icon
+		* purple_buddy_icons_has_custom_icon
+		* purple_buddy_icons_find_custom_icon
+		* purple_buddy_icons_set_custom_icon
+		* pidgin_set_custom_buddy_icon
+
+	pidgin:
+		Added:
+		* gtk_imhtml_smiley_create, gtk_imhtml_smiley_reload and
+		  gtk_imhtml_smiley_destroy to deal with GtkIMHtmlSmiley's.
+		* pidgin_pixbuf_from_imgstore to create a GdkPixbuf from a
+		  PurpleStoredImage.
+		* pidgin_themes_smiley_themeize_custom to associate custom smileys to
+		  a GtkIMHtml widget.
+		* GTK_IMHTML_CUSTOM_SMILEY flag for GtkIMHtml.
+		* GTK+ Custom Smiley API.
+
+version 2.4.2:
 	perl:
 		Added:
 		* Purple::Prefs::get_children_names.
--- a/Doxyfile.in	Mon May 19 16:18:32 2008 +0000
+++ b/Doxyfile.in	Sun May 25 04:30:41 2008 +0000
@@ -485,11 +485,11 @@
 # directories like "/usr/src/myproject". Separate the files or directories 
 # with spaces.
 
-INPUT                  = libpurple \
-                         finch \
-                         finch/libgnt \
-                         pidgin \
-                         doc
+INPUT                  = @top_srcdir@/libpurple \
+                         @top_srcdir@/finch \
+                         @top_srcdir@/finch/libgnt \
+                         @top_srcdir@/pidgin \
+                         @top_srcdir@/doc
 
 # This tag can be used to specify the character encoding of the source files that 
 # doxygen parses. Internally doxygen uses the UTF-8 encoding, which is also the default 
--- a/configure.ac	Mon May 19 16:18:32 2008 +0000
+++ b/configure.ac	Sun May 25 04:30:41 2008 +0000
@@ -43,19 +43,19 @@
 #
 # Make sure to update finch/libgnt/configure.ac with libgnt version changes.
 #
-m4_define([purple_lt_current], [4])
+m4_define([purple_lt_current], [5])
 m4_define([purple_major_version], [2])
-m4_define([purple_minor_version], [4])
-m4_define([purple_micro_version], [3])
+m4_define([purple_minor_version], [5])
+m4_define([purple_micro_version], [0])
 m4_define([purple_version_suffix], [devel])
 m4_define([purple_version],
           [purple_major_version.purple_minor_version.purple_micro_version])
 m4_define([purple_display_version], purple_version[]m4_ifdef([purple_version_suffix],[purple_version_suffix]))
 
-m4_define([gnt_lt_current], [4])
+m4_define([gnt_lt_current], [5])
 m4_define([gnt_major_version], [2])
-m4_define([gnt_minor_version], [4])
-m4_define([gnt_micro_version], [3])
+m4_define([gnt_minor_version], [5])
+m4_define([gnt_micro_version], [0])
 m4_define([gnt_version_suffix], [devel])
 m4_define([gnt_version],
           [gnt_major_version.gnt_minor_version.gnt_micro_version])
--- a/doc/conversation-signals.dox	Mon May 19 16:18:32 2008 +0000
+++ b/doc/conversation-signals.dox	Sun May 25 04:30:41 2008 +0000
@@ -7,6 +7,7 @@
   @signal sent-im-msg
   @signal receiving-im-msg
   @signal received-im-msg
+  @signal blocked-im-msg
   @signal writing-chat-msg
   @signal wrote-chat-msg
   @signal sending-chat-msg
@@ -26,7 +27,9 @@
   @signal chat-inviting-user
   @signal chat-invited-user
   @signal chat-invited
+  @signal chat-invite-blocked
   @signal chat-joined
+  @signal chat-join-failed
   @signal chat-left
   @signal chat-topic-changed
   @signal conversation-extended-menu
@@ -131,6 +134,21 @@
   @param flags   The IM message flags.
  @endsignaldef
 
+ @signaldef blocked-im-msg
+  @signalproto
+void (*blocked_im_msg)(PurpleAccount *account, const char *sender,
+        const char *message, PurpleMessageFlags flags, time_t when);
+  @endsignalproto
+  @signaldesc
+   Emitted after an IM is blocked due to privacy settings.
+  @param account The account the message was received on.
+  @param sender  The username of the sender.
+  @param message The message that was blocked.
+  @param flags   The IM message flags.
+  @param when    The time the message was sent.
+  @since 2.5.0
+ @endsignaldef
+
  @signaldef writing-chat-msg
   @signalproto
 gboolean (*writing_chat_msg)(PurpleAccount *account, const char *who,
@@ -302,6 +320,18 @@
   @param new_arrival If the buddy is a new arrival.
  @endsignaldef
 
+ @signaldef chat-join-failed
+  @signalproto
+void (*chat_join_failed)(PurpleConnection *gc, GHashTable *components);
+  @endsignalproto
+  @signaldesc
+   Emitted when an account fails to join a chat room
+  @param gc The PurpleConnection of the account which failed to join the chat.
+  @param data    The components passed to serv_join_chat() originally.
+                 The hash function should be g_str_hash() and the equal
+                 function should be g_str_equal().
+ @endsignaldef
+
  @signaldef chat-buddy-flags
   @signalproto
 void (*chat_buddy_flags)(PurpleConversation *conv, const char *name,
@@ -391,6 +421,21 @@
           default behavior will be maintained: the user will be prompted.
  @endsignaldef
 
+ @signaldef chat-invite-blocked
+  @signalproto
+void (*chat_invite_blocked)(PurpleAccount *account, const char *inviter,
+                   const char *name, const char *message, GHashTable *data);
+  @endsignalproto
+  @signaldesc
+   Emitted when an invitation to join a chat is blocked.
+  @param account  The account the invitation was sent to.
+  @param inviter  The name of the person sending the invitation.
+  @param name     The name of the chat invited to.
+  @param message  The invitation message sent.
+  @param data     Hashtable containing data about the invited chat.
+  @since 2.5.0
+ @endsignaldef
+
  @signaldef chat-joined
   @signalproto
 void (*chat_joined)(PurpleConversation *conv);
--- a/doc/finch.1.in	Mon May 19 16:18:32 2008 +0000
+++ b/doc/finch.1.in	Sun May 25 04:30:41 2008 +0000
@@ -381,6 +381,12 @@
 one of \fBa-\fR, \fBalt-\fR, \fBm-\fR or \fBmeta-\fR. You can also use
 \fBhome\fR, \fBend\fR, \fBleft\fR, \fBright\fR etc. keys.
 
+To unbind a key which has a default binding, you simply bind it to the empty string.  For example, to unbind \fBAlt + q\fR from the Quit function, you would use:
+
+[GntWM::binding]
+.br
+a-q =
+
 .SH Menus
 You can also specify key-bindings to trigger specific menuitems in windows. For example, the following entry in \fI~/.gntrc\fR will bind \fBCtrl + t\fR to the 'Send IM...' item in the buddylist:
 
--- a/doc/pidgin.1.in	Mon May 19 16:18:32 2008 +0000
+++ b/doc/pidgin.1.in	Sun May 25 04:30:41 2008 +0000
@@ -19,9 +19,9 @@
 .\" License along with this manual; if not, write to the Free
 .\" Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 .\" Boston, MA  02111-1301  USA.
-.TH pidgin 1
+.TH pidgin 1 "" "Pidgin v@VERSION@"
 .SH NAME
-Pidgin v@VERSION@ \- Instant Messaging client
+pidgin \- Instant Messaging client
 .SH SYNOPSIS
 .TP 5
 \fBpidgin \fI[options]\fR
--- a/finch/gntaccount.c	Mon May 19 16:18:32 2008 +0000
+++ b/finch/gntaccount.c	Sun May 25 04:30:41 2008 +0000
@@ -43,6 +43,7 @@
 #include <notify.h>
 #include <plugin.h>
 #include <request.h>
+#include <savedstatuses.h>
 
 #include "gntaccount.h"
 #include "gntblist.h"
@@ -67,7 +68,7 @@
 	GntWidget *screenname;
 	GntWidget *password;
 	GntWidget *alias;
-	
+
 	GntWidget *splits;
 	GList *split_entries;
 
@@ -76,6 +77,7 @@
 
 	GntWidget *newmail;
 	GntWidget *remember;
+	GntWidget *regserver;
 } AccountEditDialog;
 
 /* This is necessary to close an edit-dialog when an account is deleted */
@@ -125,7 +127,7 @@
 				_("Username of an account must be non-empty."));
 		return;
 	}
-	
+
 	username = g_string_new(value);
 
 	if (prplinfo != NULL)
@@ -183,7 +185,7 @@
 	if (prplinfo)
 	{
 		GList *iter, *entries;
-		
+
 		for (iter = prplinfo->protocol_options, entries = dialog->prpl_entries;
 				iter && entries; iter = iter->next, entries = entries->next)
 		{
@@ -228,6 +230,20 @@
 		gnt_box_give_focus_to_child(GNT_BOX(accounts.window), accounts.tree);
 	}
 
+	if (prplinfo && prplinfo->register_user &&
+			gnt_check_box_get_checked(GNT_CHECK_BOX(dialog->regserver))) {
+		purple_account_register(account);
+	} else if (dialog->account == NULL) {
+		/* This is a new account. Set it to the current status. */
+		/* Xerox from gtkaccount.c :D */
+		const PurpleSavedStatus *saved_status;
+		saved_status = purple_savedstatus_get_current();
+		if (saved_status != NULL) {
+			purple_savedstatus_activate_for_account(saved_status, account);
+			purple_account_set_enabled(account, FINCH_UI, TRUE);
+		}
+	}
+
 	gnt_widget_destroy(dialog->window);
 }
 
@@ -419,6 +435,11 @@
 			}
 		}
 	}
+
+	/* Show the registration checkbox only in a new account dialog,
+	 * and when the selected prpl has the support for it. */
+	gnt_widget_set_visible(dialog->regserver, account == NULL &&
+			prplinfo->register_user != NULL);
 }
 
 static void
@@ -559,6 +580,10 @@
 	gnt_box_add_widget(GNT_BOX(window), dialog->remember);
 	gnt_box_add_widget(GNT_BOX(window), dialog->newmail);
 
+	/* Register checkbox */
+	dialog->regserver = gnt_check_box_new(_("Create this account on the server"));
+	gnt_box_add_widget(GNT_BOX(window), dialog->regserver);
+
 	gnt_box_add_widget(GNT_BOX(window), gnt_line_new(FALSE));
 
 	/* The advanced box */
--- a/finch/gntnotify.c	Mon May 19 16:18:32 2008 +0000
+++ b/finch/gntnotify.c	Sun May 25 04:30:41 2008 +0000
@@ -208,8 +208,10 @@
 	else
 	{
 		char *to;
+		gboolean newwin = (emaildialog.window == NULL);
 
-		setup_email_dialog();
+		if (newwin)
+			setup_email_dialog();
 
 		to = g_strdup_printf("%s (%s)", tos ? *tos : purple_account_get_username(account),
 					purple_account_get_protocol_name(account));
@@ -219,7 +221,10 @@
 					*subjects),
 				NULL, NULL);
 		g_free(to);
-		gnt_widget_show(emaildialog.window);
+		if (newwin)
+			gnt_widget_show(emaildialog.window);
+		else
+			gnt_window_present(emaildialog.window);
 		return NULL;
 	}
 
--- a/finch/libgnt/configure.ac	Mon May 19 16:18:32 2008 +0000
+++ b/finch/libgnt/configure.ac	Sun May 25 04:30:41 2008 +0000
@@ -24,10 +24,10 @@
 # Make sure to update ../../configure.ac with libgnt version changes.
 #
 
-m4_define([gnt_lt_current], [4])
+m4_define([gnt_lt_current], [5])
 m4_define([gnt_major_version], [2])
-m4_define([gnt_minor_version], [4])
-m4_define([gnt_micro_version], [2])
+m4_define([gnt_minor_version], [5])
+m4_define([gnt_micro_version], [0])
 m4_define([gnt_version_suffix], [])
 m4_define([gnt_version],
           [gnt_major_version.gnt_minor_version.gnt_micro_version])
--- a/finch/libgnt/gntentry.c	Mon May 19 16:18:32 2008 +0000
+++ b/finch/libgnt/gntentry.c	Sun May 25 04:30:41 2008 +0000
@@ -551,10 +551,10 @@
 	return TRUE;
 }
 
-#define SAME(a,b)    ((g_unichar_isalpha(a) && g_unichar_isalpha(b)) || \
-				(g_unichar_isdigit(a) && g_unichar_isdigit(b)) || \
+#define SAME(a,b)    ((g_unichar_isalnum(a) && g_unichar_isalnum(b)) || \
 				(g_unichar_isspace(a) && g_unichar_isspace(b)) || \
-				(g_unichar_iswide(a) && g_unichar_iswide(b)))
+				(g_unichar_iswide(a) && g_unichar_iswide(b)) || \
+				(g_unichar_ispunct(a) && g_unichar_ispunct(b)))
 
 static const char *
 begin_word(const char *text, const char *begin)
--- a/libpurple/Makefile.am	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/Makefile.am	Sun May 25 04:30:41 2008 +0000
@@ -68,6 +68,7 @@
 	savedstatuses.c \
 	server.c \
 	signals.c \
+	smiley.c \
 	dnsquery.c \
 	dnssrv.c\
 	status.c \
@@ -120,6 +121,7 @@
 	savedstatuses.h \
 	server.h \
 	signals.h \
+	smiley.h \
 	dnsquery.h \
 	dnssrv.h \
 	status.h \
@@ -154,7 +156,7 @@
 
 dbus_exported = dbus-useful.h dbus-define-api.h account.h blist.h buddyicon.h \
                 connection.h conversation.h core.h ft.h log.h notify.h prefs.h roomlist.h \
-                savedstatuses.h status.h server.h util.h xmlnode.h prpl.h
+                savedstatuses.h smiley.h status.h server.h util.h xmlnode.h prpl.h
 
 purple_build_coreheaders = $(addprefix $(srcdir)/, $(purple_coreheaders)) \
 		$(purple_builtheaders)
--- a/libpurple/Makefile.mingw	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/Makefile.mingw	Sun May 25 04:30:41 2008 +0000
@@ -65,6 +65,7 @@
 			savedstatuses.c \
 			server.c \
 			signals.c \
+			smiley.c \
 			sound.c \
 			sslconn.c \
 			status.c \
--- a/libpurple/blist.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/blist.c	Sun May 25 04:30:41 2008 +0000
@@ -824,16 +824,25 @@
 		ops->update(purplebuddylist, (PurpleBlistNode *)buddy);
 }
 
-void purple_blist_update_buddy_icon(PurpleBuddy *buddy)
+void
+purple_blist_update_node_icon(PurpleBlistNode *node)
 {
 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
 
-	g_return_if_fail(buddy != NULL);
+	g_return_if_fail(node != NULL);
 
 	if (ops && ops->update)
-		ops->update(purplebuddylist, (PurpleBlistNode *)buddy);
+		ops->update(purplebuddylist, node);
 }
 
+#ifndef PURPLE_DISABLE_DEPRECATED
+void
+purple_blist_update_buddy_icon(PurpleBuddy *buddy)
+{
+	purple_blist_update_node_icon((PurpleBlistNode *)buddy);
+}
+#endif
+
 /*
  * TODO: Maybe remove the call to this from server.c and call it
  * from oscar.c and toc.c instead?
@@ -1197,7 +1206,7 @@
 
 	purple_signal_emit(purple_blist_get_handle(), "buddy-icon-changed", buddy);
 
-	purple_blist_update_buddy_icon(buddy);
+	purple_blist_update_node_icon((PurpleBlistNode*)buddy);
 }
 
 PurpleAccount *
@@ -2189,7 +2198,7 @@
 	g_return_val_if_fail((name != NULL) && (*name != '\0'), NULL);
 
 	for (node = purplebuddylist->root; node != NULL; node = node->next) {
-		if (!strcmp(((PurpleGroup *)node)->name, name))
+		if (!purple_utf8_strcasecmp(((PurpleGroup *)node)->name, name))
 			return (PurpleGroup *)node;
 	}
 
--- a/libpurple/blist.h	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/blist.h	Sun May 25 04:30:41 2008 +0000
@@ -317,11 +317,22 @@
 void purple_blist_update_buddy_status(PurpleBuddy *buddy, PurpleStatus *old_status);
 
 /**
+ * Updates a node's custom icon.
+ *
+ * @param node  The PurpleBlistNode whose custom icon has changed.
+ * @since 2.5.0
+ */
+void purple_blist_update_node_icon(PurpleBlistNode *node);
+
+#ifndef PURPLE_DISABLE_DEPRECATED
+/**
  * Updates a buddy's icon.
  *
  * @param buddy  The buddy whose buddy icon has changed
+ * @deprecated Use purple_blist_update_node_icon() instead.
  */
 void purple_blist_update_buddy_icon(PurpleBuddy *buddy);
+#endif
 
 /**
  * Renames a buddy in the buddy list.
--- a/libpurple/buddyicon.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/buddyicon.c	Sun May 25 04:30:41 2008 +0000
@@ -92,8 +92,10 @@
  */
 static GHashTable *icon_file_cache = NULL;
 
-/* This one is used for both custom buddy icons
- * on PurpleContacts and account icons. */
+/**
+ * This hash table is used for both custom buddy icons on PurpleBlistNodes and
+ * account icons.
+ */
 static GHashTable *pointer_icon_cache = NULL;
 
 static char       *cache_dir     = NULL;
@@ -684,14 +686,6 @@
 	return (icon ? purple_buddy_icon_ref(icon) : NULL);
 }
 
-gboolean
-purple_buddy_icons_has_custom_icon(PurpleContact *contact)
-{
-	g_return_val_if_fail(contact != NULL, FALSE);
-
-	return (purple_blist_node_get_string((PurpleBlistNode*)contact, "custom_buddy_icon") != NULL);
-}
-
 PurpleStoredImage *
 purple_buddy_icons_find_account_icon(PurpleAccount *account)
 {
@@ -807,24 +801,32 @@
 	return ret;
 }
 
-PurpleStoredImage *
-purple_buddy_icons_find_custom_icon(PurpleContact *contact)
+gboolean
+purple_buddy_icons_node_has_custom_icon(PurpleBlistNode *node)
 {
-	PurpleStoredImage *img;
-	const char *custom_icon_file;
-	const char *dirname;
+	g_return_val_if_fail(node != NULL, FALSE);
+
+	return (purple_blist_node_get_string(node, "custom_buddy_icon") != NULL);
+}
+
+PurpleStoredImage *
+purple_buddy_icons_node_find_custom_icon(PurpleBlistNode *node)
+{
 	char *path;
+	size_t len;
 	guchar *data;
-	size_t len;
+	PurpleStoredImage *img;
+	const char *custom_icon_file, *dirname;
 
-	g_return_val_if_fail(contact != NULL, NULL);
+	g_return_val_if_fail(node != NULL, NULL);
 
-	if ((img = g_hash_table_lookup(pointer_icon_cache, contact)))
+	if ((img = g_hash_table_lookup(pointer_icon_cache, node)))
 	{
 		return purple_imgstore_ref(img);
 	}
 
-	custom_icon_file = purple_blist_node_get_string((PurpleBlistNode*)contact, "custom_buddy_icon");
+	custom_icon_file = purple_blist_node_get_string(node,
+	                                                "custom_buddy_icon");
 
 	if (custom_icon_file == NULL)
 		return NULL;
@@ -836,7 +838,7 @@
 	{
 		g_free(path);
 		img = purple_buddy_icon_data_new(data, len, custom_icon_file);
-		g_hash_table_insert(pointer_icon_cache, contact, img);
+		g_hash_table_insert(pointer_icon_cache, node, img);
 		return img;
 	}
 	g_free(path);
@@ -845,66 +847,79 @@
 }
 
 PurpleStoredImage *
-purple_buddy_icons_set_custom_icon(PurpleContact *contact,
-                                   guchar *icon_data, size_t icon_len)
+purple_buddy_icons_node_set_custom_icon(PurpleBlistNode *node,
+                                        guchar *icon_data, size_t icon_len)
 {
+	char *old_icon;
 	PurpleStoredImage *old_img;
 	PurpleStoredImage *img = NULL;
-	char *old_icon;
-	PurpleBlistNode *child;
+
+	g_return_val_if_fail(node != NULL, NULL);
 
-	old_img = g_hash_table_lookup(pointer_icon_cache, contact);
+	if (!PURPLE_BLIST_NODE_IS_CONTACT(node) &&
+	    !PURPLE_BLIST_NODE_IS_CHAT(node) &&
+	    !PURPLE_BLIST_NODE_IS_GROUP(node)) {
+		return NULL;
+	}
 
-	if (icon_data != NULL && icon_len > 0)
-	{
+	old_img = g_hash_table_lookup(pointer_icon_cache, node);
+
+	if (icon_data != NULL && icon_len > 0) {
 		img = purple_buddy_icon_data_new(icon_data, icon_len, NULL);
 	}
 
-	old_icon = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)contact,
+	old_icon = g_strdup(purple_blist_node_get_string(node,
 	                                                 "custom_buddy_icon"));
-	if (img && purple_buddy_icons_is_caching())
-	{
+	if (img && purple_buddy_icons_is_caching()) {
 		const char *filename = purple_imgstore_get_filename(img);
-		purple_blist_node_set_string((PurpleBlistNode *)contact,
-		                             "custom_buddy_icon",
+		purple_blist_node_set_string(node, "custom_buddy_icon",
 		                             filename);
 		ref_filename(filename);
-	}
-	else
-	{
-		purple_blist_node_remove_setting((PurpleBlistNode *)contact,
-		                                 "custom_buddy_icon");
+	} else {
+		purple_blist_node_remove_setting(node, "custom_buddy_icon");
 	}
 	unref_filename(old_icon);
 
 	if (img)
-		g_hash_table_insert(pointer_icon_cache, contact, img);
+		g_hash_table_insert(pointer_icon_cache, node, img);
 	else
-		g_hash_table_remove(pointer_icon_cache, contact);
+		g_hash_table_remove(pointer_icon_cache, node);
 
-	for (child = contact->node.child ; child ; child = child->next)
-	{
-		PurpleBuddy *buddy;
-		PurpleConversation *conv;
+	if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
+		PurpleBlistNode *child;
+		for (child = node->child ; child ; child = child->next)
+		{
+			PurpleBuddy *buddy;
+			PurpleConversation *conv;
+
+			if (!PURPLE_BLIST_NODE_IS_BUDDY(child))
+				continue;
+
+			buddy = (PurpleBuddy *)child;
 
-		if (!PURPLE_BLIST_NODE_IS_BUDDY(child))
-			continue;
-
-		buddy = (PurpleBuddy *)child;
+			conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, purple_buddy_get_name(buddy), purple_buddy_get_account(buddy));
+			if (conv)
+				purple_conversation_update(conv, PURPLE_CONV_UPDATE_ICON);
 
-		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
-		                                             purple_buddy_get_name(buddy),
-		                                             purple_buddy_get_account(buddy));
-		if (conv)
+			/* Is this call necessary anymore? Can the buddies
+			 * themselves need updating when the custom buddy
+			 * icon changes? */
+			purple_blist_update_node_icon((PurpleBlistNode*)buddy);
+		}
+	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
+		PurpleConversation *conv = NULL;
+
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, purple_chat_get_name((PurpleChat*)node), purple_chat_get_account((PurpleChat*)node));
+		if (conv) {
 			purple_conversation_update(conv, PURPLE_CONV_UPDATE_ICON);
-
-		purple_blist_update_buddy_icon(buddy);
+		}
 	}
 
-	if (old_img)
+	purple_blist_update_node_icon(node);
+
+	if (old_img) {
 		purple_imgstore_unref(old_img);
-	else if (old_icon)
-	{
+	} else if (old_icon) {
 		/* The old icon may not have been loaded into memory.  In that
 		 * case, we'll need to uncache the filename.  The filenames
 		 * are ref-counted, so this is safe. */
@@ -915,6 +930,49 @@
 	return img;
 }
 
+PurpleStoredImage *
+purple_buddy_icons_node_set_custom_icon_from_file(PurpleBlistNode *node,
+                                                  const gchar *filename)
+{
+	size_t len;
+	guchar *data;
+
+	g_return_val_if_fail(node != NULL, NULL);
+
+	if (!PURPLE_BLIST_NODE_IS_CONTACT(node) &&
+	    !PURPLE_BLIST_NODE_IS_CHAT(node) &&
+	    !PURPLE_BLIST_NODE_IS_GROUP(node)) {
+		return NULL;
+	}
+
+	if (!read_icon_file(filename, &data, &len)) {
+		return NULL;
+	}
+
+	return purple_buddy_icons_node_set_custom_icon(node, data, len);
+}
+
+#ifndef PURPLE_DISABLE_DEPRECATED
+gboolean
+purple_buddy_icons_has_custom_icon(PurpleContact *contact)
+{
+	return purple_buddy_icons_node_has_custom_icon((PurpleBlistNode*)contact);
+}
+
+PurpleStoredImage *
+purple_buddy_icons_find_custom_icon(PurpleContact *contact)
+{
+	return purple_buddy_icons_node_find_custom_icon((PurpleBlistNode*)contact);
+}
+
+PurpleStoredImage *
+purple_buddy_icons_set_custom_icon(PurpleContact *contact, guchar *icon_data,
+                                   size_t icon_len)
+{
+	return purple_buddy_icons_node_set_custom_icon((PurpleBlistNode*)contact, icon_data, icon_len);
+}
+#endif
+
 void
 _purple_buddy_icon_set_old_icons_dir(const char *dirname)
 {
@@ -1138,7 +1196,9 @@
 				}
 			}
 		}
-		else if (PURPLE_BLIST_NODE_IS_CONTACT(node))
+		else if (PURPLE_BLIST_NODE_IS_CONTACT(node) ||
+		         PURPLE_BLIST_NODE_IS_CHAT(node) ||
+		         PURPLE_BLIST_NODE_IS_GROUP(node))
 		{
 			const char *filename;
 
--- a/libpurple/buddyicon.h	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/buddyicon.h	Sun May 25 04:30:41 2008 +0000
@@ -218,16 +218,6 @@
 purple_buddy_icons_find(PurpleAccount *account, const char *username);
 
 /**
- * Returns a boolean indicating if a given contact has a custom buddy icon.
- *
- * @param contact The contact
- *
- * @return A boolean indicating if @a contact has a custom buddy icon.
- */
-gboolean
-purple_buddy_icons_has_custom_icon(PurpleContact *contact);
-
-/**
  * Returns the buddy icon image for an account.
  *
  * The caller owns a reference to the image in the store, and must dereference
@@ -277,7 +267,18 @@
 purple_buddy_icons_get_account_icon_timestamp(PurpleAccount *account);
 
 /**
- * Returns the custom buddy icon image for a contact.
+ * Returns a boolean indicating if a given blist node has a custom buddy icon.
+ *
+ * @param node The blist node.
+ *
+ * @return A boolean indicating if @a node has a custom buddy icon.
+ * @since 2.5.0
+ */
+gboolean
+purple_buddy_icons_node_has_custom_icon(PurpleBlistNode *node);
+
+/**
+ * Returns the custom buddy icon image for a blist node.
  *
  * The caller owns a reference to the image in the store, and must dereference
  * the image with purple_imgstore_unref() for it to be freed.
@@ -286,31 +287,82 @@
  * needed, so it should be called in any case where you want the
  * appropriate icon.
  *
- * @param contact The contact
+ * @param node The node.
+ *
+ * @return The custom buddy icon.
+ * @since 2.5.0
+ */
+PurpleStoredImage *
+purple_buddy_icons_node_find_custom_icon(PurpleBlistNode *node);
+
+/**
+ * Sets a custom buddy icon for a blist node.
+ *
+ * This function will deal with saving a record of the icon, caching the data,
+ * etc.
+ *
+ * @param node      The blist node for which to set a custom icon.
+ * @param icon_data The image data of the icon, which the buddy icon code will
+ *                  free.
+ * @param icon_len  The length of the data in @a icon_data.
+ *
+ * @return The icon that was set. The caller does NOT own a reference to this,
+ *         and must call purple_imgstore_ref() if it wants one.
+ * @since 2.5.0
+ */
+PurpleStoredImage *
+purple_buddy_icons_node_set_custom_icon(PurpleBlistNode *node,
+                                        guchar *icon_data, size_t icon_len);
+
+/**
+ * Sets a custom buddy icon for a blist node.
  *
- * @return The custom buddy icon image.
+ * Convenience wrapper around purple_buddy_icons_node_set_custom_icon.
+ * @see purple_buddy_icons_node_set_custom_icon()
+ *
+ * @param node      The blist node for which to set a custom icon.
+ * @param filename  The path to the icon to set for the blist node.
+ *
+ * @return The icon that was set. The caller does NOT own a reference to this,
+ *         and must call purple_imgstore_ref() if it wants one.
+ * @since 2.5.0
+ */
+PurpleStoredImage *
+purple_buddy_icons_node_set_custom_icon_from_file(PurpleBlistNode *node,
+                                                  const gchar *filename);
+
+#ifndef PURPLE_DISABLE_DEPRECATED
+/**
+ * PurpleContact version of purple_buddy_icons_node_has_custom_icon.
+ *
+ * @copydoc purple_buddy_icons_node_has_custom_icon()
+ *
+ * @deprecated Use purple_buddy_icons_node_has_custom_icon instead.
+ */
+gboolean
+purple_buddy_icons_has_custom_icon(PurpleContact *contact);
+
+/**
+ * PurpleContact version of purple_buddy_icons_node_find_custom_icon.
+ *
+ * @copydoc purple_buddy_icons_node_find_custom_icon()
+ *
+ * @deprecated Use purple_buddy_icons_node_find_custom_icon instead.
  */
 PurpleStoredImage *
 purple_buddy_icons_find_custom_icon(PurpleContact *contact);
 
 /**
- * Sets a custom buddy icon for a user.
- *
- * This function will deal with saving a record of the icon,
- * caching the data, etc.
+ * PurpleContact version of purple_buddy_icons_node_set_custom_icon.
  *
- * @param contact   The contact for which to set a custom icon.
- * @param icon_data The image data of the icon, which the
- *                  buddy icon code will free.
- * @param icon_len  The length of the data in @a icon_data.
+ * @copydoc purple_buddy_icons_node_set_custom_icon()
  *
- * @return The icon that was set.  The caller does NOT own
- *         a reference to this, and must call purple_imgstore_ref()
- *         if it wants one.
+ * @deprecated Use purple_buddy_icons_node_set_custom_icon instead.
  */
 PurpleStoredImage *
 purple_buddy_icons_set_custom_icon(PurpleContact *contact,
                                    guchar *icon_data, size_t icon_len);
+#endif
 
 /**
  * Sets whether or not buddy icon caching is enabled.
--- a/libpurple/connection.h	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/connection.h	Sun May 25 04:30:41 2008 +0000
@@ -43,6 +43,7 @@
 	PURPLE_CONNECTION_NO_FONTSIZE = 0x0020, /**< Connection does not send/receive font sizes */
 	PURPLE_CONNECTION_NO_URLDESC = 0x0040,  /**< Connection does not support descriptions with links */ 
 	PURPLE_CONNECTION_NO_IMAGES = 0x0080,  /**< Connection does not support sending of images */
+	PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY = 0x0100, /**< Connection supports sending and receiving custom smileys */
 
 } PurpleConnectionFlags;
 
--- a/libpurple/conversation.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/conversation.c	Sun May 25 04:30:41 2008 +0000
@@ -2235,6 +2235,16 @@
 										PURPLE_SUBTYPE_CONVERSATION),
 						 purple_value_new(PURPLE_TYPE_UINT));
 
+	purple_signal_register(handle, "blocked-im-msg",
+						 purple_marshal_VOID__POINTER_POINTER_POINTER_UINT_UINT,
+						 NULL, 5,
+						 purple_value_new(PURPLE_TYPE_SUBTYPE,
+							 PURPLE_SUBTYPE_ACCOUNT),
+						 purple_value_new(PURPLE_TYPE_STRING),
+						 purple_value_new(PURPLE_TYPE_STRING),
+						 purple_value_new(PURPLE_TYPE_UINT),
+						 purple_value_new(PURPLE_TYPE_UINT));
+
 	purple_signal_register(handle, "writing-chat-msg",
 						 purple_marshal_BOOLEAN__POINTER_POINTER_POINTER_POINTER_UINT,
 						 purple_value_new(PURPLE_TYPE_BOOLEAN), 5,
@@ -2390,11 +2400,27 @@
 						 purple_value_new(PURPLE_TYPE_STRING),
 						 purple_value_new(PURPLE_TYPE_POINTER));
 
+	purple_signal_register(handle, "chat-invite-blocked",
+						 purple_marshal_VOID__POINTER_POINTER_POINTER_POINTER_POINTER,
+						 NULL, 5,
+						 purple_value_new(PURPLE_TYPE_SUBTYPE,
+							 PURPLE_SUBTYPE_ACCOUNT),
+						 purple_value_new(PURPLE_TYPE_STRING),
+						 purple_value_new(PURPLE_TYPE_STRING),
+						 purple_value_new(PURPLE_TYPE_STRING),
+						 purple_value_new(PURPLE_TYPE_BOXED, "GHashTable *"));
+
 	purple_signal_register(handle, "chat-joined",
 						 purple_marshal_VOID__POINTER, NULL, 1,
 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
 										PURPLE_SUBTYPE_CONVERSATION));
 
+	purple_signal_register(handle, "chat-join-failed",
+						   purple_marshal_VOID__POINTER_POINTER, NULL, 2,
+						   purple_value_new(PURPLE_TYPE_SUBTYPE,
+										PURPLE_SUBTYPE_CONNECTION),
+						   purple_value_new(PURPLE_TYPE_POINTER));
+
 	purple_signal_register(handle, "chat-left",
 						 purple_marshal_VOID__POINTER, NULL, 1,
 						 purple_value_new(PURPLE_TYPE_SUBTYPE,
--- a/libpurple/core.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/core.c	Sun May 25 04:30:41 2008 +0000
@@ -43,6 +43,7 @@
 #include "proxy.h"
 #include "savedstatuses.h"
 #include "signals.h"
+#include "smiley.h"
 #include "sound.h"
 #include "sslconn.h"
 #include "status.h"
@@ -166,6 +167,7 @@
 	purple_stun_init();
 	purple_xfers_init();
 	purple_idle_init();
+	purple_smileys_init();
 
 	/*
 	 * Call this early on to try to auto-detect our IP address and
@@ -194,6 +196,7 @@
 	purple_connections_disconnect_all();
 
 	/* Save .xml files, remove signals, etc. */
+	purple_smileys_uninit();
 	purple_idle_uninit();
 	purple_ssl_uninit();
 	purple_pounces_uninit();
--- a/libpurple/dbus-analyze-functions.py	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/dbus-analyze-functions.py	Sun May 25 04:30:41 2008 +0000
@@ -483,6 +483,7 @@
         self.inputiter = iter(inputfile)
         self.functionregexp = \
              re.compile("^%s(\w[^()]*)\(([^()]*)\)\s*;\s*$" % fprefix)    
+        self.typeregexp = re.compile("^\w+\s*\*?\s*$")
 
 
                 
@@ -501,7 +502,7 @@
             # accumulate lines until the parentheses are balance or an
             # empty line has been encountered
             myline = line.strip()
-            while myline.count("(") > myline.count(")"):
+            while (myline.count("(") > myline.count(")")) or self.typeregexp.match(myline):
                 newline = self.inputiter.next().strip()
                 if len(newline) == 0:
                     break
--- a/libpurple/dbus-server.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/dbus-server.c	Sun May 25 04:30:41 2008 +0000
@@ -40,6 +40,7 @@
 #include "core.h"
 #include "internal.h"
 #include "savedstatuses.h"
+#include "smiley.h"
 #include "util.h"
 #include "value.h"
 #include "xmlnode.h"
--- a/libpurple/imgstore.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/imgstore.c	Sun May 25 04:30:41 2008 +0000
@@ -68,6 +68,22 @@
 	return img;
 }
 
+PurpleStoredImage *
+purple_imgstore_new_from_file(const char *path)
+{
+	gchar *data = NULL;
+	size_t len;
+	GError *err = NULL;
+
+	if (!g_file_get_contents(path, &data, &len, &err)) {
+		purple_debug_error("imgstore", "Error reading %s: %s\n",
+				path, err->message);
+		g_error_free(err);
+		return NULL;
+	}
+	return purple_imgstore_add(data, len, path);
+}
+
 int
 purple_imgstore_add_with_id(gpointer data, size_t size, const char *filename)
 {
--- a/libpurple/imgstore.h	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/imgstore.h	Sun May 25 04:30:41 2008 +0000
@@ -63,6 +63,17 @@
 purple_imgstore_add(gpointer data, size_t size, const char *filename);
 
 /**
+ * Create an image and add it to the store.
+ *
+ * @param path  The path to the image.
+ *
+ * @return  The stored image.
+ * @since 2.X.X
+ */
+PurpleStoredImage *
+purple_imgstore_new_from_file(const char *path);
+
+/**
  * Add an image to the store, allocating an ID.
  *
  * The caller owns a reference to the image in the store, and must dereference
--- a/libpurple/network.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/network.c	Sun May 25 04:30:41 2008 +0000
@@ -663,6 +663,7 @@
 
 	if (!dbus_g_proxy_call(nm_proxy, "state", &err, G_TYPE_INVALID, G_TYPE_UINT, &state, G_TYPE_INVALID)) {
 		/* XXX: Print an error? */
+		g_error_free(err);
 		return NM_STATE_UNKNOWN;
 	}
 
--- a/libpurple/plugins/perl/Makefile.am	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/plugins/perl/Makefile.am	Sun May 25 04:30:41 2008 +0000
@@ -67,6 +67,7 @@
 	common/SavedStatuses.xs \
 	common/Server.xs \
 	common/Signal.xs \
+	common/Smiley.xs \
 	common/Sound.xs \
 	common/Status.xs \
 	common/Stringref.xs \
--- a/libpurple/plugins/perl/common/MANIFEST	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/plugins/perl/common/MANIFEST	Sun May 25 04:30:41 2008 +0000
@@ -28,6 +28,7 @@
 SavedStatuses.xs
 Server.xs
 Signal.xs
+Smiley.xs
 Sound.xs
 Status.xs
 Stringref.xs
--- a/libpurple/plugins/perl/common/Makefile.mingw	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/plugins/perl/common/Makefile.mingw	Sun May 25 04:30:41 2008 +0000
@@ -61,8 +61,9 @@
 				Roomlist.xs \
 				SSLConn.xs \
 				SavedStatuses.xs \
+				Server.xs \
 				Signal.xs \
-				Server.xs \
+				Smiley.xs \
 				Sound.xs \
 				Status.xs \
 				Stringref.xs \
--- a/libpurple/plugins/perl/common/Purple.xs	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/plugins/perl/common/Purple.xs	Sun May 25 04:30:41 2008 +0000
@@ -30,6 +30,7 @@
 PURPLE_PERL_BOOT_PROTO(SavedStatus);
 PURPLE_PERL_BOOT_PROTO(Serv);
 PURPLE_PERL_BOOT_PROTO(Signal);
+PURPLE_PERL_BOOT_PROTO(Smiley);
 PURPLE_PERL_BOOT_PROTO(Sound);
 PURPLE_PERL_BOOT_PROTO(Status);
 PURPLE_PERL_BOOT_PROTO(Stringref);
@@ -68,6 +69,7 @@
 	PURPLE_PERL_BOOT(SavedStatus);
 	PURPLE_PERL_BOOT(Serv);
 	PURPLE_PERL_BOOT(Signal);
+	PURPLE_PERL_BOOT(Smiley);
 	PURPLE_PERL_BOOT(Sound);
 	PURPLE_PERL_BOOT(Status);
 	PURPLE_PERL_BOOT(Stringref);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/plugins/perl/common/Smiley.xs	Sun May 25 04:30:41 2008 +0000
@@ -0,0 +1,81 @@
+#include "module.h"
+
+MODULE = Purple::Smiley  PACKAGE = Purple::Smiley  PREFIX = purple_smiley_
+PROTOTYPES: ENABLE
+
+Purple::Smiley
+purple_smiley_new(img, shortcut)
+	Purple::StoredImage img
+	const char * shortcut
+
+Purple::Smiley
+purple_smiley_new_from_file(shortcut, filepath)
+	const char * shortcut
+	const char * filepath
+
+void
+purple_smiley_delete(smiley)
+	Purple::Smiley smiley
+
+gboolean
+purple_smiley_set_shortcut(smiley, shortcut)
+	Purple::Smiley smiley
+	const char * shortcut
+
+void
+purple_smiley_set_data(smiley, data, data_len, keepfilename)
+	Purple::Smiley  smiley
+	guchar * data
+	size_t  data_len
+	gboolean keepfilename
+
+const char *
+purple_smiley_get_shortcut(smiley)
+	Purple::Smiley smiley
+
+const char *
+purple_smiley_get_checksum(smiley)
+	Purple::Smiley smiley
+
+Purple::StoredImage
+purple_smiley_get_stored_image(smiley)
+	Purple::Smiley smiley
+
+gconstpointer
+purple_smiley_get_data(smiley, len)
+	Purple::Smiley smiley
+	size_t * len
+
+const char *
+purple_smiley_get_extension(smiley)
+	Purple::Smiley smiley
+
+
+gchar_own *
+purple_smiley_get_full_path(smiley)
+	Purple::Smiley smiley
+
+
+MODULE = Purple::Smiley  PACKAGE = Purple::Smileys  PREFIX = purple_smileys_
+PROTOTYPES: ENABLE
+
+void
+purple_smileys_get_all()
+PREINIT:
+    GList *l;
+PPCODE:
+    for (l = purple_smileys_get_all(); l != NULL; l = g_list_delete_link(l, l)) {
+        XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::Smiley")));
+    }
+
+Purple::Smiley
+purple_smileys_find_by_shortcut(shortcut)
+	const char * shortcut
+
+Purple::Smiley
+purple_smileys_find_by_checksum(checksum)
+	const char * checksum
+
+const char *
+purple_smileys_get_storing_dir()
+
--- a/libpurple/plugins/perl/common/module.h	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/plugins/perl/common/module.h	Sun May 25 04:30:41 2008 +0000
@@ -51,6 +51,7 @@
 #include "savedstatuses.h"
 #include "server.h"
 #include "signals.h"
+#include "smiley.h"
 #include "sound.h"
 #include "sslconn.h"
 #include "status.h"
@@ -240,6 +241,9 @@
 typedef PurpleSavedStatus *		Purple__SavedStatus;
 typedef PurpleSavedStatusSub *		Purple__SavedStatus__Sub;
 
+/* smiley.h */
+typedef PurpleSmiley *		Purple__Smiley;
+
 /* sound.h */
 typedef PurpleSoundEventID		Purple__SoundEventID;
 typedef PurpleSoundUiOps *		Purple__Sound__UiOps;
--- a/libpurple/plugins/perl/common/typemap	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/plugins/perl/common/typemap	Sun May 25 04:30:41 2008 +0000
@@ -151,6 +151,7 @@
 
 Purple::Presence				T_PurpleObj
 Purple::PresenceContext			T_IV
+Purple::Smiley				T_PurpleObj
 Purple::Status				T_PurpleObj
 Purple::StatusAttr			T_PurpleObj
 Purple::StatusPrimitive			T_IV
--- a/libpurple/protocols/bonjour/bonjour.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Sun May 25 04:30:41 2008 +0000
@@ -91,7 +91,7 @@
 		purple_connection_error_reason(gc,
 			PURPLE_CONNECTION_ERROR_OTHER_ERROR,
 			_("The Apple Bonjour For Windows toolkit wasn't found, see the FAQ at: "
-			  "http://developer.pidgin.im/wiki/Using%20Pidgin#CanIusePidginforBonjourLink-LocalMessaging"
+			  "http://d.pidgin.im/BonjourWindows"
 			  " for more information."));
 		return;
 	}
@@ -413,6 +413,15 @@
 }
 
 static gboolean
+bonjour_can_receive_file(PurpleConnection *connection, const char *who)
+{
+	PurpleBuddy *buddy = purple_find_buddy(connection->account, who);
+
+	return (buddy != NULL && buddy->proto_data != NULL);
+
+}
+
+static gboolean
 plugin_unload(PurplePlugin *plugin)
 {
 	/* These shouldn't happen here because they are allocated in _init() */
@@ -483,7 +492,7 @@
 	NULL,                                                    /* roomlist_get_list */
 	NULL,                                                    /* roomlist_cancel */
 	NULL,                                                    /* roomlist_expand_category */
-	NULL,                                                    /* can_receive_file */
+	bonjour_can_receive_file,                                /* can_receive_file */
 	bonjour_send_file,                                       /* send_file */
 	bonjour_new_xfer,                                        /* new_xfer */
 	NULL,                                                    /* offline_message */
@@ -495,6 +504,7 @@
 	NULL,
 	NULL,
 	NULL,
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/bonjour/bonjour_ft.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/bonjour/bonjour_ft.c	Sun May 25 04:30:41 2008 +0000
@@ -47,9 +47,8 @@
 static void
 xep_ft_si_reject(BonjourData *bd, const char *id, const char *to, const char *error_code, const char *error_type)
 {
-	xmlnode *error_node = NULL;
-	xmlnode *tmp_node = NULL;
-	XepIq *iq = NULL;
+	xmlnode *error_node;
+	XepIq *iq;
 
 	g_return_if_fail(error_code != NULL);
 	g_return_if_fail(error_type != NULL);
@@ -68,14 +67,14 @@
 
 	/* TODO: Make this better */
 	if (!strcmp(error_code, "403")) {
-		tmp_node = xmlnode_new_child(error_node, "forbidden");
+		xmlnode *tmp_node = xmlnode_new_child(error_node, "forbidden");
 		xmlnode_set_namespace(tmp_node, "urn:ietf:params:xml:ns:xmpp-stanzas");
 
 		tmp_node = xmlnode_new_child(error_node, "text");
 		xmlnode_set_namespace(tmp_node, "urn:ietf:params:xml:ns:xmpp-stanzas");
 		xmlnode_insert_data(tmp_node, "Offer Declined", -1);
 	} else if (!strcmp(error_code, "404")) {
-		tmp_node = xmlnode_new_child(error_node, "item-not-found");
+		xmlnode *tmp_node = xmlnode_new_child(error_node, "item-not-found");
 		xmlnode_set_namespace(tmp_node, "urn:ietf:params:xml:ns:xmpp-stanzas");
 	}
 
@@ -90,11 +89,10 @@
 
 static void bonjour_xfer_request_denied(PurpleXfer *xfer)
 {
-	XepXfer *xf = NULL;
+	XepXfer *xf = xfer->data;
 
 	purple_debug_info("bonjour", "Bonjour-xfer-request-denied.\n");
 
-	xf = xfer->data;
 	if(xf)
 		xep_ft_si_reject(xf->data, xf->sid, xfer->who, "403", "cancel");
 
@@ -149,9 +147,9 @@
 static PurpleXfer*
 bonjour_si_xfer_find(BonjourData *bd, const char *sid, const char *from)
 {
-	GSList *xfers = NULL;
-	PurpleXfer *xfer = NULL;
-	XepXfer *xf = NULL;
+	GSList *xfers;
+	PurpleXfer *xfer;
+	XepXfer *xf;
 
 	if(!sid || !from || !bd)
 		return NULL;
@@ -179,19 +177,12 @@
 static void
 xep_ft_si_offer(PurpleXfer *xfer, const gchar *to)
 {
-	xmlnode *si_node = NULL;
-	xmlnode *feature = NULL;
-	xmlnode *field = NULL;
-	xmlnode *option = NULL;
-	xmlnode *value = NULL;
-	xmlnode *file = NULL;
-	xmlnode *x = NULL;
-	XepIq *iq = NULL;
-	XepXfer *xf = NULL;
+	xmlnode *si_node, *feature, *field, *file, *x;
+	XepIq *iq;
+	XepXfer *xf = xfer->data;
 	BonjourData *bd = NULL;
 	char buf[32];
 
-	xf = xfer->data;
 	if(!xf)
 		return;
 
@@ -234,13 +225,13 @@
 	xmlnode_set_attrib(field, "type", "list-single");
 
 	if (xf->mode & XEP_BYTESTREAMS) {
-		option = xmlnode_new_child(field, "option");
-		value = xmlnode_new_child(option, "value");
+		xmlnode *option = xmlnode_new_child(field, "option");
+		xmlnode *value = xmlnode_new_child(option, "value");
 		xmlnode_insert_data(value, "http://jabber.org/protocol/bytestreams", -1);
 	}
 	if (xf->mode & XEP_IBB) {
-		option = xmlnode_new_child(field, "option");
-		value = xmlnode_new_child(option, "value");
+		xmlnode *option = xmlnode_new_child(field, "option");
+		xmlnode *value = xmlnode_new_child(option, "value");
 		xmlnode_insert_data(value, "http://jabber.org/protocol/ibb", -1);
 	}
 
@@ -250,13 +241,9 @@
 static void
 xep_ft_si_result(PurpleXfer *xfer, char *to)
 {
-	xmlnode *si_node = NULL;
-	xmlnode *feature = NULL;
-	xmlnode *field = NULL;
-	xmlnode *value = NULL;
-	xmlnode *x = NULL;
-	XepIq *iq = NULL;
-	XepXfer *xf = NULL;
+	xmlnode *si_node, *feature, *field, *value, *x;
+	XepIq *iq;
+	XepXfer *xf;
 	BonjourData *bd;
 
 	if(!to || !xfer)
@@ -295,8 +282,7 @@
 static void
 bonjour_free_xfer(PurpleXfer *xfer)
 {
-	XepXfer *xf = NULL;
-	BonjourData *bd = NULL;
+	XepXfer *xf;
 
 	if(xfer == NULL) {
 		purple_debug_info("bonjour", "bonjour-free-xfer-null.\n");
@@ -307,7 +293,7 @@
 
 	xf = (XepXfer*)xfer->data;
 	if(xf != NULL) {
-		bd = (BonjourData*)xf->data;
+		BonjourData *bd = (BonjourData*)xf->data;
 		if(bd != NULL) {
 			bd->xfer_lists = g_slist_remove(bd->xfer_lists, xfer);
 			purple_debug_info("bonjour", "B free xfer from lists(%p).\n", bd->xfer_lists);
@@ -332,8 +318,8 @@
 bonjour_new_xfer(PurpleConnection *gc, const char *who)
 {
 	PurpleXfer *xfer;
-	XepXfer *xep_xfer = NULL;
-	BonjourData *bd = NULL;
+	XepXfer *xep_xfer;
+	BonjourData *bd;
 
 	if(who == NULL || gc == NULL)
 		return NULL;
@@ -367,7 +353,7 @@
 void
 bonjour_send_file(PurpleConnection *gc, const char *who, const char *file)
 {
-	PurpleXfer *xfer = NULL;
+	PurpleXfer *xfer;
 
 	g_return_if_fail(gc != NULL);
 	g_return_if_fail(who != NULL);
@@ -386,9 +372,9 @@
 static void
 bonjour_xfer_init(PurpleXfer *xfer)
 {
-	PurpleBuddy *buddy = NULL;
-	BonjourBuddy *bb = NULL;
-	XepXfer *xf = NULL;
+	PurpleBuddy *buddy;
+	BonjourBuddy *bb;
+	XepXfer *xf;
 
 	xf = (XepXfer*)xfer->data;
 	if(xf == NULL)
@@ -398,7 +384,7 @@
 
 	buddy = purple_find_buddy(xfer->account, xfer->who);
 	/* this buddy is offline. */
-	if (buddy == NULL)
+	if (buddy == NULL || buddy->proto_data == NULL)
 		return;
 
 	bb = (BonjourBuddy *)buddy->proto_data;
@@ -420,8 +406,8 @@
 xep_si_parse(PurpleConnection *pc, xmlnode *packet, PurpleBuddy *pb)
 {
 	const char *type, *id;
-	BonjourData *bd = NULL;
-	PurpleXfer *xfer = NULL;
+	BonjourData *bd;
+	PurpleXfer *xfer;
 
 	if(pc == NULL || packet == NULL || pb == NULL)
 		return;
@@ -496,12 +482,9 @@
 void
 xep_bytestreams_parse(PurpleConnection *pc, xmlnode *packet, PurpleBuddy *pb)
 {
-	const char *type = NULL, *from = NULL;
-	xmlnode *query = NULL, *streamhost = NULL;
-	BonjourData *bd = NULL;
-	PurpleXfer *xfer = NULL;
-	XepXfer *xf = NULL;
-	int portnum;
+	const char *type, *from;
+	xmlnode *query;
+	BonjourData *bd;
 
 	if(pc == NULL || packet == NULL || pb == NULL)
 		return;
@@ -519,6 +502,7 @@
 		if(!strcmp(type, "set")) {
 			const char *iq_id, *sid;
 			gboolean found = FALSE;
+			PurpleXfer *xfer;
 
 			purple_debug_info("bonjour", "bytestream offer Message type - SET.\n");
 
@@ -529,6 +513,9 @@
 
 			if(xfer) {
 				const char *jid, *host, *port;
+				xmlnode *streamhost;
+				int portnum;
+				XepXfer *xf = NULL;
 
 				xf = (XepXfer*)xfer->data;
 				for(streamhost = xmlnode_get_child(query, "streamhost");
@@ -577,9 +564,9 @@
 bonjour_xfer_receive(PurpleConnection *pc, const char *id, const char *sid, const char *from,
 		     const int filesize, const char *filename, int option)
 {
-	PurpleXfer *xfer = NULL;
-	XepXfer *xf = NULL;
-	BonjourData *bd = NULL;
+	PurpleXfer *xfer;
+	XepXfer *xf;
+	BonjourData *bd;
 
 	if(pc == NULL || id == NULL || from == NULL)
 		return;
@@ -614,11 +601,10 @@
 bonjour_sock5_request_cb(gpointer data, gint source, PurpleInputCondition cond)
 {
 	PurpleXfer *xfer = data;
-	XepXfer *xf = NULL;
+	XepXfer *xf = xfer->data;
 	int acceptfd;
 	int len = 0;
 
-	xf = xfer->data;
 	if(xf == NULL)
 		return;
 
@@ -745,10 +731,9 @@
 	XepXfer *xf;
 	XepIq *iq;
 	xmlnode *query, *streamhost;
-	char *port;
-	const char *next_ip;
-	const char *local_ip = NULL;
-	char token [] = ";";
+	gchar *port;
+	const char *next_ip, *local_ip;
+	const char token [] = ";";
 	BonjourData *bd;
 
 	purple_debug_info("bonjour", "Bonjour-bytestreams-listen. sock=%d.\n", sock);
@@ -793,7 +778,7 @@
 static void
 bonjour_bytestreams_init(PurpleXfer *xfer)
 {
-	XepXfer *xf = NULL;
+	XepXfer *xf;
 	if(xfer == NULL)
 		return;
 	purple_debug_info("bonjour", "Bonjour-bytestreams-init.\n");
@@ -813,7 +798,7 @@
 {
 	PurpleXfer *xfer = data;
 	XepXfer *xf = xfer->data;
-	XepIq *iq = NULL;
+	XepIq *iq;
 	xmlnode *q_node, *tmp_node;
 	BonjourData *bd;
 
@@ -849,10 +834,10 @@
 static void
 bonjour_bytestreams_connect(PurpleXfer *xfer, PurpleBuddy *pb)
 {
-	XepXfer *xf = NULL;
+	XepXfer *xf;
 	char dstaddr[41];
 	unsigned char hashval[20];
-	char *p = NULL;
+	char *p;
 	int i;
 
 	if(xfer == NULL)
--- a/libpurple/protocols/bonjour/buddy.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/bonjour/buddy.c	Sun May 25 04:30:41 2008 +0000
@@ -156,9 +156,9 @@
 		purple_blist_node_set_flags((PurpleBlistNode *)buddy, PURPLE_BLIST_NODE_FLAG_NO_SAVE);
 		purple_blist_add_buddy(buddy, NULL, group, NULL);
 	}
-	
+
 	buddy->proto_data = bonjour_buddy;
-	
+
 	/* Create the alias for the buddy using the first and the last name */
 	if (bonjour_buddy->nick)
 		serv_got_alias(purple_account_get_connection(account), buddy->name, bonjour_buddy->nick);
@@ -206,10 +206,12 @@
  * If the buddy is being saved, mark as offline, otherwise delete
  */
 void bonjour_buddy_signed_off(PurpleBuddy *pb) {
-	if (PURPLE_BLIST_NODE_SHOULD_SAVE(pb))
+	if (PURPLE_BLIST_NODE_SHOULD_SAVE(pb)) {
 		purple_prpl_got_user_status(purple_buddy_get_account(pb),
 					    purple_buddy_get_name(pb), "offline", NULL);
-	else {
+		bonjour_buddy_delete(pb->proto_data);
+		pb->proto_data = NULL;
+	} else {
 		purple_account_remove_buddy(purple_buddy_get_account(pb), pb, NULL);
 		purple_blist_remove_buddy(pb);
 	}
--- a/libpurple/protocols/bonjour/jabber.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/bonjour/jabber.c	Sun May 25 04:30:41 2008 +0000
@@ -385,7 +385,7 @@
 			purple_debug_warning("bonjour", "receive error: %s\n", err ? err : "(null)");
 
 			bonjour_jabber_close_conversation(bconv);
-			if (bconv->pb != NULL) {
+			if (bconv->pb != NULL && bconv->pb->proto_data != NULL) {
 				BonjourBuddy *bb = bconv->pb->proto_data;
 				bb->conversation = NULL;
 			}
@@ -957,7 +957,7 @@
 	int ret;
 
 	pb = _find_or_start_conversation(jdata, to);
-	if (pb == NULL) {
+	if (pb == NULL || pb->proto_data == NULL) {
 		purple_debug_info("bonjour", "Can't send a message to an offline buddy (%s).\n", to);
 		/* You can not send a message to an offline buddy */
 		return -10000;
@@ -1103,8 +1103,10 @@
 		buddies = purple_find_buddies(jdata->account, NULL);
 		for (l = buddies; l; l = l->next) {
 			BonjourBuddy *bb = ((PurpleBuddy*) l->data)->proto_data;
-			bonjour_jabber_close_conversation(bb->conversation);
-			bb->conversation = NULL;
+			if (bb != NULL) {
+				bonjour_jabber_close_conversation(bb->conversation);
+				bb->conversation = NULL;
+			}
 		}
 
 		g_slist_free(buddies);
--- a/libpurple/protocols/gg/gg.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/gg/gg.c	Sun May 25 04:30:41 2008 +0000
@@ -1998,7 +1998,7 @@
 
 	serv_got_chat_in(gc, id,
 			 purple_account_get_username(purple_connection_get_account(gc)),
-			 0, message, time(NULL));
+			 flags, message, time(NULL));
 
 	return 0;
 }
@@ -2152,6 +2152,7 @@
 	NULL,
 	NULL,
 	NULL,
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 /* }}} */
--- a/libpurple/protocols/irc/irc.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/irc/irc.c	Sun May 25 04:30:41 2008 +0000
@@ -737,7 +737,7 @@
 
 	irc_cmd_privmsg(irc, "msg", NULL, args);
 
-	serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), 0, what, time(NULL));
+	serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), flags, what, time(NULL));
 	g_free(tmp);
 	return 0;
 }
@@ -909,6 +909,7 @@
 	NULL,
 	NULL,
 	NULL,
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/jabber/auth.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/jabber/auth.c	Sun May 25 04:30:41 2008 +0000
@@ -499,6 +499,15 @@
 	{
 		char *mech_name = xmlnode_get_data(mechnode);
 #ifdef HAVE_CYRUS_SASL
+		/* Don't include Google Talk's X-GOOGLE-TOKEN mechanism, as we will not
+		 * support it and including it gives a false fall-back to other mechs offerred,
+		 * leading to incorrect error handling.
+		 */
+		if (mech_name && !strcmp(mech_name, "X-GOOGLE-TOKEN")) {
+			g_free(mech_name);
+			continue;
+		}
+
 		g_string_append(js->sasl_mechs, mech_name);
 		g_string_append_c(js->sasl_mechs, ' ');
 #else
@@ -936,6 +945,7 @@
 					_("Invalid challenge from server"));
 			}
 			g_free(js->expected_rspauth);
+			js->expected_rspauth = NULL;
 		} else {
 			/* assemble a response, and send it */
 			/* see RFC 2831 */
--- a/libpurple/protocols/jabber/chat.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/jabber/chat.c	Sun May 25 04:30:41 2008 +0000
@@ -197,6 +197,12 @@
 	return chat_name;
 }
 
+static void insert_in_hash_table(gpointer key, gpointer value, gpointer user_data)
+{
+	GHashTable *hash_table = (GHashTable *)user_data;
+	g_hash_table_insert(hash_table, g_strdup(key), g_strdup(value));
+}
+
 void jabber_chat_join(PurpleConnection *gc, GHashTable *data)
 {
 	JabberChat *chat;
@@ -225,18 +231,21 @@
 		char *buf = g_strdup_printf(_("%s is not a valid room name"), room);
 		purple_notify_error(gc, _("Invalid Room Name"), _("Invalid Room Name"),
 				buf);
+		purple_serv_got_join_chat_failed(gc, data);
 		g_free(buf);
 		return;
 	} else if(!jabber_nameprep_validate(server)) {
 		char *buf = g_strdup_printf(_("%s is not a valid server name"), server);
 		purple_notify_error(gc, _("Invalid Server Name"),
 				_("Invalid Server Name"), buf);
+		purple_serv_got_join_chat_failed(gc, data);
 		g_free(buf);
 		return;
 	} else if(!jabber_resourceprep_validate(handle)) {
 		char *buf = g_strdup_printf(_("%s is not a valid room handle"), handle);
 		purple_notify_error(gc, _("Invalid Room Handle"),
 				_("Invalid Room Handle"), buf);
+		purple_serv_got_join_chat_failed(gc, data);
 		g_free(buf);
 		return;
 	}
@@ -255,6 +264,11 @@
 	chat->server = g_strdup(server);
 	chat->handle = g_strdup(handle);
 
+	/* Copy the data hash table to chat->components */
+	chat->components = g_hash_table_new_full(g_str_hash, g_str_equal,
+			g_free, g_free);
+	g_hash_table_foreach(data, insert_in_hash_table, chat->components);
+
 	chat->members = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
 			(GDestroyNotify)jabber_chat_member_free);
 
@@ -315,6 +329,7 @@
 	g_free(chat->server);
 	g_free(chat->handle);
 	g_hash_table_destroy(chat->members);
+	g_hash_table_destroy(chat->components);
 	g_free(chat);
 }
 
--- a/libpurple/protocols/jabber/chat.h	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/jabber/chat.h	Sun May 25 04:30:41 2008 +0000
@@ -41,6 +41,7 @@
 	char *room;
 	char *server;
 	char *handle;
+	GHashTable *components;
 	int id;
 	PurpleConversation *conv;
 	gboolean muc;
--- a/libpurple/protocols/jabber/libxmpp.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Sun May 25 04:30:41 2008 +0000
@@ -116,7 +116,7 @@
 	jabber_send_attention,			/* send_attention */
 	jabber_attention_types,			/* attention_types */
 
-	/* padding */
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/jabber/message.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/jabber/message.c	Sun May 25 04:30:41 2008 +0000
@@ -409,12 +409,20 @@
 			const char *code = xmlnode_get_attrib(child, "code");
 			char *code_txt = NULL;
 			char *text = xmlnode_get_data(child);
+			if (!text) {
+				xmlnode *enclosed_text_node;
+				
+				if ((enclosed_text_node = xmlnode_get_child(child, "text")))
+					text = xmlnode_get_data(enclosed_text_node);
+			}
 
 			if(code)
-				code_txt = g_strdup_printf(_(" (Code %s)"), code);
+				code_txt = g_strdup_printf(_("(Code %s)"), code);
 
 			if(!jm->error)
-				jm->error = g_strdup_printf("%s%s", text ? text : "",
+				jm->error = g_strdup_printf("%s%s%s",
+						text ? text : "",
+						text && code_txt ? " " : "",
 						code_txt ? code_txt : "");
 
 			g_free(code_txt);
--- a/libpurple/protocols/jabber/presence.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/jabber/presence.c	Sun May 25 04:30:41 2008 +0000
@@ -587,6 +587,7 @@
 					serv_got_chat_left(js->gc, chat->id);
 			} else {
 				title = g_strdup_printf(_("Error joining chat %s"), from);
+				purple_serv_got_join_chat_failed(js->gc, chat->components);
 			}
 			purple_notify_error(js->gc, title, title, msg);
 			g_free(title);
--- a/libpurple/protocols/msn/cmdproc.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/msn/cmdproc.c	Sun May 25 04:30:41 2008 +0000
@@ -132,6 +132,14 @@
 		data = g_realloc(data, len + trans->payload_len);
 		memcpy(data + len, trans->payload, trans->payload_len);
 		len += trans->payload_len;
+
+		/*
+		 * We're done with trans->payload.  Free it so that the memory
+		 * doesn't sit around in cmdproc->history.
+		 */
+		g_free(trans->payload);
+		trans->payload = NULL;
+		trans->payload_len = 0;
 	}
 
 	msn_servconn_write(servconn, data, len);
--- a/libpurple/protocols/msn/msg.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/msn/msg.c	Sun May 25 04:30:41 2008 +0000
@@ -151,9 +151,9 @@
 	MsnMessage *msg;
 
 	msg = msn_message_new(MSN_MSG_NUDGE);
-	msn_message_set_content_type(msg, "text/x-msnmsgr-datacast\r\n");
+	msn_message_set_content_type(msg, "text/x-msnmsgr-datacast");
 	msn_message_set_flag(msg, 'N');
-	msn_message_set_attr(msg,"ID","1\r\n");
+	msn_message_set_bin_data(msg, "ID: 1\r\n", 7);
 
 	return msg;
 }
--- a/libpurple/protocols/msn/msn.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/msn/msn.c	Sun May 25 04:30:41 2008 +0000
@@ -32,6 +32,7 @@
 #include "pluginpref.h"
 #include "prefs.h"
 #include "session.h"
+#include "smiley.h"
 #include "state.h"
 #include "util.h"
 #include "cmds.h"
@@ -82,6 +83,12 @@
 	time_t when;
 } MsnIMData;
 
+typedef struct
+{
+	char *smile;
+	MsnObject *obj;
+} MsnEmoticon;
+
 static const char *
 msn_normalize(const PurpleAccount *account, const char *str)
 {
@@ -115,6 +122,7 @@
 		return FALSE;
 
 	msn_switchboard_send_msg(swboard, msg, TRUE);
+	msn_message_destroy(msg);
 
 	return TRUE;
 }
@@ -132,6 +140,17 @@
 	return list;
 }
 
+static GHashTable *
+msn_get_account_text_table(PurpleAccount *unused)
+{
+	GHashTable *table;
+
+	table = g_hash_table_new(g_str_hash, g_str_equal);
+
+	g_hash_table_insert(table, "login_label", (gpointer)_("E-mail Address..."));
+
+	return table;
+}
 
 static PurpleCmdRet
 msn_cmd_nudge(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
@@ -908,7 +927,8 @@
 	session = msn_session_new(account);
 
 	gc->proto_data = session;
-	gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR | PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC;
+	gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
+		PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
 
 	msn_session_set_login_step(session, MSN_LOGIN_STEP_START);
 
@@ -949,6 +969,97 @@
 	return FALSE;
 }
 
+static GString*
+msn_msg_emoticon_add(GString *current, MsnEmoticon *emoticon)
+{
+	MsnObject *obj;
+	char *strobj;
+
+	if (emoticon == NULL)
+		return current;
+
+	obj = emoticon->obj;
+
+	if (!obj)
+		return current;
+
+	strobj = msn_object_to_string(obj);
+
+	if (current)
+		g_string_append_printf(current, "\t%s\t%s",
+				emoticon->smile, strobj);
+	else {
+		current = g_string_new("");
+		g_string_printf(current,"%s\t%s",
+					emoticon->smile, strobj);
+	}
+
+	g_free(strobj);
+
+	return current;
+}
+
+static void
+msn_send_emoticons(MsnSwitchBoard *swboard, GString *body)
+{
+	MsnMessage *msg;
+
+	g_return_if_fail(body != NULL);
+
+	msg = msn_message_new(MSN_MSG_SLP);
+	msn_message_set_content_type(msg, "text/x-mms-emoticon");
+	msn_message_set_flag(msg, 'N');
+	msn_message_set_bin_data(msg, body->str, body->len);
+
+	msn_switchboard_send_msg(swboard, msg, TRUE);
+	msn_message_destroy(msg);
+}
+
+static void msn_emoticon_destroy(MsnEmoticon *emoticon)
+{
+	if (emoticon->obj)
+		msn_object_destroy(emoticon->obj);
+	g_free(emoticon->smile);
+	g_free(emoticon);
+}
+
+static GSList* msn_msg_grab_emoticons(const char *msg, const char *username)
+{
+	GSList *list;
+	GList *smileys;
+	PurpleSmiley *smiley;
+	PurpleStoredImage *img;
+	char *ptr;
+	MsnEmoticon *emoticon;
+	int length;
+
+	list = NULL;
+	smileys = purple_smileys_get_all();
+	length = strlen(msg);
+
+	for (; smileys; smileys = g_list_delete_link(smileys, smileys)) {
+		smiley = smileys->data;
+
+		ptr = g_strstr_len(msg, length, purple_smiley_get_shortcut(smiley));
+
+		if (!ptr)
+			continue;
+
+		img = purple_smiley_get_stored_image(smiley);
+
+		emoticon = g_new0(MsnEmoticon, 1);
+		emoticon->smile = g_strdup(purple_smiley_get_shortcut(smiley));
+		emoticon->obj = msn_object_new_from_image(img,
+				purple_imgstore_get_filename(img),
+				username, MSN_OBJECT_EMOTICON);
+
+		purple_imgstore_unref(img);
+		list = g_slist_prepend(list, emoticon);
+	}
+
+	return list;
+}
+
 static int
 msn_send_im(PurpleConnection *gc, const char *who, const char *message,
 			PurpleMessageFlags flags)
@@ -958,9 +1069,11 @@
 	MsnMessage *msg;
 	char *msgformat;
 	char *msgtext;
+	const char *username;
 
 	purple_debug_info("MSNP14","send IM {%s} to %s\n",message,who);
 	account = purple_connection_get_account(gc);
+	username = purple_account_get_username(account);
 
 	if (buddy) {
 		PurplePresence *p = purple_buddy_get_presence(buddy);
@@ -993,10 +1106,13 @@
 		g_free(msgtext);
 
 		purple_debug_info("MSNP14","prepare to send online Message\n");
-		if (g_ascii_strcasecmp(who, purple_account_get_username(account)))
+		if (g_ascii_strcasecmp(who, username))
 		{
 			MsnSession *session;
 			MsnSwitchBoard *swboard;
+			MsnEmoticon *smile;
+			GSList *smileys;
+			GString *emoticons = NULL;
 
 			session = gc->proto_data;
 			if(msn_user_is_yahoo(account,who)){
@@ -1006,6 +1122,19 @@
 			}else{
 				purple_debug_info("MSNP14","send via switchboard\n");
 				swboard = msn_session_get_swboard(session, who, MSN_SB_FLAG_IM);
+				smileys = msn_msg_grab_emoticons(message, username);
+				while (smileys) {
+					smile = (MsnEmoticon*)smileys->data;
+					emoticons = msn_msg_emoticon_add(emoticons, smile);
+					msn_emoticon_destroy(smile);
+					smileys = g_slist_delete_link(smileys, smileys);
+				}
+
+				if (emoticons) {
+					msn_send_emoticons(swboard, emoticons);
+					g_string_free(emoticons, TRUE);
+				}
+
 				msn_switchboard_send_msg(swboard, msg, TRUE);
 			}
 		}
@@ -1446,7 +1575,7 @@
 	g_free(msgformat);
 	g_free(msgtext);
 
-	serv_got_chat_in(gc, id, purple_account_get_username(account), 0,
+	serv_got_chat_in(gc, id, purple_account_get_username(account), flags,
 					 message, time(NULL));
 
 	return 0;
@@ -2337,8 +2466,8 @@
 	msn_send_attention,                     /* send_attention */
 	msn_attention_types,                    /* attention_types */
 
-	/* padding */
-	NULL
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
+	msn_get_account_text_table,             /* get_account_text_table */
 };
 
 static PurplePluginInfo info =
--- a/libpurple/protocols/msn/object.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/msn/object.c	Sun May 25 04:30:41 2008 +0000
@@ -23,6 +23,10 @@
  */
 #include "object.h"
 #include "debug.h"
+/* Sha1 stuff */
+#include "cipher.h"
+/* Base64 stuff */
+#include "util.h"
 
 #define GET_STRING_TAG(field, id) \
 	if ((tag = strstr(str, id "=\"")) != NULL) \
@@ -104,6 +108,74 @@
 	return obj;
 }
 
+MsnObject*
+msn_object_new_from_image(PurpleStoredImage *img, const char *location,
+		const char *creator, MsnObjectType type)
+{
+	MsnObject *msnobj;
+
+	PurpleCipherContext *ctx;
+	char *buf;
+	gconstpointer data;
+	size_t size;
+	char *base64;
+	unsigned char digest[20];
+
+	msnobj = NULL;
+
+	if (img == NULL)
+		return msnobj;
+
+	size = purple_imgstore_get_size(img);
+	data = purple_imgstore_get_data(img);
+
+	/* New object */
+	msnobj = msn_object_new();
+	msn_object_set_local(msnobj);
+	msn_object_set_type(msnobj, type);
+	msn_object_set_location(msnobj, location);
+	msn_object_set_creator(msnobj, creator);
+
+	msn_object_set_image(msnobj, img);
+
+	/* Compute the SHA1D field. */
+	memset(digest, 0, sizeof(digest));
+
+	ctx = purple_cipher_context_new_by_name("sha1", NULL);
+	purple_cipher_context_append(ctx, data, size);
+	purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
+
+	base64 = purple_base64_encode(digest, sizeof(digest));
+	msn_object_set_sha1d(msnobj, base64);
+	g_free(base64);
+
+	msn_object_set_size(msnobj, size);
+
+	/* Compute the SHA1C field. */
+	buf = g_strdup_printf(
+		"Creator%sSize%dType%dLocation%sFriendly%sSHA1D%s",
+		msn_object_get_creator(msnobj),
+		msn_object_get_size(msnobj),
+		msn_object_get_type(msnobj),
+		msn_object_get_location(msnobj),
+		msn_object_get_friendly(msnobj),
+		msn_object_get_sha1d(msnobj));
+
+	memset(digest, 0, sizeof(digest));
+
+	purple_cipher_context_reset(ctx, NULL);
+	purple_cipher_context_append(ctx, (const guchar *)buf, strlen(buf));
+	purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
+	purple_cipher_context_destroy(ctx);
+	g_free(buf);
+
+	base64 = purple_base64_encode(digest, sizeof(digest));
+	msn_object_set_sha1c(msnobj, base64);
+	g_free(base64);
+	
+	return msnobj;
+}
+
 void
 msn_object_destroy(MsnObject *obj)
 {
--- a/libpurple/protocols/msn/object.h	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/msn/object.h	Sun May 25 04:30:41 2008 +0000
@@ -71,6 +71,19 @@
 MsnObject *msn_object_new_from_string(const char *str);
 
 /**
+ * Creates a MsnObject structure from a stored image
+ *
+ * @param img		The image associated to object
+ * @param location	The object location as stored in MsnObject
+ * @param creator	The creator of the object
+ * @param type		The type of the object
+ *
+ * @return A new MsnObject structure
+ */
+MsnObject *msn_object_new_from_image(PurpleStoredImage *img,
+		const char *location, const char *creator, MsnObjectType type);
+
+/**
  * Destroys an MsnObject structure.
  *
  * @param obj The object structure.
--- a/libpurple/protocols/msn/slp.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/msn/slp.c	Sun May 25 04:30:41 2008 +0000
@@ -31,6 +31,8 @@
 #include "user.h"
 #include "switchboard.h"
 
+#include "smiley.h"
+
 /* ms to delay between sending buddy icon requests to the server. */
 #define BUDDY_ICON_DELAY 20000
 /*debug SLP*/
@@ -278,23 +280,32 @@
 		type = msn_object_get_type(obj);
 		g_free(msnobj_data);
 
-		if (!(type == MSN_OBJECT_USERTILE))
+		if ((type != MSN_OBJECT_USERTILE) && (type != MSN_OBJECT_EMOTICON))
 		{
 			purple_debug_error("msn", "Wrong object?\n");
 			msn_object_destroy(obj);
 			g_return_if_reached();
 		}
 
-		img = msn_object_get_image(obj);
+		if (type == MSN_OBJECT_EMOTICON) {
+			char *path;
+			path = g_build_filename(purple_smileys_get_storing_dir(),
+					obj->location, NULL);
+			img = purple_imgstore_new_from_file(path);
+			g_free(path);
+		} else {
+			img = msn_object_get_image(obj);
+			if (img)
+				purple_imgstore_ref(img);
+		}
+		msn_object_destroy(obj);
+
 		if (img == NULL)
 		{
 			purple_debug_error("msn", "Wrong object.\n");
-			msn_object_destroy(obj);
 			g_return_if_reached();
 		}
 
-		msn_object_destroy(obj);
-
 		slpsession = msn_slplink_find_slp_session(slplink,
 												  slpcall->session_id);
 
@@ -319,6 +330,7 @@
 #endif
 		msn_slpmsg_set_image(slpmsg, img);
 		msn_slplink_queue_slpmsg(slplink, slpmsg);
+		purple_imgstore_unref(img);
 	}
 	else if (!strcmp(euf_guid, "5D3E02AB-6190-11D3-BBBB-00C04F795683"))
 	{
--- a/libpurple/protocols/msn/user.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/msn/user.c	Sun May 25 04:30:41 2008 +0000
@@ -246,69 +246,17 @@
 void
 msn_user_set_buddy_icon(MsnUser *user, PurpleStoredImage *img)
 {
-	MsnObject *msnobj = msn_user_get_object(user);
+	MsnObject *msnobj;
 
 	g_return_if_fail(user != NULL);
 
-	if (img == NULL)
-		msn_user_set_object(user, NULL);
-	else
-	{
-		PurpleCipherContext *ctx;
-		char *buf;
-		gconstpointer data = purple_imgstore_get_data(img);
-		size_t size = purple_imgstore_get_size(img);
-		char *base64;
-		unsigned char digest[20];
-
-		if (msnobj == NULL)
-		{
-			msnobj = msn_object_new();
-			msn_object_set_local(msnobj);
-			msn_object_set_type(msnobj, MSN_OBJECT_USERTILE);
-			msn_object_set_location(msnobj, "TFR2C2.tmp");
-			msn_object_set_creator(msnobj, msn_user_get_passport(user));
-
-			msn_user_set_object(user, msnobj);
-		}
-
-		msn_object_set_image(msnobj, img);
-
-		/* Compute the SHA1D field. */
-		memset(digest, 0, sizeof(digest));
+	msnobj = msn_object_new_from_image(img, "TFR2C2.tmp",
+			user->passport, MSN_OBJECT_USERTILE);
 
-		ctx = purple_cipher_context_new_by_name("sha1", NULL);
-		purple_cipher_context_append(ctx, data, size);
-		purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
-
-		base64 = purple_base64_encode(digest, sizeof(digest));
-		msn_object_set_sha1d(msnobj, base64);
-		g_free(base64);
-
-		msn_object_set_size(msnobj, size);
+	if (!msnobj)
+		purple_debug_error("msn", "Unable to open buddy icon from %s!\n", user->passport);
 
-		/* Compute the SHA1C field. */
-		buf = g_strdup_printf(
-			"Creator%sSize%dType%dLocation%sFriendly%sSHA1D%s",
-			msn_object_get_creator(msnobj),
-			msn_object_get_size(msnobj),
-			msn_object_get_type(msnobj),
-			msn_object_get_location(msnobj),
-			msn_object_get_friendly(msnobj),
-			msn_object_get_sha1d(msnobj));
-
-		memset(digest, 0, sizeof(digest));
-
-		purple_cipher_context_reset(ctx, NULL);
-		purple_cipher_context_append(ctx, (const guchar *)buf, strlen(buf));
-		purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
-		purple_cipher_context_destroy(ctx);
-		g_free(buf);
-
-		base64 = purple_base64_encode(digest, sizeof(digest));
-		msn_object_set_sha1c(msnobj, base64);
-		g_free(base64);
-	}
+	msn_user_set_object(user, msnobj);
 }
 
 /*add group id to User object*/
--- a/libpurple/protocols/msnp9/cmdproc.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/msnp9/cmdproc.c	Sun May 25 04:30:41 2008 +0000
@@ -132,6 +132,14 @@
 		data = g_realloc(data, len + trans->payload_len);
 		memcpy(data + len, trans->payload, trans->payload_len);
 		len += trans->payload_len;
+
+		/*
+		 * We're done with trans->payload.  Free it so that the memory
+		 * doesn't sit around in cmdproc->history.
+		 */
+		g_free(trans->payload);
+		trans->payload = NULL;
+		trans->payload_len = 0;
 	}
 
 	msn_servconn_write(servconn, data, len);
--- a/libpurple/protocols/msnp9/msg.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/msnp9/msg.c	Sun May 25 04:30:41 2008 +0000
@@ -158,9 +158,9 @@
 	MsnMessage *msg;
 
 	msg = msn_message_new(MSN_MSG_NUDGE);
-	msn_message_set_content_type(msg, "text/x-msnmsgr-datacast\r\n");
+	msn_message_set_content_type(msg, "text/x-msnmsgr-datacast");
 	msn_message_set_flag(msg, 'N');
-	msn_message_set_attr(msg,"ID","1\r\n");
+	msn_message_set_bin_data(msg, "ID: 1\r\n", 7);
 
 	return msg;
 }
--- a/libpurple/protocols/msnp9/msn.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/msnp9/msn.c	Sun May 25 04:30:41 2008 +0000
@@ -33,6 +33,7 @@
 #include "pluginpref.h"
 #include "prefs.h"
 #include "session.h"
+#include "smiley.h"
 #include "state.h"
 #include "util.h"
 #include "cmds.h"
@@ -83,6 +84,12 @@
 	time_t when;
 } MsnIMData;
 
+typedef struct
+{
+	char *smile;
+	MsnObject *obj;
+} MsnEmoticon;
+
 static const char *
 msn_normalize(const PurpleAccount *account, const char *str)
 {
@@ -116,6 +123,7 @@
 		return FALSE;
 
 	msn_switchboard_send_msg(swboard, msg, TRUE);
+	msn_message_destroy(msg);
 
 	return TRUE;
 }
@@ -133,6 +141,17 @@
 	return list;
 }
 
+static GHashTable *
+msn_get_account_text_table(PurpleAccount *unused)
+{
+	GHashTable *table;
+
+	table = g_hash_table_new(g_str_hash, g_str_equal);
+
+	g_hash_table_insert(table, "login_label", (gpointer)_("E-mail Address..."));
+
+	return table;
+}
 
 static PurpleCmdRet
 msn_cmd_nudge(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
@@ -760,7 +779,8 @@
 	session = msn_session_new(account);
 
 	gc->proto_data = session;
-	gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR | PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC;
+	gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
+		PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
 
 	msn_session_set_login_step(session, MSN_LOGIN_STEP_START);
 
@@ -801,6 +821,97 @@
 	return FALSE;
 }
 
+static GString*
+msn_msg_emoticon_add(GString *current, MsnEmoticon *emoticon)
+{
+	MsnObject *obj;
+	char *strobj;
+
+	if (emoticon == NULL)
+		return current;
+
+	obj = emoticon->obj;
+
+	if (!obj)
+		return current;
+
+	strobj = msn_object_to_string(obj);
+
+	if (current)
+		g_string_append_printf(current, "\t%s\t%s",
+				emoticon->smile, strobj);
+	else {
+		current = g_string_new("");
+		g_string_printf(current,"%s\t%s",
+					emoticon->smile, strobj);
+	}
+
+	g_free(strobj);
+
+	return current;
+}
+
+static void
+msn_send_emoticons(MsnSwitchBoard *swboard, GString *body)
+{
+	MsnMessage *msg;
+
+	g_return_if_fail(body != NULL);
+
+	msg = msn_message_new(MSN_MSG_SLP);
+	msn_message_set_content_type(msg, "text/x-mms-emoticon");
+	msn_message_set_flag(msg, 'N');
+	msn_message_set_bin_data(msg, body->str, body->len);
+
+	msn_switchboard_send_msg(swboard, msg, TRUE);
+	msn_message_destroy(msg);
+}
+
+static void msn_emoticon_destroy(MsnEmoticon *emoticon)
+{
+	if (emoticon->obj)
+		msn_object_destroy(emoticon->obj);
+	g_free(emoticon->smile);
+	g_free(emoticon);
+}
+
+static GSList* msn_msg_grab_emoticons(const char *msg, const char *username)
+{
+	GSList *list;
+	GList *smileys;
+	PurpleSmiley *smiley;
+	PurpleStoredImage *img;
+	char *ptr;
+	MsnEmoticon *emoticon;
+	int length;
+
+	list = NULL;
+	smileys = purple_smileys_get_all();
+	length = strlen(msg);
+
+	for (; smileys; smileys = g_list_delete_link(smileys, smileys)) {
+		smiley = (PurpleSmiley*)smileys->data;
+
+		ptr = g_strstr_len(msg, length, purple_smiley_get_shortcut(smiley));
+
+		if (!ptr)
+			continue;
+
+		img = purple_smiley_get_stored_image(smiley);
+
+		emoticon = g_new0(MsnEmoticon, 1);
+		emoticon->smile = g_strdup(purple_smiley_get_shortcut(smiley));
+		emoticon->obj = msn_object_new_from_image(img,
+				purple_imgstore_get_filename(img),
+				username, MSN_OBJECT_EMOTICON);
+
+		purple_imgstore_unref(img);
+		list = g_slist_prepend(list, emoticon);
+	}
+
+	return list;
+}
+
 static int
 msn_send_im(PurpleConnection *gc, const char *who, const char *message,
 			PurpleMessageFlags flags)
@@ -810,8 +921,10 @@
 	MsnMessage *msg;
 	char *msgformat;
 	char *msgtext;
+	const char *username;
 
 	account = purple_connection_get_account(gc);
+	username = purple_account_get_username(account);
 
 	if (buddy) {
 		PurplePresence *p = purple_buddy_get_presence(buddy);
@@ -839,13 +952,29 @@
 	g_free(msgformat);
 	g_free(msgtext);
 
-	if (g_ascii_strcasecmp(who, purple_account_get_username(account)))
+	if (g_ascii_strcasecmp(who, username))
 	{
 		MsnSession *session;
 		MsnSwitchBoard *swboard;
+		MsnEmoticon *smile;
+		GSList *smileys;
+		GString *emoticons = NULL;
 
 		session = gc->proto_data;
 		swboard = msn_session_get_swboard(session, who, MSN_SB_FLAG_IM);
+		smileys = msn_msg_grab_emoticons(message, username);
+
+		while (smileys) {
+			smile = (MsnEmoticon*)smileys->data;
+			emoticons = msn_msg_emoticon_add(emoticons,smile);
+			msn_emoticon_destroy(smile);
+			smileys = g_slist_delete_link(smileys, smileys);
+		}
+
+		if (emoticons) {
+			msn_send_emoticons(swboard, emoticons);
+			g_string_free(emoticons, TRUE);
+		}
 
 		msn_switchboard_send_msg(swboard, msg, TRUE);
 	}
@@ -1268,7 +1397,7 @@
 	g_free(msgformat);
 	g_free(msgtext);
 
-	serv_got_chat_in(gc, id, purple_account_get_username(account), 0,
+	serv_got_chat_in(gc, id, purple_account_get_username(account), flags,
 					 message, time(NULL));
 
 	return 0;
@@ -2153,8 +2282,8 @@
 	msn_send_attention,                     /* send_attention */
 	msn_attention_types,                    /* attention_types */
 
-	/* padding */
-	NULL
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
+	msn_get_account_text_table,             /* get_account_text_table */
 };
 
 static PurplePluginInfo info =
--- a/libpurple/protocols/msnp9/object.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/msnp9/object.c	Sun May 25 04:30:41 2008 +0000
@@ -23,6 +23,10 @@
  */
 #include "object.h"
 #include "debug.h"
+/* Sha1 stuff */
+#include "cipher.h"
+/* Base64 stuff */
+#include "util.h"
 
 #define GET_STRING_TAG(field, id) \
 	if ((tag = strstr(str, id "=\"")) != NULL) \
@@ -104,6 +108,74 @@
 	return obj;
 }
 
+MsnObject*
+msn_object_new_from_image(PurpleStoredImage *img, const char *location,
+		const char *creator, MsnObjectType type)
+{
+	MsnObject *msnobj;
+
+	PurpleCipherContext *ctx;
+	char *buf;
+	gconstpointer data;
+	size_t size;
+	char *base64;
+	unsigned char digest[20];
+
+	msnobj = NULL;
+
+	if (img == NULL)
+		return msnobj;
+
+	size = purple_imgstore_get_size(img);
+	data = purple_imgstore_get_data(img);
+
+	/* New object */
+	msnobj = msn_object_new();
+	msn_object_set_local(msnobj);
+	msn_object_set_type(msnobj, type);
+	msn_object_set_location(msnobj, location);
+	msn_object_set_creator(msnobj, creator);
+
+	msn_object_set_image(msnobj, img);
+
+	/* Compute the SHA1D field. */
+	memset(digest, 0, sizeof(digest));
+
+	ctx = purple_cipher_context_new_by_name("sha1", NULL);
+	purple_cipher_context_append(ctx, data, size);
+	purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
+
+	base64 = purple_base64_encode(digest, sizeof(digest));
+	msn_object_set_sha1d(msnobj, base64);
+	g_free(base64);
+
+	msn_object_set_size(msnobj, size);
+
+	/* Compute the SHA1C field. */
+	buf = g_strdup_printf(
+		"Creator%sSize%dType%dLocation%sFriendly%sSHA1D%s",
+		msn_object_get_creator(msnobj),
+		msn_object_get_size(msnobj),
+		msn_object_get_type(msnobj),
+		msn_object_get_location(msnobj),
+		msn_object_get_friendly(msnobj),
+		msn_object_get_sha1d(msnobj));
+
+	memset(digest, 0, sizeof(digest));
+
+	purple_cipher_context_reset(ctx, NULL);
+	purple_cipher_context_append(ctx, (const guchar *)buf, strlen(buf));
+	purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
+	purple_cipher_context_destroy(ctx);
+	g_free(buf);
+
+	base64 = purple_base64_encode(digest, sizeof(digest));
+	msn_object_set_sha1c(msnobj, base64);
+	g_free(base64);
+
+	return msnobj;
+}
+
 void
 msn_object_destroy(MsnObject *obj)
 {
--- a/libpurple/protocols/msnp9/object.h	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/msnp9/object.h	Sun May 25 04:30:41 2008 +0000
@@ -71,6 +71,19 @@
 MsnObject *msn_object_new_from_string(const char *str);
 
 /**
+ * Creates a MsnObject structure from a stored image
+ *
+ * @param img		The image associated to object
+ * @param location	The object location as stored in MsnObject
+ * @param creator	The creator of the object
+ * @param type		The type of the object
+ *
+ * @return A new MsnObject structure
+ */
+MsnObject *msn_object_new_from_image(PurpleStoredImage *img,
+		const char *location, const char *creator, MsnObjectType type);
+
+/**
  * Destroys an MsnObject structure.
  *
  * @param obj The object structure.
--- a/libpurple/protocols/msnp9/slp.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/msnp9/slp.c	Sun May 25 04:30:41 2008 +0000
@@ -31,6 +31,8 @@
 #include "user.h"
 #include "switchboard.h"
 
+#include "smiley.h"
+
 /* ms to delay between sending buddy icon requests to the server. */
 #define BUDDY_ICON_DELAY 20000
 
@@ -276,23 +278,32 @@
 		type = msn_object_get_type(obj);
 		g_free(msnobj_data);
 
-		if (!(type == MSN_OBJECT_USERTILE))
+		if ((type != MSN_OBJECT_USERTILE) && (type != MSN_OBJECT_EMOTICON))
 		{
 			purple_debug_error("msn", "Wrong object?\n");
 			msn_object_destroy(obj);
 			g_return_if_reached();
 		}
 
-		img = msn_object_get_image(obj);
+		if (type == MSN_OBJECT_EMOTICON) {
+			char *path;
+			path = g_build_filename(purple_smileys_get_storing_dir(),
+					obj->location, NULL);
+			img = purple_imgstore_new_from_file(path);
+			g_free(path);
+		} else {
+			img = msn_object_get_image(obj);
+			if (img)
+				purple_imgstore_ref(img);
+		}
+		msn_object_destroy(obj);
+
 		if (img == NULL)
 		{
 			purple_debug_error("msn", "Wrong object.\n");
-			msn_object_destroy(obj);
 			g_return_if_reached();
 		}
 
-		msn_object_destroy(obj);
-
 		slpsession = msn_slplink_find_slp_session(slplink,
 												  slpcall->session_id);
 
@@ -317,6 +328,7 @@
 #endif
 		msn_slpmsg_set_image(slpmsg, img);
 		msn_slplink_queue_slpmsg(slplink, slpmsg);
+		purple_imgstore_unref(img);
 	}
 	else if (!strcmp(euf_guid, "5D3E02AB-6190-11D3-BBBB-00C04F795683"))
 	{
--- a/libpurple/protocols/msnp9/user.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/msnp9/user.c	Sun May 25 04:30:41 2008 +0000
@@ -153,69 +153,17 @@
 void
 msn_user_set_buddy_icon(MsnUser *user, PurpleStoredImage *img)
 {
-	MsnObject *msnobj = msn_user_get_object(user);
+	MsnObject *msnobj = NULL;
 
 	g_return_if_fail(user != NULL);
 
-	if (img == NULL)
-		msn_user_set_object(user, NULL);
-	else
-	{
-		PurpleCipherContext *ctx;
-		char *buf;
-		gconstpointer data = purple_imgstore_get_data(img);
-		size_t size = purple_imgstore_get_size(img);
-		char *base64;
-		unsigned char digest[20];
-
-		if (msnobj == NULL)
-		{
-			msnobj = msn_object_new();
-			msn_object_set_local(msnobj);
-			msn_object_set_type(msnobj, MSN_OBJECT_USERTILE);
-			msn_object_set_location(msnobj, "TFR2C2.tmp");
-			msn_object_set_creator(msnobj, msn_user_get_passport(user));
-
-			msn_user_set_object(user, msnobj);
-		}
-
-		msn_object_set_image(msnobj, img);
-
-		/* Compute the SHA1D field. */
-		memset(digest, 0, sizeof(digest));
+	msnobj = msn_object_new_from_image(img, "TFR2C2.tmp",
+			user->passport, MSN_OBJECT_USERTILE);
 
-		ctx = purple_cipher_context_new_by_name("sha1", NULL);
-		purple_cipher_context_append(ctx, data, size);
-		purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
-
-		base64 = purple_base64_encode(digest, sizeof(digest));
-		msn_object_set_sha1d(msnobj, base64);
-		g_free(base64);
-
-		msn_object_set_size(msnobj, size);
+	if(!msnobj)
+		purple_debug_error("msn", "Unable to open buddy icon from %s!\n", user->passport);
 
-		/* Compute the SHA1C field. */
-		buf = g_strdup_printf(
-			"Creator%sSize%dType%dLocation%sFriendly%sSHA1D%s",
-			msn_object_get_creator(msnobj),
-			msn_object_get_size(msnobj),
-			msn_object_get_type(msnobj),
-			msn_object_get_location(msnobj),
-			msn_object_get_friendly(msnobj),
-			msn_object_get_sha1d(msnobj));
-
-		memset(digest, 0, sizeof(digest));
-
-		purple_cipher_context_reset(ctx, NULL);
-		purple_cipher_context_append(ctx, (const guchar *)buf, strlen(buf));
-		purple_cipher_context_digest(ctx, sizeof(digest), digest, NULL);
-		purple_cipher_context_destroy(ctx);
-		g_free(buf);
-
-		base64 = purple_base64_encode(digest, sizeof(digest));
-		msn_object_set_sha1c(msnobj, base64);
-		g_free(base64);
-	}
+	msn_user_set_object(user, msnobj);
 }
 
 void
--- a/libpurple/protocols/myspace/myspace.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Sun May 25 04:30:41 2008 +0000
@@ -2449,6 +2449,18 @@
 	return normalized;
 }
 
+static GHashTable *
+msim_get_account_text_table(PurpleAccount *unused)
+{
+	GHashTable *table;
+
+	table = g_hash_table_new(g_str_hash, g_str_equal);
+
+	g_hash_table_insert(table, "login_label", (gpointer)_("E-mail Address..."));
+
+	return table;
+}
+
 /** Return whether the buddy can be messaged while offline.
  *
  * The protocol supports offline messages in just the same way as online
@@ -3128,14 +3140,16 @@
 	NULL,                  /* unregister_user */
 	msim_send_attention,   /* send_attention */
 	msim_attention_types,  /* attention_types */
-	NULL                /* _purple_reserved4 */
+
+	sizeof(PurplePluginProtocolInfo),  /* struct_size */
+	msim_get_account_text_table,              /* get_account_text_table */
 };
 
 
 
 /** Based on MSN's plugin info comments. */
 static PurplePluginInfo info = {
-	PURPLE_PLUGIN_MAGIC,                                
+	PURPLE_PLUGIN_MAGIC,
 	PURPLE_MAJOR_VERSION,
 	PURPLE_MINOR_VERSION,
 	PURPLE_PLUGIN_PROTOCOL,                           /**< type           */
--- a/libpurple/protocols/novell/novell.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/novell/novell.c	Sun May 25 04:30:41 2008 +0000
@@ -2506,7 +2506,7 @@
 						}
 					}
 
-					serv_got_chat_in(gc, id, name, 0, text, time(NULL));
+					serv_got_chat_in(gc, id, name, flags, text, time(NULL));
 					return 0;
 				} else
 					return -1;
@@ -3517,6 +3517,7 @@
 	NULL,
 	NULL,
 	NULL,
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/oscar/libaim.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/oscar/libaim.c	Sun May 25 04:30:41 2008 +0000
@@ -96,7 +96,7 @@
 	NULL,					/* send_attention */
 	NULL,					/* get_attention_types */
 
-	/* padding */
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/oscar/libicq.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/oscar/libicq.c	Sun May 25 04:30:41 2008 +0000
@@ -96,7 +96,7 @@
 	NULL,					/* send_attention */
 	NULL,					/* get_attention_types */
 
-	/* padding */
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/qq/qq.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/qq/qq.c	Sun May 25 04:30:41 2008 +0000
@@ -706,6 +706,7 @@
 	NULL,
 	NULL,
 	NULL,
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/silc/chat.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/silc/chat.c	Sun May 25 04:30:41 2008 +0000
@@ -1315,7 +1315,7 @@
 			g_free(tmp);
 
 			if (ret)
-				  serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), 0, msg, time(NULL));
+				  serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), msgflags, msg, time(NULL));
 			return ret;
 		}
 	}
@@ -1326,7 +1326,7 @@
 					       (unsigned char *)msg2,
 					       strlen(msg2));
 	if (ret) {
-		serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), 0, msg,
+		serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), msgflags, msg,
 				 time(NULL));
 	}
 	g_free(tmp);
--- a/libpurple/protocols/silc/silc.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/silc/silc.c	Sun May 25 04:30:41 2008 +0000
@@ -2071,6 +2071,7 @@
 	NULL,
 	NULL,
 	NULL,
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/silc10/chat.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/silc10/chat.c	Sun May 25 04:30:41 2008 +0000
@@ -1351,7 +1351,7 @@
 					       flags, (unsigned char *)msg2,
 					       strlen(msg2), TRUE);
 	if (ret) {
-		serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), 0, msg,
+		serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), msgflags, msg,
 				 time(NULL));
 	}
 	g_free(tmp);
--- a/libpurple/protocols/silc10/silc.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/silc10/silc.c	Sun May 25 04:30:41 2008 +0000
@@ -1800,10 +1800,10 @@
 	NULL,                       /* send_raw */
 	NULL,                       /* roomlist_room_serialize */
 
-	/* padding */
 	NULL,
 	NULL,
 	NULL,
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/simple/simple.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/simple/simple.c	Sun May 25 04:30:41 2008 +0000
@@ -2068,6 +2068,7 @@
 	NULL,
 	NULL,
 	NULL,
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/yahoo/util.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/yahoo/util.c	Sun May 25 04:30:41 2008 +0000
@@ -120,13 +120,10 @@
 		return botch_utf((gchar *)str, strlen((gchar *)str), &newlen);
 	}
 
-	if (yd->jp)
-		to_codeset = "SHIFT_JIS";
-	else
-		to_codeset = purple_account_get_string(purple_connection_get_account(gc), "local_charset",  "ISO-8859-1");
+	to_codeset = purple_account_get_string(purple_connection_get_account(gc), "local_charset",  "ISO-8859-1");
+	ret = g_convert_with_fallback(str, -1, to_codeset, "UTF-8", "?", NULL, NULL, NULL);
 
-	ret = g_convert_with_fallback(str, strlen(str), to_codeset, "UTF-8", "?", NULL, NULL, NULL);
-
+	ret = g_convert_with_fallback(str, -1, to_codeset, "UTF-8", "?", NULL, NULL, NULL);
 	if (ret)
 		return ret;
 	else
--- a/libpurple/protocols/yahoo/yahoo.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Sun May 25 04:30:41 2008 +0000
@@ -4403,7 +4403,7 @@
 	yahoo_send_attention,
 	yahoo_attention_types,
 
-	/* padding */
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/yahoo/yahoo_aliases.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo_aliases.c	Sun May 25 04:30:41 2008 +0000
@@ -48,7 +48,8 @@
  */
 struct callback_data {
 	PurpleConnection *gc;
-	char *id;
+	gchar *id;
+	gchar *who;
 };
 
 
@@ -57,7 +58,7 @@
  **************************************************************************/
 
 static void
-yahoo_fetch_aliases_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,const gchar *url_text, size_t len, const gchar *error_message)
+yahoo_fetch_aliases_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message)
 {
 	struct callback_data *cb = user_data;
 	PurpleConnection *gc = cb->gc;
@@ -69,8 +70,8 @@
 		purple_debug_info("yahoo", "No Aliases to process.%s%s\n",
 						  error_message ? " Error:" : "", error_message ? error_message : "");
 	} else {
-		gchar *full_name, *nick_name, *alias;
-		const char *yid, *id, *fn, *ln, *nn;
+		gchar *full_name, *nick_name;
+		const char *yid, *id, *fn, *ln, *nn, *alias;
 		YahooFriend *f;
 		PurpleBuddy *b;
 		xmlnode *item, *contacts;
@@ -79,7 +80,10 @@
 		contacts = xmlnode_from_str(url_text, -1);
 
 		if (contacts == NULL) {
-			purple_debug_error("yahoo_aliases","Badly formed XML\n");
+			purple_debug_error("yahoo", "Badly formed Alias XML\n");
+			g_free(cb->who);
+			g_free(cb->id);
+			g_free(cb);
 			return;
 		}
 		purple_debug_info("yahoo", "Fetched %" G_GSIZE_FORMAT
@@ -89,20 +93,21 @@
 		for(item = xmlnode_get_child(contacts, "ct"); item; item = xmlnode_get_next_twin(item)) {
 			/* Yahoo replies with two types of contact (ct) record, we are only interested in the alias ones */
 			if ((yid = xmlnode_get_attrib(item, "yi"))) {
-		                /* Grab all the bits of information we can */
-				fn = xmlnode_get_attrib(item,"fn");
-				ln = xmlnode_get_attrib(item,"ln");
-				nn = xmlnode_get_attrib(item,"nn");
-				id = xmlnode_get_attrib(item,"id");
+				/* Grab all the bits of information we can */
+				fn = xmlnode_get_attrib(item, "fn");
+				ln = xmlnode_get_attrib(item, "ln");
+				nn = xmlnode_get_attrib(item, "nn");
+				id = xmlnode_get_attrib(item, "id");
 
-				full_name = nick_name = alias = NULL;
+				full_name = nick_name = NULL;
+				alias = NULL;
 
 				/* Yahoo stores first and last names separately, lets put them together into a full name */
 				if (yd->jp)
 					full_name = g_strstrip(g_strdup_printf("%s %s", (ln != NULL ? ln : "") , (fn != NULL ? fn : "")));
 				else
 					full_name = g_strstrip(g_strdup_printf("%s %s", (fn != NULL ? fn : "") , (ln != NULL ? ln : "")));
-				nick_name = (nn != NULL ? g_strstrip(g_strdup_printf("%s", nn)) : NULL);
+				nick_name = (nn != NULL ? g_strstrip(g_strdup(nn)) : NULL);
 
 				if (nick_name != NULL)
 					alias = nick_name;   /* If we have a nickname from Yahoo, let's use it */
@@ -115,19 +120,18 @@
 
 				/*  If we don't find a matching buddy, ignore the alias !!  */
 				if (f != NULL && b != NULL) {
+					const char *buddy_alias = purple_buddy_get_alias(b);
 					yahoo_friend_set_alias_id(f, id);
 
 					/* Finally, if we received an alias, we better update the buddy list */
 					if (alias != NULL) {
 						serv_got_alias(cb->gc, yid, alias);
-						purple_debug_info("yahoo","Fetched alias '%s' (%s)\n",alias,id);
-					} else if (b->alias != alias && strcmp(b->alias, "") != 0) {
+						purple_debug_info("yahoo", "Fetched alias '%s' (%s)\n", alias, id);
+					} else if (buddy_alias != NULL && strcmp(buddy_alias, "") != 0) {
 					/* Or if we have an alias that Yahoo doesn't, send it up */
-						yahoo_update_alias(cb->gc, yid, b->alias);
-						purple_debug_info("yahoo","Sent alias '%s'\n", b->alias);
+						yahoo_update_alias(cb->gc, yid, buddy_alias);
+						purple_debug_info("yahoo", "Sent updated alias '%s'\n", buddy_alias);
 					}
-				} else {
-					purple_debug_info("yahoo", "Bizarre, received alias for %s, but they are not on your list...\n", yid);
 				}
 
 				g_free(full_name);
@@ -136,6 +140,8 @@
 		}
 		xmlnode_free(contacts);
 	}
+
+	g_free(cb->who);
 	g_free(cb->id);
 	g_free(cb);
 }
@@ -146,14 +152,15 @@
 	struct yahoo_data *yd = gc->proto_data;
 	struct callback_data *cb;
 	const char *url;
-	char *request, *webpage, *webaddress;
+	gchar *request, *webpage, *webaddress;
 	PurpleUtilFetchUrlData *url_data;
 
 	gboolean use_whole_url = FALSE;
 
 	/* use whole URL if using HTTP Proxy */
-	if ((gc->account->proxy_info) && (gc->account->proxy_info->type == PURPLE_PROXY_HTTP))
-	    use_whole_url = TRUE;
+	if ((gc->account->proxy_info)
+	    && (gc->account->proxy_info->type == PURPLE_PROXY_HTTP))
+		use_whole_url = TRUE;
 
 	/* Using callback_data so I have access to gc in the callback function */
 	cb = g_new0(struct callback_data, 1);
@@ -167,13 +174,16 @@
 				 "Cookie: T=%s; Y=%s\r\n"
 				 "Host: %s\r\n"
 				 "Cache-Control: no-cache\r\n\r\n",
-				  use_whole_url ? "http://" : "", use_whole_url ? webaddress : "", webpage, yd->cookie_t,yd->cookie_y, webaddress);
+				  use_whole_url ? "http://" : "", use_whole_url ? webaddress : "", webpage,
+				  yd->cookie_t, yd->cookie_y,
+				  webaddress);
 
 	/* We have a URL and some header information, let's connect and get some aliases  */
-	url_data = purple_util_fetch_url_request(url, use_whole_url, NULL, TRUE, request, FALSE, yahoo_fetch_aliases_cb, cb);
-	if (url_data != NULL) {
+	url_data = purple_util_fetch_url_request(url, use_whole_url, NULL, TRUE,
+						 request, FALSE,
+						 yahoo_fetch_aliases_cb, cb);
+	if (url_data != NULL)
 		yd->url_datas = g_slist_prepend(yd->url_datas, url_data);
-	}
 
 	g_free(webaddress);
 	g_free(webpage);
@@ -185,7 +195,7 @@
  **************************************************************************/
 
 static void
-yahoo_update_alias_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,const gchar *url_text, size_t len, const gchar *error_message)
+yahoo_update_alias_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message)
 {
 	xmlnode *node, *result;
 	struct callback_data *cb = user_data;
@@ -196,8 +206,10 @@
 	yd->url_datas = g_slist_remove(yd->url_datas, url_data);
 
 	if (len == 0 || error_message != NULL) {
-		purple_debug_info("yahoo", "Error updating alias: %s\n",
-						  error_message ? error_message : "");
+		purple_debug_info("yahoo", "Error updating alias for %s: %s\n",
+				  cb->who,
+				  error_message ? error_message : "");
+		g_free(cb->who);
 		g_free(cb->id);
 		g_free(cb);
 		return;
@@ -205,24 +217,42 @@
 
 	result = xmlnode_from_str(url_text, -1);
 
-	purple_debug_info("yahoo", "ID: %s, Return data: %s\n",cb->id, url_text);
-
 	if (result == NULL) {
-		purple_debug_error("yahoo","Alias update failed: Badly formed response\n");
+		purple_debug_error("yahoo", "Alias update for %s failed: Badly formed response\n",
+				   cb->who);
+		g_free(cb->who);
 		g_free(cb->id);
 		g_free(cb);
 		return;
 	}
 
 	if ((node = xmlnode_get_child(result, "ct"))) {
-		if (g_ascii_strncasecmp(xmlnode_get_attrib(node, "id"), cb->id, strlen(cb->id))==0)
-			purple_debug_info("yahoo", "Alias update succeeded\n");
-		else
-			purple_debug_error("yahoo", "Alias update failed (Contact record return mismatch)\n");
-	} else {
-		purple_debug_info("yahoo", "Alias update failed (No contact record returned)\n");
-	}
+		if (cb->id == NULL) {
+			const char *new_id = xmlnode_get_attrib(node, "id");
+			if (new_id != NULL) {
+				/* We now have an addressbook id for the friend; we should save it */
+				YahooFriend *f = yahoo_friend_find(cb->gc, cb->who);
+
+				purple_debug_info("yahoo", "Alias creation for %s succeeded\n", cb->who);
 
+				if (f)
+					yahoo_friend_set_alias_id(f, new_id);
+				else
+					purple_debug_error("yahoo", "Missing YahooFriend. Unable to store new addressbook id.\n");
+			} else
+				purple_debug_error("yahoo", "Missing new addressbook id in add response for %s (weird).\n",
+						   cb->who);
+		} else {
+			if (g_ascii_strncasecmp(xmlnode_get_attrib(node, "id"), cb->id, strlen(cb->id))==0)
+				purple_debug_info("yahoo", "Alias update for %s succeeded\n", cb->who);
+			else
+				purple_debug_error("yahoo", "Alias update for %s failed (Contact record return mismatch)\n",
+						   cb->who);
+		}
+	} else
+		purple_debug_info("yahoo", "Alias update for %s failed (No contact record returned)\n", cb->who);
+
+	g_free(cb->who);
 	g_free(cb->id);
 	g_free(cb);
 	xmlnode_free(result);
@@ -232,27 +262,27 @@
 yahoo_update_alias(PurpleConnection *gc, const char *who, const char *alias)
 {
 	struct yahoo_data *yd;
-	char *content, *url, *request, *webpage, *webaddress, *strtmp;
-	char *escaped_alias, *alias_jp, *converted_alias_jp;
-	int inttmp;
+	const char *url;
+	gchar *content, *request, *webpage, *webaddress;
 	struct callback_data *cb;
 	PurpleUtilFetchUrlData *url_data;
 	gboolean use_whole_url = FALSE;
 	YahooFriend *f;
 
 	/* use whole URL if using HTTP Proxy */
-	if ((gc->account->proxy_info) && (gc->account->proxy_info->type == PURPLE_PROXY_HTTP))
-	    use_whole_url = TRUE;
+	if ((gc->account->proxy_info)
+	    && (gc->account->proxy_info->type == PURPLE_PROXY_HTTP))
+		use_whole_url = TRUE;
 
-	g_return_if_fail(alias != NULL);
 	g_return_if_fail(who != NULL);
 	g_return_if_fail(gc != NULL);
 
-	purple_debug_info("yahoo", "Sending '%s' as new alias for user '%s'.\n", alias, who);
+	if (alias == NULL)
+		alias = "";
 
 	f = yahoo_friend_find(gc, who);
 	if (f == NULL) {
-		purple_debug_info("yahoo", "Missing proto_data (get_yahoo_aliases must have failed), bailing out\n");
+		purple_debug_error("yahoo", "Missing YahooFriend. Unable to set server alias.\n");
 		return;
 	}
 
@@ -260,28 +290,55 @@
 
 	/* Using callback_data so I have access to gc in the callback function */
 	cb = g_new0(struct callback_data, 1);
+	cb->who = g_strdup(who);
 	cb->id = g_strdup(yahoo_friend_get_alias_id(f));
 	cb->gc = gc;
 
 	/*  Build all the info to make the web request */
-	url = yd->jp? YAHOOJP_ALIAS_UPDATE_URL: YAHOO_ALIAS_UPDATE_URL;
-	purple_url_parse(url, &webaddress, &inttmp, &webpage, &strtmp, &strtmp);
+	url = yd->jp ? YAHOOJP_ALIAS_UPDATE_URL: YAHOO_ALIAS_UPDATE_URL;
+	purple_url_parse(url, &webaddress, NULL, &webpage, NULL, NULL);
+
+	if (cb->id == NULL) {
+		/* No id for this buddy, so create an address book entry */
+		purple_debug_info("yahoo", "Creating '%s' as new alias for user '%s'\n", alias, who);
 
-	if (yd->jp) {
-		alias_jp = g_convert(alias, strlen(alias), "EUC-JP", "UTF-8", NULL, NULL, NULL);
-		converted_alias_jp = yahoo_convert_to_numeric(alias_jp);
-		content = g_strdup_printf("<ab k=\"%s\" cc=\"1\">\n"
-		                          "<ct e=\"1\"  yi='%s' id='%s' nn='%s' pr='0' />\n</ab>\r\n",
-		                          gc->account->username, who, yahoo_friend_get_alias_id(f), converted_alias_jp);
-		free(converted_alias_jp);
-		g_free(alias_jp);
-	}
-	else {
-		escaped_alias = g_markup_escape_text(alias, strlen(alias));
-		content = g_strdup_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?><ab k=\"%s\" cc=\"1\">\n"
-		                          "<ct e=\"1\"  yi='%s' id='%s' nn='%s' pr='0' />\n</ab>\r\n",
-		                          gc->account->username, who, yahoo_friend_get_alias_id(f), escaped_alias);
-		g_free(escaped_alias);
+		if (yd->jp) {
+			gchar *alias_jp = g_convert(alias, -1, "EUC-JP", "UTF-8", NULL, NULL, NULL);
+			gchar *converted_alias_jp = yahoo_convert_to_numeric(alias_jp);
+			content = g_strdup_printf("<ab k=\"%s\" cc=\"9\">\n"
+						  "<ct a=\"1\" yi='%s' nn='%s' />\n</ab>\r\n",
+						  purple_account_get_username(gc->account),
+						  who, converted_alias_jp);
+			free(converted_alias_jp);
+			g_free(alias_jp);
+		} else {
+			gchar *escaped_alias = g_markup_escape_text(alias, -1);
+			content = g_strdup_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?><ab k=\"%s\" cc=\"9\">\n"
+						  "<ct a=\"1\" yi='%s' nn='%s' />\n</ab>\r\n",
+						  purple_account_get_username(gc->account),
+						  who, escaped_alias);
+			g_free(escaped_alias);
+		}
+	} else {
+		purple_debug_info("yahoo", "Updating '%s' as new alias for user '%s'\n", alias, who);
+
+		if (yd->jp) {
+			gchar *alias_jp = g_convert(alias, -1, "EUC-JP", "UTF-8", NULL, NULL, NULL);
+			gchar *converted_alias_jp = yahoo_convert_to_numeric(alias_jp);
+			content = g_strdup_printf("<ab k=\"%s\" cc=\"1\">\n"
+						  "<ct e=\"1\"  yi='%s' id='%s' nn='%s' pr='0' />\n</ab>\r\n",
+						  purple_account_get_username(gc->account),
+						  who, cb->id, converted_alias_jp);
+			free(converted_alias_jp);
+			g_free(alias_jp);
+		} else {
+			gchar *escaped_alias = g_markup_escape_text(alias, -1);
+			content = g_strdup_printf("<?xml version=\"1.0\" encoding=\"utf-8\"?><ab k=\"%s\" cc=\"1\">\n"
+						  "<ct e=\"1\"  yi='%s' id='%s' nn='%s' pr='0' />\n</ab>\r\n",
+						  purple_account_get_username(gc->account),
+						  who, cb->id, escaped_alias);
+			g_free(escaped_alias);
+		}
 	}
 
 	request = g_strdup_printf("POST %s%s/%s HTTP/1.1\r\n"
@@ -291,15 +348,19 @@
 				  "Content-Length: %" G_GSIZE_FORMAT "\r\n"
 				  "Cache-Control: no-cache\r\n\r\n"
 				  "%s",
-				  use_whole_url ? "http://" : "", use_whole_url ? webaddress : "", webpage, yd->cookie_t,yd->cookie_y, webaddress,
-			 	  strlen(content), content);
+				  use_whole_url ? "http://" : "", use_whole_url ? webaddress : "", webpage,
+				  yd->cookie_t, yd->cookie_y,
+				  webaddress,
+				  strlen(content),
+				  content);
 
 	/* We have a URL and some header information, let's connect and update the alias  */
 	url_data = purple_util_fetch_url_request(url, use_whole_url, NULL, TRUE, request, FALSE, yahoo_update_alias_cb, cb);
-	if (url_data != NULL) {
+	if (url_data != NULL)
 		yd->url_datas = g_slist_prepend(yd->url_datas, url_data);
-	}
 
+	g_free(webpage);
+	g_free(webaddress);
 	g_free(content);
 	g_free(request);
 }
--- a/libpurple/protocols/yahoo/yahoo_picture.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo_picture.c	Sun May 25 04:30:41 2008 +0000
@@ -137,9 +137,6 @@
 		if (url_data != NULL) {
 			yd = gc->proto_data;
 			yd->url_datas = g_slist_prepend(yd->url_datas, url_data);
-		} else {
-			g_free(data->who);
-			g_free(data);
 		}
 	} else if (who && send_icon_info) {
 		yahoo_send_picture_info(gc, who);
--- a/libpurple/protocols/yahoo/yahoo_profile.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo_profile.c	Sun May 25 04:30:41 2008 +0000
@@ -950,11 +950,6 @@
 				FALSE, yahoo_got_photo, info2_data);
 		if (url_data != NULL)
 			yd->url_datas = g_slist_prepend(yd->url_datas, url_data);
-		else {
-			g_free(info2_data->info_data->name);
-			g_free(info2_data->info_data);
-			g_free(info2_data);
-		}
 	} else {
 		/* Emulate a callback */
 		yahoo_got_photo(NULL, info2_data, NULL, 0, NULL);
@@ -1295,10 +1290,6 @@
 	url_data = purple_util_fetch_url(url, TRUE, NULL, FALSE, yahoo_got_info, data);
 	if (url_data != NULL)
 		yd->url_datas = g_slist_prepend(yd->url_datas, url_data);
-	else {
-		g_free(data->name);
-		g_free(data);
-	}
 
 	g_free(url);
 }
--- a/libpurple/protocols/yahoo/yahoochat.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoochat.c	Sun May 25 04:30:41 2008 +0000
@@ -1043,7 +1043,7 @@
 						purple_conversation_get_name(c), what, flags);
 		if (!ret)
 			serv_got_chat_in(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(c)),
-					purple_connection_get_display_name(gc), 0, what, time(NULL));
+					purple_connection_get_display_name(gc), flags, what, time(NULL));
 	}
 	return ret;
 }
--- a/libpurple/protocols/zephyr/Makefile.am	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/zephyr/Makefile.am	Sun May 25 04:30:41 2008 +0000
@@ -59,9 +59,9 @@
 	mit-copyright.h \
 	mit-sipb-copyright.h \
 	sysdep.h \
-	zephyr.h \
 	zephyr_err.c \
 	zephyr_err.h \
+	zephyr_internal.h \
 	\
 	zephyr.c
 
--- a/libpurple/protocols/zephyr/internal.h	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/zephyr/internal.h	Sun May 25 04:30:41 2008 +0000
@@ -1,10 +1,14 @@
-
 #ifndef __INTERNAL_H__
 #define __INTERNAL_H__
 
 #include <sysdep.h>
 
-#include <zephyr.h>
+#ifdef LIBZEPHYR_EXT
+#include <zephyr/zephyr.h>
+#else
+#include <zephyr_internal.h>
+#endif
+
 #ifndef WIN32
 #include <netdb.h>
 #endif
--- a/libpurple/protocols/zephyr/zephyr.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/protocols/zephyr/zephyr.c	Sun May 25 04:30:41 2008 +0000
@@ -38,7 +38,6 @@
 #include "privacy.h"
 #include "version.h"
 
-#include "zephyr.h"
 #include "internal.h"
 
 #include <strings.h>
@@ -2907,10 +2906,10 @@
 	NULL,					/* send_raw */
 	NULL,					/* roomlist_room_serialize */
 
-	/* padding */
 	NULL,
 	NULL,
 	NULL,
+	sizeof(PurplePluginProtocolInfo),       /* struct_size */
 	NULL
 };
 
--- a/libpurple/protocols/zephyr/zephyr.h	Mon May 19 16:18:32 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,339 +0,0 @@
-/* This file is part of the Project Athena Zephyr Notification System.
- * It contains global definitions
- *
- *	Created by:	Robert French
- *
- *	Copyright (c) 1987,1988,1991 by the Massachusetts Institute of
- *	Technology. For copying and distribution information, see the
- *	file "mit-copyright.h".
- */
-
-#ifndef __ZEPHYR_H__
-#define __ZEPHYR_H__
-
-#include <config.h>
-
-#include <glib.h>
-
-#include <sys/types.h>
-#include <sys/time.h>
-
-#include <zephyr_err.h>
-
-#ifndef IPPROTO_MAX	/* Make sure not already included */
-#ifndef WIN32
-#include <netinet/in.h>
-#endif
-#endif
-
-/* Use __STDC__ to guess whether we can use stdarg, prototypes, and const.
- * This is a public header file, so autoconf can't help us here. */
-#ifdef __STDC__
-# include <stdarg.h>
-# define ZP(x) x
-# define ZCONST const
-#else
-# define ZP(x) ()
-# define ZCONST
-#endif
-
-#ifdef WIN32
-/* this really should be uint32_t */
-/*typedef unsigned int in_addr_t;
-struct in_addr
-{
-  in_addr_t s_addr;
-}; */
-#include <winsock2.h>
-#endif
-
-/* Service names */
-#define	HM_SVCNAME		"zephyr-hm"
-#define HM_SRV_SVCNAME		"zephyr-hm-srv"
-#define	SERVER_SVCNAME		"zephyr-clt"
-#define SERVER_SERVICE		"zephyr"
-#define SERVER_INSTANCE		"zephyr"
-
-#define ZVERSIONHDR	"ZEPH"
-#define ZVERSIONMAJOR	0
-#define ZVERSIONMINOR	2
-
-#define Z_MAXPKTLEN		1024
-#define Z_MAXHEADERLEN		800
-#define Z_MAXOTHERFIELDS	10	/* Max unknown fields in ZNotice_t */
-#define Z_NUMFIELDS		17
-
-/* Authentication levels returned by ZCheckAuthentication */
-#define ZAUTH_FAILED    	(-1)
-#define ZAUTH_YES       	1
-#define ZAUTH_NO        	0
-
-typedef char ZPacket_t[Z_MAXPKTLEN];
-
-/* Packet type */
-typedef enum {
-    UNSAFE, UNACKED, ACKED, HMACK, HMCTL, SERVACK, SERVNAK, CLIENTACK, STAT
-} ZNotice_Kind_t;
-extern ZCONST char *ZNoticeKinds[9];
-
-/* Unique ID format */
-typedef struct _ZUnique_Id_t {
-    struct	in_addr zuid_addr;
-    struct	timeval	tv;
-} ZUnique_Id_t;
-
-/* Checksum */
-typedef unsigned long ZChecksum_t;
-
-/* Notice definition */
-typedef struct _ZNotice_t {
-    char		*z_packet;
-    char		*z_version;
-    ZNotice_Kind_t	z_kind;
-    ZUnique_Id_t	z_uid;
-#define z_sender_addr	z_uid.zuid_addr
-    struct		timeval z_time;
-    unsigned short	z_port;
-    int			z_auth;
-    int			z_checked_auth;
-    int			z_authent_len;
-    char		*z_ascii_authent;
-    char		*z_class;
-    const char		*z_class_inst;
-    char		*z_opcode;
-    char		*z_sender;
-    const char		*z_recipient;
-    char		*z_default_format;
-    char		*z_multinotice;
-    ZUnique_Id_t	z_multiuid;
-    ZChecksum_t		z_checksum;
-    int			z_num_other_fields;
-    char		*z_other_fields[Z_MAXOTHERFIELDS];
-    caddr_t		z_message;
-    int			z_message_len;
-} ZNotice_t;
-
-/* Subscription structure */
-typedef struct _ZSubscriptions_t {
-    char	*zsub_recipient;
-    char	*zsub_class;
-    char	*zsub_classinst;
-} ZSubscription_t;
-
-/* Function return code */
-typedef int Code_t;
-
-/* Locations structure */
-typedef struct _ZLocations_t {
-    char	*host;
-    char	*time;
-    char	*tty;
-} ZLocations_t;
-
-typedef struct _ZAsyncLocateData_t {
-    char		*user;
-    ZUnique_Id_t	uid;
-    char		*version;
-} ZAsyncLocateData_t;
-
-/* for ZSetDebug */
-#ifdef Z_DEBUG
-void (*__Z_debug_print) ZP((ZCONST char *fmt, va_list args, void *closure));
-void *__Z_debug_print_closure;
-#endif
-
-int ZCompareUIDPred ZP((ZNotice_t *, void *));
-int ZCompareMultiUIDPred ZP((ZNotice_t *, void *));
-
-/* Defines for ZFormatNotice, et al. */
-typedef Code_t (*Z_AuthProc) ZP((ZNotice_t*, char *, int, int *));
-Code_t ZMakeAuthentication ZP((ZNotice_t*, char *,int, int*));
-
-char *ZGetSender ZP((void));
-char *ZGetVariable ZP((char *));
-Code_t ZSetVariable ZP((char *var, char *value));
-Code_t ZUnsetVariable ZP((char *var));
-int ZGetWGPort ZP((void));
-Code_t ZSetDestAddr ZP((struct sockaddr_in *));
-Code_t ZFormatNoticeList ZP((ZNotice_t*, char**, int,
-			     char **, int*, Z_AuthProc));
-Code_t ZParseNotice ZP((char*, int, ZNotice_t *));
-Code_t ZReadAscii ZP((char*, int, unsigned char*, int));
-Code_t ZReadAscii32 ZP((char *, int, unsigned long *));
-Code_t ZReadAscii16 ZP((char *, int, unsigned short *));
-Code_t ZSendPacket ZP((char*, int, int));
-Code_t ZSendList ZP((ZNotice_t*, char *[], int, Z_AuthProc));
-Code_t ZSrvSendList ZP((ZNotice_t*, char*[], int, Z_AuthProc, Code_t (*)()));
-Code_t ZSendNotice ZP((ZNotice_t *, Z_AuthProc));
-Code_t ZSrvSendNotice ZP((ZNotice_t*, Z_AuthProc, Code_t (*)()));
-Code_t ZFormatNotice ZP((ZNotice_t*, char**, int*, Z_AuthProc));
-Code_t ZFormatSmallNotice ZP((ZNotice_t*, ZPacket_t, int*, Z_AuthProc));
-Code_t ZFormatRawNoticeList ZP((ZNotice_t *notice, char *list[], int nitems,
-				char **buffer, int *ret_len));
-Code_t ZLocateUser ZP((char *, int *, Z_AuthProc));
-Code_t ZRequestLocations ZP((const char *, ZAsyncLocateData_t *,
-			     ZNotice_Kind_t, Z_AuthProc));
-Code_t ZhmStat ZP((struct in_addr *, ZNotice_t *));
-Code_t ZInitialize ZP((void));
-Code_t ZSetServerState ZP((int));
-Code_t ZSetFD ZP((int));
-Code_t ZFormatSmallRawNotice ZP((ZNotice_t*, ZPacket_t, int*));
-int ZCompareUID ZP((ZUnique_Id_t*, ZUnique_Id_t*));
-Code_t ZMakeAscii ZP((char*, int, unsigned char*, int));
-Code_t ZMakeAscii32 ZP((char *, int, unsigned long));
-Code_t ZMakeAscii16 ZP((char *, int, unsigned int));
-Code_t ZReceivePacket ZP((ZPacket_t, int*, struct sockaddr_in*));
-Code_t ZCheckAuthentication ZP((ZNotice_t*, struct sockaddr_in*));
-Code_t ZSetLocation ZP((char *exposure));
-Code_t ZUnsetLocation ZP((void));
-Code_t ZFlushMyLocations ZP((void));
-Code_t ZFormatRawNotice ZP((ZNotice_t *, char**, int *));
-Code_t ZRetrieveSubscriptions ZP((unsigned short, int*));
-Code_t ZOpenPort ZP((unsigned short *port));
-Code_t ZClosePort ZP((void));
-Code_t ZFlushLocations ZP((void));
-Code_t ZFlushSubscriptions ZP((void));
-Code_t ZFreeNotice ZP((ZNotice_t *notice));
-Code_t ZParseLocations ZP((register ZNotice_t *notice,
-			   register ZAsyncLocateData_t *zald, int *nlocs,
-			   char **user));
-int ZCompareALDPred ZP((ZNotice_t *notice, void *zald));
-void ZFreeALD ZP((register ZAsyncLocateData_t *zald));
-Code_t ZCheckIfNotice ZP((ZNotice_t *notice, struct sockaddr_in *from,
-			  register int (*predicate) ZP((ZNotice_t *,void *)),
-			  void *args));
-Code_t ZPeekPacket ZP((char **buffer, int *ret_len,
-		       struct sockaddr_in *from));
-Code_t ZPeekNotice ZP((ZNotice_t *notice, struct sockaddr_in *from));
-Code_t ZIfNotice ZP((ZNotice_t *notice, struct sockaddr_in *from,
-		     int (*predicate) ZP((ZNotice_t *, void *)), void *args));
-Code_t ZSubscribeTo ZP((ZSubscription_t *sublist, int nitems,
-			unsigned int port));
-Code_t ZSubscribeToSansDefaults ZP((ZSubscription_t *sublist, int nitems,
-				    unsigned int port));
-Code_t ZUnsubscribeTo ZP((ZSubscription_t *sublist, int nitems,
-			  unsigned int port));
-Code_t ZCancelSubscriptions ZP((unsigned int port));
-int ZPending ZP((void));
-Code_t ZReceiveNotice ZP((ZNotice_t *notice, struct sockaddr_in *from));
-#ifdef Z_DEBUG
-void Z_debug ZP((ZCONST char *, ...));
-#endif
-
-#undef ZP
-
-/* Compatibility */
-#define	ZNewLocateUser ZLocateUser
-
-/* Macros to retrieve Zephyr library values. */
-extern int __Zephyr_fd;
-extern int __Q_CompleteLength;
-extern struct sockaddr_in __HM_addr;
-extern char __Zephyr_realm[];
-#define ZGetFD()	__Zephyr_fd
-#define ZQLength()	__Q_CompleteLength
-#define ZGetDestAddr()	__HM_addr
-#define ZGetRealm()	__Zephyr_realm
-
-#ifdef Z_DEBUG
-void ZSetDebug ZP((void (*)(ZCONST char *, va_list, void *), void *));
-#define ZSetDebug(proc,closure)    (__Z_debug_print=(proc), \
-				    __Z_debug_print_closure=(closure), \
-				    (void) 0)
-#else
-#define	ZSetDebug(proc,closure)
-#endif
-
-/* Maximum queue length */
-#define Z_MAXQLEN 		30
-
-/* Successful function return */
-#define ZERR_NONE		0
-
-/* Hostmanager wait time (in secs) */
-#define HM_TIMEOUT		1
-
-/* Server wait time (in secs) */
-#define	SRV_TIMEOUT		30
-
-#define ZAUTH (ZMakeAuthentication)
-#define ZNOAUTH ((Z_AuthProc)0)
-
-/* Packet strings */
-#define ZSRVACK_SENT		"SENT"	/* SERVACK codes */
-#define ZSRVACK_NOTSENT		"LOST"
-#define ZSRVACK_FAIL		"FAIL"
-
-/* Server internal class */
-#define ZEPHYR_ADMIN_CLASS	"ZEPHYR_ADMIN"	/* Class */
-
-/* Control codes sent to a server */
-#define ZEPHYR_CTL_CLASS	"ZEPHYR_CTL"	/* Class */
-
-#define ZEPHYR_CTL_CLIENT	"CLIENT"	/* Inst: From client */
-#define CLIENT_SUBSCRIBE	"SUBSCRIBE"	/* Opcode: Subscribe */
-#define CLIENT_SUBSCRIBE_NODEFS	"SUBSCRIBE_NODEFS"	/* Opcode: Subscribe */
-#define CLIENT_UNSUBSCRIBE	"UNSUBSCRIBE"	/* Opcode: Unsubsubscribe */
-#define CLIENT_CANCELSUB	"CLEARSUB"	/* Opcode: Clear all subs */
-#define CLIENT_GIMMESUBS	"GIMME"		/* Opcode: Give me subs */
-#define	CLIENT_GIMMEDEFS	"GIMMEDEFS"	/* Opcode: Give me default
-						 * subscriptions */
-
-#define ZEPHYR_CTL_HM		"HM"		/* Inst: From HM */
-#define HM_BOOT			"BOOT"		/* Opcode: Boot msg */
-#define HM_FLUSH		"FLUSH"		/* Opcode: Flush me */
-#define HM_DETACH		"DETACH"	/* Opcode: Detach me */
-#define HM_ATTACH		"ATTACH"	/* Opcode: Attach me */
-
-/* Control codes send to a HostManager */
-#define	HM_CTL_CLASS		"HM_CTL"	/* Class */
-
-#define HM_CTL_SERVER		"SERVER"	/* Inst: From server */
-#define SERVER_SHUTDOWN		"SHUTDOWN"	/* Opcode: Server shutdown */
-#define SERVER_PING		"PING"		/* Opcode: PING */
-
-#define HM_CTL_CLIENT           "CLIENT"        /* Inst: From client */
-#define CLIENT_FLUSH            "FLUSH"         /* Opcode: Send flush to srv */
-#define CLIENT_NEW_SERVER       "NEWSERV"       /* Opcode: Find new server */
-
-/* HM Statistics */
-#define HM_STAT_CLASS		"HM_STAT"	/* Class */
-
-#define HM_STAT_CLIENT		"HMST_CLIENT"	/* Inst: From client */
-#define HM_GIMMESTATS		"GIMMESTATS"	/* Opcode: get stats */
-
-/* Login class messages */
-#define LOGIN_CLASS		"LOGIN"		/* Class */
-
-/* Class Instance is principal of user who is logging in or logging out */
-
-#define EXPOSE_NONE		"NONE"		/* Opcode: Not visible */
-#define EXPOSE_OPSTAFF		"OPSTAFF"	/* Opcode: Opstaff visible */
-#define EXPOSE_REALMVIS		"REALM-VISIBLE"	/* Opcode: Realm visible */
-#define EXPOSE_REALMANN		"REALM-ANNOUNCED"/* Opcode: Realm announced */
-#define EXPOSE_NETVIS		"NET-VISIBLE"	/* Opcode: Net visible */
-#define EXPOSE_NETANN		"NET-ANNOUNCED"	/* Opcode: Net announced */
-#define	LOGIN_USER_LOGIN	"USER_LOGIN"	/* Opcode: user login
-						   (from server) */
-#define LOGIN_USER_LOGOUT	"USER_LOGOUT"	/* Opcode: User logout */
-#define	LOGIN_USER_FLUSH	"USER_FLUSH"	/* Opcode: flush all locs */
-
-/* Locate class messages */
-#define LOCATE_CLASS		"USER_LOCATE"	/* Class */
-
-#define LOCATE_HIDE		"USER_HIDE"	/* Opcode: Hide me */
-#define LOCATE_UNHIDE		"USER_UNHIDE"	/* Opcode: Unhide me */
-
-/* Class Instance is principal of user to locate */
-#define LOCATE_LOCATE		"LOCATE"	/* Opcode: Locate user */
-
-/* WG_CTL class messages */
-#define WG_CTL_CLASS		"WG_CTL"	/* Class */
-
-#define WG_CTL_USER		"USER"		/* Inst: User request */
-#define USER_REREAD		"REREAD"	/* Opcode: Reread desc file */
-#define USER_SHUTDOWN		"SHUTDOWN"	/* Opcode: Go catatonic */
-#define USER_STARTUP		"STARTUP"	/* Opcode: Come out of it */
-#define USER_EXIT		"EXIT"		/* Opcode: Exit the client */
-
-#endif /* __ZEPHYR_H__ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/zephyr/zephyr_internal.h	Sun May 25 04:30:41 2008 +0000
@@ -0,0 +1,339 @@
+/* This file is part of the Project Athena Zephyr Notification System.
+ * It contains global definitions
+ *
+ *	Created by:	Robert French
+ *
+ *	Copyright (c) 1987,1988,1991 by the Massachusetts Institute of
+ *	Technology. For copying and distribution information, see the
+ *	file "mit-copyright.h".
+ */
+
+#ifndef __ZEPHYR_H__
+#define __ZEPHYR_H__
+
+#include <config.h>
+
+#include <glib.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include <zephyr_err.h>
+
+#ifndef IPPROTO_MAX	/* Make sure not already included */
+#ifndef WIN32
+#include <netinet/in.h>
+#endif
+#endif
+
+/* Use __STDC__ to guess whether we can use stdarg, prototypes, and const.
+ * This is a public header file, so autoconf can't help us here. */
+#ifdef __STDC__
+# include <stdarg.h>
+# define ZP(x) x
+# define ZCONST const
+#else
+# define ZP(x) ()
+# define ZCONST
+#endif
+
+#ifdef WIN32
+/* this really should be uint32_t */
+/*typedef unsigned int in_addr_t;
+struct in_addr
+{
+  in_addr_t s_addr;
+}; */
+#include <winsock2.h>
+#endif
+
+/* Service names */
+#define	HM_SVCNAME		"zephyr-hm"
+#define HM_SRV_SVCNAME		"zephyr-hm-srv"
+#define	SERVER_SVCNAME		"zephyr-clt"
+#define SERVER_SERVICE		"zephyr"
+#define SERVER_INSTANCE		"zephyr"
+
+#define ZVERSIONHDR	"ZEPH"
+#define ZVERSIONMAJOR	0
+#define ZVERSIONMINOR	2
+
+#define Z_MAXPKTLEN		1024
+#define Z_MAXHEADERLEN		800
+#define Z_MAXOTHERFIELDS	10	/* Max unknown fields in ZNotice_t */
+#define Z_NUMFIELDS		17
+
+/* Authentication levels returned by ZCheckAuthentication */
+#define ZAUTH_FAILED    	(-1)
+#define ZAUTH_YES       	1
+#define ZAUTH_NO        	0
+
+typedef char ZPacket_t[Z_MAXPKTLEN];
+
+/* Packet type */
+typedef enum {
+    UNSAFE, UNACKED, ACKED, HMACK, HMCTL, SERVACK, SERVNAK, CLIENTACK, STAT
+} ZNotice_Kind_t;
+extern ZCONST char *ZNoticeKinds[9];
+
+/* Unique ID format */
+typedef struct _ZUnique_Id_t {
+    struct	in_addr zuid_addr;
+    struct	timeval	tv;
+} ZUnique_Id_t;
+
+/* Checksum */
+typedef unsigned long ZChecksum_t;
+
+/* Notice definition */
+typedef struct _ZNotice_t {
+    char		*z_packet;
+    char		*z_version;
+    ZNotice_Kind_t	z_kind;
+    ZUnique_Id_t	z_uid;
+#define z_sender_addr	z_uid.zuid_addr
+    struct		timeval z_time;
+    unsigned short	z_port;
+    int			z_auth;
+    int			z_checked_auth;
+    int			z_authent_len;
+    char		*z_ascii_authent;
+    char		*z_class;
+    const char		*z_class_inst;
+    char		*z_opcode;
+    char		*z_sender;
+    const char		*z_recipient;
+    char		*z_default_format;
+    char		*z_multinotice;
+    ZUnique_Id_t	z_multiuid;
+    ZChecksum_t		z_checksum;
+    int			z_num_other_fields;
+    char		*z_other_fields[Z_MAXOTHERFIELDS];
+    caddr_t		z_message;
+    int			z_message_len;
+} ZNotice_t;
+
+/* Subscription structure */
+typedef struct _ZSubscriptions_t {
+    char	*zsub_recipient;
+    char	*zsub_class;
+    char	*zsub_classinst;
+} ZSubscription_t;
+
+/* Function return code */
+typedef int Code_t;
+
+/* Locations structure */
+typedef struct _ZLocations_t {
+    char	*host;
+    char	*time;
+    char	*tty;
+} ZLocations_t;
+
+typedef struct _ZAsyncLocateData_t {
+    char		*user;
+    ZUnique_Id_t	uid;
+    char		*version;
+} ZAsyncLocateData_t;
+
+/* for ZSetDebug */
+#ifdef Z_DEBUG
+void (*__Z_debug_print) ZP((ZCONST char *fmt, va_list args, void *closure));
+void *__Z_debug_print_closure;
+#endif
+
+int ZCompareUIDPred ZP((ZNotice_t *, void *));
+int ZCompareMultiUIDPred ZP((ZNotice_t *, void *));
+
+/* Defines for ZFormatNotice, et al. */
+typedef Code_t (*Z_AuthProc) ZP((ZNotice_t*, char *, int, int *));
+Code_t ZMakeAuthentication ZP((ZNotice_t*, char *,int, int*));
+
+char *ZGetSender ZP((void));
+char *ZGetVariable ZP((char *));
+Code_t ZSetVariable ZP((char *var, char *value));
+Code_t ZUnsetVariable ZP((char *var));
+int ZGetWGPort ZP((void));
+Code_t ZSetDestAddr ZP((struct sockaddr_in *));
+Code_t ZFormatNoticeList ZP((ZNotice_t*, char**, int,
+			     char **, int*, Z_AuthProc));
+Code_t ZParseNotice ZP((char*, int, ZNotice_t *));
+Code_t ZReadAscii ZP((char*, int, unsigned char*, int));
+Code_t ZReadAscii32 ZP((char *, int, unsigned long *));
+Code_t ZReadAscii16 ZP((char *, int, unsigned short *));
+Code_t ZSendPacket ZP((char*, int, int));
+Code_t ZSendList ZP((ZNotice_t*, char *[], int, Z_AuthProc));
+Code_t ZSrvSendList ZP((ZNotice_t*, char*[], int, Z_AuthProc, Code_t (*)()));
+Code_t ZSendNotice ZP((ZNotice_t *, Z_AuthProc));
+Code_t ZSrvSendNotice ZP((ZNotice_t*, Z_AuthProc, Code_t (*)()));
+Code_t ZFormatNotice ZP((ZNotice_t*, char**, int*, Z_AuthProc));
+Code_t ZFormatSmallNotice ZP((ZNotice_t*, ZPacket_t, int*, Z_AuthProc));
+Code_t ZFormatRawNoticeList ZP((ZNotice_t *notice, char *list[], int nitems,
+				char **buffer, int *ret_len));
+Code_t ZLocateUser ZP((char *, int *, Z_AuthProc));
+Code_t ZRequestLocations ZP((const char *, ZAsyncLocateData_t *,
+			     ZNotice_Kind_t, Z_AuthProc));
+Code_t ZhmStat ZP((struct in_addr *, ZNotice_t *));
+Code_t ZInitialize ZP((void));
+Code_t ZSetServerState ZP((int));
+Code_t ZSetFD ZP((int));
+Code_t ZFormatSmallRawNotice ZP((ZNotice_t*, ZPacket_t, int*));
+int ZCompareUID ZP((ZUnique_Id_t*, ZUnique_Id_t*));
+Code_t ZMakeAscii ZP((char*, int, unsigned char*, int));
+Code_t ZMakeAscii32 ZP((char *, int, unsigned long));
+Code_t ZMakeAscii16 ZP((char *, int, unsigned int));
+Code_t ZReceivePacket ZP((ZPacket_t, int*, struct sockaddr_in*));
+Code_t ZCheckAuthentication ZP((ZNotice_t*, struct sockaddr_in*));
+Code_t ZSetLocation ZP((char *exposure));
+Code_t ZUnsetLocation ZP((void));
+Code_t ZFlushMyLocations ZP((void));
+Code_t ZFormatRawNotice ZP((ZNotice_t *, char**, int *));
+Code_t ZRetrieveSubscriptions ZP((unsigned short, int*));
+Code_t ZOpenPort ZP((unsigned short *port));
+Code_t ZClosePort ZP((void));
+Code_t ZFlushLocations ZP((void));
+Code_t ZFlushSubscriptions ZP((void));
+Code_t ZFreeNotice ZP((ZNotice_t *notice));
+Code_t ZParseLocations ZP((register ZNotice_t *notice,
+			   register ZAsyncLocateData_t *zald, int *nlocs,
+			   char **user));
+int ZCompareALDPred ZP((ZNotice_t *notice, void *zald));
+void ZFreeALD ZP((register ZAsyncLocateData_t *zald));
+Code_t ZCheckIfNotice ZP((ZNotice_t *notice, struct sockaddr_in *from,
+			  register int (*predicate) ZP((ZNotice_t *,void *)),
+			  void *args));
+Code_t ZPeekPacket ZP((char **buffer, int *ret_len,
+		       struct sockaddr_in *from));
+Code_t ZPeekNotice ZP((ZNotice_t *notice, struct sockaddr_in *from));
+Code_t ZIfNotice ZP((ZNotice_t *notice, struct sockaddr_in *from,
+		     int (*predicate) ZP((ZNotice_t *, void *)), void *args));
+Code_t ZSubscribeTo ZP((ZSubscription_t *sublist, int nitems,
+			unsigned int port));
+Code_t ZSubscribeToSansDefaults ZP((ZSubscription_t *sublist, int nitems,
+				    unsigned int port));
+Code_t ZUnsubscribeTo ZP((ZSubscription_t *sublist, int nitems,
+			  unsigned int port));
+Code_t ZCancelSubscriptions ZP((unsigned int port));
+int ZPending ZP((void));
+Code_t ZReceiveNotice ZP((ZNotice_t *notice, struct sockaddr_in *from));
+#ifdef Z_DEBUG
+void Z_debug ZP((ZCONST char *, ...));
+#endif
+
+#undef ZP
+
+/* Compatibility */
+#define	ZNewLocateUser ZLocateUser
+
+/* Macros to retrieve Zephyr library values. */
+extern int __Zephyr_fd;
+extern int __Q_CompleteLength;
+extern struct sockaddr_in __HM_addr;
+extern char __Zephyr_realm[];
+#define ZGetFD()	__Zephyr_fd
+#define ZQLength()	__Q_CompleteLength
+#define ZGetDestAddr()	__HM_addr
+#define ZGetRealm()	__Zephyr_realm
+
+#ifdef Z_DEBUG
+void ZSetDebug ZP((void (*)(ZCONST char *, va_list, void *), void *));
+#define ZSetDebug(proc,closure)    (__Z_debug_print=(proc), \
+				    __Z_debug_print_closure=(closure), \
+				    (void) 0)
+#else
+#define	ZSetDebug(proc,closure)
+#endif
+
+/* Maximum queue length */
+#define Z_MAXQLEN 		30
+
+/* Successful function return */
+#define ZERR_NONE		0
+
+/* Hostmanager wait time (in secs) */
+#define HM_TIMEOUT		1
+
+/* Server wait time (in secs) */
+#define	SRV_TIMEOUT		30
+
+#define ZAUTH (ZMakeAuthentication)
+#define ZNOAUTH ((Z_AuthProc)0)
+
+/* Packet strings */
+#define ZSRVACK_SENT		"SENT"	/* SERVACK codes */
+#define ZSRVACK_NOTSENT		"LOST"
+#define ZSRVACK_FAIL		"FAIL"
+
+/* Server internal class */
+#define ZEPHYR_ADMIN_CLASS	"ZEPHYR_ADMIN"	/* Class */
+
+/* Control codes sent to a server */
+#define ZEPHYR_CTL_CLASS	"ZEPHYR_CTL"	/* Class */
+
+#define ZEPHYR_CTL_CLIENT	"CLIENT"	/* Inst: From client */
+#define CLIENT_SUBSCRIBE	"SUBSCRIBE"	/* Opcode: Subscribe */
+#define CLIENT_SUBSCRIBE_NODEFS	"SUBSCRIBE_NODEFS"	/* Opcode: Subscribe */
+#define CLIENT_UNSUBSCRIBE	"UNSUBSCRIBE"	/* Opcode: Unsubsubscribe */
+#define CLIENT_CANCELSUB	"CLEARSUB"	/* Opcode: Clear all subs */
+#define CLIENT_GIMMESUBS	"GIMME"		/* Opcode: Give me subs */
+#define	CLIENT_GIMMEDEFS	"GIMMEDEFS"	/* Opcode: Give me default
+						 * subscriptions */
+
+#define ZEPHYR_CTL_HM		"HM"		/* Inst: From HM */
+#define HM_BOOT			"BOOT"		/* Opcode: Boot msg */
+#define HM_FLUSH		"FLUSH"		/* Opcode: Flush me */
+#define HM_DETACH		"DETACH"	/* Opcode: Detach me */
+#define HM_ATTACH		"ATTACH"	/* Opcode: Attach me */
+
+/* Control codes send to a HostManager */
+#define	HM_CTL_CLASS		"HM_CTL"	/* Class */
+
+#define HM_CTL_SERVER		"SERVER"	/* Inst: From server */
+#define SERVER_SHUTDOWN		"SHUTDOWN"	/* Opcode: Server shutdown */
+#define SERVER_PING		"PING"		/* Opcode: PING */
+
+#define HM_CTL_CLIENT           "CLIENT"        /* Inst: From client */
+#define CLIENT_FLUSH            "FLUSH"         /* Opcode: Send flush to srv */
+#define CLIENT_NEW_SERVER       "NEWSERV"       /* Opcode: Find new server */
+
+/* HM Statistics */
+#define HM_STAT_CLASS		"HM_STAT"	/* Class */
+
+#define HM_STAT_CLIENT		"HMST_CLIENT"	/* Inst: From client */
+#define HM_GIMMESTATS		"GIMMESTATS"	/* Opcode: get stats */
+
+/* Login class messages */
+#define LOGIN_CLASS		"LOGIN"		/* Class */
+
+/* Class Instance is principal of user who is logging in or logging out */
+
+#define EXPOSE_NONE		"NONE"		/* Opcode: Not visible */
+#define EXPOSE_OPSTAFF		"OPSTAFF"	/* Opcode: Opstaff visible */
+#define EXPOSE_REALMVIS		"REALM-VISIBLE"	/* Opcode: Realm visible */
+#define EXPOSE_REALMANN		"REALM-ANNOUNCED"/* Opcode: Realm announced */
+#define EXPOSE_NETVIS		"NET-VISIBLE"	/* Opcode: Net visible */
+#define EXPOSE_NETANN		"NET-ANNOUNCED"	/* Opcode: Net announced */
+#define	LOGIN_USER_LOGIN	"USER_LOGIN"	/* Opcode: user login
+						   (from server) */
+#define LOGIN_USER_LOGOUT	"USER_LOGOUT"	/* Opcode: User logout */
+#define	LOGIN_USER_FLUSH	"USER_FLUSH"	/* Opcode: flush all locs */
+
+/* Locate class messages */
+#define LOCATE_CLASS		"USER_LOCATE"	/* Class */
+
+#define LOCATE_HIDE		"USER_HIDE"	/* Opcode: Hide me */
+#define LOCATE_UNHIDE		"USER_UNHIDE"	/* Opcode: Unhide me */
+
+/* Class Instance is principal of user to locate */
+#define LOCATE_LOCATE		"LOCATE"	/* Opcode: Locate user */
+
+/* WG_CTL class messages */
+#define WG_CTL_CLASS		"WG_CTL"	/* Class */
+
+#define WG_CTL_USER		"USER"		/* Inst: User request */
+#define USER_REREAD		"REREAD"	/* Opcode: Reread desc file */
+#define USER_SHUTDOWN		"SHUTDOWN"	/* Opcode: Go catatonic */
+#define USER_STARTUP		"STARTUP"	/* Opcode: Come out of it */
+#define USER_EXIT		"EXIT"		/* Opcode: Exit the client */
+
+#endif /* __ZEPHYR_H__ */
--- a/libpurple/proxy.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/proxy.c	Sun May 25 04:30:41 2008 +0000
@@ -837,33 +837,10 @@
 }
 
 static void
-http_canwrite(gpointer data, gint source, PurpleInputCondition cond)
-{
+http_start_connect_tunneling(PurpleProxyConnectData *connect_data) {
 	GString *request;
-	PurpleProxyConnectData *connect_data;
-	int error = ETIMEDOUT;
 	int ret;
 
-	connect_data = data;
-
-	purple_debug_info("proxy", "Connected to %s:%d.\n",
-		connect_data->host, connect_data->port);
-
-	if (connect_data->inpa > 0)
-	{
-		purple_input_remove(connect_data->inpa);
-		connect_data->inpa = 0;
-	}
-
-	ret = purple_input_get_error(connect_data->fd, &error);
-	if ((ret != 0) || (error != 0))
-	{
-		if (ret != 0)
-			error = errno;
-		purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
-		return;
-	}
-
 	purple_debug_info("proxy", "Using CONNECT tunneling for %s:%d\n",
 		connect_data->host, connect_data->port);
 
@@ -912,7 +889,45 @@
 
 	connect_data->inpa = purple_input_add(connect_data->fd,
 			PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
-	proxy_do_write(connect_data, connect_data->fd, cond);
+	proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
+}
+
+static void
+http_canwrite(gpointer data, gint source, PurpleInputCondition cond) {
+	PurpleProxyConnectData *connect_data = data;
+	int ret, error = ETIMEDOUT;
+
+	purple_debug_info("proxy", "Connected to %s:%d.\n",
+		connect_data->host, connect_data->port);
+
+	if (connect_data->inpa > 0)	{
+		purple_input_remove(connect_data->inpa);
+		connect_data->inpa = 0;
+	}
+
+	ret = purple_input_get_error(connect_data->fd, &error);
+	if (ret != 0 || error != 0) {
+		if (ret != 0)
+			error = errno;
+		purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
+		return;
+	}
+
+	if (connect_data->port == 80) {
+		/*
+		 * If we're trying to connect to something running on
+		 * port 80 then we assume the traffic using this
+		 * connection is going to be HTTP traffic.  If it's
+		 * not then this will fail (uglily).  But it's good
+		 * to avoid using the CONNECT method because it's
+		 * not always allowed.
+		 */
+		purple_debug_info("proxy", "HTTP proxy connection established\n");
+		purple_proxy_connect_data_connected(connect_data);
+	} else {
+		http_start_connect_tunneling(connect_data);
+	}
+
 }
 
 static void
@@ -940,39 +955,15 @@
 	fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
 #endif
 
-	if (connect(connect_data->fd, addr, addrlen) != 0)
-	{
-		if ((errno == EINPROGRESS) || (errno == EINTR))
-		{
+	if (connect(connect_data->fd, addr, addrlen) != 0) {
+		if (errno == EINPROGRESS || errno == EINTR) {
 			purple_debug_info("proxy", "Connection in progress\n");
 
-			if (connect_data->port != 80)
-			{
-				/* we need to do CONNECT first */
-				connect_data->inpa = purple_input_add(connect_data->fd,
-						PURPLE_INPUT_WRITE, http_canwrite, connect_data);
-			}
-			else
-			{
-				/*
-				 * If we're trying to connect to something running on
-				 * port 80 then we assume the traffic using this
-				 * connection is going to be HTTP traffic.  If it's
-				 * not then this will fail (uglily).  But it's good
-				 * to avoid using the CONNECT method because it's
-				 * not always allowed.
-				 */
-				purple_debug_info("proxy", "HTTP proxy connection established\n");
-				purple_proxy_connect_data_connected(connect_data);
-			}
-		}
-		else
-		{
+			connect_data->inpa = purple_input_add(connect_data->fd,
+					PURPLE_INPUT_WRITE, http_canwrite, connect_data);
+		} else
 			purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
-		}
-	}
-	else
-	{
+	} else {
 		purple_debug_info("proxy", "Connected immediately.\n");
 
 		http_canwrite(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
@@ -1145,7 +1136,7 @@
 	int len;
 
 	if (connect_data->read_buffer == NULL) {
-		connect_data->read_buf_len = 4;
+		connect_data->read_buf_len = 5;
 		connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
 		connect_data->read_len = 0;
 	}
@@ -1214,6 +1205,11 @@
 				return;
 			buf += 4 + 16;
 			break;
+		default:
+			purple_debug_error("socks5 proxy", "Invalid ATYP received (0x%X)\n", buf[3]);
+			purple_proxy_connect_data_disconnect(connect_data,
+					_("Received invalid data on connection with server."));
+			return;
 	}
 
 	/* Skip past BND.PORT */
--- a/libpurple/prpl.h	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/prpl.h	Sun May 25 04:30:41 2008 +0000
@@ -399,9 +399,45 @@
 	gboolean (*send_attention)(PurpleConnection *gc, const char *username, guint type);
 	GList *(*get_attention_types)(PurpleAccount *acct);
 
-	void (*_purple_reserved4)(void);
+	/**
+	 * The size of the PurplePluginProtocolInfo. This should always be sizeof(PurplePluginProtocolInfo).
+	 * This allows adding more functions to this struct without requiring a major version bump.
+	 */
+	unsigned long struct_size;
+
+	/* NOTE:
+	 * If more functions are added, they should accessed using the following syntax:
+	 *
+	 *		if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl, new_function))
+	 *			prpl->new_function(...);
+	 *
+	 * instead of
+	 *
+	 *		if (prpl->new_function != NULL)
+	 *			prpl->new_function(...);
+	 *
+	 * The PURPLE_PROTOCOL_PLUGIN_HAS_FUNC macro can be used for the older member
+	 * functions (e.g. login, send_im etc.) too.
+	 */
+
+	/** This allows protocols to specify additional strings to be used for
+	 * various purposes.  The idea is to stuff a bunch of strings in this hash
+	 * table instead of expanding the struct for every addition.  This hash
+	 * table is allocated every call and MUST be unrefed by the caller.
+	 *
+	 * @param account The account to specify.  This can be NULL.
+	 * @return The protocol's string hash table. The hash table should be
+	 *         destroyed by the caller when it's no longer needed.
+	 */
+	GHashTable *(*get_account_text_table)(PurpleAccount *account);
 };
 
+#define PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl, member) \
+	(((G_STRUCT_OFFSET(PurplePluginProtocolInfo, member) < G_STRUCT_OFFSET(PurplePluginProtocolInfo, struct_size)) \
+	  || (G_STRUCT_OFFSET(PurplePluginProtocolInfo, member) < prpl->struct_size)) && \
+	 prpl->member != NULL)
+
+
 #define PURPLE_IS_PROTOCOL_PLUGIN(plugin) \
 	((plugin)->info->type == PURPLE_PLUGIN_PROTOCOL)
 
--- a/libpurple/purple-remote	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/purple-remote	Sun May 25 04:30:41 2008 +0000
@@ -35,7 +35,7 @@
             raise "Error: " + self.attr + " " + str(args) + " returned " + str(result)
         return result
             
-def show_help():
+def show_help(requested=False):
     print """This program uses D-Bus to communicate with purple.
 
 Usage:
@@ -66,6 +66,10 @@
     PurpleAccountsFindConnected?name=&protocol=prpl-jabber
     PurpleAccountsFindConnected(,prpl-jabber)
 """ % sys.argv[0]
+    if (requested):
+        sys.exit(0)
+    else:
+        sys.exit(1)
 
 cpurple = CheckedObject(purple)
 
@@ -213,10 +217,11 @@
                                 raise "Don't know how to handle type \"%s\"" % type
                     return purple.__getattr__(command)(*methodparams)
             show_help()
-            raise "Unknown command: %s" % command
 
 if len(sys.argv) == 1:
     show_help()
+elif (sys.argv[1] == "--help" or sys.argv[1] == "-h"):
+    show_help(True)
 elif (obj == None):
     print "No existing libpurple instance detected."
     sys.exit(1);
--- a/libpurple/purple-url-handler	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/purple-url-handler	Sun May 25 04:30:41 2008 +0000
@@ -295,10 +295,14 @@
 
 
 def main(argv=sys.argv):
-    if len(argv) != 2:
+    if len(argv) != 2 or argv[1] == "--help" or argv[1] == "-h":
         print "Usage: %s URI" % argv[0]
         print "Example: %s \"xmpp:romeo@montague.net?message\"" % argv[0]
-        return
+
+        if len(argv) != 2:
+            sys.exit(1)
+        else:
+            return 0
 
     uri = argv[1]
     type = uri.split(":")[0]
--- a/libpurple/server.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/server.c	Sun May 25 04:30:41 2008 +0000
@@ -671,8 +671,11 @@
 
 	if (PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc))->set_permit_deny == NULL) {
 		/* protocol does not support privacy, handle it ourselves */
-		if (!purple_privacy_check(account, who))
+		if (!purple_privacy_check(account, who)) {
+			purple_signal_emit(purple_conversations_get_handle(), "blocked-im-msg",
+					account, who, msg, flags, (unsigned int)mtime);
 			return;
+		}
 	}
 
 	/*
@@ -891,8 +894,11 @@
 	account = purple_connection_get_account(gc);
 	if (PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc))->set_permit_deny == NULL) {
 		/* protocol does not support privacy, handle it ourselves */
-		if (!purple_privacy_check(account, who))
+		if (!purple_privacy_check(account, who)) {
+			purple_signal_emit(purple_conversations_get_handle(), "chat-invite-blocked",
+					account, who, name, message, data);
 			return;
+		}
 	}
 
 	plugin_return = GPOINTER_TO_INT(purple_signal_emit_return_1(
@@ -979,6 +985,12 @@
 	purple_signal_emit(purple_conversations_get_handle(), "chat-left", conv);
 }
 
+void purple_serv_got_join_chat_failed(PurpleConnection *gc, GHashTable *data)
+{
+	purple_signal_emit(purple_conversations_get_handle(), "chat-join-failed",
+					gc, data);
+}
+
 void serv_got_chat_in(PurpleConnection *g, int id, const char *who,
 					  PurpleMessageFlags flags, const char *message, time_t mtime)
 {
--- a/libpurple/server.h	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/server.h	Sun May 25 04:30:41 2008 +0000
@@ -166,6 +166,17 @@
 
 PurpleConversation *serv_got_joined_chat(PurpleConnection *gc,
 									   int id, const char *name);
+/**
+ * Called by a prpl when an attempt to join a chat via serv_join_chat()
+ * fails.
+ *
+ * @param gc      The connection on which chat joining failed
+ * @param data    The components passed to serv_join_chat() originally.
+ *                The hash function should be g_str_hash() and the equal
+ *                function should be g_str_equal().
+ */
+void purple_serv_got_join_chat_failed(PurpleConnection *gc, GHashTable *data);
+	
 void serv_got_chat_left(PurpleConnection *g, int id);
 void serv_got_chat_in(PurpleConnection *g, int id, const char *who,
 					  PurpleMessageFlags flags, const char *message, time_t mtime);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/smiley.c	Sun May 25 04:30:41 2008 +0000
@@ -0,0 +1,915 @@
+/**
+ * @file smiley.c Simley API
+ * @ingroup core
+ */
+
+/* purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#include "internal.h"
+#include "dbus-maybe.h"
+#include "debug.h"
+#include "imgstore.h"
+#include "smiley.h"
+#include "util.h"
+#include "xmlnode.h"
+
+/**************************************************************************/
+/* Main structures, members and constants                                 */
+/**************************************************************************/
+
+struct _PurpleSmiley
+{
+	GObject parent;
+	PurpleStoredImage *img;        /**< The id of the stored image with the
+	                                    the smiley data.        */
+	char *shortcut;                /**< Shortcut associated with the custom
+	                                    smiley. This field will work as a
+	                                    unique key by this API. */
+	char *checksum;                /**< The smiley checksum.        */
+};
+
+struct _PurpleSmileyClass
+{
+	GObjectClass parent_class;
+};
+
+static GHashTable *smiley_shortcut_index = NULL; /* shortcut (char *) => smiley (PurpleSmiley*) */
+static GHashTable *smiley_checksum_index = NULL; /* checksum (char *) => smiley (PurpleSmiley*) */
+
+static guint save_timer = 0;
+static gboolean smileys_loaded = FALSE;
+static char *smileys_dir = NULL;
+
+#define SMILEYS_DEFAULT_FOLDER			"custom_smiley"
+#define SMILEYS_LOG_ID				"smileys"
+
+#define XML_FILE_NAME				"smileys.xml"
+
+#define XML_ROOT_TAG				"smileys"
+#define XML_PROFILE_TAG			"profile"
+#define XML_PROFILE_NAME_ATTRIB_TAG		"name"
+#define XML_ACCOUNT_TAG			"account"
+#define XML_ACCOUNT_USERID_ATTRIB_TAG		"userid"
+#define XML_SMILEY_SET_TAG			"smiley_set"
+#define XML_SMILEY_TAG				"smiley"
+#define XML_SHORTCUT_ATTRIB_TAG		"shortcut"
+#define XML_CHECKSUM_ATRIB_TAG			"checksum"
+#define XML_FILENAME_ATRIB_TAG			"filename"
+
+
+/******************************************************************************
+ * XML descriptor file layout                                                 *
+ ******************************************************************************
+ *
+ * Althought we are creating the profile XML structure here, now we
+ * won't handle it.
+ * So, we just add one profile named "default" that has no associated
+ * account elements, and have only the smiley_set that will contain
+ * all existent custom smiley.
+ *
+ * It's our "Highlander Profile" :-)
+ *
+ ******************************************************************************
+ *
+ * <smileys>
+ *   <profile name="john.doe">
+ *     <account userid="john.doe@jabber.org">
+ *     <account userid="john.doe@gmail.com">
+ *     <smiley_set>
+ *       <smiley shortcut="aaa" checksum="xxxxxxxx" filename="file_name1.gif"/>
+ *       <smiley shortcut="bbb" checksum="yyyyyyy" filename="file_name2.gif"/>
+ *     </smiley_set>
+ *   </profile>
+ * </smiley>
+ *
+ *****************************************************************************/
+
+
+/*********************************************************************
+ * Forward declarations                                              *
+ *********************************************************************/
+
+static gboolean read_smiley_file(const char *path, guchar **data, size_t *len);
+
+static char *get_file_full_path(const char *filename);
+
+static PurpleSmiley *purple_smiley_create(const char *shortcut);
+
+static PurpleSmiley *purple_smiley_load_file(const char *shortcut, const char *checksum,
+		const char *filename);
+
+static void
+purple_smiley_set_data_impl(PurpleSmiley *smiley, guchar *smiley_data,
+		size_t smiley_data_len, const char *filename);
+
+static void
+purple_smiley_data_store(PurpleStoredImage *stored_img);
+
+static void
+purple_smiley_data_unstore(const char *filename);
+
+/*********************************************************************
+ * Writing to disk                                                   *
+ *********************************************************************/
+
+static xmlnode *
+smiley_to_xmlnode(PurpleSmiley *smiley)
+{
+	xmlnode *smiley_node = NULL;
+
+	smiley_node = xmlnode_new(XML_SMILEY_TAG);
+
+	if (!smiley_node)
+		return NULL;
+
+	xmlnode_set_attrib(smiley_node, XML_SHORTCUT_ATTRIB_TAG,
+			smiley->shortcut);
+
+	xmlnode_set_attrib(smiley_node, XML_CHECKSUM_ATRIB_TAG,
+			smiley->checksum);
+
+	xmlnode_set_attrib(smiley_node, XML_FILENAME_ATRIB_TAG,
+			purple_imgstore_get_filename(smiley->img));
+
+	return smiley_node;
+}
+
+static void
+add_smiley_to_main_node(gpointer key, gpointer value, gpointer user_data)
+{
+	xmlnode *child_node;
+
+	child_node = smiley_to_xmlnode(value);
+	xmlnode_insert_child((xmlnode*)user_data, child_node);
+}
+
+static xmlnode *
+smileys_to_xmlnode()
+{
+	xmlnode *root_node, *profile_node, *smileyset_node;
+
+	root_node = xmlnode_new(XML_ROOT_TAG);
+	xmlnode_set_attrib(root_node, "version", "1.0");
+
+	/* See the top comment's above to understand why initial tag elements
+	 * are not being considered by now. */
+	profile_node = xmlnode_new(XML_PROFILE_TAG);
+	if (profile_node) {
+		xmlnode_set_attrib(profile_node, XML_PROFILE_NAME_ATTRIB_TAG, "Default");
+		xmlnode_insert_child(root_node, profile_node);
+
+		smileyset_node = xmlnode_new(XML_SMILEY_SET_TAG);
+		if (smileyset_node) {
+			xmlnode_insert_child(profile_node, smileyset_node);
+			g_hash_table_foreach(smiley_shortcut_index, add_smiley_to_main_node, smileyset_node);
+		}
+	}
+
+	return root_node;
+}
+
+static void
+sync_smileys()
+{
+	xmlnode *root_node;
+	char *data;
+
+	if (!smileys_loaded) {
+		purple_debug_error(SMILEYS_LOG_ID, "Attempted to save smileys before it "
+						 "was read!\n");
+		return;
+	}
+
+	root_node = smileys_to_xmlnode();
+	data = xmlnode_to_formatted_str(root_node, NULL);
+	purple_util_write_data_to_file(XML_FILE_NAME, data, -1);
+
+	g_free(data);
+	xmlnode_free(root_node);
+}
+
+static gboolean
+save_smileys_cb(gpointer data)
+{
+	sync_smileys();
+	save_timer = 0;
+	return FALSE;
+}
+
+static void
+purple_smileys_save()
+{
+	if (save_timer == 0)
+		save_timer = purple_timeout_add_seconds(5, save_smileys_cb, NULL);
+}
+
+
+/*********************************************************************
+ * Reading from disk                                                 *
+ *********************************************************************/
+
+static PurpleSmiley *
+parse_smiley(xmlnode *smiley_node)
+{
+	PurpleSmiley *smiley;
+	const char *shortcut = NULL;
+	const char *checksum = NULL;
+	const char *filename = NULL;
+
+	shortcut = xmlnode_get_attrib(smiley_node, XML_SHORTCUT_ATTRIB_TAG);
+	checksum = xmlnode_get_attrib(smiley_node, XML_CHECKSUM_ATRIB_TAG);
+	filename = xmlnode_get_attrib(smiley_node, XML_FILENAME_ATRIB_TAG);
+
+	if ((shortcut == NULL) || (checksum == NULL) || (filename == NULL))
+		return NULL;
+
+	smiley = purple_smiley_load_file(shortcut, checksum, filename);
+
+	return smiley;
+}
+
+static void
+purple_smileys_load()
+{
+	xmlnode *root_node, *profile_node;
+	xmlnode *smileyset_node = NULL;
+	xmlnode *smiley_node;
+
+	smileys_loaded = TRUE;
+
+	root_node = purple_util_read_xml_from_file(XML_FILE_NAME,
+			_(SMILEYS_LOG_ID));
+
+	if (root_node == NULL)
+		return;
+
+	/* See the top comment's above to understand why initial tag elements
+	 * are not being considered by now. */
+	profile_node = xmlnode_get_child(root_node, XML_PROFILE_TAG);
+	if (profile_node)
+		smileyset_node = xmlnode_get_child(profile_node, XML_SMILEY_SET_TAG);
+
+	if (smileyset_node) {
+		smiley_node = xmlnode_get_child(smileyset_node, XML_SMILEY_TAG);
+		for (; smiley_node != NULL;
+				smiley_node = xmlnode_get_next_twin(smiley_node)) {
+			PurpleSmiley *smiley;
+
+			smiley = parse_smiley(smiley_node);
+		}
+	}
+
+	xmlnode_free(root_node);
+}
+
+/*********************************************************************
+ * GObject Stuff                                                     *
+ *********************************************************************/
+enum
+{
+	PROP_0,
+	PROP_SHORTCUT,
+	PROP_IMGSTORE,
+};
+
+#define PROP_SHORTCUT_S "shortcut"
+#define PROP_IMGSTORE_S "image"
+
+enum
+{
+	SIG_DESTROY,
+	SIG_LAST
+};
+
+static guint signals[SIG_LAST];
+static GObjectClass *parent_class;
+
+static void
+purple_smiley_init(GTypeInstance *instance, gpointer klass)
+{
+	PurpleSmiley *smiley = PURPLE_SMILEY(instance);
+	PURPLE_DBUS_REGISTER_POINTER(smiley, PurpleSmiley);
+}
+
+static void
+purple_smiley_get_property(GObject *object, guint param_id, GValue *value,
+		GParamSpec *spec)
+{
+	PurpleSmiley *smiley = PURPLE_SMILEY(object);
+	switch (param_id) {
+		case PROP_SHORTCUT:
+			g_value_set_string(value, smiley->shortcut);
+			break;
+		case PROP_IMGSTORE:
+			g_value_set_pointer(value, smiley->img);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, spec);
+			break;
+	}
+}
+
+static void
+purple_smiley_set_property(GObject *object, guint param_id, const GValue *value,
+		GParamSpec *spec)
+{
+	PurpleSmiley *smiley = PURPLE_SMILEY(object);
+	switch (param_id) {
+		case PROP_SHORTCUT:
+			{
+				const char *shortcut = g_value_get_string(value);
+				purple_smiley_set_shortcut(smiley, shortcut);
+			}
+			break;
+		case PROP_IMGSTORE:
+			{
+				PurpleStoredImage *img = g_value_get_pointer(value);
+
+				purple_imgstore_unref(smiley->img);
+				g_free(smiley->checksum);
+
+				smiley->img = img;
+				if (img) {
+					smiley->checksum = purple_util_get_image_checksum(
+							purple_imgstore_get_data(img),
+							purple_imgstore_get_size(img));
+					purple_smiley_data_store(img);
+				} else {
+					smiley->checksum = NULL;
+				}
+
+				g_object_notify(object, PROP_IMGSTORE_S);
+			}
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, spec);
+			break;
+	}
+}
+
+static void
+purple_smiley_finalize(GObject *obj)
+{
+	PurpleSmiley *smiley = PURPLE_SMILEY(obj);
+
+	if (g_hash_table_lookup(smiley_shortcut_index, smiley->shortcut)) {
+		g_hash_table_remove(smiley_shortcut_index, smiley->shortcut);
+		g_hash_table_remove(smiley_checksum_index, smiley->checksum);
+	}
+
+	g_free(smiley->shortcut);
+	g_free(smiley->checksum);
+	if (smiley->img)
+		purple_smiley_data_unstore(purple_imgstore_get_filename(smiley->img));
+	purple_imgstore_unref(smiley->img);
+
+	PURPLE_DBUS_UNREGISTER_POINTER(smiley);
+
+	purple_smileys_save();
+}
+
+static void
+purple_smiley_dispose(GObject *gobj)
+{
+	g_signal_emit(gobj, signals[SIG_DESTROY], 0);
+	parent_class->dispose(gobj);
+}
+
+static void
+purple_smiley_class_init(PurpleSmileyClass *klass)
+{
+	GObjectClass *gobj_class = G_OBJECT_CLASS(klass);
+	GParamSpec *pspec;
+
+	parent_class = g_type_class_peek_parent(klass);
+
+	gobj_class->get_property = purple_smiley_get_property;
+	gobj_class->set_property = purple_smiley_set_property;
+	gobj_class->finalize = purple_smiley_finalize;
+	gobj_class->dispose = purple_smiley_dispose;
+
+	/* Shortcut */
+	pspec = g_param_spec_string(PROP_SHORTCUT_S, _("Shortcut"),
+			_("The text-shortcut for the smiley"),
+			NULL,
+			G_PARAM_READWRITE);
+	g_object_class_install_property(gobj_class, PROP_SHORTCUT, pspec);
+
+	/* Stored Image */
+	pspec = g_param_spec_pointer(PROP_IMGSTORE_S, _("Stored Image"),
+			_("Stored Image. (that'll have to do for now)"),
+			G_PARAM_READWRITE);
+	g_object_class_install_property(gobj_class, PROP_IMGSTORE, pspec);
+
+	signals[SIG_DESTROY] = g_signal_new("destroy",
+			G_OBJECT_CLASS_TYPE(klass),
+			G_SIGNAL_RUN_LAST,
+			0, NULL, NULL,
+			g_cclosure_marshal_VOID__VOID,
+			G_TYPE_NONE, 0);
+}
+
+GType
+purple_smiley_get_type(void)
+{
+	static GType type = 0;
+
+	if(type == 0) {
+		static const GTypeInfo info = {
+			sizeof(PurpleSmileyClass),
+			NULL,
+			NULL,
+			(GClassInitFunc)purple_smiley_class_init,
+			NULL,
+			NULL,
+			sizeof(PurpleSmiley),
+			0,
+			purple_smiley_init,
+			NULL,
+		};
+
+		type = g_type_register_static(G_TYPE_OBJECT,
+				"PurpleSmiley",
+				&info, 0);
+	}
+
+	return type;
+}
+
+/*********************************************************************
+ * Other Stuff                                                             *
+ *********************************************************************/
+
+static char *get_file_full_path(const char *filename)
+{
+	char *path;
+
+	path = g_build_filename(purple_smileys_get_storing_dir(), filename, NULL);
+
+	if (!g_file_test(path, G_FILE_TEST_EXISTS)) {
+		g_free(path);
+		return NULL;
+	}
+
+	return path;
+}
+
+static PurpleSmiley *
+purple_smiley_load_file(const char *shortcut, const char *checksum, const char *filename)
+{
+	PurpleSmiley *smiley = NULL;
+	guchar *smiley_data;
+	size_t smiley_data_len;
+	char *fullpath = NULL;
+
+	g_return_val_if_fail(shortcut  != NULL, NULL);
+	g_return_val_if_fail(checksum  != NULL, NULL);
+	g_return_val_if_fail(filename != NULL, NULL);
+
+	fullpath = get_file_full_path(filename);
+	if (!fullpath)
+		return NULL;
+
+	smiley = purple_smiley_create(shortcut);
+	if (!smiley) {
+		g_free(fullpath);
+		return NULL;
+	}
+
+	smiley->checksum = g_strdup(checksum);
+
+	if (read_smiley_file(fullpath, &smiley_data, &smiley_data_len))
+		purple_smiley_set_data_impl(smiley, smiley_data,
+				smiley_data_len, filename);
+	else
+		purple_smiley_delete(smiley);
+
+	g_free(fullpath);
+
+	return smiley;
+}
+
+static void
+purple_smiley_data_store(PurpleStoredImage *stored_img)
+{
+	const char *dirname;
+	char *path;
+	FILE *file = NULL;
+
+	g_return_if_fail(stored_img != NULL);
+
+	if (!smileys_loaded)
+		return;
+
+	dirname  = purple_smileys_get_storing_dir();
+	path = g_build_filename(dirname, purple_imgstore_get_filename(stored_img), NULL);
+
+	if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) {
+		purple_debug_info(SMILEYS_LOG_ID, "Creating smileys directory.\n");
+
+		if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0) {
+			purple_debug_error(SMILEYS_LOG_ID,
+			                   "Unable to create directory %s: %s\n",
+			                   dirname, g_strerror(errno));
+		}
+	}
+
+	if ((file = g_fopen(path, "wb")) != NULL) {
+		if (!fwrite(purple_imgstore_get_data(stored_img),
+				purple_imgstore_get_size(stored_img), 1, file)) {
+			purple_debug_error(SMILEYS_LOG_ID, "Error writing %s: %s\n",
+			                   path, g_strerror(errno));
+		} else {
+			purple_debug_info(SMILEYS_LOG_ID, "Wrote cache file: %s\n", path);
+		}
+
+		fclose(file);
+	} else {
+		purple_debug_error(SMILEYS_LOG_ID, "Unable to create file %s: %s\n",
+		                   path, g_strerror(errno));
+		g_free(path);
+
+		return;
+	}
+
+	g_free(path);
+}
+
+static void
+purple_smiley_data_unstore(const char *filename)
+{
+	const char *dirname;
+	char *path;
+
+	g_return_if_fail(filename != NULL);
+
+	dirname  = purple_smileys_get_storing_dir();
+	path = g_build_filename(dirname, filename, NULL);
+
+	if (g_file_test(path, G_FILE_TEST_EXISTS)) {
+		if (g_unlink(path))
+			purple_debug_error(SMILEYS_LOG_ID, "Failed to delete %s: %s\n",
+			                   path, g_strerror(errno));
+		else
+			purple_debug_info(SMILEYS_LOG_ID, "Deleted cache file: %s\n", path);
+	}
+
+	g_free(path);
+}
+
+static gboolean
+read_smiley_file(const char *path, guchar **data, size_t *len)
+{
+	GError *err = NULL;
+
+	if (!g_file_get_contents(path, (gchar **)data, len, &err)) {
+		purple_debug_error(SMILEYS_LOG_ID, "Error reading %s: %s\n",
+				path, err->message);
+		g_error_free(err);
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static PurpleStoredImage *
+purple_smiley_data_new(guchar *smiley_data, size_t smiley_data_len)
+{
+	char *filename;
+	PurpleStoredImage *stored_img;
+
+	g_return_val_if_fail(smiley_data != NULL,   NULL);
+	g_return_val_if_fail(smiley_data_len  > 0,  NULL);
+
+	filename = purple_util_get_image_filename(smiley_data, smiley_data_len);
+
+	if (filename == NULL) {
+		g_free(smiley_data);
+		return NULL;
+	}
+
+	stored_img = purple_imgstore_add(smiley_data, smiley_data_len, filename);
+
+	g_free(filename);
+
+	return stored_img;
+}
+
+static void
+purple_smiley_set_data_impl(PurpleSmiley *smiley, guchar *smiley_data,
+				size_t smiley_data_len, const char *filename)
+{
+	PurpleStoredImage *old_img, *new_img;
+	const char *old_filename = NULL;
+	const char *new_filename = NULL;
+
+	g_return_if_fail(smiley     != NULL);
+	g_return_if_fail(smiley_data != NULL);
+	g_return_if_fail(smiley_data_len > 0);
+
+	old_img = smiley->img;
+
+	if (filename)
+		new_img = purple_imgstore_add(smiley_data, smiley_data_len, filename);
+	else
+		new_img = purple_smiley_data_new(smiley_data, smiley_data_len);
+
+	g_object_set(G_OBJECT(smiley), PROP_IMGSTORE_S, new_img, NULL);
+
+	/* If the old and new image files have different names we need
+	 * to unstore old image file. */
+	if (!old_img)
+		return;
+
+	old_filename = purple_imgstore_get_filename(old_img);
+	new_filename = purple_imgstore_get_filename(smiley->img);
+
+	if (g_ascii_strcasecmp(old_filename, new_filename)) {
+		purple_smiley_data_unstore(old_filename);
+		purple_imgstore_unref(old_img);
+	}
+}
+
+
+/*****************************************************************************
+ * Public API functions                                                      *
+ *****************************************************************************/
+
+static PurpleSmiley *
+purple_smiley_create(const char *shortcut)
+{
+	PurpleSmiley *smiley;
+
+	smiley = PURPLE_SMILEY(g_object_new(PURPLE_TYPE_SMILEY, PROP_SHORTCUT_S, shortcut, NULL));
+
+	return smiley;
+}
+
+PurpleSmiley *
+purple_smiley_new(PurpleStoredImage *img, const char *shortcut)
+{
+	PurpleSmiley *smiley = NULL;
+
+	g_return_val_if_fail(shortcut  != NULL, NULL);
+	g_return_val_if_fail(img       != NULL, NULL);
+
+	smiley = purple_smileys_find_by_shortcut(shortcut);
+	if (smiley)
+		return smiley;
+
+	smiley = purple_smiley_create(shortcut);
+	if (!smiley)
+		return NULL;
+
+	g_object_set(G_OBJECT(smiley), PROP_IMGSTORE_S, img, NULL);
+
+	return smiley;
+}
+
+static PurpleSmiley *
+purple_smiley_new_from_stream(const char *shortcut, guchar *smiley_data,
+			size_t smiley_data_len, const char *filename)
+{
+	PurpleSmiley *smiley;
+
+	g_return_val_if_fail(shortcut  != NULL,    NULL);
+	g_return_val_if_fail(smiley_data != NULL,  NULL);
+	g_return_val_if_fail(smiley_data_len  > 0, NULL);
+
+	smiley = purple_smileys_find_by_shortcut(shortcut);
+	if (smiley)
+		return smiley;
+
+	/* purple_smiley_create() sets shortcut */
+	smiley = purple_smiley_create(shortcut);
+	if (!smiley)
+		return NULL;
+
+	purple_smiley_set_data_impl(smiley, smiley_data, smiley_data_len, filename);
+
+	purple_smiley_data_store(smiley->img);
+
+	return smiley;
+}
+
+PurpleSmiley *
+purple_smiley_new_from_file(const char *shortcut, const char *filepath)
+{
+	PurpleSmiley *smiley = NULL;
+	guchar *smiley_data;
+	size_t smiley_data_len;
+	char *filename;
+
+	g_return_val_if_fail(shortcut  != NULL,  NULL);
+	g_return_val_if_fail(filepath != NULL,  NULL);
+
+	filename = g_path_get_basename(filepath);
+	if (read_smiley_file(filepath, &smiley_data, &smiley_data_len))
+		smiley = purple_smiley_new_from_stream(shortcut, smiley_data,
+				smiley_data_len, filename);
+	g_free(filename);
+
+	return smiley;
+}
+
+void
+purple_smiley_delete(PurpleSmiley *smiley)
+{
+	g_return_if_fail(smiley != NULL);
+
+	g_object_unref(smiley);
+}
+
+gboolean
+purple_smiley_set_shortcut(PurpleSmiley *smiley, const char *shortcut)
+{
+	g_return_val_if_fail(smiley  != NULL, FALSE);
+	g_return_val_if_fail(shortcut != NULL, FALSE);
+
+	/* Check out whether the new shortcut is already being used. */
+	if (g_hash_table_lookup(smiley_shortcut_index, shortcut))
+		return FALSE;
+
+	/* Remove the old shortcut. */
+	if (smiley->shortcut)
+		g_hash_table_remove(smiley_shortcut_index, smiley->shortcut);
+
+	/* Insert the new shortcut. */
+	g_hash_table_insert(smiley_shortcut_index, g_strdup(shortcut), smiley);
+
+	g_free(smiley->shortcut);
+	smiley->shortcut = g_strdup(shortcut);
+
+	g_object_notify(G_OBJECT(smiley), PROP_SHORTCUT_S);
+
+	purple_smileys_save();
+
+	return TRUE;
+}
+
+void
+purple_smiley_set_data(PurpleSmiley *smiley, guchar *smiley_data,
+			   size_t smiley_data_len, gboolean keepfilename)
+{
+	g_return_if_fail(smiley     != NULL);
+	g_return_if_fail(smiley_data != NULL);
+	g_return_if_fail(smiley_data_len > 0);
+
+	/* Remove the previous entry */
+	g_hash_table_remove(smiley_checksum_index, smiley->checksum);
+
+	/* Update the file data. This also updates the checksum. */
+	if ((keepfilename) && (smiley->img) &&
+			(purple_imgstore_get_filename(smiley->img)))
+		purple_smiley_set_data_impl(smiley, smiley_data,
+				smiley_data_len,
+				purple_imgstore_get_filename(smiley->img));
+	else
+		purple_smiley_set_data_impl(smiley, smiley_data,
+				smiley_data_len, NULL);
+
+	/* Reinsert the index item. */
+	g_hash_table_insert(smiley_checksum_index, g_strdup(smiley->checksum), smiley);
+
+	purple_smileys_save();
+}
+
+PurpleStoredImage *
+purple_smiley_get_stored_image(const PurpleSmiley *smiley)
+{
+	return purple_imgstore_ref(smiley->img);
+}
+
+const char *purple_smiley_get_shortcut(const PurpleSmiley *smiley)
+{
+	g_return_val_if_fail(smiley != NULL, NULL);
+
+	return smiley->shortcut;
+}
+
+const char *
+purple_smiley_get_checksum(const PurpleSmiley *smiley)
+{
+	g_return_val_if_fail(smiley != NULL, NULL);
+
+	return smiley->checksum;
+}
+
+gconstpointer
+purple_smiley_get_data(const PurpleSmiley *smiley, size_t *len)
+{
+	g_return_val_if_fail(smiley != NULL, NULL);
+
+	if (smiley->img) {
+		if (len != NULL)
+			*len = purple_imgstore_get_size(smiley->img);
+
+		return purple_imgstore_get_data(smiley->img);
+	}
+
+	return NULL;
+}
+
+const char *
+purple_smiley_get_extension(const PurpleSmiley *smiley)
+{
+	if (smiley->img != NULL)
+		return purple_imgstore_get_extension(smiley->img);
+
+	return NULL;
+}
+
+char *purple_smiley_get_full_path(PurpleSmiley *smiley)
+{
+	g_return_val_if_fail(smiley != NULL, NULL);
+
+	if (smiley->img == NULL)
+		return NULL;
+
+	return get_file_full_path(purple_imgstore_get_filename(smiley->img));
+}
+
+static void add_smiley_to_list(gpointer key, gpointer value, gpointer user_data)
+{
+	GList** returninglist = (GList**)user_data;
+
+	*returninglist = g_list_append(*returninglist, value);
+}
+
+GList *
+purple_smileys_get_all(void)
+{
+	GList *returninglist = NULL;
+
+	g_hash_table_foreach(smiley_shortcut_index, add_smiley_to_list, &returninglist);
+
+	return returninglist;
+}
+
+PurpleSmiley *
+purple_smileys_find_by_shortcut(const char *shortcut)
+{
+	g_return_val_if_fail(shortcut != NULL, NULL);
+
+	return g_hash_table_lookup(smiley_shortcut_index, shortcut);
+}
+
+PurpleSmiley *
+purple_smileys_find_by_checksum(const char *checksum)
+{
+	g_return_val_if_fail(checksum != NULL, NULL);
+
+	return g_hash_table_lookup(smiley_checksum_index, checksum);
+}
+
+const char *
+purple_smileys_get_storing_dir(void)
+{
+	return smileys_dir;
+}
+
+void
+purple_smileys_init()
+{
+	smiley_shortcut_index = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+	smiley_checksum_index = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
+	smileys_dir = g_build_filename(purple_user_dir(), SMILEYS_DEFAULT_FOLDER, NULL);
+
+	purple_smileys_load();
+}
+
+void
+purple_smileys_uninit()
+{
+	if (save_timer != 0) {
+		purple_timeout_remove(save_timer);
+		save_timer = 0;
+		sync_smileys();
+	}
+
+	g_hash_table_destroy(smiley_shortcut_index);
+	g_hash_table_destroy(smiley_checksum_index);
+	g_free(smileys_dir);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/smiley.h	Sun May 25 04:30:41 2008 +0000
@@ -0,0 +1,271 @@
+/**
+ * @file smiley.h Smiley API
+ * @ingroup core
+ * @since 2.5.0
+ */
+
+/* purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ *
+ */
+
+#ifndef _PURPLE_SMILEY_H_
+#define _PURPLE_SMILEY_H_
+
+#include <glib-object.h>
+
+#include "imgstore.h"
+#include "util.h"
+
+/**
+ * A custom smiley.
+ * This contains everything Purple will ever need to know about a custom smiley.
+ * Everything.
+ *
+ * PurpleSmiley is a GObject.
+ */
+typedef struct _PurpleSmiley        PurpleSmiley;
+typedef struct _PurpleSmileyClass   PurpleSmileyClass;
+
+#define PURPLE_TYPE_SMILEY             (purple_smiley_get_type ())
+#define PURPLE_SMILEY(smiley)          (G_TYPE_CHECK_INSTANCE_CAST ((smiley), PURPLE_TYPE_SMILEY, PurpleSmiley))
+#define PURPLE_SMILEY_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), PURPLE_TYPE_SMILEY, PurpleSmileyClass))
+#define PURPLE_IS_SMILEY(smiley)       (G_TYPE_CHECK_INSTANCE_TYPE ((smiley), PURPLE_TYPE_SMILEY))
+#define PURPLE_IS_SMILEY_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), PURPLE_TYPE_SMILEY))
+#define PURPLE_SMILEY_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), PURPLE_TYPE_SMILEY, PurpleSmileyClass))
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**************************************************************************/
+/** @name Custom Smiley API                                               */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * GObject foo.
+ * @internal.
+ */
+GType purple_smiley_get_type(void);
+
+/**
+ * Creates a new custom smiley structure and populates it.
+ *
+ * If a custom smiley with the informed shortcut already exist, it
+ * will be automaticaly returned.
+ *
+ * @param img         The image associated with the smiley.
+ * @param shortcut    The custom smiley associated shortcut.
+ *
+ * @return The custom smiley structure filled up.
+ */
+PurpleSmiley *
+purple_smiley_new(PurpleStoredImage *img, const char *shortcut);
+
+/**
+ * Creates a new custom smiley structure and populates it.
+ *
+ * The data is retrieved from an already existent file.
+ *
+ * If a custom smiley with the informed shortcut already exist, it
+ * will be automaticaly returned.
+ *
+ * @param shortcut           The custom smiley associated shortcut.
+ * @param filepath           The image file to be imported to a
+ *                           new custom smiley.
+ *
+ * @return The custom smiley structure filled up.
+ */
+PurpleSmiley *
+purple_smiley_new_from_file(const char *shortcut, const char *filepath);
+
+/**
+ * Destroy the custom smiley and release the associated resources.
+ *
+ * @param smiley    The custom smiley.
+ */
+void
+purple_smiley_delete(PurpleSmiley *smiley);
+
+/**
+ * Changes the custom smiley's shortcut.
+ *
+ * @param smiley    The custom smiley.
+ * @param shortcut  The custom smiley associated shortcut.
+ *
+ * @return TRUE whether the shortcut is not associated with another
+ *         custom smiley and the parameters are valid. FALSE otherwise.
+ */
+gboolean
+purple_smiley_set_shortcut(PurpleSmiley *smiley, const char *shortcut);
+
+/**
+ * Changes the custom smiley's data.
+ *
+ * When the filename controling is made outside this API, the param
+ * #keepfilename must be TRUE.
+ * Otherwise, the file and filename will be regenerated, and the
+ * old one will be removed.
+ *
+ * @param smiley             The custom smiley.
+ * @param smiley_data        The custom smiley data.
+ * @param smiley_data_len    The custom smiley data length.
+ * @param keepfilename      The current custom smiley's filename must be
+ *                           kept.
+ */
+void
+purple_smiley_set_data(PurpleSmiley *smiley, guchar *smiley_data,
+                                           size_t smiley_data_len, gboolean keepfilename);
+
+/**
+ * Returns the custom smiley's associated shortcut.
+ *
+ * @param smiley   The custom smiley.
+ *
+ * @return The shortcut.
+ */
+const char *purple_smiley_get_shortcut(const PurpleSmiley *smiley);
+
+/**
+ * Returns the custom smiley data's checksum.
+ *
+ * @param smiley   The custom smiley.
+ *
+ * @return The checksum.
+ */
+const char *purple_smiley_get_checksum(const PurpleSmiley *smiley);
+
+/**
+ * Returns the PurpleStoredImage with the reference counter incremented.
+ *
+ * The returned PurpleStoredImage reference counter must be decremented
+ * after use.
+ *
+ * @param smiley   The custom smiley.
+ *
+ * @return A PurpleStoredImage reference.
+ */
+PurpleStoredImage *purple_smiley_get_stored_image(const PurpleSmiley *smiley);
+
+/**
+ * Returns the custom smiley's data.
+ *
+ * @param smiley  The custom smiley.
+ * @param len     If not @c NULL, the length of the icon data returned
+ *                will be set in the location pointed to by this.
+ *
+ * @return A pointer to the custom smiley data.
+ */
+gconstpointer purple_smiley_get_data(const PurpleSmiley *smiley, size_t *len);
+
+/**
+ * Returns an extension corresponding to the custom smiley's file type.
+ *
+ * @param smiley  The custom smiley.
+ *
+ * @return The custom smiley's extension, "icon" if unknown, or @c NULL if
+ *         the image data has disappeared.
+ */
+const char *purple_smiley_get_extension(const PurpleSmiley *smiley);
+
+/**
+ * Returns a full path to an custom smiley.
+ *
+ * If the custom smiley has data and the file exists in the cache, this
+ * will return a full path to the cached file.
+ *
+ * In general, it is not appropriate to be poking in the file cached
+ * directly.  If you find yourself wanting to use this function, think
+ * very long and hard about it, and then don't.
+ *
+ * @param smiley  The custom smiley.
+ *
+ * @return A full path to the file, or @c NULL under various conditions.
+ *         The caller should use #g_free to free the returned string.
+ */
+char *purple_smiley_get_full_path(PurpleSmiley *smiley);
+
+/*@}*/
+
+
+/**************************************************************************/
+/** @name Custom Smiley Subsystem API                                     */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Returns a list of all custom smileys. The caller should free the list.
+ *
+ * @return A list of all custom smileys.
+ */
+GList *
+purple_smileys_get_all(void);
+
+/**
+ * Returns the custom smiley given it's shortcut.
+ *
+ * @param shortcut The custom smiley's shortcut.
+ *
+ * @return The custom smiley (with a reference for the caller) if found,
+ *         or @c NULL if not found.
+ */
+PurpleSmiley *
+purple_smileys_find_by_shortcut(const char *shortcut);
+
+/**
+ * Returns the custom smiley given it's checksum.
+ *
+ * @param checksum The custom smiley's checksum.
+ *
+ * @return The custom smiley (with a reference for the caller) if found,
+ *         or @c NULL if not found.
+ */
+PurpleSmiley *
+purple_smileys_find_by_checksum(const char *checksum);
+
+/**
+ * Returns the directory used to store custom smiley cached files.
+ *
+ * The default directory is PURPLEDIR/smileys, unless otherwise specified
+ * by purple_buddy_icons_set_cache_dir().
+ *
+ * @return The directory to store custom smyles cached files to.
+ */
+const char *purple_smileys_get_storing_dir(void);
+
+/**
+ * Initializes the custom smiley subsystem.
+ */
+void purple_smileys_init(void);
+
+/**
+ * Uninitializes the custom smiley subsystem.
+ */
+void purple_smileys_uninit(void);
+
+/*@}*/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PURPLE_SMILEY_H_ */
+
--- a/libpurple/status.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/status.c	Sun May 25 04:30:41 2008 +0000
@@ -111,6 +111,12 @@
 
 	gboolean active;
 
+	/*
+	 * The current values of the attributes for this status.  The
+	 * key is a string containing the name of the attribute.  It is
+	 * a borrowed reference from the list of attrs in the
+	 * PurpleStatusType.  The value is a PurpleValue.
+	 */
 	GHashTable *attr_values;
 };
 
@@ -563,7 +569,7 @@
 	status->presence = presence;
 
 	status->attr_values =
-		g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+		g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
 		(GDestroyNotify)purple_value_destroy);
 
 	for (l = purple_status_type_get_attrs(status_type); l != NULL; l = l->next)
@@ -573,7 +579,7 @@
 		PurpleValue *new_value = purple_value_dup(value);
 
 		g_hash_table_insert(status->attr_values,
-							g_strdup(purple_status_attr_get_id(attr)),
+							(char *)purple_status_attr_get_id(attr),
 							new_value);
 	}
 
--- a/libpurple/util.c	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/util.c	Sun May 25 04:30:41 2008 +0000
@@ -939,7 +939,8 @@
 	else if(IS_ENTITY("&apos;"))
 		pln = "\'";
 	else if(*(text+1) == '#' &&
-			(sscanf(text, "&#%u%1[;]", &pound, temp) == 2 || sscanf(text, "&#x%x%1[;]", &pound, temp) == 2) &&
+			(sscanf(text, "&#%u%1[;]", &pound, temp) == 2 ||
+			 sscanf(text, "&#x%x%1[;]", &pound, temp) == 2) &&
 			pound != 0) {
 		static char buf[7];
 		int buflen = g_unichar_to_utf8((gunichar)pound, buf);
@@ -2891,7 +2892,7 @@
 }
 
 char *
-purple_util_get_image_filename(gconstpointer image_data, size_t image_len)
+purple_util_get_image_checksum(gconstpointer image_data, size_t image_len)
 {
 	PurpleCipherContext *context;
 	gchar digest[41];
@@ -2912,9 +2913,18 @@
 	}
 	purple_cipher_context_destroy(context);
 
+	return g_strdup(digest);
+}
+
+char *
+purple_util_get_image_filename(gconstpointer image_data, size_t image_len)
+{
 	/* Return the filename */
-	return g_strdup_printf("%s.%s", digest,
+	char *checksum = purple_util_get_image_checksum(image_data, image_len);
+	char *filename = g_strdup_printf("%s.%s", checksum,
 	                       purple_util_get_image_extension(image_data, image_len));
+	g_free(checksum);
+	return filename;
 }
 
 gboolean
--- a/libpurple/util.h	Mon May 19 16:18:32 2008 +0000
+++ b/libpurple/util.h	Sun May 25 04:30:41 2008 +0000
@@ -706,6 +706,11 @@
 purple_util_get_image_extension(gconstpointer data, size_t len);
 
 /**
+ * Returns a SHA-1 hash string of the data passed in.
+ */
+char *purple_util_get_image_checksum(gconstpointer image_data, size_t image_len);
+
+/**
  * @return A hex encoded version of the SHA-1 hash of the data passed
  *         in with the correct file extention appended.  The file
  *         extension is determined by calling
--- a/pidgin.spec.in	Mon May 19 16:18:32 2008 +0000
+++ b/pidgin.spec.in	Sun May 25 04:30:41 2008 +0000
@@ -228,6 +228,7 @@
                                     --disable-schemas-install \
                                     %{!?_with_dbus:--disable-dbus} \
                                     %{!?_with_avahi:--disable-avahi} \
+                                    %{!?_with_meanwhile:--disable-meanwhile} \
                                     %{?_without_gstreamer:--disable-gstreamer} \
                                     %{?_without_gtkspell:--disable-gtkspell} \
                                     %{?_without_nm:--disable-nm} \
@@ -473,6 +474,9 @@
 %endif
 
 %changelog
+* Mon May 19 2008 Stu Tomlinson <stu@nosnilmot.com>
+- Fix building without meanwhile support
+
 * Fri May 16 2008 Stu Tomlinson <stu@nosnilmot.com>
 - Add "--without nm" support to build without NetworkManager
 
--- a/pidgin/Makefile.am	Mon May 19 16:18:32 2008 +0000
+++ b/pidgin/Makefile.am	Sun May 25 04:30:41 2008 +0000
@@ -111,6 +111,7 @@
 	gtksavedstatuses.c \
 	gtkscrollbook.c \
 	gtksession.c \
+	gtksmiley.c \
 	gtksound.c \
 	gtksourceiter.c \
 	gtksourceundomanager.c \
@@ -163,6 +164,7 @@
 	gtksavedstatuses.h \
 	gtkscrollbook.h \
 	gtksession.h \
+	gtksmiley.h \
 	gtksound.h \
 	gtksourceiter.h \
 	gtksourceundomanager.h \
--- a/pidgin/Makefile.mingw	Mon May 19 16:18:32 2008 +0000
+++ b/pidgin/Makefile.mingw	Sun May 25 04:30:41 2008 +0000
@@ -84,6 +84,7 @@
 			gtkroomlist.c \
 			gtksavedstatuses.c \
 			gtkscrollbook.c \
+			gtksmiley.c \
 			gtksound.c \
 			gtksourceiter.c \
 			gtksourceundomanager.c \
--- a/pidgin/gtkaccount.c	Mon May 19 16:18:32 2008 +0000
+++ b/pidgin/gtkaccount.c	Sun May 25 04:30:41 2008 +0000
@@ -176,14 +176,7 @@
 	}
 
 	if (dialog->icon_img != NULL) {
-		GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
-		gdk_pixbuf_loader_write(loader, purple_imgstore_get_data(dialog->icon_img),
-		                        purple_imgstore_get_size(dialog->icon_img), NULL);
-		gdk_pixbuf_loader_close(loader, NULL);
-		pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
-		if (pixbuf)
-			g_object_ref(pixbuf);
-		g_object_unref(loader);
+		pixbuf = pidgin_pixbuf_from_imgstore(dialog->icon_img);
 	}
 
 	if (pixbuf && dialog->prpl_info &&
@@ -255,6 +248,25 @@
 	}
 }
 
+static gboolean
+screenname_focus_cb(GtkWidget *widget, GdkEventFocus *event, AccountPrefsDialog *dialog)
+{
+	GHashTable *table;
+	const char *label;
+
+	table = dialog->prpl_info->get_account_text_table(NULL);
+	label = g_hash_table_lookup(table, "login_label");
+
+	if(!strcmp(gtk_entry_get_text(GTK_ENTRY(widget)), label)) {
+		gtk_entry_set_text(GTK_ENTRY(widget), "");
+		gtk_widget_modify_text(widget, GTK_STATE_NORMAL,NULL);
+	}
+
+	g_hash_table_destroy(table);
+
+	return FALSE;
+}
+
 static void
 screenname_changed_cb(GtkEntry *entry, AccountPrefsDialog *dialog)
 {
@@ -270,6 +282,32 @@
 	}
 }
 
+static gboolean
+screenname_nofocus_cb(GtkWidget *widget, GdkEventFocus *event, AccountPrefsDialog *dialog)
+{
+	GdkColor color = {0, 34952, 35466, 34181};
+	GHashTable *table;
+	const char *label;
+
+	table = dialog->prpl_info->get_account_text_table(NULL);
+	label = g_hash_table_lookup(table, "login_label");
+
+	if (*gtk_entry_get_text(GTK_ENTRY(widget)) == '\0') {
+		/* We have to avoid hitting the screenname_changed_cb function 
+		 * because it enables buttons we don't want enabled yet ;)
+		 */
+		g_signal_handlers_block_by_func(widget, G_CALLBACK(screenname_changed_cb), dialog);
+		gtk_entry_set_text(GTK_ENTRY(widget), label);
+		/* Make sure we can hit it again */
+		g_signal_handlers_unblock_by_func(widget, G_CALLBACK(screenname_changed_cb), dialog);
+		gtk_widget_modify_text(widget, GTK_STATE_NORMAL, &color);
+	}
+
+	g_hash_table_destroy(table);
+
+	return FALSE;
+}
+
 static void
 icon_filesel_choose_cb(const char *filename, gpointer data)
 {
@@ -410,6 +448,25 @@
 
 	add_pref_box(dialog, vbox, _("_Username:"), dialog->screenname_entry);
 
+	if (dialog->account != NULL)
+		username = g_strdup(purple_account_get_username(dialog->account));
+
+	if (!username && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(dialog->prpl_info, get_account_text_table)) {
+		GdkColor color = {0, 34952, 35466, 34181};
+		GHashTable *table;
+		const char *label;
+		table = dialog->prpl_info->get_account_text_table(NULL);
+		label = g_hash_table_lookup(table, "login_label");
+
+		gtk_entry_set_text(GTK_ENTRY(dialog->screenname_entry), label);
+		g_signal_connect(G_OBJECT(dialog->screenname_entry), "focus-in-event",
+				G_CALLBACK(screenname_focus_cb), dialog);
+		g_signal_connect(G_OBJECT(dialog->screenname_entry), "focus-out-event",
+				G_CALLBACK(screenname_nofocus_cb), dialog);
+		gtk_widget_modify_text(dialog->screenname_entry, GTK_STATE_NORMAL, &color);
+		g_hash_table_destroy(table);
+	}
+
 	g_signal_connect(G_OBJECT(dialog->screenname_entry), "changed",
 					 G_CALLBACK(screenname_changed_cb), dialog);
 
@@ -419,9 +476,6 @@
 	else
 		user_splits = dialog->prpl_info->user_splits;
 
-	if (dialog->account != NULL)
-		username = g_strdup(purple_account_get_username(dialog->account));
-
 	if (dialog->user_split_entries != NULL) {
 		g_list_free(dialog->user_split_entries);
 		dialog->user_split_entries = NULL;
@@ -1470,7 +1524,8 @@
 	add_login_options(dialog, vbox);
 	add_user_options(dialog, vbox);
 
-	button = gtk_check_button_new_with_label(_("Create this new account on the server"));
+	button = gtk_check_button_new_with_mnemonic(
+		_("Create _this new account on the server"));
 	gtk_box_pack_start(GTK_BOX(main_vbox), button, FALSE, FALSE, 0);
 	gtk_widget_show(button);
 	dialog->register_button = button;
@@ -1513,6 +1568,8 @@
 
 	/* Show the window. */
 	gtk_widget_show(win);
+	if (!account)
+		gtk_widget_grab_focus(dialog->protocol_menu);
 }
 
 /**************************************************************************
@@ -1753,39 +1810,6 @@
 	return FALSE;
 }
 
-static gboolean
-configure_cb(GtkWidget *w, GdkEventConfigure *event, AccountsWindow *dialog)
-{
-	if (GTK_WIDGET_VISIBLE(w)) {
-		int old_width = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/accounts/dialog/width");
-		int col_width;
-		int difference;
-
-		purple_prefs_set_int(PIDGIN_PREFS_ROOT "/accounts/dialog/width",  event->width);
-		purple_prefs_set_int(PIDGIN_PREFS_ROOT "/accounts/dialog/height", event->height);
-
-		col_width = gtk_tree_view_column_get_width(dialog->screenname_col);
-
-		if (col_width == 0)
-			return FALSE;
-
-		difference = (MAX(old_width, event->width) -
-					  MIN(old_width, event->width));
-
-		if (difference == 0)
-			return FALSE;
-
-		if (old_width < event->width)
-			gtk_tree_view_column_set_min_width(dialog->screenname_col,
-					col_width + difference);
-		else
-			gtk_tree_view_column_set_max_width(dialog->screenname_col,
-					col_width - difference);
-	}
-
-	return FALSE;
-}
-
 static void
 add_account_cb(GtkWidget *w, AccountsWindow *dialog)
 {
@@ -1908,14 +1932,14 @@
 	column = gtk_tree_view_column_new_with_attributes(_("Enabled"),
 			 renderer, "active", COLUMN_ENABLED, NULL);
 
-	gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1);
-	gtk_tree_view_column_set_resizable(column, TRUE);
+	gtk_tree_view_column_set_resizable(column, FALSE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
 
 	/* Screen Name column */
 	column = gtk_tree_view_column_new();
 	gtk_tree_view_column_set_title(column, _("Username"));
-	gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1);
 	gtk_tree_view_column_set_resizable(column, TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
 
 	/* Buddy Icon */
 	renderer = gtk_cell_renderer_pixbuf_new();
@@ -1934,8 +1958,8 @@
 	/* Protocol name */
 	column = gtk_tree_view_column_new();
 	gtk_tree_view_column_set_title(column, _("Protocol"));
-	gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1);
-	gtk_tree_view_column_set_resizable(column, TRUE);
+	gtk_tree_view_column_set_resizable(column, FALSE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
 
 	/* Icon */
 	renderer = gtk_cell_renderer_pixbuf_new();
@@ -1977,21 +2001,14 @@
 	}
 
 	if (img != NULL) {
-		GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
 		GdkPixbuf *buddyicon_pixbuf;
-
-		gdk_pixbuf_loader_write(loader, purple_imgstore_get_data(img),
-		                        purple_imgstore_get_size(img), NULL);
-		gdk_pixbuf_loader_close(loader, NULL);
-		buddyicon_pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
-
+		buddyicon_pixbuf = pidgin_pixbuf_from_imgstore(img);
 		purple_imgstore_unref(img);
 
 		if (buddyicon_pixbuf != NULL) {
 			buddyicon = gdk_pixbuf_scale_simple(buddyicon_pixbuf, 22, 22, GDK_INTERP_HYPER);
+			g_object_unref(G_OBJECT(buddyicon_pixbuf));
 		}
-
-		g_object_unref(loader);
 	}
 
 	gtk_list_store_set(store, iter,
@@ -2196,6 +2213,7 @@
 	gtk_container_add(GTK_CONTAINER(sw), treeview);
 
 	add_columns(treeview, dialog);
+	gtk_tree_view_columns_autosize(GTK_TREE_VIEW(treeview));
 
 	if (populate_accounts_list(dialog))
 		gtk_notebook_set_current_page(GTK_NOTEBOOK(accounts_window->notebook), 1);
@@ -2265,8 +2283,6 @@
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
 					 G_CALLBACK(accedit_win_destroy_cb), accounts_window);
-	g_signal_connect(G_OBJECT(win), "configure_event",
-					 G_CALLBACK(configure_cb), accounts_window);
 
 	/* Setup the vbox */
 	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER);
--- a/pidgin/gtkblist.c	Mon May 19 16:18:32 2008 +0000
+++ b/pidgin/gtkblist.c	Sun May 25 04:30:41 2008 +0000
@@ -57,6 +57,7 @@
 #include "gtkroomlist.h"
 #include "gtkstatusbox.h"
 #include "gtkscrollbook.h"
+#include "gtksmiley.h"
 #include "gtkutils.h"
 #include "pidgin/minidialog.h"
 #include "pidgin/pidgintooltip.h"
@@ -1533,6 +1534,48 @@
 	return FALSE;
 }
 
+static void
+set_node_custom_icon_cb(const gchar *filename, gpointer data)
+{
+	if (filename) {
+		PurpleBlistNode *node = (PurpleBlistNode*)data;
+
+		purple_buddy_icons_node_set_custom_icon_from_file(node,
+		                                                  filename);
+	}
+}
+
+static void
+set_node_custom_icon(GtkWidget *w, PurpleBlistNode *node)
+{
+	/* This doesn't keep track of the returned dialog (so that successive
+	 * calls could be made to re-display that dialog). Do we want that? */
+	GtkWidget *win = pidgin_buddy_icon_chooser_new(NULL, set_node_custom_icon_cb, node);
+	gtk_widget_show_all(win);
+}
+
+static void
+remove_node_custom_icon(GtkWidget *w, PurpleBlistNode *node)
+{
+	purple_buddy_icons_node_set_custom_icon(node, NULL, 0);
+}
+
+static void
+add_buddy_icon_menu_items(GtkWidget *menu, PurpleBlistNode *node)
+{
+	GtkWidget *item;
+
+	pidgin_new_item_from_stock(menu, _("Set Custom Icon"), NULL,
+	                           G_CALLBACK(set_node_custom_icon), node, 0,
+	                           0, NULL);
+
+	item = pidgin_new_item_from_stock(menu, _("Remove Custom Icon"), NULL,
+	                           G_CALLBACK(remove_node_custom_icon), node,
+	                           0, 0, NULL);
+	if (!purple_buddy_icons_node_has_custom_icon(node))
+		gtk_widget_set_sensitive(item, FALSE);
+}
+
 static GtkWidget *
 create_group_menu (PurpleBlistNode *node, PurpleGroup *g)
 {
@@ -1556,12 +1599,13 @@
 				NULL, G_CALLBACK(gtk_blist_menu_showoffline_cb), node, 0, 0, NULL);
 	}
 
+	add_buddy_icon_menu_items(menu, node);
+
 	pidgin_append_blist_node_extended_menu(menu, node);
 
 	return menu;
 }
 
-
 static GtkWidget *
 create_chat_menu(PurpleBlistNode *node, PurpleChat *c)
 {
@@ -1594,6 +1638,8 @@
 	pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
 				 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
 
+	add_buddy_icon_menu_items(menu, node);
+
 	return menu;
 }
 
@@ -1615,6 +1661,8 @@
 	pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE,
 				 G_CALLBACK(pidgin_blist_remove_cb), node, 0, 0, NULL);
 
+	add_buddy_icon_menu_items(menu, node);
+
 	pidgin_separator(menu);
 
 	pidgin_new_item_from_stock(menu, _("_Collapse"), GTK_STOCK_ZOOM_OUT,
@@ -1639,6 +1687,8 @@
 	if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
 		pidgin_separator(menu);
 
+		add_buddy_icon_menu_items(menu, node);
+
 		if(gtknode->contact_expanded) {
 			pidgin_new_item_from_stock(menu, _("_Collapse"),
 						 GTK_STOCK_ZOOM_OUT,
@@ -2498,51 +2548,71 @@
 
 
 static GdkPixbuf *pidgin_blist_get_buddy_icon(PurpleBlistNode *node,
-		gboolean scaled, gboolean greyed)
-{
-	GdkPixbuf *buf, *ret = NULL;
+                                              gboolean scaled, gboolean greyed)
+{
+	gsize len;
 	GdkPixbufLoader *loader;
-	PurpleBuddyIcon *icon = NULL;
-	const guchar *data = NULL;
-	gsize len;
 	PurpleBuddy *buddy = NULL;
+	PurpleGroup *group = NULL;
+	const guchar *data = NULL;
+	GdkPixbuf *buf, *ret = NULL;
+	PurpleBuddyIcon *icon = NULL;
 	PurpleAccount *account = NULL;
-	PurplePluginProtocolInfo *prpl_info = NULL;
+	PurpleContact *contact = NULL;
 	PurpleStoredImage *custom_img;
-
-	if(PURPLE_BLIST_NODE_IS_CONTACT(node)) {
+	PurplePluginProtocolInfo *prpl_info = NULL;
+	gint orig_width, orig_height, scale_width, scale_height;
+
+	if (PURPLE_BLIST_NODE_IS_CONTACT(node)) {
 		buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
-	} else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		contact = (PurpleContact*)node;
+	} else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) {
 		buddy = (PurpleBuddy*)node;
+		contact = purple_buddy_get_contact(buddy);
+	} else if (PURPLE_BLIST_NODE_IS_GROUP(node)) {
+		group = (PurpleGroup*)node;
+	} else if (PURPLE_BLIST_NODE_IS_CHAT(node)) {
+		/* We don't need to do anything here. We just need to not fall
+		 * into the else block and return. */
 	} else {
 		return NULL;
 	}
 
-	if(buddy == NULL)
-		return NULL;
-
-	account = purple_buddy_get_account(buddy);
-
-	if(account && account->gc)
+	if (buddy) {
+		account = purple_buddy_get_account(buddy);
+	}
+
+	if(account && account->gc) {
 		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(account->gc->prpl);
+	}
 
 #if 0
 	if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"))
 		return NULL;
 #endif
 
-	custom_img = purple_buddy_icons_find_custom_icon(purple_buddy_get_contact(buddy));
-	if (custom_img)
-	{
+	/* If we have a contact then this is either a contact or a buddy and
+	 * we want to fetch the custom icon for the contact. If we don't have
+	 * a contact then this is a group or some other type of node and we
+	 * want to use that directly. */
+	if (contact) {
+		custom_img = purple_buddy_icons_node_find_custom_icon((PurpleBlistNode*)contact);
+	} else {
+		custom_img = purple_buddy_icons_node_find_custom_icon(node);
+	}
+
+	if (custom_img) {
 		data = purple_imgstore_get_data(custom_img);
 		len = purple_imgstore_get_size(custom_img);
 	}
 
 	if (data == NULL) {
-		/* Not sure I like this...*/
-		if (!(icon = purple_buddy_icons_find(buddy->account, buddy->name)))
-			return NULL;
-		data = purple_buddy_icon_get_data(icon, &len);
+		if (buddy) {
+			/* Not sure I like this...*/
+			if (!(icon = purple_buddy_icons_find(buddy->account, buddy->name)))
+				return NULL;
+			data = purple_buddy_icon_get_data(icon, &len);
+		}
 
 		if(data == NULL)
 			return NULL;
@@ -2560,56 +2630,68 @@
 		g_object_ref(G_OBJECT(buf));
 	g_object_unref(G_OBJECT(loader));
 
-	if (buf) {
-		int orig_width, orig_height;
-		int scale_width, scale_height;
-
-		if (greyed) {
+	if (!buf) {
+		return NULL;
+	}
+
+	if (greyed) {
+		gboolean offline = FALSE, idle = FALSE;
+
+		if (buddy) {
 			PurplePresence *presence = purple_buddy_get_presence(buddy);
 			if (!PURPLE_BUDDY_IS_ONLINE(buddy))
-				gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.0, FALSE);
+				offline = TRUE;
 			if (purple_presence_is_idle(presence))
-				gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.25, FALSE);
+				idle = TRUE;
+		} else if (group) {
+			if (purple_blist_get_group_online_count(group) == 0)
+				offline = TRUE;
 		}
 
-		/* i'd use the pidgin_buddy_icon_get_scale_size() thing,
-		 * but it won't tell me the original size, which I need for scaling
-		 * purposes */
-		scale_width = orig_width = gdk_pixbuf_get_width(buf);
-		scale_height = orig_height = gdk_pixbuf_get_height(buf);
-
-		if (prpl_info && prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_DISPLAY)
-			purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &scale_width, &scale_height);
-
-		if (scaled || scale_height > 200 || scale_width > 200) {
-			GdkPixbuf *tmpbuf;
-			float scale_size = scaled ? 32.0 : 200.0;
-			if(scale_height > scale_width) {
-				scale_width = scale_size * (double)scale_width / (double)scale_height;
-				scale_height = scale_size;
-			} else {
-				scale_height = scale_size * (double)scale_height / (double)scale_width;
-				scale_width = scale_size;
-			}
-			/* scale & round before making square, so rectangular (but non-square)
-			 * images get rounded corners too */
-			tmpbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height);
-			gdk_pixbuf_fill(tmpbuf, 0x00000000);
-			gdk_pixbuf_scale(buf, tmpbuf, 0, 0, scale_width, scale_height, 0, 0, (double)scale_width/(double)orig_width, (double)scale_height/(double)orig_height, GDK_INTERP_BILINEAR);
-			if (pidgin_gdk_pixbuf_is_opaque(tmpbuf))
-				pidgin_gdk_pixbuf_make_round(tmpbuf);
-			ret = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_size, scale_size);
-			gdk_pixbuf_fill(ret, 0x00000000);
-			gdk_pixbuf_copy_area(tmpbuf, 0, 0, scale_width, scale_height, ret, (scale_size-scale_width)/2, (scale_size-scale_height)/2);
-			g_object_unref(G_OBJECT(tmpbuf));
+		if (offline)
+			gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.0, FALSE);
+
+		if (idle)
+			gdk_pixbuf_saturate_and_pixelate(buf, buf, 0.25, FALSE);
+	}
+
+	/* I'd use the pidgin_buddy_icon_get_scale_size() thing, but it won't
+	 * tell me the original size, which I need for scaling purposes. */
+	scale_width = orig_width = gdk_pixbuf_get_width(buf);
+	scale_height = orig_height = gdk_pixbuf_get_height(buf);
+
+	if (prpl_info && prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_DISPLAY)
+		purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &scale_width, &scale_height);
+
+	if (scaled || scale_height > 200 || scale_width > 200) {
+		GdkPixbuf *tmpbuf;
+		float scale_size = scaled ? 32.0 : 200.0;
+		if(scale_height > scale_width) {
+			scale_width = scale_size * (double)scale_width / (double)scale_height;
+			scale_height = scale_size;
 		} else {
-			ret = gdk_pixbuf_scale_simple(buf,scale_width,scale_height, GDK_INTERP_BILINEAR);
+			scale_height = scale_size * (double)scale_height / (double)scale_width;
+			scale_width = scale_size;
 		}
-		g_object_unref(G_OBJECT(buf));
-	}
+		/* Scale & round before making square, so rectangular (but
+		 * non-square) images get rounded corners too. */
+		tmpbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_width, scale_height);
+		gdk_pixbuf_fill(tmpbuf, 0x00000000);
+		gdk_pixbuf_scale(buf, tmpbuf, 0, 0, scale_width, scale_height, 0, 0, (double)scale_width/(double)orig_width, (double)scale_height/(double)orig_height, GDK_INTERP_BILINEAR);
+		if (pidgin_gdk_pixbuf_is_opaque(tmpbuf))
+			pidgin_gdk_pixbuf_make_round(tmpbuf);
+		ret = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, scale_size, scale_size);
+		gdk_pixbuf_fill(ret, 0x00000000);
+		gdk_pixbuf_copy_area(tmpbuf, 0, 0, scale_width, scale_height, ret, (scale_size-scale_width)/2, (scale_size-scale_height)/2);
+		g_object_unref(G_OBJECT(tmpbuf));
+	} else {
+		ret = gdk_pixbuf_scale_simple(buf,scale_width,scale_height, GDK_INTERP_BILINEAR);
+	}
+	g_object_unref(G_OBJECT(buf));
 
 	return ret;
 }
+
 /* # - Status Icon
  * P - Protocol Icon
  * A - Buddy Icon
@@ -3188,6 +3270,7 @@
 	{ N_("/_Tools"), NULL, NULL, 0, "<Branch>", NULL },
 	{ N_("/Tools/Buddy _Pounces"), NULL, pidgin_pounces_manager_show, 1, "<Item>", NULL },
 	{ N_("/Tools/_Certificates"), NULL, pidgin_certmgr_show, 0, "<Item>", NULL },
+	{ N_("/Tools/Smile_y"), "<CTL>Y", pidgin_smiley_manager_show, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_SMILEY },
 	{ N_("/Tools/Plu_gins"), "<CTL>U", pidgin_plugin_dialog_show, 2, "<StockItem>", PIDGIN_STOCK_TOOLBAR_PLUGINS },
 	{ N_("/Tools/Pr_eferences"), "<CTL>P", pidgin_prefs_show, 0, "<StockItem>", GTK_STOCK_PREFERENCES },
 	{ N_("/Tools/Pr_ivacy"), NULL, pidgin_privacy_dialog_show, 0, "<Item>", NULL },
@@ -3262,7 +3345,8 @@
 					g_list_length(purple_conv_chat_get_users(PURPLE_CONV_CHAT(conv))));
 
 			if (prpl_info && (prpl_info->options & OPT_PROTO_CHAT_TOPIC)) {
-				char *topic = g_markup_escape_text(purple_conv_chat_get_topic(PURPLE_CONV_CHAT(conv)), -1);
+				const char *chattopic = purple_conv_chat_get_topic(PURPLE_CONV_CHAT(conv));
+				char *topic = chattopic ? g_markup_escape_text(chattopic, -1) : NULL;
 				g_string_append_printf(str, _("\n<b>Topic:</b> %s"), topic ? topic : _("(no topic set)"));
 				g_free(topic);
 			}
@@ -5831,14 +5915,16 @@
 	return FALSE;
 }
 
-/*This version of pidgin_blist_update_group can take the original buddy
-or a group, but has much better algorithmic performance with a pre-known buddy*/
-static void pidgin_blist_update_group(PurpleBuddyList *list, PurpleBlistNode *node)
-{
+/* This version of pidgin_blist_update_group can take the original buddy or a
+ * group, but has much better algorithmic performance with a pre-known buddy.
+ */
+static void pidgin_blist_update_group(PurpleBuddyList *list,
+                                      PurpleBlistNode *node)
+{
+	gint count;
 	PurpleGroup *group;
-	int count;
+	PurpleBlistNode* gnode;
 	gboolean show = FALSE, show_offline = FALSE;
-	PurpleBlistNode* gnode;
 
 	g_return_if_fail(node != NULL);
 
@@ -5873,12 +5959,13 @@
 	}
 
 	if (show) {
+		gchar *title;
+		gboolean biglist;
 		GtkTreeIter iter;
 		GtkTreePath *path;
 		gboolean expanded;
 		GdkColor bgcolor;
-		char *title;
-
+		GdkPixbuf *avatar = NULL;
 
 		if(!insert_node(list, gnode, &iter))
 			return;
@@ -5890,17 +5977,23 @@
 		gtk_tree_path_free(path);
 
 		title = pidgin_get_group_title(gnode, expanded);
+		biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
+
+		if (biglist) {
+			avatar = pidgin_blist_get_buddy_icon(gnode, TRUE, TRUE);
+		}
 
 		gtk_tree_store_set(gtkblist->treemodel, &iter,
 				   STATUS_ICON_VISIBLE_COLUMN, FALSE,
 				   STATUS_ICON_COLUMN, NULL,
 				   NAME_COLUMN, title,
 				   NODE_COLUMN, gnode,
-	/* 			   BGCOLOR_COLUMN, &bgcolor,     */
+	/*			   BGCOLOR_COLUMN, &bgcolor,     */
 				   GROUP_EXPANDER_COLUMN, TRUE,
 				   GROUP_EXPANDER_VISIBLE_COLUMN, TRUE,
 				   CONTACT_EXPANDER_VISIBLE_COLUMN, FALSE,
-				   BUDDY_ICON_VISIBLE_COLUMN, FALSE,
+				   BUDDY_ICON_COLUMN, avatar,
+				   BUDDY_ICON_VISIBLE_COLUMN, biglist,
 				   IDLE_VISIBLE_COLUMN, FALSE,
 				   EMBLEM_VISIBLE_COLUMN, FALSE,
 				   -1);
@@ -6190,7 +6283,7 @@
 				STATUS_ICON_COLUMN, status,
 				STATUS_ICON_VISIBLE_COLUMN, TRUE,
 				BUDDY_ICON_COLUMN, avatar ? avatar : gtkblist->empty_avatar,
-				BUDDY_ICON_VISIBLE_COLUMN,  purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"),
+				BUDDY_ICON_VISIBLE_COLUMN, showicons,
 				EMBLEM_COLUMN, emblem,
 				EMBLEM_VISIBLE_COLUMN, emblem != NULL,
 				PROTOCOL_ICON_COLUMN, prpl_icon,
--- a/pidgin/gtkconv.c	Mon May 19 16:18:32 2008 +0000
+++ b/pidgin/gtkconv.c	Sun May 25 04:30:41 2008 +0000
@@ -97,10 +97,10 @@
 
 #define	PIDGIN_CONV_ALL	((1 << 7) - 1)
 
+/* XXX: These color defines shouldn't really be here. But the nick-color
+ * generation algorithm uses them, so keeping these around until we fix that. */
 #define DEFAULT_SEND_COLOR "#204a87"
-#define DEFAULT_RECV_COLOR "#cc0000"
 #define DEFAULT_HIGHLIGHT_COLOR "#AF7F00"
-#define DEFAULT_ACTION_COLOR "#062585"
 
 #define BUDDYICON_SIZE_MIN    32
 #define BUDDYICON_SIZE_MAX    96
@@ -164,10 +164,9 @@
 static void update_typing_message(PidginConversation *gtkconv, const char *message);
 static const char *item_factory_translate_func (const char *path, gpointer func_data);
 gboolean pidgin_conv_has_focus(PurpleConversation *conv);
-static void pidgin_conv_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data);
-static void pidgin_conv_custom_smiley_closed(GdkPixbufLoader *loader, gpointer user_data);
 static GdkColor* generate_nick_colors(guint *numcolors, GdkColor background);
 static gboolean color_is_visible(GdkColor foreground, GdkColor background, int color_contrast, int brightness_contrast);
+static GtkTextTag *get_buddy_tag(PurpleConversation *conv, const char *who, gboolean create);
 static void pidgin_conv_update_fields(PurpleConversation *conv, PidginConvFields fields);
 static void focus_out_from_menubar(GtkWidget *wid, PidginWindow *win);
 static void pidgin_conv_tab_pack(PidginWindow *win, PidginConversation *gtkconv);
@@ -2569,10 +2568,6 @@
 		PurpleBuddy *b = purple_find_buddy(conv->account, conv->name);
 		if (b)
 			emblem = pidgin_blist_get_emblem((PurpleBlistNode*)b);
-	} else {
-		PurpleChat *c = purple_blist_find_chat(conv->account, conv->name);
-		if (c)
-			emblem = pidgin_blist_get_emblem((PurpleBlistNode*)c);
 	}
 
 	g_return_if_fail(status != NULL);
@@ -2780,10 +2775,22 @@
 custom_icon_sel_cb(const char *filename, gpointer data)
 {
 	if (filename) {
+		const gchar *name;
+		PurpleBuddy *buddy;
+		PurpleContact *contact;
 		PidginConversation *gtkconv = data;
 		PurpleConversation *conv = gtkconv->active_conv;
 		PurpleAccount *account = purple_conversation_get_account(conv);
-		pidgin_set_custom_buddy_icon(account, purple_conversation_get_name(conv), filename);
+
+		name = purple_conversation_get_name(conv);
+		buddy = purple_find_buddy(account, name);
+		if (!buddy) {
+			purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n");
+			return;
+		}
+		contact = purple_buddy_get_contact(buddy);
+
+		purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, filename);
 	}
 }
 
@@ -2825,12 +2832,21 @@
 static void
 remove_custom_icon_cb(GtkWidget *widget, PidginConversation *gtkconv)
 {
-	PurpleConversation *conv;
+	const gchar *name;
+	PurpleBuddy *buddy;
 	PurpleAccount *account;
-
-	conv = gtkconv->active_conv;
+	PurpleContact *contact;
+	PurpleConversation *conv = gtkconv->active_conv;
+
 	account = purple_conversation_get_account(conv);
-	pidgin_set_custom_buddy_icon(account, purple_conversation_get_name(conv), NULL);
+	name = purple_conversation_get_name(conv);
+	buddy = purple_find_buddy(account, name);
+	if (!buddy) {
+		return;
+	}
+	contact = purple_buddy_get_contact(buddy);
+
+	purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, NULL);
 }
 
 static void
@@ -2931,7 +2947,7 @@
 	if (buddy)
 	{
 		PurpleContact *contact = purple_buddy_get_contact(buddy);
-		if (contact && purple_buddy_icons_has_custom_icon(contact))
+		if (contact && purple_buddy_icons_node_has_custom_icon((PurpleBlistNode*)contact))
 		{
 			pidgin_new_item_from_stock(menu, _("Remove Custom Icon"), NULL,
 			                           G_CALLBACK(remove_custom_icon_cb), gtkconv,
@@ -3948,6 +3964,7 @@
 	gboolean is_buddy;
 	gchar *tmp, *alias_key, *name, *alias;
 	int flags;
+	GdkColor *color = NULL;
 
 	alias = cb->alias;
 	name  = cb->name;
@@ -3974,71 +3991,50 @@
 	alias_key = g_utf8_collate_key(tmp, -1);
 	g_free(tmp);
 
-	if (is_me)
-	{
-		GdkColor send_color;
-		gdk_color_parse(DEFAULT_SEND_COLOR, &send_color);
+	if (is_me) {
+		GtkTextTag *tag = gtk_text_tag_table_lookup(
+				gtk_text_buffer_get_tag_table(GTK_IMHTML(gtkconv->imhtml)->text_buffer),
+				"send-name");
+		g_object_get(tag, "foreground-gdk", &color, NULL);
+	} else {
+		color = (GdkColor*)get_nick_color(gtkconv, name);
+	}
 
 #if GTK_CHECK_VERSION(2,6,0)
-		gtk_list_store_insert_with_values(ls, &iter,
+	gtk_list_store_insert_with_values(ls, &iter,
 /*
- * The GTK docs are mute about the effects of the "row" value for performance.
- * X-Chat hardcodes their value to 0 (prepend) and -1 (append), so we will too.
- * It *might* be faster to search the gtk_list_store and set row accurately,
- * but no one in #gtk+ seems to know anything about it either.
- * Inserting in the "wrong" location has no visible ill effects. - F.P.
- */
-		                                  -1, /* "row" */
-		                                  CHAT_USERS_ICON_COLUMN,  pixbuf,
-		                                  CHAT_USERS_ALIAS_COLUMN, alias,
-		                                  CHAT_USERS_ALIAS_KEY_COLUMN, alias_key,
-		                                  CHAT_USERS_NAME_COLUMN,  name,
-		                                  CHAT_USERS_FLAGS_COLUMN, flags,
-		                                  CHAT_USERS_COLOR_COLUMN, &send_color,
-		                                  CHAT_USERS_WEIGHT_COLUMN, is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
-		                                  -1);
-	}
-	else
-	{
-		gtk_list_store_insert_with_values(ls, &iter,
-		                                  -1, /* "row" */
-		                                  CHAT_USERS_ICON_COLUMN,  pixbuf,
-		                                  CHAT_USERS_ALIAS_COLUMN, alias,
-		                                  CHAT_USERS_ALIAS_KEY_COLUMN, alias_key,
-		                                  CHAT_USERS_NAME_COLUMN,  name,
-		                                  CHAT_USERS_FLAGS_COLUMN, flags,
-		                                  CHAT_USERS_COLOR_COLUMN, get_nick_color(gtkconv, name),
-		                                  CHAT_USERS_WEIGHT_COLUMN, is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
-		                                  -1);
+* The GTK docs are mute about the effects of the "row" value for performance.
+* X-Chat hardcodes their value to 0 (prepend) and -1 (append), so we will too.
+* It *might* be faster to search the gtk_list_store and set row accurately,
+* but no one in #gtk+ seems to know anything about it either.
+* Inserting in the "wrong" location has no visible ill effects. - F.P.
+*/
+			-1, /* "row" */
+			CHAT_USERS_ICON_COLUMN,  pixbuf,
+			CHAT_USERS_ALIAS_COLUMN, alias,
+			CHAT_USERS_ALIAS_KEY_COLUMN, alias_key,
+			CHAT_USERS_NAME_COLUMN,  name,
+			CHAT_USERS_FLAGS_COLUMN, flags,
+			CHAT_USERS_COLOR_COLUMN, color,
+			CHAT_USERS_WEIGHT_COLUMN, is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
+			-1);
 #else
-		gtk_list_store_append(ls, &iter);
-		gtk_list_store_set(ls, &iter,
-		                   CHAT_USERS_ICON_COLUMN,  pixbuf,
-		                   CHAT_USERS_ALIAS_COLUMN, alias,
-		                   CHAT_USERS_ALIAS_KEY_COLUMN, alias_key,
-		                   CHAT_USERS_NAME_COLUMN,  name,
-		                   CHAT_USERS_FLAGS_COLUMN, flags,
-		                   CHAT_USERS_COLOR_COLUMN, &send_color,
-		                   CHAT_USERS_WEIGHT_COLUMN, is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
-		                   -1);
-	}
-	else
-	{
-		gtk_list_store_append(ls, &iter);
-		gtk_list_store_set(ls, &iter,
-		                   CHAT_USERS_ICON_COLUMN,  pixbuf,
-		                   CHAT_USERS_ALIAS_COLUMN, alias,
-		                   CHAT_USERS_ALIAS_KEY_COLUMN, alias_key,
-		                   CHAT_USERS_NAME_COLUMN,  name,
-		                   CHAT_USERS_FLAGS_COLUMN, flags,
-		                   CHAT_USERS_COLOR_COLUMN, get_nick_color(gtkconv, name),
-		                   CHAT_USERS_WEIGHT_COLUMN, is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
-		                   -1);
+	gtk_list_store_append(ls, &iter);
+	gtk_list_store_set(ls, &iter,
+			CHAT_USERS_ICON_COLUMN,  pixbuf,
+			CHAT_USERS_ALIAS_COLUMN, alias,
+			CHAT_USERS_ALIAS_KEY_COLUMN, alias_key,
+			CHAT_USERS_NAME_COLUMN,  name,
+			CHAT_USERS_FLAGS_COLUMN, flags,
+			CHAT_USERS_COLOR_COLUMN, color,
+			CHAT_USERS_WEIGHT_COLUMN, is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
+			-1);
 #endif
-	}
 
 	if (pixbuf)
 		g_object_unref(pixbuf);
+	if (is_me && color)
+		gdk_color_free(color);
 	g_free(alias_key);
 }
 
@@ -4433,6 +4429,7 @@
 	GtkTreeModel *model;
 	char *normalized_name;
 	GtkTreeIter iter;
+	GtkTextTag *texttag;
 	int f;
 
 	g_return_if_fail(buddy != NULL);
@@ -4470,6 +4467,11 @@
 	g_free(normalized_name);
 
 	blist_node_aliased_cb((PurpleBlistNode *)buddy, NULL, conv);
+
+	texttag = get_buddy_tag(conv, purple_buddy_get_name(buddy), FALSE); /* XXX: do we want the normalized name? */
+	if (texttag) {
+		g_object_set(texttag, "weight", is_buddy ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL, NULL);
+	}
 }
 
 static void
@@ -5270,6 +5272,12 @@
 		nbr_nick_colors = NUM_NICK_COLORS;
 		nick_colors = generate_nick_colors(&nbr_nick_colors, gtk_widget_get_style(gtkconv->imhtml)->base[GTK_STATE_NORMAL]);
 	}
+
+	/* We don't want to see the custom smileys if our buddy send us the
+	 * defined shortcut. */
+	pidgin_themes_smiley_themeize(gtkconv->imhtml);
+	/* We want to see our smileys in the entry */
+	pidgin_themes_smiley_themeize_custom(gtkconv->entry);
 }
 
 static void
@@ -5446,7 +5454,8 @@
 	return FALSE;
 }
 
-static GtkTextTag *get_buddy_tag(PurpleConversation *conv, const char *who) {
+static GtkTextTag *get_buddy_tag(PurpleConversation *conv, const char *who, gboolean create)
+{
 	PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
 	GtkTextTag *buddytag;
 	gchar *str;
@@ -5457,9 +5466,12 @@
 			gtk_text_buffer_get_tag_table(
 				GTK_IMHTML(gtkconv->imhtml)->text_buffer), str);
 
-	if (buddytag == NULL) {
+	if (buddytag == NULL && create) {
 		buddytag = gtk_text_buffer_create_tag(
-				GTK_IMHTML(gtkconv->imhtml)->text_buffer, str, NULL);
+				GTK_IMHTML(gtkconv->imhtml)->text_buffer, str,
+				"foreground-gdk", get_nick_color(gtkconv, who),
+				"weight", purple_find_buddy(purple_conversation_get_account(conv), who) ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
+				NULL);
 
 		g_signal_connect(G_OBJECT(buddytag), "event",
 				G_CALLBACK(buddytag_event), conv);
@@ -5565,7 +5577,6 @@
 	char buf2[BUF_LONG];
 	gboolean show_date;
 	char *mdate;
-	char color[10];
 	char *str;
 	char *with_font_tag;
 	char *sml_attrib = NULL;
@@ -5576,8 +5587,6 @@
 	char *bracket;
 	int tag_count = 0;
 	gboolean is_rtl_message = FALSE;
-	GtkSmileyTree *tree = NULL;
-	GHashTable *smiley_data = NULL;
 
 	g_return_if_fail(conv != NULL);
 	gtkconv = PIDGIN_CONVERSATION(conv);
@@ -5736,14 +5745,8 @@
 
 	if (!(flags & PURPLE_MESSAGE_RECV))
 	{
-		/* Temporarily revert to the original smiley-data to avoid showing up
-		 * custom smileys of the buddy when sending message
-		 */
-		tree = GTK_IMHTML(gtkconv->imhtml)->default_smilies;
-		GTK_IMHTML(gtkconv->imhtml)->default_smilies =
-								GTK_IMHTML(gtkconv->entry)->default_smilies;
-		smiley_data = GTK_IMHTML(gtkconv->imhtml)->smiley_data;
-		GTK_IMHTML(gtkconv->imhtml)->smiley_data = GTK_IMHTML(gtkconv->entry)->smiley_data;
+		/* We want to see our own smileys. Need to revert it after send*/
+		pidgin_themes_smiley_themeize_custom(gtkconv->imhtml);
 	}
 
 	/* TODO: These colors should not be hardcoded so log.c can use them */
@@ -5774,169 +5777,109 @@
 		char *alias_escaped = (alias ? g_markup_escape_text(alias, strlen(alias)) : g_strdup(""));
 		/* The initial offset is to deal with
 		 * escaped entities making the string longer */
-		int tag_start_offset = alias ? (strlen(alias_escaped) - strlen(alias)) : 0;
+		int tag_start_offset = 0;
 		int tag_end_offset = 0;
+		const char *tagname = NULL;
+
+		GtkTextIter start, end;
+		GtkTextMark *mark;
+		GtkTextTag *tag;
+		GtkTextBuffer *buffer = GTK_IMHTML(gtkconv->imhtml)->text_buffer;
 
 		/* Enforce direction on alias */
 		if (is_rtl_message)
 			str_embed_direction_chars(&alias_escaped);
 
+		str = g_malloc(1024);
 		if (flags & PURPLE_MESSAGE_WHISPER) {
-			str = g_malloc(1024);
-
 			/* If we're whispering, it's not an autoresponse. */
 			if (purple_message_meify(new_message, -1 )) {
 				g_snprintf(str, 1024, "***%s", alias_escaped);
-				strcpy(color, "#6C2585");
 				tag_start_offset += 3;
+				tagname = "whisper-action-name";
 			}
 			else {
 				g_snprintf(str, 1024, "*%s*:", alias_escaped);
 				tag_start_offset += 1;
 				tag_end_offset = 2;
-				strcpy(color, "#00FF00");
+				tagname = "whisper-name";
 			}
-		}
-		else {
+		} else {
 			if (purple_message_meify(new_message, -1)) {
-				GdkColor *col;
-				str = g_malloc(1024);
-
 				if (flags & PURPLE_MESSAGE_AUTO_RESP) {
 					g_snprintf(str, 1024, "%s ***%s", AUTO_RESPONSE, alias_escaped);
-					tag_start_offset += 4
-						+ strlen(AUTO_RESPONSE);
+					tag_start_offset += strlen(AUTO_RESPONSE) - 6 + 4;
 				} else {
 					g_snprintf(str, 1024, "***%s", alias_escaped);
 					tag_start_offset += 3;
 				}
 
 				if (flags & PURPLE_MESSAGE_NICK)
-					gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "highlight-name-color", &col, NULL);
+					tagname = "highlight-name";
 				else
-					gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "action-name-color", &col, NULL);
-
-				if(col) {
-					g_snprintf(color, sizeof(color), "#%02X%02X%02X",
-						col->red >> 8, col->green >> 8, col->blue >> 8);
-					gdk_color_free(col);
-				} else {
-					if (flags & PURPLE_MESSAGE_NICK)
-						strcpy(color, DEFAULT_HIGHLIGHT_COLOR);
-					else
-						strcpy(color, DEFAULT_ACTION_COLOR);
-				}
-			}
-			else {
-				str = g_malloc(1024);
+					tagname = "action-name";
+			} else {
 				if (flags & PURPLE_MESSAGE_AUTO_RESP) {
 					g_snprintf(str, 1024, "%s %s", alias_escaped, AUTO_RESPONSE);
-					tag_start_offset += 1
-						+ strlen(AUTO_RESPONSE);
+					tag_start_offset += strlen(AUTO_RESPONSE) - 6 + 1;
 				} else {
 					g_snprintf(str, 1024, "%s:", alias_escaped);
 					tag_end_offset = 1;
 				}
+
 				if (flags & PURPLE_MESSAGE_NICK) {
-					GdkColor *col;
-					gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "highlight-name-color", &col, NULL);
-					if(col) {
-						g_snprintf(color, sizeof(color), "#%02X%02X%02X",
-							col->red >> 8, col->green >> 8, col->blue >> 8);
-						gdk_color_free(col);
-					} else {
-						strcpy(color, DEFAULT_HIGHLIGHT_COLOR);
+					tagname = "highlight-name";
+				} else if (flags & PURPLE_MESSAGE_RECV) {
+					/* The tagname for chats is handled by get_buddy_tag */
+					if (type == PURPLE_CONV_TYPE_IM) {
+						tagname = "receive-name";
 					}
-				}
-				else if (flags & PURPLE_MESSAGE_RECV) {
-					if (type == PURPLE_CONV_TYPE_CHAT) {
-						const GdkColor *col = get_nick_color(gtkconv, name);
-
-						g_snprintf(color, sizeof(color), "#%02X%02X%02X",
-							   col->red >> 8, col->green >> 8, col->blue >> 8);
-					} else {
-						GdkColor *col;
-						gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "receive-name-color", &col, NULL);
-						if(col) {
-							g_snprintf(color, sizeof(color), "#%02X%02X%02X",
-								col->red >> 8, col->green >> 8, col->blue >> 8);
-							gdk_color_free(col);
-						} else {
-							strcpy(color, DEFAULT_RECV_COLOR);
-						}
-					}
-				}
-				else if (flags & PURPLE_MESSAGE_SEND) {
-					GdkColor *col;
-					gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "send-name-color", &col, NULL);
-					if(col) {
-						g_snprintf(color, sizeof(color), "#%02X%02X%02X",
-							col->red >> 8, col->green >> 8, col->blue >> 8);
-						gdk_color_free(col);
-					} else {
-						strcpy(color, DEFAULT_SEND_COLOR);
-					}
-				}
-				else {
+				} else if (flags & PURPLE_MESSAGE_SEND) {
+					tagname = "send-name";
+				} else {
 					purple_debug_error("gtkconv", "message missing flags\n");
-					strcpy(color, "#000000");
 				}
 			}
 		}
 
 		g_free(alias_escaped);
 
-		/* Are we in a chat where we can tell which users are buddies? */
-		if  (prpl_info && !(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME) &&
-		     purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
-
-			/* Bold buddies to make them stand out from non-buddies. */
-			if (flags & PURPLE_MESSAGE_SEND ||
-			    flags & PURPLE_MESSAGE_NICK ||
-			    purple_find_buddy(account, name) != NULL) {
-				g_snprintf(buf2, BUF_LONG,
-					   "<FONT COLOR=\"%s\" %s><FONT SIZE=\"2\"><!--%s --></FONT>"
-					   "<B>%s</B></FONT> ",
-					   color, sml_attrib ? sml_attrib : "", mdate, str);
-			} else {
-				g_snprintf(buf2, BUF_LONG,
-					   "<FONT COLOR=\"%s\" %s><FONT SIZE=\"2\"><!--%s --></FONT>"
-					   "%s</FONT> ",
-					   color, sml_attrib ? sml_attrib : "", mdate, str);
-
-			}
-		} else {
-			/* Bold everyone's name to make the name stand out from the message. */
-			g_snprintf(buf2, BUF_LONG,
-				   "<FONT COLOR=\"%s\" %s><FONT SIZE=\"2\"><!--%s --></FONT>"
-				   "<B>%s</B></FONT> ",
-				   color, sml_attrib ? sml_attrib : "", mdate, str);
-		}
-
+		if (tagname)
+			tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), tagname);
+		else
+			tag = get_buddy_tag(conv, name, TRUE);
+
+		if (GTK_IMHTML(gtkconv->imhtml)->show_comments) {
+			/* The color for the timestamp has to be set in the font-tags, unfortunately.
+			 * Applying the nick-tag to timestamps would work, but that can make it
+			 * bold. I thought applying the "comment" tag again, which has "weight" set
+			 * to PANGO_WEIGHT_NORMAL, would remove the boldness. But it doesn't. So
+			 * this will have to do. I don't terribly like it.  -- sadrul */
+			GdkColor *color = NULL;
+			gboolean set = FALSE;
+			char colcode[] = "COLOR=\"#XXXXXX\"";
+			g_object_get(G_OBJECT(tag), "foreground-set", &set, "foreground-gdk", &color, NULL);
+			if (set && color)
+				g_snprintf(colcode, sizeof(colcode), "COLOR=\"#%02x%02x%02x\"",
+						color->red >> 8, color->green >> 8, color->blue >> 8);
+			else
+				colcode[0] = '\0';
+			g_snprintf(buf2, BUF_LONG, "<FONT %s SIZE=\"2\"><!--%s --></FONT>", colcode, mdate);
+			gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all | GTK_IMHTML_NO_SCROLL);
+			if (color)
+				gdk_color_free(color);
+		}
+
+		gtk_text_buffer_get_end_iter(buffer, &end);
+		mark = gtk_text_buffer_create_mark(buffer, NULL, &end, TRUE);
+
+		g_snprintf(buf2, BUF_LONG, "<FONT %s>%s</FONT> ", sml_attrib ? sml_attrib : "", str);
 		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all | GTK_IMHTML_NO_SCROLL);
 
-		if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT &&
-		    !(flags & PURPLE_MESSAGE_SEND)) {
-
-			GtkTextIter start, end;
-			GtkTextTag *buddytag = get_buddy_tag(conv, name);
-
-			gtk_text_buffer_get_end_iter(
-					GTK_IMHTML(gtkconv->imhtml)->text_buffer,
-					&end);
-			gtk_text_iter_backward_chars(&end,
-					tag_end_offset + 1);
-
-			gtk_text_buffer_get_end_iter(
-					GTK_IMHTML(gtkconv->imhtml)->text_buffer,
-					&start);
-			gtk_text_iter_backward_chars(&start,
-					strlen(str) + 1 - tag_start_offset);
-
-			gtk_text_buffer_apply_tag(
-					GTK_IMHTML(gtkconv->imhtml)->text_buffer,
-					buddytag, &start, &end);
-		}
+		gtk_text_buffer_get_end_iter(buffer, &end);
+		gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
+		gtk_text_buffer_apply_tag(buffer, tag, &start, &end);
+		gtk_text_buffer_delete_mark(buffer, mark);
 
 		g_free(str);
 
@@ -5954,8 +5897,7 @@
 
 			length += pre_len + post_len;
 			g_free(pre);
-		}
-		else
+		} else
 			with_font_tag = g_memdup(new_message, length);
 
 		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml),
@@ -5989,8 +5931,7 @@
 	if (!(flags & PURPLE_MESSAGE_RECV))
 	{
 		/* Restore the smiley-data */
-		GTK_IMHTML(gtkconv->imhtml)->default_smilies = tree;
-		GTK_IMHTML(gtkconv->imhtml)->smiley_data = smiley_data;
+		pidgin_themes_smiley_themeize(gtkconv->imhtml);
 	}
 
 	purple_signal_emit(pidgin_conversations_get_handle(),
@@ -6218,119 +6159,24 @@
 	return FALSE;
 }
 
-static void pidgin_conv_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data)
-{
-	GtkIMHtmlSmiley *smiley;
-
-	smiley = (GtkIMHtmlSmiley *)user_data;
-	smiley->icon = gdk_pixbuf_loader_get_animation(loader);
-
-	if (smiley->icon)
-		g_object_ref(G_OBJECT(smiley->icon));
-#ifdef DEBUG_CUSTOM_SMILEY
-	purple_debug_info("custom-smiley", "pidgin_conv_custom_smiley_allocated(): got GdkPixbufAnimation %p for smiley '%s'\n", smiley->icon, smiley->smile);
-#endif
-}
-
-static void pidgin_conv_custom_smiley_closed(GdkPixbufLoader *loader, gpointer user_data)
-{
-	GtkIMHtmlSmiley *smiley;
-	GtkWidget *icon = NULL;
-	GtkTextChildAnchor *anchor = NULL;
-	GSList *current = NULL;
-
-	smiley = (GtkIMHtmlSmiley *)user_data;
-	if (!smiley->imhtml) {
-#ifdef DEBUG_CUSTOM_SMILEY
-		purple_debug_error("custom-smiley", "pidgin_conv_custom_smiley_closed(): orphan smiley found: %p\n", smiley);
-#endif
-		g_object_unref(G_OBJECT(loader));
-		smiley->loader = NULL;
-		return;
-	}
-
-	for (current = smiley->anchors; current; current = g_slist_next(current)) {
-
-		icon = gtk_image_new_from_animation(smiley->icon);
-
-#ifdef DEBUG_CUSTOM_SMILEY
-		purple_debug_info("custom-smiley", "pidgin_conv_custom_smiley_closed(): got GtkImage %p from GtkPixbufAnimation %p for smiley '%s'\n",
-				icon, smiley->icon, smiley->smile);
-#endif
-		if (icon) {
-			GList *wids;
-			gtk_widget_show(icon);
-
-			anchor = GTK_TEXT_CHILD_ANCHOR(current->data);
-			wids = gtk_text_child_anchor_get_widgets(anchor);
-
-			g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", purple_unescape_html(smiley->smile), g_free);
-			g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley->smile), g_free);
-
-			if (smiley->imhtml) {
-				if (wids) {
-					GList *children = gtk_container_get_children(GTK_CONTAINER(wids->data));
-					g_list_foreach(children, (GFunc)gtk_widget_destroy, NULL);
-					g_list_free(children);
-					gtk_container_add(GTK_CONTAINER(wids->data), icon);
-				} else
-					gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(smiley->imhtml), icon, anchor);
-			}
-			g_list_free(wids);
-		}
-
-	}
-
-	g_slist_free(smiley->anchors);
-	smiley->anchors = NULL;
-
-	g_object_unref(G_OBJECT(loader));
-	smiley->loader = NULL;
-}
-
 static gboolean
 add_custom_smiley_for_imhtml(GtkIMHtml *imhtml, const char *sml, const char *smile)
 {
 	GtkIMHtmlSmiley *smiley;
-	GdkPixbufLoader *loader;
 
 	smiley = gtk_imhtml_smiley_get(imhtml, sml, smile);
 
 	if (smiley) {
-
 		if (!(smiley->flags & GTK_IMHTML_SMILEY_CUSTOM)) {
 			return FALSE;
 		}
-
-		/* Close the old GdkPixbufAnimation, then create a new one for
-		 * the smiley we are about to receive */
-		g_object_unref(G_OBJECT(smiley->icon));
-
-		/* XXX: Is it necessary to _unref the loader first? */
-		smiley->loader = gdk_pixbuf_loader_new();
-		smiley->icon = NULL;
-
-		g_signal_connect(smiley->loader, "area_prepared", G_CALLBACK(pidgin_conv_custom_smiley_allocated), smiley);
-		g_signal_connect(smiley->loader, "closed", G_CALLBACK(pidgin_conv_custom_smiley_closed), smiley);
-
+		gtk_imhtml_smiley_reload(smiley);
 		return TRUE;
 	}
 
-	loader = gdk_pixbuf_loader_new();
-
-	/* this is wrong, this file ought not call g_new on GtkIMHtmlSmiley */
-	/* Let gtk_imhtml have a gtk_imhtml_smiley_new function, and let
-	   GtkIMHtmlSmiley by opaque */
-	smiley = g_new0(GtkIMHtmlSmiley, 1);
-	smiley->file   = NULL;
-	smiley->smile  = g_strdup(smile);
-	smiley->loader = loader;
-	smiley->flags  = smiley->flags | GTK_IMHTML_SMILEY_CUSTOM;
-
-	g_signal_connect(smiley->loader, "area_prepared", G_CALLBACK(pidgin_conv_custom_smiley_allocated), smiley);
-	g_signal_connect(smiley->loader, "closed", G_CALLBACK(pidgin_conv_custom_smiley_closed), smiley);
-
+	smiley = gtk_imhtml_smiley_create(NULL, smile, FALSE, GTK_IMHTML_SMILEY_CUSTOM);
 	gtk_imhtml_associate_smiley(imhtml, sml, smiley);
+	g_signal_connect_swapped(imhtml, "destroy", G_CALLBACK(gtk_imhtml_smiley_destroy), smiley);
 
 	return TRUE;
 }
@@ -6555,6 +6401,11 @@
 		if(conv->features & PURPLE_CONNECTION_NO_IMAGES)
 			buttons &= ~GTK_IMHTML_IMAGE;
 
+		if (conv->features & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY)
+			buttons |= GTK_IMHTML_CUSTOM_SMILEY;
+		else
+			buttons &= ~GTK_IMHTML_CUSTOM_SMILEY;
+
 		gtk_imhtml_set_format_functions(GTK_IMHTML(gtkconv->entry), buttons);
 		if (account != NULL)
 			gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(gtkconv->toolbar), purple_account_get_protocol_id(account));
@@ -7023,7 +6874,7 @@
 	{
 		PurpleContact *contact = purple_buddy_get_contact(buddy);
 		if (contact) {
-			custom_img = purple_buddy_icons_find_custom_icon(contact);
+			custom_img = purple_buddy_icons_node_find_custom_icon((PurpleBlistNode*)contact);
 			if (custom_img) {
 				/* There is a custom icon for this user */
 				data = purple_imgstore_get_data(custom_img);
--- a/pidgin/gtkdialogs.c	Mon May 19 16:18:32 2008 +0000
+++ b/pidgin/gtkdialogs.c	Sun May 25 04:30:41 2008 +0000
@@ -686,7 +686,7 @@
 #ifdef LIBZEPHYR_EXT
 	g_string_append(str, "    <b>Zephyr library (libzephyr):</b> External<br/>");
 #else
-	g_string_append(str, "    <b>Zephyr library (libzephyr):</b> Not External<br/>");
+	g_string_append(str, "    <b>Zephyr library (libzephyr):</b> Internal<br/>");
 #endif
 
 #ifdef ZEPHYR_USES_KERBEROS
--- a/pidgin/gtkimhtml.c	Mon May 19 16:18:32 2008 +0000
+++ b/pidgin/gtkimhtml.c	Sun May 25 04:30:41 2008 +0000
@@ -32,10 +32,14 @@
 #include "internal.h"
 #include "pidgin.h"
 #include "pidginstock.h"
+#include "gtkutils.h"
+#include "smiley.h"
+#include "imgstore.h"
 
 #include "debug.h"
 #include "util.h"
 #include "gtkimhtml.h"
+#include "gtksmiley.h"
 #include "gtksourceiter.h"
 #include "gtksourceundomanager.h"
 #include "gtksourceview-marshal.h"
@@ -397,6 +401,55 @@
 		gtk_imhtml_scroll_to_end(imhtml, FALSE);
 }
 
+#define DEFAULT_SEND_COLOR "#204a87"
+#define DEFAULT_RECV_COLOR "#cc0000"
+#define DEFAULT_HIGHLIGHT_COLOR "#AF7F00"
+#define DEFAULT_ACTION_COLOR "#062585"
+#define DEFAULT_WHISPER_ACTION_COLOR "#6C2585"
+#define DEFAULT_WHISPER_COLOR "#00FF00"
+
+static void (*parent_style_set)(GtkWidget *widget, GtkStyle *prev_style);
+
+static void
+gtk_imhtml_style_set(GtkWidget *widget, GtkStyle *prev_style)
+{
+	int i;
+	struct {
+		const char *tag;
+		const char *color;
+		const char *def;
+	} styles[] = {
+		{"send-name", "send-name-color", DEFAULT_SEND_COLOR},
+		{"receive-name", "receive-name-color", DEFAULT_RECV_COLOR},
+		{"highlight-name", "highlight-name-color", DEFAULT_HIGHLIGHT_COLOR},
+		{"action-name", "action-name-color", DEFAULT_ACTION_COLOR},
+		{"whisper-action-name", "whisper-action-name-color", DEFAULT_WHISPER_ACTION_COLOR},
+		{"whisper-name", "whisper-name-color", DEFAULT_WHISPER_COLOR},
+		{NULL, NULL, NULL}
+	};
+	GtkIMHtml *imhtml = GTK_IMHTML(widget);
+	GtkTextTagTable *table = gtk_text_buffer_get_tag_table(imhtml->text_buffer);
+
+	for (i = 0; styles[i].tag; i++) {
+		GdkColor *color = NULL;
+		GtkTextTag *tag = gtk_text_tag_table_lookup(table, styles[i].tag);
+		if (!tag) {
+			purple_debug_warning("gtkimhtml", "Cannot find tag '%s'. This should never happen. Please file a bug.\n", styles[i].tag);
+			continue;
+		}
+		gtk_widget_style_get(widget, styles[i].color, &color, NULL);
+		if (color) {
+			g_object_set(tag, "foreground-gdk", color, NULL);
+			gdk_color_free(color);
+		} else {
+			GdkColor defcolor;
+			gdk_color_parse(styles[i].def, &defcolor);
+			g_object_set(tag, "foreground-gdk", &defcolor, NULL);
+		}
+	}
+	parent_style_set(widget, prev_style);
+}
+
 static gint
 gtk_imhtml_tip_paint (GtkIMHtml *imhtml)
 {
@@ -1433,6 +1486,8 @@
 	widget_class->expose_event = gtk_imhtml_expose_event;
 	parent_size_allocate = widget_class->size_allocate;
 	widget_class->size_allocate = gtk_imhtml_size_allocate;
+	parent_style_set = widget_class->style_set;
+	widget_class->style_set = gtk_imhtml_style_set;
 
 	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("hyperlink-color",
 	                                        _("Hyperlink color"),
@@ -1458,6 +1513,14 @@
 	                                        _("Action Message Name Color"),
 	                                        _("Color to draw the name of an action message."),
 	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
+	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("whisper-action-name-color",
+	                                        _("Action Message Name Color for Whispered Message"),
+	                                        _("Color to draw the name of an action message."),
+	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
+	gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("whisper-name-color",
+	                                        _("Whisper Message Name Color"),
+	                                        _("Color to draw the name of an action message."),
+	                                        GDK_TYPE_COLOR, G_PARAM_READABLE));
 
 	/* Customizable typing notification ... sort of. Example:
 	 *   GtkIMHtml::typing-notification-font = "monospace italic light 8.0"
@@ -1519,9 +1582,18 @@
 	gtk_text_buffer_create_tag(imhtml->text_buffer, "SUP", "rise", 5000, NULL);
 	gtk_text_buffer_create_tag(imhtml->text_buffer, "PRE", "family", "Monospace", NULL);
 	gtk_text_buffer_create_tag(imhtml->text_buffer, "search", "background", "#22ff00", "weight", "bold", NULL);
+	gtk_text_buffer_create_tag(imhtml->text_buffer, "comment", "weight", PANGO_WEIGHT_NORMAL,
 #if FALSE && GTK_CHECK_VERSION(2,10,10)
-	gtk_text_buffer_create_tag(imhtml->text_buffer, "comment", "invisible", FALSE, NULL);
+			"invisible", FALSE,
 #endif
+			NULL);
+
+	gtk_text_buffer_create_tag(imhtml->text_buffer, "send-name", "weight", PANGO_WEIGHT_BOLD, NULL);
+	gtk_text_buffer_create_tag(imhtml->text_buffer, "receive-name", "weight", PANGO_WEIGHT_BOLD, NULL);
+	gtk_text_buffer_create_tag(imhtml->text_buffer, "highlight-name", "weight", PANGO_WEIGHT_BOLD, NULL);
+	gtk_text_buffer_create_tag(imhtml->text_buffer, "action-name", "weight", PANGO_WEIGHT_BOLD, NULL);
+	gtk_text_buffer_create_tag(imhtml->text_buffer, "whisper-action-name", "weight", PANGO_WEIGHT_BOLD, NULL);
+	gtk_text_buffer_create_tag(imhtml->text_buffer, "whisper-name", "weight", PANGO_WEIGHT_BOLD, NULL);
 
 	/* When hovering over a link, we show the hand cursor--elsewhere we show the plain ol' pointer cursor */
 	imhtml->hand_cursor = gdk_cursor_new (GDK_HAND2);
@@ -3063,7 +3135,7 @@
 #else
 					if (imhtml->show_comments && !(options & GTK_IMHTML_NO_COMMENTS)) {
 						wpos = g_snprintf (ws, len, "%s", tag);
-						gtk_text_buffer_insert(imhtml->text_buffer, iter, ws, wpos);
+						gtk_text_buffer_insert_with_tags_by_name(imhtml->text_buffer, iter, ws, wpos, "comment", NULL);
 					}
 #endif
 					ws[0] = '\0'; wpos = 0;
@@ -3631,6 +3703,15 @@
 	gtk_widget_show(image->filesel);
 }
 
+static void
+gtk_imhtml_custom_smiley_save(GtkWidget *w, GtkIMHtmlImage *image)
+{
+	/* Create an add dialog */
+	PidginSmiley *editor = pidgin_smiley_edit(NULL, NULL);
+	pidgin_smiley_editor_set_shortcut(editor, image->filename);
+	pidgin_smiley_editor_set_image(editor, image->pixbuf);
+}
+
 /*
  * So, um, AIM Direct IM lets you send any file, not just images.  You can
  * just insert a sound or a file or whatever in a conversation.  It's
@@ -3655,6 +3736,19 @@
 			g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(gtk_imhtml_image_save), image);
 			gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
 
+			/* Add menu item for adding custom smiley to local smileys */
+			/* we only add the menu if the image is of "custom smiley size"
+			  <= 96x96 pixels */
+			if (image->width <= 96 && image->height <= 96) {
+				text = g_strdup_printf(_("_Add Custom Smiley..."));
+				img = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
+				item = gtk_image_menu_item_new_with_mnemonic(text);
+				gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
+				g_signal_connect(G_OBJECT(item), "activate",
+								 G_CALLBACK(gtk_imhtml_custom_smiley_save), image);
+				gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
+			}
+
 			gtk_widget_show_all(menu);
 			gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
 							event_button->button, event_button->time);
@@ -3676,7 +3770,7 @@
 	GdkPixbufAnimation *anim = NULL;
 	GtkIMHtmlScalable *image = NULL;
 	gboolean ret;
-	
+
 	if (event->type != GDK_BUTTON_RELEASE || ((GdkEventButton*)event)->button != 3)
 		return FALSE;
 
@@ -4912,7 +5006,67 @@
 		g_snprintf(buf, sizeof(buf), "<font size=\"%s\">", &name[10]);
 		return buf;
 	} else {
-		return "";
+		char *str = buf;
+		gboolean isset;
+		int ivalue = 0;
+		GdkColor *color = NULL;
+		GObject *obj = G_OBJECT(tag);
+		gboolean empty = TRUE;
+
+		str += g_snprintf(str, sizeof(buf) - (str - buf), "<span style='");
+
+		/* Weight */
+		g_object_get(obj, "weight-set", &isset, "weight", &ivalue, NULL);
+		if (isset) {
+			const char *weight = "";
+			if (ivalue >= PANGO_WEIGHT_ULTRABOLD)
+				weight = "bolder";
+			else if (ivalue >= PANGO_WEIGHT_BOLD)
+				weight = "bold";
+			else if (ivalue >= PANGO_WEIGHT_NORMAL)
+				weight = "normal";
+			else
+				weight = "lighter";
+
+			str += g_snprintf(str, sizeof(buf) - (str - buf), "font-weight: %s;", weight);
+			empty = FALSE;
+		}
+
+		/* Foreground color */
+		g_object_get(obj, "foreground-set", &isset, "foreground-gdk", &color, NULL);
+		if (isset && color) {
+			str += g_snprintf(str, sizeof(buf) - (str - buf),
+					"color: #%02x%02x%02x;",
+					color->red >> 8, color->green >> 8, color->blue >> 8);
+			gdk_color_free(color);
+			empty = FALSE;
+		}
+
+		/* Background color */
+		g_object_get(obj, "background-set", &isset, "background-gdk", &color, NULL);
+		if (isset && color) {
+			str += g_snprintf(str, sizeof(buf) - (str - buf),
+					"background: #%02x%02x%02x;",
+					color->red >> 8, color->green >> 8, color->blue >> 8);
+			gdk_color_free(color);
+			empty = FALSE;
+		}
+
+		/* Underline */
+		g_object_get(obj, "underline-set", &isset, "underline", &ivalue, NULL);
+		if (isset) {
+			switch (ivalue) {
+				case PANGO_UNDERLINE_NONE:
+				case PANGO_UNDERLINE_ERROR:
+					break;
+				default:
+					str += g_snprintf(str, sizeof(buf) - (str - buf), "text-decoration: underline;");
+			}
+		}
+
+		g_snprintf(str, sizeof(buf) - (str - buf), "'>");
+
+		return (empty ? "" : buf);
 	}
 }
 
@@ -4944,6 +5098,16 @@
 	} else if (strncmp(name, "FONT SIZE ", 10) == 0) {
 		return "</font>";
 	} else {
+		const char *props[] = {"weight-set", "foreground-set", "background-set",
+			"size-set", "underline-set", NULL};
+		int i;
+		for (i = 0; props[i]; i++) {
+			gboolean set = FALSE;
+			g_object_get(G_OBJECT(tag), props[i], &set, NULL);
+			if (set)
+				return "</span>";
+		}
+
 		return "";
 	}
 }
@@ -5273,7 +5437,150 @@
 	if (flags & PURPLE_CONNECTION_NO_IMAGES)
 		buttons &= ~GTK_IMHTML_IMAGE;
 
+	if (flags & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY)
+		buttons |= GTK_IMHTML_CUSTOM_SMILEY;
+	else
+		buttons &= ~GTK_IMHTML_CUSTOM_SMILEY;
+
 	gtk_imhtml_set_format_functions(imhtml, buttons);
 }
 
-
+/*******
+ * GtkIMHtmlSmiley functions
+ *******/
+static void gtk_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data)
+{
+	GtkIMHtmlSmiley *smiley;
+
+	smiley = (GtkIMHtmlSmiley *)user_data;
+	smiley->icon = gdk_pixbuf_loader_get_animation(loader);
+
+	if (smiley->icon)
+		g_object_ref(G_OBJECT(smiley->icon));
+#ifdef DEBUG_CUSTOM_SMILEY
+	purple_debug_info("custom-smiley", "gtk_custom_smiley_allocated(): got GdkPixbufAnimation %p for smiley '%s'\n", smiley->icon, smiley->smile);
+#endif
+}
+
+static void gtk_custom_smiley_closed(GdkPixbufLoader *loader, gpointer user_data)
+{
+	GtkIMHtmlSmiley *smiley;
+	GtkWidget *icon = NULL;
+	GtkTextChildAnchor *anchor = NULL;
+	GSList *current = NULL;
+
+	smiley = (GtkIMHtmlSmiley *)user_data;
+	if (!smiley->imhtml) {
+#ifdef DEBUG_CUSTOM_SMILEY
+		purple_debug_error("custom-smiley", "gtk_custom_smiley_closed(): orphan smiley found: %p\n", smiley);
+#endif
+		g_object_unref(G_OBJECT(loader));
+		smiley->loader = NULL;
+		return;
+	}
+
+	for (current = smiley->anchors; current; current = g_slist_next(current)) {
+
+		icon = gtk_image_new_from_animation(smiley->icon);
+
+#ifdef DEBUG_CUSTOM_SMILEY
+		purple_debug_info("custom-smiley", "gtk_custom_smiley_closed(): got GtkImage %p from GtkPixbufAnimation %p for smiley '%s'\n",
+				icon, smiley->icon, smiley->smile);
+#endif
+		if (icon) {
+			GList *wids;
+			gtk_widget_show(icon);
+
+			anchor = GTK_TEXT_CHILD_ANCHOR(current->data);
+			wids = gtk_text_child_anchor_get_widgets(anchor);
+
+			g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_plaintext", purple_unescape_html(smiley->smile), g_free);
+			g_object_set_data_full(G_OBJECT(anchor), "gtkimhtml_htmltext", g_strdup(smiley->smile), g_free);
+
+			if (smiley->imhtml) {
+				if (wids) {
+					GList *children = gtk_container_get_children(GTK_CONTAINER(wids->data));
+					g_list_foreach(children, (GFunc)gtk_widget_destroy, NULL);
+					g_list_free(children);
+					gtk_container_add(GTK_CONTAINER(wids->data), icon);
+				} else
+					gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(smiley->imhtml), icon, anchor);
+			}
+			g_list_free(wids);
+		}
+
+	}
+
+	g_slist_free(smiley->anchors);
+	smiley->anchors = NULL;
+
+	g_object_unref(G_OBJECT(loader));
+	smiley->loader = NULL;
+}
+
+static void
+gtk_custom_smiley_size_prepared(GdkPixbufLoader *loader, gint width, gint height, gpointer data)
+{
+#define CUSTOM_SMILEY_SIZE 96	/* XXX: Should this be a theme setting? */
+	if (width <= CUSTOM_SMILEY_SIZE && height <= CUSTOM_SMILEY_SIZE)
+		return;
+
+	if (width >= height) {
+		height = height * CUSTOM_SMILEY_SIZE / width;
+		width = CUSTOM_SMILEY_SIZE;
+	} else {
+		width = width * CUSTOM_SMILEY_SIZE / height;
+		height = CUSTOM_SMILEY_SIZE;
+	}
+
+	gdk_pixbuf_loader_set_size(loader, width, height);
+}
+
+void
+gtk_imhtml_smiley_reload(GtkIMHtmlSmiley *smiley)
+{
+	if (smiley->icon)
+		g_object_unref(smiley->icon);
+	if (smiley->loader)
+		g_object_unref(smiley->loader);  /* XXX: does this crash? */
+
+	smiley->icon = NULL;
+	smiley->loader = NULL;
+
+	if (smiley->file) {
+		/* We do not use the pixbuf loader for a smiley that can be loaded
+		 * from a file. (e.g., local custom smileys)
+		 */
+		return;
+	}
+
+	smiley->loader = gdk_pixbuf_loader_new();
+
+	g_signal_connect(smiley->loader, "area_prepared", G_CALLBACK(gtk_custom_smiley_allocated), smiley);
+	g_signal_connect(smiley->loader, "closed", G_CALLBACK(gtk_custom_smiley_closed), smiley);
+	g_signal_connect(smiley->loader, "size_prepared", G_CALLBACK(gtk_custom_smiley_size_prepared), smiley);
+}
+
+GtkIMHtmlSmiley *gtk_imhtml_smiley_create(const char *file, const char *shortcut, gboolean hide,
+		GtkIMHtmlSmileyFlags flags)
+{
+	GtkIMHtmlSmiley *smiley = g_new0(GtkIMHtmlSmiley, 1);
+	smiley->file = g_strdup(file);
+	smiley->smile = g_strdup(shortcut);
+	smiley->hidden = hide;
+	smiley->flags = flags;
+	gtk_imhtml_smiley_reload(smiley);
+	return smiley;
+}
+
+void gtk_imhtml_smiley_destroy(GtkIMHtmlSmiley *smiley)
+{
+	g_free(smiley->smile);
+	g_free(smiley->file);
+	if (smiley->icon)
+		g_object_unref(smiley->icon);
+	if (smiley->loader)
+		g_object_unref(smiley->loader);
+	g_free(smiley);
+}
+
--- a/pidgin/gtkimhtml.h	Mon May 19 16:18:32 2008 +0000
+++ b/pidgin/gtkimhtml.h	Sun May 25 04:30:41 2008 +0000
@@ -76,6 +76,8 @@
 	GTK_IMHTML_SMILEY =     1 << 11,
 	GTK_IMHTML_LINKDESC =   1 << 12,
 	GTK_IMHTML_STRIKE =     1 << 13,
+	/** Show custom smileys when appropriate. @since 2.5.0 */
+	GTK_IMHTML_CUSTOM_SMILEY = 1 << 14,
 	GTK_IMHTML_ALL =       -1
 } GtkIMHtmlButtons;
 
@@ -852,6 +854,12 @@
  */
 void gtk_imhtml_setup_entry(GtkIMHtml *imhtml, PurpleConnectionFlags flags);
 
+GtkIMHtmlSmiley *gtk_imhtml_smiley_create(const char *file, const char *shortcut, gboolean hide,
+		GtkIMHtmlSmileyFlags flags);
+
+void gtk_imhtml_smiley_reload(GtkIMHtmlSmiley *smiley);
+
+void gtk_imhtml_smiley_destroy(GtkIMHtmlSmiley *smiley);
 /*@}*/
 
 #ifdef __cplusplus
--- a/pidgin/gtkimhtmltoolbar.c	Mon May 19 16:18:32 2008 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Sun May 25 04:30:41 2008 +0000
@@ -36,6 +36,7 @@
 
 #include "gtkdialogs.h"
 #include "gtkimhtmltoolbar.h"
+#include "gtksmiley.h"
 #include "gtkthemes.h"
 #include "gtkutils.h"
 
@@ -579,8 +580,7 @@
 }
 
 static gboolean
-close_smiley_dialog(GtkWidget *widget, GdkEvent *event,
-					GtkIMHtmlToolbar *toolbar)
+close_smiley_dialog(GtkIMHtmlToolbar *toolbar)
 {
 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar->smiley), FALSE);
 	return FALSE;
@@ -601,24 +601,30 @@
 
 	g_free(escaped_smiley);
 
-	close_smiley_dialog(NULL, NULL, toolbar);
+	close_smiley_dialog(toolbar);
 }
 
 /* smiley buttons list */
 struct smiley_button_list {
 	int width, height;
 	GtkWidget *button;
+	const GtkIMHtmlSmiley *smiley;
 	struct smiley_button_list *next;
 };
 
 static struct smiley_button_list *
-sort_smileys(struct smiley_button_list *ls, GtkIMHtmlToolbar *toolbar, int *width, char *filename, char *face)
+sort_smileys(struct smiley_button_list *ls, GtkIMHtmlToolbar *toolbar,
+			 int *width, const GtkIMHtmlSmiley *smiley,
+			 const GSList *custom_smileys)
 {
 	GtkWidget *image;
 	GtkWidget *button;
 	GtkRequisition size;
 	struct smiley_button_list *cur;
 	struct smiley_button_list *it, *it_last;
+	const gchar *filename = smiley->file;
+	gchar *face = smiley->smile;
+	PurpleSmiley *psmiley = NULL;
 
 	cur = g_new0(struct smiley_button_list, 1);
 	it = ls;
@@ -626,6 +632,35 @@
 	image = gtk_image_new_from_file(filename);
 
 	gtk_widget_size_request(image, &size);
+
+	if (size.width > 24 &&
+			smiley->flags & GTK_IMHTML_SMILEY_CUSTOM) { /* This is a custom smiley, let's scale it */
+		GdkPixbuf *pixbuf = NULL;
+		GtkImageType type;
+
+		type = gtk_image_get_storage_type(GTK_IMAGE(image));
+
+		if (type == GTK_IMAGE_PIXBUF) {
+			pixbuf = gtk_image_get_pixbuf(GTK_IMAGE(image));
+		} else if (type == GTK_IMAGE_ANIMATION) {
+			GdkPixbufAnimation *animation;
+
+			animation = gtk_image_get_animation(GTK_IMAGE(image));
+
+			pixbuf = gdk_pixbuf_animation_get_static_image(animation);
+		}
+
+		if (pixbuf != NULL) {
+			GdkPixbuf *resized;
+			resized = gdk_pixbuf_scale_simple(pixbuf, 24, 24,
+					GDK_INTERP_HYPER);
+
+			gtk_image_set_from_pixbuf(GTK_IMAGE(image), resized); /* This unrefs pixbuf */
+			gtk_widget_size_request(image, &size);
+			g_object_unref(G_OBJECT(resized));
+		}
+	}
+
 	(*width) += size.width;
 
 	button = gtk_button_new();
@@ -639,10 +674,26 @@
 	/* these look really weird with borders */
 	gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
 
+	psmiley = purple_smileys_find_by_shortcut(smiley->smile);
+	/* If this is a "non-custom" smiley, check to see if its shortcut is
+	  "shadowed" by any custom smiley. This can only happen if the connection
+	  is custom smiley-enabled */
+	if (psmiley && !(smiley->flags & GTK_IMHTML_SMILEY_CUSTOM)) {
+		gtk_tooltips_set_tip(toolbar->tooltips, button,
+				_("This smiley is disabled because a custom smiley exists for this shortcut."),
+				NULL);
+		gtk_widget_set_sensitive(button, FALSE);
+	} else if (psmiley) {
+		/* Remove the button if the smiley is destroyed */
+		g_signal_connect_object(G_OBJECT(psmiley), "destroy", G_CALLBACK(gtk_widget_destroy),
+				button, G_CONNECT_SWAPPED);
+	}
+
 	/* set current element to add */
 	cur->height = size.height;
 	cur->width = size.width;
 	cur->button = button;
+	cur->smiley = smiley;
 	cur->next = ls;
 
 	/* check where to insert by height */
@@ -661,7 +712,7 @@
 smiley_is_unique(GSList *list, GtkIMHtmlSmiley *smiley)
 {
 	while (list) {
-		GtkIMHtmlSmiley *cur = list->data;
+		GtkIMHtmlSmiley *cur = (GtkIMHtmlSmiley *) list->data;
 		if (!strcmp(cur->file, smiley->file))
 			return FALSE;
 		list = list->next;
@@ -675,7 +726,7 @@
 	if ((event->type == GDK_KEY_PRESS && event->key.keyval == GDK_Escape) ||
 	    (event->type == GDK_BUTTON_PRESS && event->button.button == 1))
 	{
-		close_smiley_dialog(NULL, NULL, toolbar);
+		close_smiley_dialog(toolbar);
 		return TRUE;
 	}
 
@@ -683,11 +734,43 @@
 }
 
 static void
+add_smiley_list(GtkWidget *container, struct smiley_button_list *list,
+		int max_width, gboolean custom)
+{
+	GtkWidget *line;
+	int line_width = 0;
+
+	if (!list)
+		return;
+
+	line = gtk_hbox_new(FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(container), line, FALSE, FALSE, 0);
+	for (; list; list = list->next) {
+		if (custom != !!(list->smiley->flags & GTK_IMHTML_SMILEY_CUSTOM))
+			continue;
+		gtk_box_pack_start(GTK_BOX(line), list->button, FALSE, FALSE, 0);
+		gtk_widget_show(list->button);
+		line_width += list->width;
+		if (line_width >= max_width) {
+			if (list->next) {
+				line = gtk_hbox_new(FALSE, 0);
+				gtk_box_pack_start(GTK_BOX(container), line, FALSE, FALSE, 0);
+			}
+			line_width = 0;
+		}
+	}
+}
+
+static void
 insert_smiley_cb(GtkWidget *smiley, GtkIMHtmlToolbar *toolbar)
 {
-	GtkWidget *dialog;
+	GtkWidget *dialog, *vbox;
 	GtkWidget *smiley_table = NULL;
 	GSList *smileys, *unique_smileys = NULL;
+	const GSList *custom_smileys = NULL;
+	gboolean supports_custom = FALSE;
+	GtkRequisition req;
+	GtkWidget *scrolled, *viewport;
 
 	if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(smiley))) {
 		destroy_smiley_dialog(toolbar);
@@ -701,61 +784,74 @@
 		smileys = pidgin_themes_get_proto_smileys(NULL);
 
 	while(smileys) {
-		GtkIMHtmlSmiley *smiley = smileys->data;
+		GtkIMHtmlSmiley *smiley = (GtkIMHtmlSmiley *) smileys->data;
 		if(!smiley->hidden) {
-			if(smiley_is_unique(unique_smileys, smiley))
+			if(smiley_is_unique(unique_smileys, smiley)) {
 				unique_smileys = g_slist_append(unique_smileys, smiley);
+			}
 		}
 		smileys = smileys->next;
 	}
+	supports_custom = (gtk_imhtml_get_format_functions(GTK_IMHTML(toolbar->imhtml)) & GTK_IMHTML_CUSTOM_SMILEY);
+	if (toolbar->imhtml && supports_custom) {
+		const GSList *iterator = NULL;
+		custom_smileys = pidgin_smileys_get_all();
+
+		for (iterator = custom_smileys ; iterator ;
+			 iterator = g_slist_next(iterator)) {
+			GtkIMHtmlSmiley *smiley = (GtkIMHtmlSmiley *) iterator->data;
+			unique_smileys = g_slist_append(unique_smileys, smiley);
+		}
+	}
 
 	dialog = pidgin_create_dialog(_("Smile!"), 0, "smiley_dialog", FALSE);
-
 	gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
+	vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(dialog), FALSE, 0);
 
 	if (unique_smileys != NULL) {
-		struct smiley_button_list *ls, *it, *it_tmp;
-		GtkWidget *line;
-		int line_width = 0;
-		int max_line_width, num_lines;
-		int col=0;
+		struct smiley_button_list *ls;
+		int max_line_width, num_lines, button_width = 0;
 
 		/* We use hboxes packed in a vbox */
 		ls = NULL;
-		line = gtk_hbox_new(FALSE, 0);
-		line_width = 0;
 		max_line_width = 0;
 		num_lines = floor(sqrt(g_slist_length(unique_smileys)));
 		smiley_table = gtk_vbox_new(FALSE, 0);
 
+		if (supports_custom) {
+			GtkWidget *manage = gtk_button_new_with_mnemonic(_("_Manage custom smileys"));
+			GtkRequisition req;
+			g_signal_connect(G_OBJECT(manage), "clicked",
+					G_CALLBACK(pidgin_smiley_manager_show), NULL);
+			g_signal_connect_swapped(G_OBJECT(manage), "clicked",
+					G_CALLBACK(gtk_widget_destroy), dialog);
+			gtk_box_pack_end(GTK_BOX(vbox), manage, FALSE, TRUE, 0);
+			gtk_widget_size_request(manage, &req);
+			button_width = req.width;
+		}
+
 		/* create list of smileys sorted by height */
 		while (unique_smileys) {
-			GtkIMHtmlSmiley *smiley = unique_smileys->data;
+			GtkIMHtmlSmiley *smiley = (GtkIMHtmlSmiley *) unique_smileys->data;
 			if (!smiley->hidden) {
-				ls = sort_smileys(ls, toolbar, &max_line_width, smiley->file, smiley->smile);
+				ls = sort_smileys(ls, toolbar, &max_line_width, smiley, custom_smileys);
 			}
 			unique_smileys = g_slist_delete_link(unique_smileys, unique_smileys);
 		}
+		/* The window will be at least as wide as the 'Manage ..' button */
+		max_line_width = MAX(button_width, max_line_width / num_lines);
+
 		/* pack buttons of the list */
-		max_line_width = max_line_width / num_lines;
-		it = ls;
-		while (it != NULL)
-		{
-			it_tmp = it;
-			gtk_box_pack_start(GTK_BOX(line), it->button, FALSE, FALSE, 0);
-			gtk_widget_show(it->button);
-			line_width += it->width;
-			if (line_width >= max_line_width) {
-				gtk_box_pack_start(GTK_BOX(smiley_table), line, FALSE, FALSE, 0);
-				line = gtk_hbox_new(FALSE, 0);
-				line_width = 0;
-				col = 0;
-			}
-			col++;
-			it = it->next;
-			g_free(it_tmp);
+		add_smiley_list(smiley_table, ls, max_line_width, FALSE);
+		if (supports_custom) {
+			gtk_box_pack_start(GTK_BOX(smiley_table), gtk_hseparator_new(), TRUE, FALSE, 0);
+			add_smiley_list(smiley_table, ls, max_line_width, TRUE);
 		}
-		gtk_box_pack_start(GTK_BOX(smiley_table), line, FALSE, TRUE, 0);
+		while (ls) {
+			struct smiley_button_list *tmp = ls->next;
+			g_free(ls);
+			ls = tmp;
+		}
 
 		gtk_widget_add_events(dialog, GDK_KEY_PRESS_MASK);
 	}
@@ -765,19 +861,41 @@
 		g_signal_connect(G_OBJECT(dialog), "button-press-event", (GCallback)smiley_dialog_input_cb, toolbar);
 	}
 
-	g_signal_connect(G_OBJECT(dialog), "key-press-event", (GCallback)smiley_dialog_input_cb, toolbar);
-	gtk_container_add(GTK_CONTAINER(pidgin_dialog_get_vbox(GTK_DIALOG(dialog))), smiley_table);
+	scrolled = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW (scrolled), GTK_SHADOW_NONE);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled),
+			GTK_POLICY_NEVER, GTK_POLICY_NEVER);
+	gtk_box_pack_start(GTK_BOX(vbox), scrolled, TRUE, TRUE, 0);
+	gtk_widget_show(scrolled);
 
+	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled), smiley_table);
 	gtk_widget_show(smiley_table);
 
+	viewport = gtk_widget_get_parent(smiley_table);
+	gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
+
 	/* connect signals */
-	g_signal_connect(G_OBJECT(dialog), "delete_event",
-					 G_CALLBACK(close_smiley_dialog), toolbar);
+	g_signal_connect_swapped(G_OBJECT(dialog), "destroy", G_CALLBACK(close_smiley_dialog), toolbar);
+	g_signal_connect(G_OBJECT(dialog), "key-press-event", G_CALLBACK(smiley_dialog_input_cb), toolbar);
+
+	gtk_window_set_transient_for(GTK_WINDOW(dialog),
+			GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(toolbar))));
 
 	/* show everything */
 	gtk_widget_show_all(dialog);
-	gtk_window_set_transient_for(GTK_WINDOW(dialog),
-			GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(toolbar))));
+
+	gtk_widget_size_request(viewport, &req);
+	gtk_widget_set_size_request(scrolled, MIN(300, req.width), MIN(290, req.height));
+
+	/* The window has to be made resizable, and the scrollbars in the scrolled window
+	 * enabled only after setting the desired size of the window. If we do either of
+	 * these tasks before now, GTK+ miscalculates the required size, and erronously
+	 * makes one or both scrollbars visible (sometimes).
+	 * I too think this hack is gross. But I couldn't find a better way -- sadrul */
+	gtk_window_set_resizable(GTK_WINDOW(dialog), TRUE);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled),
+			GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+
 #ifdef _WIN32
 	winpidgin_ensure_onscreen(dialog);
 #endif
--- a/pidgin/gtkmain.c	Mon May 19 16:18:32 2008 +0000
+++ b/pidgin/gtkmain.c	Sun May 25 04:30:41 2008 +0000
@@ -62,6 +62,7 @@
 #include "gtkroomlist.h"
 #include "gtksavedstatuses.h"
 #include "gtksession.h"
+#include "gtksmiley.h"
 #include "gtksound.h"
 #include "gtkthemes.h"
 #include "gtkutils.h"
@@ -315,6 +316,7 @@
 	pidgin_roomlist_init();
 	pidgin_log_init();
 	pidgin_docklet_init();
+	pidgin_smileys_init();
 }
 
 static GHashTable *ui_info = NULL;
@@ -331,6 +333,7 @@
 	pidgin_plugins_save();
 
 	/* Uninit */
+	pidgin_smileys_uninit();
 	pidgin_conversations_uninit();
 	pidgin_status_uninit();
 	pidgin_docklet_uninit();
--- a/pidgin/gtkplugin.c	Mon May 19 16:18:32 2008 +0000
+++ b/pidgin/gtkplugin.c	Sun May 25 04:30:41 2008 +0000
@@ -421,6 +421,8 @@
 		"<span size=\"smaller\">%s</span>",
 		name, version);
 	gtk_label_set_markup(plugin_name, buf);
+	g_free(name);
+	g_free(version);
 	g_free(buf);
 
 	gtk_text_buffer_set_text(plugin_desc, purple_plugin_get_description(plug), -1);
@@ -694,6 +696,8 @@
 	gtk_label_set_markup(GTK_LABEL(label), _("<b>Filename:</b>"));
 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
 
+	g_object_unref(sg);
+
 	return GTK_WIDGET(vbox);
 }
 
--- a/pidgin/gtkpounce.c	Mon May 19 16:18:32 2008 +0000
+++ b/pidgin/gtkpounce.c	Sun May 25 04:30:41 2008 +0000
@@ -100,6 +100,7 @@
 	GtkWidget *play_sound_entry;
 	GtkWidget *play_sound_browse;
 	GtkWidget *play_sound_test;
+	GtkWidget *play_sound_reset;
 
 	GtkWidget *save_pounce;
 
@@ -166,13 +167,26 @@
 pounce_test_sound(GtkWidget *w, GtkWidget *entry)
 {
 	const char *filename;
+	gboolean temp_mute;
+
+	temp_mute = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/sound/mute");
+
+	if (temp_mute) purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/sound/mute", FALSE);
 
 	filename = gtk_entry_get_text(GTK_ENTRY(entry));
 
-	if (filename != NULL && *filename != '\0')
+	if (filename != NULL && *filename != '\0' && strcmp(filename, _("(default)")))
 		purple_sound_play_file(filename, NULL);
 	else
 		purple_sound_play_event(PURPLE_SOUND_POUNCE_DEFAULT, NULL);
+
+	if (temp_mute) purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/sound/mute", TRUE);
+}
+
+static void
+pounce_reset_sound(GtkWidget *w, GtkWidget *entry)
+{
+	gtk_entry_set_text(GTK_ENTRY(entry), _("(default)"));
 }
 
 static void
@@ -304,7 +318,7 @@
 		message = NULL;
 	}
 	if (*command == '\0') command = NULL;
-	if (*sound   == '\0') sound   = NULL;
+	if (*sound   == '\0' || !strcmp(sound, _("(default)"))) sound   = NULL;
 
 	/* If the pounce has already been triggered, let's pretend it is a new one */
 	if (dialog->pounce != NULL
@@ -652,7 +666,7 @@
 	/* Create the "Action" frame. */
 	frame = pidgin_make_frame(vbox2, _("Action"));
 
-	table = gtk_table_new(3, 5, FALSE);
+	table = gtk_table_new(3, 6, FALSE);
 	gtk_container_add(GTK_CONTAINER(frame), table);
 	gtk_table_set_col_spacings(GTK_TABLE(table), PIDGIN_HIG_BORDER);
 	gtk_widget_show(table);
@@ -674,8 +688,11 @@
 	dialog->popup_entry       = gtk_entry_new();
 	dialog->exec_cmd_browse   = gtk_button_new_with_mnemonic(_("Brows_e..."));
 	dialog->play_sound_entry  = gtk_entry_new();
+	gtk_entry_set_text(GTK_ENTRY(dialog->play_sound_entry), _("(default)"));
+	gtk_editable_set_editable(GTK_EDITABLE(dialog->play_sound_entry), FALSE);
 	dialog->play_sound_browse = gtk_button_new_with_mnemonic(_("Br_owse..."));
 	dialog->play_sound_test   = gtk_button_new_with_mnemonic(_("Pre_view"));
+	dialog->play_sound_reset  = gtk_button_new_with_mnemonic(_("Reset"));
 
 	gtk_widget_set_sensitive(send_msg_imhtml,           FALSE);
 	gtk_widget_set_sensitive(dialog->exec_cmd_entry,    FALSE);
@@ -684,6 +701,7 @@
 	gtk_widget_set_sensitive(dialog->play_sound_entry,  FALSE);
 	gtk_widget_set_sensitive(dialog->play_sound_browse, FALSE);
 	gtk_widget_set_sensitive(dialog->play_sound_test,   FALSE);
+	gtk_widget_set_sensitive(dialog->play_sound_reset,  FALSE);
 
 	g_object_unref(sg);
 
@@ -698,6 +716,7 @@
 	gtk_size_group_add_widget(sg, dialog->play_sound_entry);
 	gtk_size_group_add_widget(sg, dialog->play_sound_browse);
 	gtk_size_group_add_widget(sg, dialog->play_sound_test);
+	gtk_size_group_add_widget(sg, dialog->play_sound_reset);
 
 	g_object_unref(sg);
 	sg = NULL;
@@ -706,11 +725,11 @@
 					 GTK_FILL, 0, 0, 0);
 	gtk_table_attach(GTK_TABLE(table), dialog->popup,            0, 1, 1, 2,
 					 GTK_FILL, 0, 0, 0);
-	gtk_table_attach(GTK_TABLE(table), dialog->popup_entry,      1, 4, 1, 2,
+	gtk_table_attach(GTK_TABLE(table), dialog->popup_entry,      1, 5, 1, 2,
 					 GTK_FILL, 0, 0, 0);
-	gtk_table_attach(GTK_TABLE(table), dialog->send_msg,         0, 4, 2, 3,
+	gtk_table_attach(GTK_TABLE(table), dialog->send_msg,         0, 5, 2, 3,
 					 GTK_FILL, 0, 0, 0);
-	gtk_table_attach(GTK_TABLE(table), send_msg_imhtml,          0, 4, 3, 4,
+	gtk_table_attach(GTK_TABLE(table), send_msg_imhtml,          0, 5, 3, 4,
 					 GTK_FILL, 0, 0, 0);
 	gtk_table_attach(GTK_TABLE(table), dialog->exec_cmd,         0, 1, 4, 5,
 					 GTK_FILL, 0, 0, 0);
@@ -726,6 +745,8 @@
 					 GTK_FILL | GTK_EXPAND, 0, 0, 0);
 	gtk_table_attach(GTK_TABLE(table), dialog->play_sound_test,  3, 4, 5, 6,
 					 GTK_FILL | GTK_EXPAND, 0, 0, 0);
+	gtk_table_attach(GTK_TABLE(table), dialog->play_sound_reset, 4, 5, 5, 6,
+					 GTK_FILL | GTK_EXPAND, 0, 0, 0);
 
 	gtk_table_set_row_spacings(GTK_TABLE(table), PIDGIN_HIG_BOX_SPACE / 2);
 
@@ -741,6 +762,7 @@
 	gtk_widget_show(dialog->play_sound_entry);
 	gtk_widget_show(dialog->play_sound_browse);
 	gtk_widget_show(dialog->play_sound_test);
+	gtk_widget_show(dialog->play_sound_reset);
 
 	g_signal_connect(G_OBJECT(dialog->message_recv), "clicked",
 					 G_CALLBACK(message_recv_toggle),
@@ -771,6 +793,7 @@
 	g_ptr_array_add(sound_widgets,dialog->play_sound_entry);
 	g_ptr_array_add(sound_widgets,dialog->play_sound_browse);
 	g_ptr_array_add(sound_widgets,dialog->play_sound_test);
+	g_ptr_array_add(sound_widgets,dialog->play_sound_reset);
 
 	g_signal_connect(G_OBJECT(dialog->play_sound), "clicked",
 					 G_CALLBACK(pidgin_toggle_sensitive_array),
@@ -781,6 +804,9 @@
 	g_signal_connect(G_OBJECT(dialog->play_sound_test), "clicked",
 					 G_CALLBACK(pounce_test_sound),
 					 dialog->play_sound_entry);
+	g_signal_connect(G_OBJECT(dialog->play_sound_reset), "clicked",
+					 G_CALLBACK(pounce_reset_sound),
+					 dialog->play_sound_entry);
 	g_object_set_data_full(G_OBJECT(dialog->window), "sound-widgets",
 				sound_widgets, (GDestroyNotify)g_ptr_array_free);
 
@@ -795,8 +821,6 @@
 					 G_CALLBACK(save_pounce_cb), dialog);
 	g_signal_connect(G_OBJECT(dialog->exec_cmd_entry), "activate",
 					 G_CALLBACK(save_pounce_cb), dialog);
-	g_signal_connect(G_OBJECT(dialog->play_sound_entry), "activate",
-					 G_CALLBACK(save_pounce_cb), dialog);
 
 	/* Create the "Options" frame. */
 	frame = pidgin_make_frame(vbox2, _("Options"));
@@ -924,7 +948,7 @@
 													  "play-sound",
 													  "filename")) != NULL)
 		{
-			gtk_entry_set_text(GTK_ENTRY(dialog->play_sound_entry), value);
+			gtk_entry_set_text(GTK_ENTRY(dialog->play_sound_entry), (value && *value != '\0') ? value : _("(default)"));
 		}
 	}
 	else
--- a/pidgin/gtkprefs.c	Mon May 19 16:18:32 2008 +0000
+++ b/pidgin/gtkprefs.c	Sun May 25 04:30:41 2008 +0000
@@ -2004,18 +2004,18 @@
 	gtk_editable_set_editable(GTK_EDITABLE(sound_entry), FALSE);
 	gtk_box_pack_start(GTK_BOX(hbox), sound_entry, FALSE, FALSE, PIDGIN_HIG_BOX_SPACE);
 
-	button = gtk_button_new_with_label(_("Test"));
+	button = gtk_button_new_with_mnemonic(_("_Browse..."));
+	g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(select_sound), NULL);
+	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 1);
+
+	button = gtk_button_new_with_mnemonic(_("Pre_view"));
 	g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(test_sound), NULL);
 	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 1);
 
-	button = gtk_button_new_with_label(_("Reset"));
+	button = gtk_button_new_with_mnemonic(_("_Reset"));
 	g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(reset_sound), NULL);
 	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 1);
 
-	button = gtk_button_new_with_label(_("Choose..."));
-	g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(select_sound), NULL);
-	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 1);
-
 	gtk_widget_show_all(ret);
 	g_object_unref(sg);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtksmiley.c	Sun May 25 04:30:41 2008 +0000
@@ -0,0 +1,705 @@
+/**
+ * @file gtksmiley.c GTK+ Smiley Manager API
+ * @ingroup pidgin
+ */
+
+/*
+ * pidgin
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#include "internal.h"
+#include "pidgin.h"
+
+#include "debug.h"
+#include "notify.h"
+#include "smiley.h"
+
+#include "gtkimhtml.h"
+#include "gtksmiley.h"
+#include "gtkutils.h"
+#include "pidginstock.h"
+
+#define PIDGIN_RESPONSE_EDIT 1000
+
+struct _PidginSmiley
+{
+	PurpleSmiley *smiley;
+	GtkWidget *parent;
+	GtkWidget *smile;
+	GtkWidget *smiley_image;
+	gchar *filename;
+	GdkPixbuf *custom_pixbuf;
+};
+
+typedef struct
+{
+	GtkWidget *window;
+
+	GtkWidget *treeview;
+	GtkListStore *model;
+} SmileyManager;
+
+enum
+{
+	ICON,
+	SHORTCUT,
+	SMILEY,
+	N_COL
+};
+
+static SmileyManager *smiley_manager = NULL;
+static GSList *gtk_smileys = NULL;
+
+static void
+pidgin_smiley_destroy(PidginSmiley *smiley)
+{
+	gtk_widget_destroy(smiley->parent);
+	g_free(smiley->filename);
+	if (smiley->custom_pixbuf)
+		gdk_pixbuf_unref(smiley->custom_pixbuf);
+	g_free(smiley);
+}
+
+/******************************************************************************
+ * GtkIMHtmlSmileys stuff
+ *****************************************************************************/
+/* Perhaps these should be in gtkimhtml.c instead. -- sadrul */
+static void add_gtkimhtml_to_list(GtkIMHtmlSmiley *gtksmiley)
+{
+	gtk_smileys = g_slist_prepend(gtk_smileys, gtksmiley);
+
+	purple_debug_info("gtksmiley", "adding %s to gtk_smileys\n", gtksmiley->smile);
+}
+
+static void
+shortcut_changed_cb(PurpleSmiley *smiley, gpointer dontcare, GtkIMHtmlSmiley *gtksmiley)
+{
+	g_free(gtksmiley->smile);
+	gtksmiley->smile = g_strdup(purple_smiley_get_shortcut(smiley));
+}
+
+static GtkIMHtmlSmiley *smiley_purple_to_gtkimhtml(PurpleSmiley *smiley)
+{
+	GtkIMHtmlSmiley *gtksmiley;
+	gchar *filename;
+	const gchar *file;
+
+	file = purple_imgstore_get_filename(purple_smiley_get_stored_image(smiley));
+
+	filename = g_build_filename(purple_smileys_get_storing_dir(), file, NULL);
+
+	gtksmiley = gtk_imhtml_smiley_create(filename, purple_smiley_get_shortcut(smiley),
+			FALSE, GTK_IMHTML_SMILEY_CUSTOM);
+	g_free(filename);
+
+	/* Make sure the shortcut for the GtkIMHtmlSmiley is updated with the PurpleSmiley */
+	g_signal_connect(G_OBJECT(smiley), "notify::shortcut",
+			G_CALLBACK(shortcut_changed_cb), gtksmiley);
+
+	return gtksmiley;
+}
+
+void pidgin_smiley_del_from_list(PurpleSmiley *smiley)
+{
+	GSList *list = NULL;
+	GtkIMHtmlSmiley *gtksmiley;
+
+	if (gtk_smileys == NULL)
+		return;
+
+	list = gtk_smileys;
+
+	for (; list; list = list->next) {
+		gtksmiley = (GtkIMHtmlSmiley*)list->data;
+
+		if (strcmp(gtksmiley->smile, purple_smiley_get_shortcut(smiley)))
+			continue;
+
+		gtk_imhtml_smiley_destroy(gtksmiley);
+		g_signal_handlers_disconnect_matched(G_OBJECT(smiley), G_SIGNAL_MATCH_DATA,
+				0, 0, NULL, NULL, gtksmiley);
+		break;
+	}
+
+	if (list)
+		gtk_smileys = g_slist_delete_link(gtk_smileys, list);
+}
+
+void pidgin_smiley_add_to_list(PurpleSmiley *smiley)
+{
+	GtkIMHtmlSmiley *gtksmiley;
+
+	gtksmiley = smiley_purple_to_gtkimhtml(smiley);
+	add_gtkimhtml_to_list(gtksmiley);
+	g_signal_connect(G_OBJECT(smiley), "destroy", G_CALLBACK(pidgin_smiley_del_from_list), NULL);
+}
+
+void pidgin_smileys_init(void)
+{
+	GList *smileys;
+	PurpleSmiley *smiley;
+
+	if (gtk_smileys != NULL)
+		return;
+
+	smileys = purple_smileys_get_all();
+
+	for (; smileys; smileys = g_list_delete_link(smileys, smileys)) {
+		smiley = (PurpleSmiley*)smileys->data;
+
+		pidgin_smiley_add_to_list(smiley);
+	}
+}
+
+void pidgin_smileys_uninit(void)
+{
+	GSList *list;
+	GtkIMHtmlSmiley *gtksmiley;
+
+	list = gtk_smileys;
+
+	if (list == NULL)
+		return;
+
+	for (; list; list = g_slist_delete_link(list, list)) {
+		gtksmiley = (GtkIMHtmlSmiley*)list->data;
+		gtk_imhtml_smiley_destroy(gtksmiley);
+	}
+
+	gtk_smileys = NULL;
+}
+
+GSList *pidgin_smileys_get_all(void)
+{
+	return gtk_smileys;
+}
+
+/******************************************************************************
+ * Manager stuff
+ *****************************************************************************/
+
+static void refresh_list(void);
+
+/******************************************************************************
+ * The Add dialog
+ ******************************************************************************/
+
+static void do_add(GtkWidget *widget, PidginSmiley *s)
+{
+	const gchar *entry;
+	PurpleSmiley *emoticon;
+
+	entry = gtk_entry_get_text(GTK_ENTRY(s->smile));
+	emoticon = purple_smileys_find_by_shortcut(entry);
+	if (emoticon && emoticon != s->smiley) {
+		purple_notify_error(s->parent, _("Custom Smiley"),
+				_("Duplicate Shortcut"),
+				_("A custom smiley for the selected shortcut already exists. Please specify a different shortcut."));
+		return;
+	}
+
+	if (s->smiley) {
+		if (s->filename) {
+			gchar *data = NULL;
+			size_t len;
+			GError *err = NULL;
+
+			if (!g_file_get_contents(s->filename, &data, &len, &err)) {
+				purple_debug_error("gtksmiley", "Error reading %s: %s\n",
+						s->filename, err->message);
+				g_error_free(err);
+
+				return;
+			}
+			purple_smiley_set_data(s->smiley, (guchar*)data, len, FALSE);
+		}
+		purple_smiley_set_shortcut(s->smiley, entry);
+	} else {
+		if ((s->filename == NULL && s->custom_pixbuf == NULL)
+				|| *entry == 0) {
+			purple_notify_error(s->parent, _("Custom Smiley"),
+					_("More Data needed"),
+					s->filename ? _("Please provide a shortcut to associate with the smiley.")
+					: _("Please select an image for the smiley."));
+			return;
+		}
+
+		purple_debug_info("gtksmiley", "adding a new smiley\n");
+
+		if (s->filename == NULL) {
+			/* Get the smiley from the custom pixbuf */
+			gchar *buffer = NULL;
+			gsize size = 0;
+			gchar *filename;
+
+			gdk_pixbuf_save_to_bufferv(s->custom_pixbuf, &buffer, &size,
+									   "png", NULL, NULL, NULL);
+			filename = purple_util_get_image_filename(buffer, size);
+			s->filename = g_build_filename(purple_smileys_get_storing_dir(), filename, NULL);
+			purple_util_write_data_to_file_absolute(s->filename, buffer, size);
+			g_free(filename);
+			g_free(buffer);
+		}
+		emoticon = purple_smiley_new_from_file(entry, s->filename);
+		pidgin_smiley_add_to_list(emoticon);
+	}
+
+	if (smiley_manager != NULL)
+		refresh_list();
+
+	gtk_widget_destroy(s->parent);
+}
+
+static void do_add_select_cb(GtkWidget *widget, gint resp, PidginSmiley *s)
+{
+	switch (resp) {
+		case GTK_RESPONSE_ACCEPT:
+			do_add(widget, s);
+			break;
+		case GTK_RESPONSE_DELETE_EVENT:
+		case GTK_RESPONSE_CANCEL:
+			gtk_widget_destroy(s->parent);
+			break;
+		default:
+			purple_debug_error("gtksmiley", "no valid response\n");
+			break;
+	}
+}
+
+static void do_add_file_cb(const char *filename, gpointer data)
+{
+	PidginSmiley *s = data;
+	GdkPixbuf *pixbuf;
+
+	if (!filename)
+		return;
+
+	g_free(s->filename);
+	s->filename = g_strdup(filename);
+	pixbuf = gdk_pixbuf_new_from_file_at_scale(filename, 64, 64, FALSE, NULL);
+	gtk_image_set_from_pixbuf(GTK_IMAGE(s->smiley_image), pixbuf);
+	if (pixbuf)
+		gdk_pixbuf_unref(pixbuf);
+	gtk_widget_grab_focus(s->smile);
+}
+
+static void
+open_image_selector(GtkWidget *widget, PidginSmiley *psmiley)
+{
+	GtkWidget *file_chooser;
+	file_chooser = pidgin_buddy_icon_chooser_new(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
+			do_add_file_cb, psmiley);
+	gtk_window_set_title(GTK_WINDOW(file_chooser), _("Custom Smiley"));
+	gtk_window_set_role(GTK_WINDOW(file_chooser), "file-selector-custom-smiley");
+	gtk_widget_show_all(file_chooser);
+}
+
+PidginSmiley *
+pidgin_smiley_edit(GtkWidget *widget, PurpleSmiley *smiley)
+{
+	GtkWidget *vbox;
+	GtkWidget *hbox;
+	GtkWidget *label;
+	GtkWidget *filech;
+	GtkWidget *window;
+	GdkPixbuf *pixbuf = NULL;
+	PurpleStoredImage *stored_img;
+
+	PidginSmiley *s = g_new0(PidginSmiley, 1);
+	s->smiley = smiley;
+
+	window = gtk_dialog_new_with_buttons(smiley ? _("Edit Smiley") : _("Add Smiley"),
+			widget ? GTK_WINDOW(widget) : NULL,
+			GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
+			smiley ? GTK_STOCK_SAVE : GTK_STOCK_ADD, GTK_RESPONSE_ACCEPT,
+			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+			NULL);
+	s->parent = window;
+
+	gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER);
+
+	g_signal_connect(window, "response", G_CALLBACK(do_add_select_cb), s);
+
+	/* The vbox */
+	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
+	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(window)->vbox), vbox);
+	gtk_widget_show(vbox);
+
+	/* The hbox */
+	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
+	gtk_container_add(GTK_CONTAINER(GTK_VBOX(vbox)), hbox);
+
+	label = gtk_label_new_with_mnemonic(_("Smiley _Image"));
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+	gtk_widget_show(label);
+
+	filech = gtk_button_new();
+	gtk_box_pack_end(GTK_BOX(hbox), filech, FALSE, FALSE, 0);
+	pidgin_set_accessible_label(filech, label);
+
+	s->smiley_image = gtk_image_new();
+	gtk_container_add(GTK_CONTAINER(filech), s->smiley_image);
+	if (smiley && (stored_img = purple_smiley_get_stored_image(smiley))) {
+		pixbuf = pidgin_pixbuf_from_imgstore(stored_img);
+		purple_imgstore_unref(stored_img);
+	} else {
+		GtkIconSize icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL);
+		pixbuf = gtk_widget_render_icon(window, PIDGIN_STOCK_TOOLBAR_SELECT_AVATAR,
+				icon_size, "PidginSmiley");
+	}
+
+	gtk_image_set_from_pixbuf(GTK_IMAGE(s->smiley_image), pixbuf);
+	if (pixbuf != NULL)
+		g_object_unref(G_OBJECT(pixbuf));
+	g_signal_connect(G_OBJECT(filech), "clicked", G_CALLBACK(open_image_selector), s);
+
+	gtk_widget_show_all(hbox);
+
+	/* info */
+	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER);
+	gtk_container_add(GTK_CONTAINER(GTK_VBOX(vbox)),hbox);
+
+	/* Smiley shortcut */
+	label = gtk_label_new_with_mnemonic(_("Smiley S_hortcut"));
+	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+	gtk_widget_show(label);
+
+	s->smile = gtk_entry_new();
+	gtk_entry_set_activates_default(GTK_ENTRY(s->smile), TRUE);
+	pidgin_set_accessible_label(s->smile, label);
+	if (smiley)
+		gtk_entry_set_text(GTK_ENTRY(s->smile), purple_smiley_get_shortcut(smiley));
+
+	g_signal_connect(s->smile, "activate", G_CALLBACK(do_add), s);
+
+	gtk_box_pack_end(GTK_BOX(hbox), s->smile, FALSE, FALSE, 0);
+	gtk_widget_show(s->smile);
+
+	gtk_widget_show(hbox);
+
+	gtk_widget_show(GTK_WIDGET(window));
+	g_signal_connect_swapped(G_OBJECT(window), "destroy", G_CALLBACK(pidgin_smiley_destroy), s);
+	g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(purple_notify_close_with_handle), s);
+
+	return s;
+}
+
+void
+pidgin_smiley_editor_set_shortcut(PidginSmiley *editor, const gchar *shortcut)
+{
+	gtk_entry_set_text(GTK_ENTRY(editor->smile), shortcut ? shortcut : "");
+}
+
+void
+pidgin_smiley_editor_set_image(PidginSmiley *editor, GdkPixbuf *image)
+{
+	if (editor->custom_pixbuf)
+		gdk_pixbuf_unref(editor->custom_pixbuf);
+	editor->custom_pixbuf = image ? gdk_pixbuf_ref(image) : NULL;
+	if (image)
+		gtk_image_set_from_pixbuf(GTK_IMAGE(editor->smiley_image), image);
+}
+
+/******************************************************************************
+ * Delete smiley
+ *****************************************************************************/
+static void delete_foreach(GtkTreeModel *model, GtkTreePath *path,
+		GtkTreeIter *iter, gpointer data)
+{
+	PurpleSmiley *smiley = NULL;
+	SmileyManager *dialog;
+
+	dialog = (SmileyManager*)data;
+
+	gtk_tree_model_get(model, iter,
+			SMILEY, &smiley,
+			-1);
+
+	if(smiley != NULL) {
+		g_object_unref(G_OBJECT(smiley));
+		pidgin_smiley_del_from_list(smiley);
+		purple_smiley_delete(smiley);
+	}
+}
+
+static void append_to_list(GtkTreeModel *model, GtkTreePath *path,
+		GtkTreeIter *iter, gpointer data)
+{
+	GList **list = data;
+	*list = g_list_prepend(*list, gtk_tree_path_copy(path));
+}
+
+static void smiley_delete(SmileyManager *dialog)
+{
+	GtkTreeSelection *selection;
+	GList *list = NULL;
+
+	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
+	gtk_tree_selection_selected_foreach(selection, delete_foreach, dialog);
+	gtk_tree_selection_selected_foreach(selection, append_to_list, &list);
+
+	while (list) {
+		GtkTreeIter iter;
+		if (gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, list->data))
+			gtk_list_store_remove(GTK_LIST_STORE(dialog->model), &iter);
+		gtk_tree_path_free(list->data);
+		list = g_list_delete_link(list, list);
+	}
+}
+/******************************************************************************
+ * The Smiley Manager
+ *****************************************************************************/
+static void add_columns(GtkWidget *treeview, SmileyManager *dialog)
+{
+	GtkCellRenderer *rend;
+	GtkTreeViewColumn *column;
+
+	/* Icon */
+	column = gtk_tree_view_column_new();
+	gtk_tree_view_column_set_title(column, _("Smiley"));
+	gtk_tree_view_column_set_resizable(column, TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
+
+	rend = gtk_cell_renderer_pixbuf_new();
+	gtk_tree_view_column_pack_start(column, rend, FALSE);
+	gtk_tree_view_column_add_attribute(column, rend, "pixbuf", ICON);
+
+	/* Shortcut */
+	column = gtk_tree_view_column_new();
+	gtk_tree_view_column_set_title(column, _("Shortcut"));
+	gtk_tree_view_column_set_resizable(column, TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
+
+	rend = gtk_cell_renderer_text_new();
+	gtk_tree_view_column_pack_start(column, rend, TRUE);
+	gtk_tree_view_column_add_attribute(column, rend, "text", SHORTCUT);
+}
+
+static void store_smiley_add(PurpleSmiley *smiley)
+{
+	GtkTreeIter iter;
+	PurpleStoredImage *img;
+	GdkPixbuf *sized_smiley = NULL;
+
+	if (smiley_manager == NULL)
+		return;
+
+	img = purple_smiley_get_stored_image(smiley);
+
+	if (img != NULL) {
+		GdkPixbuf *smiley_image = pidgin_pixbuf_from_imgstore(img);
+		purple_imgstore_unref(img);
+
+		if (smiley_image != NULL)
+			sized_smiley = gdk_pixbuf_scale_simple(smiley_image,
+					22, 22, GDK_INTERP_HYPER);
+		g_object_unref(G_OBJECT(smiley_image));
+	}
+
+
+	gtk_list_store_append(smiley_manager->model, &iter);
+
+	gtk_list_store_set(smiley_manager->model, &iter,
+			ICON, sized_smiley,
+			SHORTCUT, purple_smiley_get_shortcut(smiley),
+			SMILEY, smiley,
+			-1);
+
+	if (sized_smiley != NULL)
+		g_object_unref(G_OBJECT(sized_smiley));
+}
+
+static void populate_smiley_list(SmileyManager *dialog)
+{
+	GList *list;
+	PurpleSmiley *emoticon;
+
+	gtk_list_store_clear(dialog->model);
+
+	for(list = purple_smileys_get_all(); list != NULL;
+			list = g_list_delete_link(list, list)) {
+		emoticon = (PurpleSmiley*)list->data;
+
+		store_smiley_add(emoticon);
+	}
+}
+
+static void smile_selected_cb(GtkTreeSelection *sel, SmileyManager *dialog)
+{
+	gint selected;
+
+	selected = gtk_tree_selection_count_selected_rows(sel);
+
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog->window),
+			GTK_RESPONSE_NO, selected > 0);
+
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog->window),
+			PIDGIN_RESPONSE_EDIT, selected > 0);
+}
+
+static void
+smiley_edit_iter(SmileyManager *dialog, GtkTreeIter *iter)
+{
+	PurpleSmiley *smiley = NULL;
+	gtk_tree_model_get(GTK_TREE_MODEL(dialog->model), iter, SMILEY, &smiley, -1);
+	pidgin_smiley_edit(gtk_widget_get_toplevel(GTK_WIDGET(dialog->treeview)), smiley);
+	g_object_unref(G_OBJECT(smiley));
+}
+
+static void smiley_edit_cb(GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *col, gpointer data)
+{
+	GtkTreeIter iter;
+	SmileyManager *dialog = data;
+
+	gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, path);
+	smiley_edit_iter(dialog, &iter);
+}
+
+static void
+edit_selected_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
+{
+	smiley_edit_iter(data, iter);
+}
+
+static GtkWidget *smiley_list_create(SmileyManager *dialog)
+{
+	GtkWidget *sw;
+	GtkWidget *treeview;
+	GtkTreeSelection *sel;
+
+	sw = gtk_scrolled_window_new(NULL, NULL);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
+			GTK_POLICY_AUTOMATIC,
+			GTK_POLICY_AUTOMATIC);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
+			GTK_SHADOW_IN);
+	gtk_widget_show(sw);
+
+	/* Create the list model */
+	dialog->model = gtk_list_store_new(N_COL,
+			GDK_TYPE_PIXBUF,	/* ICON */
+			G_TYPE_STRING,		/* SHORTCUT */
+			G_TYPE_OBJECT		/* SMILEY */
+			);
+
+	/* the actual treeview */
+	treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dialog->model));
+	dialog->treeview = treeview;
+	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
+	gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(dialog->model), SHORTCUT, GTK_SORT_ASCENDING);
+	g_object_unref(G_OBJECT(dialog->model));
+
+	sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
+	gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
+	gtk_container_add(GTK_CONTAINER(sw), treeview);
+
+	g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(smile_selected_cb), dialog);
+	g_signal_connect(G_OBJECT(treeview), "row_activated", G_CALLBACK(smiley_edit_cb), dialog);
+
+	gtk_widget_show(treeview);
+
+	add_columns(treeview, dialog);
+	populate_smiley_list(dialog);
+
+	return sw;
+}
+
+static void refresh_list()
+{
+	populate_smiley_list(smiley_manager);
+}
+
+static void smiley_manager_select_cb(GtkWidget *widget, gint resp, SmileyManager *dialog)
+{
+	GtkTreeSelection *selection = NULL;
+
+	switch (resp) {
+		case GTK_RESPONSE_YES:
+			pidgin_smiley_edit(dialog->window, NULL);
+			break;
+		case GTK_RESPONSE_NO:
+			smiley_delete(dialog);
+			break;
+		case GTK_RESPONSE_DELETE_EVENT:
+		case GTK_RESPONSE_CLOSE:
+			gtk_widget_destroy(dialog->window);
+			g_free(smiley_manager);
+			smiley_manager = NULL;
+			break;
+		case PIDGIN_RESPONSE_EDIT:
+			/* Find smiley of selection... */
+			selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview));
+			gtk_tree_selection_selected_foreach(selection, edit_selected_cb, dialog);
+			break;
+		default:
+			purple_debug_info("gtksmiley", "No valid selection\n");
+			break;
+	}
+}
+
+void pidgin_smiley_manager_show(void)
+{
+	SmileyManager *dialog;
+	GtkWidget *win;
+	GtkWidget *sw;
+	GtkWidget *vbox;
+
+	if (smiley_manager) {
+		gtk_window_present(GTK_WINDOW(smiley_manager->window));
+		return;
+	}
+
+	dialog = g_new0(SmileyManager, 1);
+	smiley_manager = dialog;
+
+	dialog->window = win = gtk_dialog_new_with_buttons(
+			_("Custom Smiley Manager"),
+			NULL,
+			GTK_DIALOG_DESTROY_WITH_PARENT,
+			GTK_STOCK_ADD, GTK_RESPONSE_YES,
+			GTK_STOCK_EDIT, PIDGIN_RESPONSE_EDIT,
+			GTK_STOCK_DELETE, GTK_RESPONSE_NO,
+			GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
+			NULL);
+
+	gtk_window_set_default_size(GTK_WINDOW(win), 50, 400);
+	gtk_window_set_role(GTK_WINDOW(win), "custom_smiley_manager");
+	gtk_container_set_border_width(GTK_CONTAINER(win),PIDGIN_HIG_BORDER);
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(win), GTK_RESPONSE_NO, FALSE);
+	gtk_dialog_set_response_sensitive(GTK_DIALOG(win), PIDGIN_RESPONSE_EDIT,
+									  FALSE);
+
+	g_signal_connect(win, "response", G_CALLBACK(smiley_manager_select_cb),
+			dialog);
+
+	/* The vbox */
+	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
+	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(win)->vbox), vbox);
+	gtk_widget_show(vbox);
+
+	/* get the scrolled window with all stuff */
+	sw = smiley_list_create(dialog);
+	gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
+	gtk_widget_show(sw);
+
+	gtk_widget_show(win);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtksmiley.h	Sun May 25 04:30:41 2008 +0000
@@ -0,0 +1,103 @@
+/**
+ * @file gtksmiley.h GTK+ Custom Smiley API
+ * @ingroup pidgin
+ * @since 2.5.0
+ */
+
+/* pidgin
+ *
+ * Pidgin is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#ifndef _PIDGIN_GTKSMILEY_H_
+#define _PIDGIN_GTKSMILEY_H_
+
+#include "smiley.h"
+
+typedef struct _PidginSmiley PidginSmiley;
+
+/**
+ * Add a PurpleSmiley to the GtkIMHtmlSmiley's list to be able to use it
+ * in pidgin
+ *
+ * @param smiley	The smiley to be added.
+ */
+void pidgin_smiley_add_to_list(PurpleSmiley *smiley);
+
+/**
+ * Delete a PurpleSmiley from the GtkIMHtmlSmiley's list
+ *
+ * @param smiley	The smiley to be deleted.
+ */
+void pidgin_smiley_del_from_list(PurpleSmiley *smiley);
+
+/**
+ * Load the GtkIMHtml list
+ */
+void pidgin_smileys_init(void);
+
+/**
+ * Uninit the GtkIMHtml list
+ */
+void pidgin_smileys_uninit(void);
+
+/**
+ * Returns a GSList with the GtkIMHtmlSmiley of each custom smiley
+ *
+ * @constreturn A GtkIMHmlSmiley list
+ */
+GSList* pidgin_smileys_get_all(void);
+
+/******************************************************************************
+ * Smiley Manager
+ *****************************************************************************/
+/**
+ * Displays the Smiley Manager Window
+ */
+void pidgin_smiley_manager_show(void);
+
+/**
+ * Shows an editor for a smiley.
+ *
+ * @param widget The parent widget to be linked or @c NULL
+ * @param smiley The PurpleSmiley to be edited, or @c NULL for a new smiley
+ * @return The smiley add dialog
+ *
+ * @see pidgin_smiley_editor_set_shortcut
+ * @see pidgin_smiley_editor_set_image
+ */
+PidginSmiley *pidgin_smiley_edit(GtkWidget *widget, PurpleSmiley *smiley);
+
+/**
+ * Set the shortcut in a smiley add dialog
+ *
+ * @param editor A smiley editor dialog (created by pidgin_smiley_edit)
+ * @param shortcut The shortcut to set
+ */
+void pidgin_smiley_editor_set_shortcut(PidginSmiley *editor, const gchar *shortcut);
+
+/**
+ * Set the image in a smiley add dialog
+ *
+ * @param editor A smiley editor dialog
+ * @param image A GdkPixbuf image
+ */
+void pidgin_smiley_editor_set_image(PidginSmiley *editor, GdkPixbuf *image);
+
+#endif /* _PIDGIN_GTKSMILEY_H_*/
--- a/pidgin/gtksound.c	Mon May 19 16:18:32 2008 +0000
+++ b/pidgin/gtksound.c	Sun May 25 04:30:41 2008 +0000
@@ -488,29 +488,13 @@
 		return;
 	volume = (float)(CLAMP(purple_prefs_get_int(PIDGIN_PREFS_ROOT "/sound/volume"),0,100)) / 50;
 	if (!strcmp(method, "automatic")) {
-		if (purple_running_gnome()) {
-			sink = gst_element_factory_make("gconfaudiosink", "sink");
-		}
-		if (!sink)
-			sink = gst_element_factory_make("autoaudiosink", "sink");
-		if (!sink) {
-			purple_debug_error("sound", "Unable to create GStreamer audiosink.\n");
-			return;
-		}
+		sink = gst_element_factory_make("gconfaudiosink", "sink");
 	}
 #ifndef _WIN32
 	else if (!strcmp(method, "esd")) {
 		sink = gst_element_factory_make("esdsink", "sink");
-		if (!sink) {
-			purple_debug_error("sound", "Unable to create GStreamer audiosink.\n");
-			return;
-		}
 	} else if (!strcmp(method, "alsa")) {
 		sink = gst_element_factory_make("alsasink", "sink");
-		if (!sink) {
-			purple_debug_error("sound", "Unable to create GStreamer audiosink.\n");
-			return;
-		}
 	}
 #endif
 	else {
@@ -518,6 +502,11 @@
 		return;
 	}
 
+	if (strcmp(method, "automatic") != 0 && !sink) {
+		purple_debug_error("sound", "Unable to create GStreamer audiosink.\n");
+		return;
+	}
+
 	play = gst_element_factory_make("playbin", "play");
 
 	if (play == NULL) {
--- a/pidgin/gtkthemes.c	Mon May 19 16:18:32 2008 +0000
+++ b/pidgin/gtkthemes.c	Sun May 25 04:30:41 2008 +0000
@@ -31,6 +31,7 @@
 #include "gtkconv.h"
 #include "gtkdialogs.h"
 #include "gtkimhtml.h"
+#include "gtksmiley.h"
 #include "gtkthemes.h"
 
 GSList *smiley_themes = NULL;
@@ -119,7 +120,7 @@
 	g_free(theme_dir);
 }
 
-void pidgin_themes_smiley_themeize(GtkWidget *imhtml)
+static void _pidgin_themes_smiley_themeize(GtkWidget *imhtml, gboolean custom)
 {
 	struct smiley_list *list;
 	if (!current_smiley_theme)
@@ -134,10 +135,30 @@
 			gtk_imhtml_associate_smiley(GTK_IMHTML(imhtml), sml, icons->data);
 			icons = icons->next;
 		}
+
+		if (custom == TRUE) {
+			icons = pidgin_smileys_get_all();
+
+			while (icons) {
+				gtk_imhtml_associate_smiley(GTK_IMHTML(imhtml), sml, icons->data);
+				icons = icons->next;
+			}
+		}
+
 		list = list->next;
 	}
 }
 
+void pidgin_themes_smiley_themeize(GtkWidget *imhtml)
+{
+	_pidgin_themes_smiley_themeize(imhtml, FALSE);
+}
+
+void pidgin_themes_smiley_themeize_custom(GtkWidget *imhtml)
+{
+	_pidgin_themes_smiley_themeize(imhtml, TRUE);
+}
+
 static void
 pidgin_themes_destroy_smiley_theme_smileys(struct smiley_theme *theme)
 {
@@ -274,7 +295,6 @@
 		} else if (load && list) {
 			gboolean hidden = FALSE;
 			char *sfile = NULL;
-			gboolean have_used_sfile = FALSE;
 
 			if (*i == '!' && *(i + 1) == ' ') {
 				hidden = TRUE;
@@ -288,17 +308,12 @@
 						i++;
 					l[li++] = *(i++);
 				}
+				l[li] = 0;
 				if (!sfile) {
-					l[li] = 0;
 					sfile = g_build_filename(dirname, l, NULL);
 				} else {
-					GtkIMHtmlSmiley *smiley = g_new0(GtkIMHtmlSmiley, 1);
-					l[li] = 0;
-					smiley->file = sfile;
-					smiley->smile = g_strdup(l);
-					smiley->hidden = hidden;
+					GtkIMHtmlSmiley *smiley = gtk_imhtml_smiley_create(sfile, l, hidden, 0);
 					list->smileys = g_slist_prepend(list->smileys, smiley);
-					have_used_sfile = TRUE;
 				}
 				while (isspace(*i))
 					i++;
@@ -306,8 +321,7 @@
 			}
 
 
-			if (!have_used_sfile)
-				g_free(sfile);
+			g_free(sfile);
 		}
 	}
 
@@ -340,8 +354,9 @@
 			PurpleConversation *conv = cnv->data;
 
 			if (PIDGIN_IS_PIDGIN_CONVERSATION(conv)) {
+				/* We want to see our custom smileys on our entry if we write the shortcut */
 				pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->imhtml);
-				pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->entry);
+				pidgin_themes_smiley_themeize_custom(PIDGIN_CONVERSATION(conv)->entry);
 			}
 		}
 	}
--- a/pidgin/gtkthemes.h	Mon May 19 16:18:32 2008 +0000
+++ b/pidgin/gtkthemes.h	Sun May 25 04:30:41 2008 +0000
@@ -51,6 +51,11 @@
 
 void pidgin_themes_smiley_themeize(GtkWidget *);
 
+/**
+ * @since 2.5.0
+ */
+void pidgin_themes_smiley_themeize_custom(GtkWidget *);
+
 void pidgin_themes_smiley_theme_probe(void);
 
 void pidgin_themes_load_smiley_theme(const char *file, gboolean load);
--- a/pidgin/gtkutils.c	Mon May 19 16:18:32 2008 +0000
+++ b/pidgin/gtkutils.c	Sun May 25 04:30:41 2008 +0000
@@ -103,7 +103,7 @@
 	g_signal_connect(G_OBJECT(imhtml), "url_clicked",
 					 G_CALLBACK(url_clicked_cb), NULL);
 
-	pidgin_themes_smiley_themeize(imhtml);
+	pidgin_themes_smiley_themeize_custom(imhtml);
 
 	gtk_imhtml_set_funcs(GTK_IMHTML(imhtml), &gtkimhtml_cbs);
 
@@ -1436,7 +1436,7 @@
 
 static void dnd_image_ok_callback(_DndData *data, int choice)
 {
-	char *filedata;
+	gchar *filedata;
 	size_t size;
 	struct stat st;
 	GError *err = NULL;
@@ -1444,6 +1444,8 @@
 	PidginConversation *gtkconv;
 	GtkTextIter iter;
 	int id;
+	PurpleBuddy *buddy;
+	PurpleContact *contact;
 	switch (choice) {
 	case DND_BUDDY_ICON:
 		if (g_stat(data->filename, &st)) {
@@ -1459,7 +1461,13 @@
 			return;
 		}
 
-		pidgin_set_custom_buddy_icon(data->account, data->who, data->filename);
+		buddy = purple_find_buddy(data->account, data->who);
+		if (!buddy) {
+			purple_debug_info("custom-icon", "You can only set custom icons for people on your buddylist.\n");
+			break;
+		}
+		contact = purple_buddy_get_contact(buddy);
+		purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, data->filename);
 		break;
 	case DND_FILE_TRANSFER:
 		serv_send_file(purple_account_get_connection(data->account), data->who, data->filename);
@@ -2869,12 +2877,11 @@
 }
 #endif /* ! Gtk 2.6.0 */
 
+#ifndef PURPLE_DISABLE_DEPRECATED
 void pidgin_set_custom_buddy_icon(PurpleAccount *account, const char *who, const char *filename)
 {
 	PurpleBuddy *buddy;
 	PurpleContact *contact;
-	gpointer data = NULL;
-	size_t len = 0;
 
 	buddy = purple_find_buddy(account, who);
 	if (!buddy) {
@@ -2883,20 +2890,9 @@
 	}
 
 	contact = purple_buddy_get_contact(buddy);
-
-	if (filename) {
-		const char *prpl_id = purple_account_get_protocol_id(account);
-		PurplePlugin *prpl = purple_find_prpl(prpl_id);
-
-		data = pidgin_convert_buddy_icon(prpl, filename, &len);
-
-		/* We don't want to delete the old icon if the new one didn't load. */
-		if (data == NULL)
-			return;
-	}
-
-	purple_buddy_icons_set_custom_icon(contact, data, len);
+	purple_buddy_icons_node_set_custom_icon_from_file((PurpleBlistNode*)contact, filename);
 }
+#endif
 
 char *pidgin_make_pretty_arrows(const char *str)
 {
@@ -3469,6 +3465,20 @@
 #endif
 }
 
+GdkPixbuf * pidgin_pixbuf_from_imgstore(PurpleStoredImage *image)
+{
+	GdkPixbuf *pixbuf;
+	GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
+	gdk_pixbuf_loader_write(loader, purple_imgstore_get_data(image),
+			purple_imgstore_get_size(image), NULL);
+	gdk_pixbuf_loader_close(loader, NULL);
+	pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
+	if (pixbuf)
+		g_object_ref(pixbuf);
+	g_object_unref(loader);
+	return pixbuf;
+}
+
 gchar *
 pidgin_gtk_ellipsis_text(GtkWidget *widget, const char *text, gint min_width, gchar *ellipsis)
 {
--- a/pidgin/gtkutils.h	Mon May 19 16:18:32 2008 +0000
+++ b/pidgin/gtkutils.h	Sun May 25 04:30:41 2008 +0000
@@ -638,6 +638,7 @@
 											 GError **error);
 #endif
 
+#ifndef PURPLE_DISABLE_DEPRECATED
 /**
  * Set or unset a custom buddyicon for a user.
  *
@@ -645,8 +646,10 @@
  * @param who       The name of the user.
  * @param filename  The path of the custom icon. If this is @c NULL, then any
  *                  previously set custom buddy icon for the user is removed.
+ * @deprecated See purple_buddy_icons_node_set_custom_icon_from_file()
  */
 void pidgin_set_custom_buddy_icon(PurpleAccount *account, const char *who, const char *filename);
+#endif
 
 /**
  * Converts "->" and "<-" in strings to Unicode arrow characters, for use in referencing
@@ -689,7 +692,7 @@
  */
 GtkWidget *pidgin_make_mini_dialog(PurpleConnection *handle,
 	const char* stock_id, const char *primary, const char *secondary,
-	void *user_data, ...);
+	void *user_data, ...) G_GNUC_NULL_TERMINATED;
 
 /**
  * This is a callback function to be used for Ctrl+F searching in treeviews.
@@ -809,6 +812,16 @@
  */
 GtkWidget *pidgin_add_widget_to_vbox(GtkBox *vbox, const char *widget_label, GtkSizeGroup *sg, GtkWidget *widget, gboolean expand, GtkWidget **p_label);
 
+/**
+ * Create a GdkPixbuf from a PurpleStoredImage.
+ *
+ * @param  image   A PurpleStoredImage.
+ *
+ * @return   A GdkPixbuf created from the stored image.
+ * @since 2.5.0
+ */
+GdkPixbuf * pidgin_pixbuf_from_imgstore(PurpleStoredImage *image);
+
 gchar *pidgin_gtk_ellipsis_text(GtkWidget *widget, const char *text, gint min_width, gchar *ellipsis);
 
 #endif /* _PIDGINUTILS_H_ */
--- a/po/POTFILES.in	Mon May 19 16:18:32 2008 +0000
+++ b/po/POTFILES.in	Sun May 25 04:30:41 2008 +0000
@@ -180,6 +180,7 @@
 libpurple/request.h
 libpurple/savedstatuses.c
 libpurple/server.c
+libpurple/smiley.c
 libpurple/sslconn.c
 libpurple/status.c
 libpurple/util.c
@@ -209,6 +210,7 @@
 pidgin/gtkrequest.c
 pidgin/gtkroomlist.c
 pidgin/gtksavedstatuses.c
+pidgin/gtksmiley.c
 pidgin/gtksound.c
 pidgin/gtkstatusbox.c
 pidgin/gtkutils.c
--- a/po/de.po	Mon May 19 16:18:32 2008 +0000
+++ b/po/de.po	Sun May 25 04:30:41 2008 +0000
@@ -11,9 +11,9 @@
 msgstr ""
 "Project-Id-Version: de\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-05-17 21:01+0200\n"
-"PO-Revision-Date: 2008-05-17 19:31+0200\n"
-"Last-Translator: Björn Voigt <bjoern@cs.tu-berlin.de>\n"
+"POT-Creation-Date: 2008-05-24 10:32+0200\n"
+"PO-Revision-Date: 2008-05-24 10:31+0200\n"
+"Last-Translator: Jochen Kemnade <jochenkemnade@web.de>\n"
 "Language-Team: Deutsch <de@li.org>\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
@@ -100,6 +100,10 @@
 msgid "Alias:"
 msgstr "Alias:"
 
+#. Register checkbox
+msgid "Create this account on the server"
+msgstr "Dieses Konto auf dem Server anlegen"
+
 #. Cancel button
 #. Cancel
 msgid "Cancel"
@@ -2092,8 +2096,10 @@
 msgid "ABI version mismatch %d.%d.x (need %d.%d.x)"
 msgstr "ABI Versionskonflikt %d.%d.x (brauche %d.%d.x)"
 
-msgid "Plugin does not implement all required functions"
-msgstr "Plugin enthält nicht alle benötigten Funktionen"
+msgid ""
+"Plugin does not implement all required functions (list_icon, login and close)"
+msgstr ""
+"Plugin enthält nicht alle benötigten Funktionen (list_icon, login und close)"
 
 #, c-format
 msgid ""
@@ -2757,13 +2763,11 @@
 "dann installieren Sie ActiveTCL von http://www.activestate.com\n"
 
 msgid ""
-"The Apple Bonjour For Windows toolkit wasn't found, see the FAQ at: http://"
-"developer.pidgin.im/wiki/Using%20Pidgin#CanIusePidginforBonjourLink-"
-"LocalMessaging for more information."
+"The Apple Bonjour For Windows toolkit wasn't found, see the FAQ at: http://d."
+"pidgin.im/BonjourWindows for more information."
 msgstr ""
 "Apples Bonjour-Toolkit für Windows konnte nicht gefunden werden, für weitere "
-"Informationen besuchen Sie die FAQ unter: http://developer.pidgin.im/wiki/"
-"Using%20Pidgin#CanIusePidginforBonjourLink-LocalMessaging."
+"Informationen besuchen Sie die FAQ unter: http://d.pidgin.im/BonjourWindows"
 
 msgid "Unable to listen for incoming IM connections\n"
 msgstr "Kann nicht auf eingehende IM-Verbindungen hören\n"
@@ -4507,8 +4511,8 @@
 msgstr "XMPP-Nachrichtenfehler"
 
 #, c-format
-msgid " (Code %s)"
-msgstr " (Code %s)"
+msgid "(Code %s)"
+msgstr "(Code %s)"
 
 msgid "XML Parse error"
 msgstr "Fehler bei Einlesen von XML-Daten"
@@ -4806,6 +4810,9 @@
 msgid "Nudging %s..."
 msgstr "%s anstoßen..."
 
+msgid "E-mail Address..."
+msgstr "E-Mail-Adresse..."
+
 msgid "Your new MSN friendly name is too long."
 msgstr "Ihr neuer MSN-Benutzername zu lang."
 
@@ -9683,6 +9690,21 @@
 msgid "Accept chat invitation?"
 msgstr "Akzeptieren Sie die Chat-Einladung?"
 
+#. Shortcut
+msgid "Shortcut"
+msgstr "Tastenkombination"
+
+msgid "The text-shortcut for the smiley"
+msgstr "Die Tastenkombination für den Smiley"
+
+#. Stored Image
+msgid "Stored Image"
+msgstr "Gespeichertes Bild"
+
+#, fuzzy
+msgid "Stored Image. (that'll have to do for now)"
+msgstr "Gespeichertes Bild. (Das muss erstmal reichen)"
+
 msgid "SSL Connection Failed"
 msgstr "SSL-Verbindung gescheitert"
 
@@ -9957,8 +9979,8 @@
 msgid "_Basic"
 msgstr "_Einfach"
 
-msgid "Create this new account on the server"
-msgstr "Dieses neue Konto auf dem Server anlegen"
+msgid "Create _this new account on the server"
+msgstr "Dieses _neue Konto auf dem Server anlegen"
 
 msgid "_Advanced"
 msgstr "_Erweitert"
@@ -10064,6 +10086,12 @@
 msgid "_Remove"
 msgstr "_Entfernen"
 
+msgid "Set Custom Icon"
+msgstr "Setze benutzerdefiniertes Icon"
+
+msgid "Remove Custom Icon"
+msgstr "Benutzerdefiniertes Icon entfernen"
+
 msgid "Add _Buddy..."
 msgstr "_Buddy hinzufügen..."
 
@@ -10176,6 +10204,9 @@
 msgid "/Tools/_Certificates"
 msgstr "/Werkzeuge/_Zertifikate"
 
+msgid "/Tools/Smile_y"
+msgstr "/Werkzeuge/Smile_y"
+
 msgid "/Tools/Plu_gins"
 msgstr "/Werkzeuge/Plu_gins"
 
@@ -10521,9 +10552,6 @@
 msgid "Change Size"
 msgstr "Ändere Größe"
 
-msgid "Remove Custom Icon"
-msgstr "Benutzerdefiniertes Icon entfernen"
-
 msgid "Show All"
 msgstr "Alle anzeigen"
 
@@ -10963,6 +10991,9 @@
 msgid "Norwegian Nynorsk"
 msgstr "Norwegisch (Nynorsk)"
 
+msgid "Occitan"
+msgstr "Okzitanisch"
+
 msgid "Punjabi"
 msgstr "Pandschabi"
 
@@ -11338,6 +11369,13 @@
 msgid "Color to draw the name of an action message."
 msgstr "Farbe, mit der der Name in einer Aktions-Nachricht dargestellt wird."
 
+#, fuzzy
+msgid "Action Message Name Color for Whispered Message"
+msgstr "Farbe des Absendernamens für Aktions-Nachrichten"
+
+msgid "Whisper Message Name Color"
+msgstr "Farbe des Absendernamens für Flüster-Nachrichten"
+
 msgid "Typing notification color"
 msgstr "Farbe der Tipp-Benachrichtigung"
 
@@ -11408,6 +11446,10 @@
 msgid "_Save Image..."
 msgstr "Bild _speichern..."
 
+#, fuzzy
+msgid "_Add Custom Smiley..."
+msgstr "Benutzerdefinierten Smiley _hinzufügen..."
+
 msgid "Select Font"
 msgstr "Schriftart wählen"
 
@@ -11446,9 +11488,19 @@
 msgid "Insert Image"
 msgstr "Bild einfügen"
 
+msgid ""
+"This smiley is disabled because a custom smiley exists for this shortcut."
+msgstr ""
+"Dieser Smiley ist deaktiviert, da ein benutzerdefinierter Smiley für diese "
+"Tastenkombination existiert."
+
 msgid "Smile!"
 msgstr "Lächeln!"
 
+#, fuzzy
+msgid "_Manage custom smileys"
+msgstr "Benutzerdefinierte Smileys _verwalten"
+
 msgid "This theme has no available smileys."
 msgstr "Dieses Thema verfügt über keine Smileys."
 
@@ -12164,6 +12216,12 @@
 msgid "Play"
 msgstr "Abspielen"
 
+msgid "_Browse..."
+msgstr "Aus_wählen..."
+
+msgid "_Reset"
+msgstr "_Zurücksetzen"
+
 msgid "_Report idle time:"
 msgstr "Inaktivitätszei_ten anzeigen:"
 
@@ -12342,6 +12400,47 @@
 msgid "Status for %s"
 msgstr "Status für %s"
 
+msgid "Custom Smiley"
+msgstr "Benutzerdefinierter Smiley"
+
+msgid "Duplicate Shortcut"
+msgstr "Doppelte Tastenkombination"
+
+msgid ""
+"A custom smiley for the selected shortcut already exists. Please specify a "
+"different shortcut."
+msgstr ""
+"Für diese Tastenkombination existiert bereits ein benutzerdefinierter "
+"Smiley. Bitten wählen Sie eine andere Tastenkombination."
+
+msgid "More Data needed"
+msgstr "Weitere Daten benötigt"
+
+msgid "Please provide a shortcut to associate with the smiley."
+msgstr "Bitte geben Sie eine Tastenkombination für den Smiley an."
+
+msgid "Please select an image for the smiley."
+msgstr "Bitte wählen Sie ein Bild für den Smiley."
+
+msgid "Edit Smiley"
+msgstr "Smiley bearbeiten"
+
+msgid "Add Smiley"
+msgstr "Smiley hinzufügen"
+
+msgid "Smiley _Image"
+msgstr "Smiley-_Bild"
+
+#. Smiley shortcut
+msgid "Smiley S_hortcut"
+msgstr "_Tastenkombination"
+
+msgid "Smiley"
+msgstr "Smiley"
+
+msgid "Custom Smiley Manager"
+msgstr "Verwaltung für benutzerdefinierte Smileys"
+
 msgid "Waiting for network connection"
 msgstr "Warte auf Netzwerkverbindung"