changeset 23155:bc58e2d9f0f5

merge of '979e05e68e2d32cdffe65f350b71fd6e6ae89af9' and 'df0ddcef268608879c9e3ecd095cb74903d279e8'
author Etan Reisner <pidgin@unreliablesource.net>
date Fri, 16 May 2008 03:18:27 +0000
parents b17d6defb922 (diff) 0ca259d5f928 (current diff)
children da5d1ecc5c20
files ChangeLog.API libpurple/blist.c pidgin/gtkconv.c pidgin/gtkutils.c pidgin/gtkutils.h
diffstat 77 files changed, 3100 insertions(+), 495 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Thu May 15 05:49:40 2008 +0000
+++ b/COPYRIGHT	Fri May 16 03:18:27 2008 +0000
@@ -231,6 +231,7 @@
 Shlomi Loubaton
 Uli Luckas
 Matthew Luckie
+Marcus Lundblad
 Mike Lundy
 Jason Lynch
 Iain MacDonnell
@@ -287,6 +288,7 @@
 John Oyler
 Matt Pandina
 Laszlo Pandy
+Giulio 'Twain28' Pascali
 Ricardo Fernandez Pascual
 Riley Patterson
 Havoc Pennington
--- a/ChangeLog.API	Thu May 15 05:49:40 2008 +0000
+++ b/ChangeLog.API	Fri May 16 03:18:27 2008 +0000
@@ -1,5 +1,23 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+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.
+
+	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.5.0:
 	libpurple:
 		Added:
--- a/libpurple/Makefile.am	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/Makefile.am	Fri May 16 03:18:27 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	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/Makefile.mingw	Fri May 16 03:18:27 2008 +0000
@@ -65,6 +65,7 @@
 			savedstatuses.c \
 			server.c \
 			signals.c \
+			smiley.c \
 			sound.c \
 			sslconn.c \
 			status.c \
--- a/libpurple/blist.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/blist.c	Fri May 16 03:18:27 2008 +0000
@@ -2082,9 +2082,7 @@
 
 const char *purple_chat_get_name(PurpleChat *chat)
 {
-	struct proto_chat_entry *pce;
-	GList *parts;
-	char *ret;
+	char *ret = NULL;
 	PurplePlugin *prpl;
 	PurplePluginProtocolInfo *prpl_info = NULL;
 
@@ -2096,11 +2094,14 @@
 	prpl = purple_find_prpl(purple_account_get_protocol_id(chat->account));
 	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
 
-	parts = prpl_info->chat_info(purple_account_get_connection(chat->account));
-	pce = parts->data;
-	ret = g_hash_table_lookup(chat->components, pce->identifier);
-	g_list_foreach(parts, (GFunc)g_free, NULL);
-	g_list_free(parts);
+	if (prpl_info->chat_info) {
+		struct proto_chat_entry *pce;
+		GList *parts = prpl_info->chat_info(purple_account_get_connection(chat->account));
+		pce = parts->data;
+		ret = g_hash_table_lookup(chat->components, pce->identifier);
+		g_list_foreach(parts, (GFunc)g_free, NULL);
+		g_list_free(parts);
+	}
 
 	return ret;
 }
@@ -2202,7 +2203,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/connection.h	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/connection.h	Fri May 16 03:18:27 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/core.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/core.c	Fri May 16 03:18:27 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"
@@ -164,6 +165,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
@@ -192,6 +194,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	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/dbus-analyze-functions.py	Fri May 16 03:18:27 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	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/dbus-server.c	Fri May 16 03:18:27 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	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/imgstore.c	Fri May 16 03:18:27 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	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/imgstore.h	Fri May 16 03:18:27 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/plugins/perl/Makefile.am	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/plugins/perl/Makefile.am	Fri May 16 03:18:27 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	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/plugins/perl/common/MANIFEST	Fri May 16 03:18:27 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	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/plugins/perl/common/Makefile.mingw	Fri May 16 03:18:27 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	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/plugins/perl/common/Purple.xs	Fri May 16 03:18:27 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	Fri May 16 03:18:27 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	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/plugins/perl/common/module.h	Fri May 16 03:18:27 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	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/plugins/perl/common/typemap	Fri May 16 03:18:27 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	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Fri May 16 03:18:27 2008 +0000
@@ -24,6 +24,7 @@
 #include <pwd.h>
 #else
 #define UNICODE
+#include <winsock2.h>
 #include <windows.h>
 #include <lm.h>
 #include "dns_sd_proxy.h"
--- a/libpurple/protocols/bonjour/dns_sd_proxy.h	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/bonjour/dns_sd_proxy.h	Fri May 16 03:18:27 2008 +0000
@@ -21,10 +21,13 @@
 #ifndef _DNS_SD_PROXY
 #define _DNS_SD_PROXY
 
+
+#ifndef _MSC_VER
 #include <stdint.h>
+#endif
 
 /* fixup to make pidgin compile against win32 bonjour */
-#ifdef _WIN32
+#if defined(_WIN32) && !defined(_MSC_VER)
 #define _MSL_STDINT_H
 #endif
 
--- a/libpurple/protocols/bonjour/jabber.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/bonjour/jabber.c	Fri May 16 03:18:27 2008 +0000
@@ -39,7 +39,9 @@
 #endif
 
 #include <glib.h>
+#ifdef HAVE_UNISTD_H
 #include <unistd.h>
+#endif
 #include <fcntl.h>
 
 #include "network.h"
--- a/libpurple/protocols/bonjour/parser.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/bonjour/parser.c	Fri May 16 03:18:27 2008 +0000
@@ -157,38 +157,38 @@
 }
 
 static xmlSAXHandler bonjour_parser_libxml = {
-	.internalSubset         = NULL,
-	.isStandalone           = NULL,
-	.hasInternalSubset      = NULL,
-	.hasExternalSubset      = NULL,
-	.resolveEntity          = NULL,
-	.getEntity              = NULL,
-	.entityDecl             = NULL,
-	.notationDecl           = NULL,
-	.attributeDecl          = NULL,
-	.elementDecl            = NULL,
-	.unparsedEntityDecl     = NULL,
-	.setDocumentLocator     = NULL,
-	.startDocument          = NULL,
-	.endDocument            = NULL,
-	.startElement           = NULL,
-	.endElement             = NULL,
-	.reference              = NULL,
-	.characters             = bonjour_parser_element_text_libxml,
-	.ignorableWhitespace    = NULL,
-	.processingInstruction  = NULL,
-	.comment                = NULL,
-	.warning                = NULL,
-	.error                  = NULL,
-	.fatalError             = NULL,
-	.getParameterEntity     = NULL,
-	.cdataBlock             = NULL,
-	.externalSubset         = NULL,
-	.initialized            = XML_SAX2_MAGIC,
-	._private               = NULL,
-	.startElementNs         = bonjour_parser_element_start_libxml,
-	.endElementNs           = bonjour_parser_element_end_libxml,
-	.serror                 = NULL
+	NULL,									/*internalSubset*/
+	NULL,									/*isStandalone*/
+	NULL,									/*hasInternalSubset*/
+	NULL,									/*hasExternalSubset*/
+	NULL,									/*resolveEntity*/
+	NULL,									/*getEntity*/
+	NULL,									/*entityDecl*/
+	NULL,									/*notationDecl*/
+	NULL,									/*attributeDecl*/
+	NULL,									/*elementDecl*/
+	NULL,									/*unparsedEntityDecl*/
+	NULL,									/*setDocumentLocator*/
+	NULL,									/*startDocument*/
+	NULL,									/*endDocument*/
+	NULL,									/*startElement*/
+	NULL,									/*endElement*/
+	NULL,									/*reference*/
+	bonjour_parser_element_text_libxml,		/*characters*/
+	NULL,									/*ignorableWhitespace*/
+	NULL,									/*processingInstruction*/
+	NULL,									/*comment*/
+	NULL,									/*warning*/
+	NULL,									/*error*/
+	NULL,									/*fatalError*/
+	NULL,									/*getParameterEntity*/
+	NULL,									/*cdataBlock*/
+	NULL,									/*externalSubset*/
+	XML_SAX2_MAGIC,							/*initialized*/
+	NULL,									/*_private*/
+	bonjour_parser_element_start_libxml,	/*startElementNs*/
+	bonjour_parser_element_end_libxml,		/*endElementNs*/
+	NULL									/*serror*/
 };
 
 void
--- a/libpurple/protocols/gg/gg.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/gg/gg.c	Fri May 16 03:18:27 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;
 }
--- a/libpurple/protocols/irc/irc.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/irc/irc.c	Fri May 16 03:18:27 2008 +0000
@@ -733,7 +733,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;
 }
--- a/libpurple/protocols/jabber/iq.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/jabber/iq.c	Fri May 16 03:18:27 2008 +0000
@@ -374,7 +374,7 @@
 	}
 }
 
-void jabber_iq_register_handler(const char *xmlns, JabberIqHandler handlerfunc)
+void jabber_iq_register_handler(const char *xmlns, JabberIqHandler *handlerfunc)
 {
 	g_hash_table_replace(iq_handlers, g_strdup(xmlns), handlerfunc);
 }
--- a/libpurple/protocols/jabber/parser.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/jabber/parser.c	Fri May 16 03:18:27 2008 +0000
@@ -133,38 +133,38 @@
 }
 
 static xmlSAXHandler jabber_parser_libxml = {
-	.internalSubset         = NULL,
-	.isStandalone           = NULL,
-	.hasInternalSubset      = NULL,
-	.hasExternalSubset      = NULL,
-	.resolveEntity          = NULL,
-	.getEntity              = NULL,
-	.entityDecl             = NULL,
-	.notationDecl           = NULL,
-	.attributeDecl          = NULL,
-	.elementDecl            = NULL,
-	.unparsedEntityDecl     = NULL,
-	.setDocumentLocator     = NULL,
-	.startDocument          = NULL,
-	.endDocument            = NULL,
-	.startElement           = NULL,
-	.endElement             = NULL,
-	.reference              = NULL,
-	.characters             = jabber_parser_element_text_libxml,
-	.ignorableWhitespace    = NULL,
-	.processingInstruction  = NULL,
-	.comment                = NULL,
-	.warning                = NULL,
-	.error                  = NULL,
-	.fatalError             = NULL,
-	.getParameterEntity     = NULL,
-	.cdataBlock             = NULL,
-	.externalSubset         = NULL,
-	.initialized            = XML_SAX2_MAGIC,
-	._private               = NULL,
-	.startElementNs         = jabber_parser_element_start_libxml,
-	.endElementNs           = jabber_parser_element_end_libxml,
-	.serror                 = NULL
+	NULL,									/*internalSubset*/
+	NULL,									/*isStandalone*/
+	NULL,									/*hasInternalSubset*/
+	NULL,									/*hasExternalSubset*/
+	NULL,									/*resolveEntity*/
+	NULL,									/*getEntity*/
+	NULL,									/*entityDecl*/
+	NULL,									/*notationDecl*/
+	NULL,									/*attributeDecl*/
+	NULL,									/*elementDecl*/
+	NULL,									/*unparsedEntityDecl*/
+	NULL,									/*setDocumentLocator*/
+	NULL,									/*startDocument*/
+	NULL,									/*endDocument*/
+	NULL,									/*startElement*/
+	NULL,									/*endElement*/
+	NULL,									/*reference*/
+	jabber_parser_element_text_libxml,		/*characters*/
+	NULL,									/*ignorableWhitespace*/
+	NULL,									/*processingInstruction*/
+	NULL,									/*comment*/
+	NULL,									/*warning*/
+	NULL,									/*error*/
+	NULL,									/*fatalError*/
+	NULL,									/*getParameterEntity*/
+	NULL,									/*cdataBlock*/
+	NULL,									/*externalSubset*/
+	XML_SAX2_MAGIC,							/*initialized*/
+	NULL,									/*_private*/
+	jabber_parser_element_start_libxml,		/*startElementNs*/
+	jabber_parser_element_end_libxml,		/*endElementNs*/
+	NULL									/*serror*/
 };
 
 void
--- a/libpurple/protocols/jabber/si.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/jabber/si.c	Fri May 16 03:18:27 2008 +0000
@@ -84,6 +84,21 @@
 	return NULL;
 }
 
+static void
+jabber_si_free_streamhost(gpointer data, gpointer user_data)
+{
+	JabberBytestreamsStreamhost *sh = data;
+
+	if(!data)
+		return;
+
+	g_free(sh->jid);
+	g_free(sh->host);
+	g_free(sh->zeroconf);
+	g_free(sh);
+}
+
+
 
 static void jabber_si_bytestreams_attempt_connect(PurpleXfer *xfer);
 
@@ -110,10 +125,7 @@
 				streamhost->jid, streamhost->host,
 				error_message ? error_message : "(null)");
 		jsx->streamhosts = g_list_remove(jsx->streamhosts, streamhost);
-		g_free(streamhost->jid);
-		g_free(streamhost->host);
-		g_free(streamhost->zeroconf);
-		g_free(streamhost);
+		jabber_si_free_streamhost(streamhost, NULL);
 		jabber_si_bytestreams_attempt_connect(xfer);
 		return;
 	}
@@ -245,10 +257,7 @@
 	if (jsx->connect_data == NULL)
 	{
 		jsx->streamhosts = g_list_remove(jsx->streamhosts, streamhost);
-		g_free(streamhost->jid);
-		g_free(streamhost->host);
-		g_free(streamhost->zeroconf);
-		g_free(streamhost);
+		jabber_si_free_streamhost(streamhost, NULL);
 		jabber_si_bytestreams_attempt_connect(xfer);
 	}
 }
@@ -299,6 +308,7 @@
 			sh->host = g_strdup(host);
 			sh->port = portnum;
 			sh->zeroconf = g_strdup(zeroconf);
+			/* If there were a lot of these, it'd be worthwhile to prepend and reverse. */
 			jsx->streamhosts = g_list_append(jsx->streamhosts, sh);
 		}
 	}
@@ -593,21 +603,6 @@
 	return strcmp(sh->jid, (char *)b);
 }
 
-
-static void
-jabber_si_free_streamhost(gpointer data, gpointer user_data)
-{
-	JabberBytestreamsStreamhost *sh = data;
-
-	if(!data)
-		return;
-
-	g_free(sh->jid);
-	g_free(sh->host);
-	g_free(sh->zeroconf);
-	g_free(sh);
-}
-
 static void
 jabber_si_xfer_bytestreams_send_connected_cb(gpointer data, gint source,
 		PurpleInputCondition cond)
@@ -826,7 +821,7 @@
 		sh2 = g_new0(JabberBytestreamsStreamhost, 1);
 		sh2->jid = g_strdup(sh->jid);
 		sh2->host = g_strdup(sh->host);
-		sh2->zeroconf = g_strdup(sh->zeroconf);
+		/*sh2->zeroconf = g_strdup(sh->zeroconf);*/
 		sh2->port = sh->port;
 
 		jsx->streamhosts = g_list_prepend(jsx->streamhosts, sh2);
--- a/libpurple/protocols/jabber/win32/posix.uname.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/jabber/win32/posix.uname.c	Fri May 16 03:18:27 2008 +0000
@@ -33,10 +33,12 @@
 /*#define _ANONYMOUS_STRUCT*/
 /*#define _ANONYMOUS_UNION*/
 #include <windows.h>
+#ifdef __MINGW32__
 #include <_mingw.h>
+#endif
 
 int
-uname( struct utsname *uts )
+jabber_win32_uname( struct utsname *uts )
 {
   DWORD sLength;
   OSVERSIONINFO OS_version;
@@ -52,7 +54,7 @@
   GetVersionEx ( &OS_version );
   GetSystemInfo ( &System_Info );
 
-  strcpy( uts->sysname, "MINGW_" );
+  strcpy( uts->sysname, "WIN32_" );
   switch( OS_version.dwPlatformId )
   {
     case VER_PLATFORM_WIN32_NT:
@@ -82,8 +84,10 @@
       break;
   }
 
+#ifdef __MINGW32__
   sprintf( uts->version, "%i", __MINGW32_MAJOR_VERSION );
   sprintf( uts->release, "%i", __MINGW32_MINOR_VERSION );
+#endif
 
   switch( System_Info.wProcessorArchitecture )
   {
--- a/libpurple/protocols/jabber/win32/utsname.h	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/jabber/win32/utsname.h	Fri May 16 03:18:27 2008 +0000
@@ -14,7 +14,8 @@
   char machine[20];
 };
 
-int uname (struct utsname *);
+int jabber_win32_uname (struct utsname *);
+#define uname(utsname) jabber_win32_uname(utsname)
 
 #ifdef __cplusplus
 }
--- a/libpurple/protocols/msn/httpconn.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/msn/httpconn.c	Fri May 16 03:18:27 2008 +0000
@@ -438,7 +438,7 @@
 static gboolean
 write_raw(MsnHttpConn *httpconn, const char *data, size_t data_len)
 {
-	ssize_t res; /* result of the write operation */
+	gssize res; /* result of the write operation */
 
 	if (httpconn->tx_handler == 0)
 		res = write(httpconn->fd, data, data_len);
@@ -551,7 +551,7 @@
 	return TRUE;
 }
 
-ssize_t
+gssize
 msn_httpconn_write(MsnHttpConn *httpconn, const char *body, size_t body_len)
 {
 	char *params;
--- a/libpurple/protocols/msn/httpconn.h	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/msn/httpconn.h	Fri May 16 03:18:27 2008 +0000
@@ -89,7 +89,7 @@
  *
  * @return The number of bytes written.
  */
-ssize_t msn_httpconn_write(MsnHttpConn *httpconn, const char *data, size_t data_len);
+gssize msn_httpconn_write(MsnHttpConn *httpconn, const char *data, size_t data_len);
 
 /**
  * Connects the HTTP connection object to a host.
--- a/libpurple/protocols/msn/msn.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/msn/msn.c	Fri May 16 03:18:27 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;
 }
@@ -895,7 +903,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);
 
@@ -936,6 +945,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)
@@ -945,9 +1045,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);
@@ -980,10 +1082,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)){
@@ -993,6 +1098,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);
 			}
 		}
@@ -1433,7 +1551,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;
--- a/libpurple/protocols/msn/object.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/msn/object.c	Fri May 16 03:18:27 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	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/msn/object.h	Fri May 16 03:18:27 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/servconn.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/msn/servconn.c	Fri May 16 03:18:27 2008 +0000
@@ -323,10 +323,10 @@
 	purple_circ_buffer_mark_read(servconn->tx_buf, ret);
 }
 
-ssize_t
+gssize
 msn_servconn_write(MsnServConn *servconn, const char *buf, size_t len)
 {
-	ssize_t ret = 0;
+	gssize ret = 0;
 
 	g_return_val_if_fail(servconn != NULL, 0);
 
--- a/libpurple/protocols/msn/servconn.h	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/msn/servconn.h	Fri May 16 03:18:27 2008 +0000
@@ -157,7 +157,7 @@
  * @param buf The data to write.
  * @param size The size of the data.
  */
-ssize_t msn_servconn_write(MsnServConn *servconn, const char *buf,
+gssize msn_servconn_write(MsnServConn *servconn, const char *buf,
 						  size_t size);
 
 /**
--- a/libpurple/protocols/msn/slp.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/msn/slp.c	Fri May 16 03:18:27 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/soap2.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/msn/soap2.c	Fri May 16 03:18:27 2008 +0000
@@ -33,7 +33,9 @@
 #include "xmlnode.h"
 
 #include <glib.h>
+#if !defined(_WIN32) || !defined(_WINERROR_)
 #include <error.h>
+#endif
 
 #define SOAP_TIMEOUT (5 * 60)
 
--- a/libpurple/protocols/msn/user.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/msn/user.c	Fri May 16 03:18:27 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/msn.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/msnp9/msn.c	Fri May 16 03:18:27 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;
 }
@@ -766,7 +774,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);
 
@@ -807,6 +816,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)
@@ -816,8 +916,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);
@@ -845,13 +947,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);
 	}
@@ -1274,7 +1392,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;
--- a/libpurple/protocols/msnp9/object.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/msnp9/object.c	Fri May 16 03:18:27 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	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/msnp9/object.h	Fri May 16 03:18:27 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	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/msnp9/slp.c	Fri May 16 03:18:27 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	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/msnp9/user.c	Fri May 16 03:18:27 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/novell/novell.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/novell/novell.c	Fri May 16 03:18:27 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;
--- a/libpurple/protocols/oscar/flap_connection.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/oscar/flap_connection.c	Fri May 16 03:18:27 2008 +0000
@@ -159,7 +159,7 @@
  *        of this SNAC.  For empty SNACs this should be NULL.
  */
 void
-flap_connection_send_snac(OscarData *od, FlapConnection *conn, guint16 family, guint16 subtype, guint16 flags, aim_snacid_t snacid, ByteStream *data)
+flap_connection_send_snac(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, guint16 flags, aim_snacid_t snacid, ByteStream *data)
 {
 	FlapFrame *frame;
 	guint32 length;
@@ -788,7 +788,7 @@
 flap_connection_recv_cb(gpointer data, gint source, PurpleInputCondition cond)
 {
 	FlapConnection *conn;
-	ssize_t read;
+	gssize read;
 
 	conn = data;
 
--- a/libpurple/protocols/oscar/msgcookie.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/oscar/msgcookie.c	Fri May 16 03:18:27 2008 +0000
@@ -132,7 +132,7 @@
  *         on success; returns NULL on error/not found
  */
 
-IcbmCookie *aim_checkcookie(OscarData *od, const guint8 *cookie, int type)
+IcbmCookie *aim_checkcookie(OscarData *od, const guint8 *cookie, const int type)
 {
 	IcbmCookie *cur;
 
--- a/libpurple/protocols/oscar/odc.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/oscar/odc.c	Fri May 16 03:18:27 2008 +0000
@@ -430,7 +430,7 @@
 	PeerConnection *conn;
 	OdcFrame *frame;
 	ByteStream *bs;
-	ssize_t read;
+	gssize read;
 
 	conn = data;
 	frame = conn->frame;
--- a/libpurple/protocols/oscar/oscar.h	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/oscar/oscar.h	Fri May 16 03:18:27 2008 +0000
@@ -409,7 +409,7 @@
 
 	int fd;
 	guint8 header[6];
-	ssize_t header_received;
+	gssize header_received;
 	FlapFrame buffer_incoming;
 	PurpleCircBuffer *buffer_outgoing;
 	guint watcher_incoming;
@@ -1389,8 +1389,8 @@
 /* TLV handling functions */
 char *aim_tlv_getvalue_as_string(aim_tlv_t *tlv);
 
-aim_tlv_t *aim_tlv_gettlv(GSList *list, guint16 type, const int nth);
-int aim_tlv_getlength(GSList *list, guint16 type, const int nth);
+aim_tlv_t *aim_tlv_gettlv(GSList *list, const guint16 type, const int nth);
+int aim_tlv_getlength(GSList *list, const guint16 type, const int nth);
 char *aim_tlv_getstr(GSList *list, const guint16 type, const int nth);
 guint8 aim_tlv_get8(GSList *list, const guint16 type, const int nth);
 guint16 aim_tlv_get16(GSList *list, const guint16 type, const int nth);
--- a/libpurple/protocols/oscar/peer.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/oscar/peer.c	Fri May 16 03:18:27 2008 +0000
@@ -291,7 +291,7 @@
 peer_connection_recv_cb(gpointer data, gint source, PurpleInputCondition cond)
 {
 	PeerConnection *conn;
-	ssize_t read;
+	gssize read;
 
 	conn = data;
 
@@ -407,7 +407,7 @@
 {
 	PeerConnection *conn;
 	gsize writelen;
-	ssize_t wrotelen;
+	gssize wrotelen;
 
 	conn = data;
 	writelen = purple_circ_buffer_get_max_read(conn->buffer_outgoing);
--- a/libpurple/protocols/oscar/peer.h	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/oscar/peer.h	Fri May 16 03:18:27 2008 +0000
@@ -179,9 +179,9 @@
 
 	int fd;
 	guint8 header[6];
-	ssize_t header_received;
+	gssize header_received;
 	guint8 proxy_header[12];
-	ssize_t proxy_header_received;
+	gssize proxy_header_received;
 	ByteStream buffer_incoming;
 	PurpleCircBuffer *buffer_outgoing;
 	guint watcher_incoming;
--- a/libpurple/protocols/oscar/peer_proxy.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/oscar/peer_proxy.c	Fri May 16 03:18:27 2008 +0000
@@ -202,7 +202,7 @@
 peer_proxy_connection_recv_cb(gpointer data, gint source, PurpleInputCondition cond)
 {
 	PeerConnection *conn;
-	ssize_t read;
+	gssize read;
 	ProxyFrame *frame;
 
 	conn = data;
--- a/libpurple/protocols/silc/chat.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/silc/chat.c	Fri May 16 03:18:27 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/silc10/chat.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/silc10/chat.c	Fri May 16 03:18:27 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/yahoo/yahoo_picture.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo_picture.c	Fri May 16 03:18:27 2008 +0000
@@ -413,7 +413,7 @@
 {
 	struct yahoo_buddy_icon_upload_data *d = data;
 	PurpleConnection *gc = d->gc;
-	ssize_t wrote;
+	gssize wrote;
 
 	if (!PURPLE_CONNECTION_IS_VALID(gc)) {
 		yahoo_buddy_icon_upload_data_free(d);
--- a/libpurple/protocols/yahoo/yahoo_profile.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoo_profile.c	Fri May 16 03:18:27 2008 +0000
@@ -1003,7 +1003,7 @@
 	purple_debug_misc("yahoo", "url_buffer = %p\n", url_buffer);
 
 	/* convert to utf8 */
-	if (strings && strings->charset != XX) {
+	if (strings && strings->charset) {
 		p = g_convert(stripped, -1, "utf-8", strings->charset,
 				NULL, NULL, NULL);
 		if (!p) {
@@ -1023,7 +1023,7 @@
 	p = NULL;
 
 	/* "Last updated" should also be converted to utf8 and with &nbsp; killed */
-	if (strings && strings->charset != XX) {
+	if (strings && strings->charset) {
 		last_updated_utf8_string = g_convert(last_updated_string, -1, "utf-8",
 				strings->charset, NULL, NULL, NULL);
 		yahoo_remove_nonbreaking_spaces(last_updated_utf8_string);
--- a/libpurple/protocols/yahoo/yahoochat.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/protocols/yahoo/yahoochat.c	Fri May 16 03:18:27 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;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/smiley.c	Fri May 16 03:18:27 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	Fri May 16 03:18:27 2008 +0000
@@ -0,0 +1,270 @@
+/**
+ * @file smiley.h Smiley 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
+ *
+ */
+
+#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/util.c	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/util.c	Fri May 16 03:18:27 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);
@@ -2889,7 +2890,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];
@@ -2910,9 +2911,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	Thu May 15 05:49:40 2008 +0000
+++ b/libpurple/util.h	Fri May 16 03:18:27 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/Makefile.am	Thu May 15 05:49:40 2008 +0000
+++ b/pidgin/Makefile.am	Fri May 16 03:18:27 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	Thu May 15 05:49:40 2008 +0000
+++ b/pidgin/Makefile.mingw	Fri May 16 03:18:27 2008 +0000
@@ -86,6 +86,7 @@
 			gtkroomlist.c \
 			gtksavedstatuses.c \
 			gtkscrollbook.c \
+			gtksmiley.c \
 			gtksound.c \
 			gtksourceiter.c \
 			gtksourceundomanager.c \
--- a/pidgin/gtkaccount.c	Thu May 15 05:49:40 2008 +0000
+++ b/pidgin/gtkaccount.c	Fri May 16 03:18:27 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 &&
@@ -1816,39 +1809,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)
 {
@@ -1971,14 +1931,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();
@@ -1997,8 +1957,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();
@@ -2040,21 +2000,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,
@@ -2259,6 +2212,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);
@@ -2328,8 +2282,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	Thu May 15 05:49:40 2008 +0000
+++ b/pidgin/gtkblist.c	Fri May 16 03:18:27 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"
@@ -3178,6 +3179,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 },
--- a/pidgin/gtkconv.c	Thu May 15 05:49:40 2008 +0000
+++ b/pidgin/gtkconv.c	Fri May 16 03:18:27 2008 +0000
@@ -159,8 +159,6 @@
 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 void pidgin_conv_update_fields(PurpleConversation *conv, PidginConvFields fields);
@@ -5201,6 +5199,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
@@ -5507,8 +5511,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);
@@ -5667,14 +5669,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 */
@@ -5920,8 +5916,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(),
@@ -6149,119 +6144,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;
 }
@@ -6486,6 +6386,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));
--- a/pidgin/gtkimhtml.c	Thu May 15 05:49:40 2008 +0000
+++ b/pidgin/gtkimhtml.c	Fri May 16 03:18:27 2008 +0000
@@ -5263,7 +5263,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	Thu May 15 05:49:40 2008 +0000
+++ b/pidgin/gtkimhtml.h	Fri May 16 03:18:27 2008 +0000
@@ -76,6 +76,7 @@
 	GTK_IMHTML_SMILEY =     1 << 11,
 	GTK_IMHTML_LINKDESC =   1 << 12,
 	GTK_IMHTML_STRIKE =     1 << 13,
+	GTK_IMHTML_CUSTOM_SMILEY = 1 << 14,
 	GTK_IMHTML_ALL =       -1
 } GtkIMHtmlButtons;
 
@@ -852,6 +853,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	Thu May 15 05:49:40 2008 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Fri May 16 03:18:27 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	Thu May 15 05:49:40 2008 +0000
+++ b/pidgin/gtkmain.c	Fri May 16 03:18:27 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();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtksmiley.c	Fri May 16 03:18:27 2008 +0000
@@ -0,0 +1,667 @@
+/**
+ * @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
+
+typedef struct
+{
+	PurpleSmiley *smiley;
+	GtkWidget *parent;
+	GtkWidget *smile;
+	GtkWidget *smiley_image;
+	gchar *filename;
+} PidginSmiley;
+
+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);
+	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 || *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");
+		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);
+}
+
+void 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"),
+			GTK_WINDOW(widget),
+			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);
+}
+
+/******************************************************************************
+ * 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	Fri May 16 03:18:27 2008 +0000
@@ -0,0 +1,80 @@
+/**
+ * @file gtksmiley.h GTK+ Custom Smiley 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
+ */
+
+#ifndef _PIDGIN_GTKSMILEY_H_
+#define _PIDGIN_GTKSMILEY_H_
+
+#include "smiley.h"
+
+/**
+ * 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
+ */
+void pidgin_smiley_edit(GtkWidget *widget, PurpleSmiley *smiley);
+
+#endif /* _PIDGIN_GTKSMILEY_H_*/
--- a/pidgin/gtkthemes.c	Thu May 15 05:49:40 2008 +0000
+++ b/pidgin/gtkthemes.c	Fri May 16 03:18:27 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	Thu May 15 05:49:40 2008 +0000
+++ b/pidgin/gtkthemes.h	Fri May 16 03:18:27 2008 +0000
@@ -51,6 +51,8 @@
 
 void pidgin_themes_smiley_themeize(GtkWidget *);
 
+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	Thu May 15 05:49:40 2008 +0000
+++ b/pidgin/gtkutils.c	Fri May 16 03:18:27 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);
 
@@ -3465,3 +3465,17 @@
 #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;
+}
+
--- a/pidgin/gtkutils.h	Thu May 15 05:49:40 2008 +0000
+++ b/pidgin/gtkutils.h	Fri May 16 03:18:27 2008 +0000
@@ -692,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.
@@ -812,5 +812,15 @@
  */
 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);
+
 #endif /* _PIDGINUTILS_H_ */
 
--- a/po/POTFILES.in	Thu May 15 05:49:40 2008 +0000
+++ b/po/POTFILES.in	Fri May 16 03:18:27 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