changeset 32611:3d5a0e9654ed

merge of '0acd4d77f5bd8bb02a3a73b6800be683bd7068f2' and '6bee9aeec299196df3421d8fa5d0c663ae9f9e88'
author Elliott Sales de Andrade <qulogic@pidgin.im>
date Mon, 12 Sep 2011 02:42:41 +0000
parents 513b8ec76077 (current diff) 62c34bc73f18 (diff)
children 4d64ab35c73b
files
diffstat 55 files changed, 2649 insertions(+), 506 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog.API	Sun Sep 11 04:19:01 2011 +0000
+++ b/ChangeLog.API	Mon Sep 12 02:42:41 2011 +0000
@@ -107,10 +107,14 @@
 		* PidginConversation.sg
 		* purple_account_add_buddies_with_invite
 		* purple_account_add_buddy_with_invite
+		* purple_blist_update_buddy_icon
+		* purple_buddy_get_local_alias
 		* purple_buddy_icons_has_custom_icon
 		* purple_buddy_icons_find_custom_icon
 		* purple_buddy_icons_set_custom_icon
 		* purple_connection_error_reason
+		* purple_contact_set_alias
+		* purple_conv_chat_set_users
 		* purple_core_migrate
 		* purple_dnsquery_a_account
 		* purple_notify_searchresults_column_get_title
@@ -125,6 +129,7 @@
 		* purple_plugins_unregister_unload_notify_cb
 		* purple_presence_add_status
 		* purple_presence_add_list
+		* purple_proxy_connect_socks5
 		* purple_srv_cancel
 		* purple_srv_resolve_account
 		* purple_status_set_attr_boolean
@@ -144,6 +149,7 @@
 		* PurpleConnectionUiOps.report_disconnect_reason
 		* PurplePluginProtocolInfo.add_buddy_with_invite
 		* PurplePluginProtocolInfo.add_buddies_with_invite
+		* PurplePluginProtocolInfo.get_cb_away
 		* serv_got_attention
 		* serv_send_attention
 		* struct _GtkIMHtmlFontDetail
--- a/configure.ac	Sun Sep 11 04:19:01 2011 +0000
+++ b/configure.ac	Mon Sep 12 02:42:41 2011 +0000
@@ -434,6 +434,16 @@
 	PKG_CHECK_MODULES(PANGO, [pango >= 1.4.0],
 			AC_DEFINE(HAVE_PANGO14, 1, [Define if we have Pango 1.4 or newer.]),:)
 
+	PKG_CHECK_MODULES(WEBKIT, [webkit-1.0 >= 1.1.1], , [
+		AC_MSG_RESULT(no)
+		AC_MSG_ERROR([
+You must have WebKit 1.1.1 or newer development headers installed to compile
+Pidgin.  If you want to build only Finch then specify --disable-gtkui when
+running configure.
+])])
+	AC_SUBST(WEBKIT_CFLAGS)
+	AC_SUBST(WEBKIT_LIBS)
+
 	dnl #######################################################################
 	dnl # Check if we should compile with X support
 	dnl #######################################################################
@@ -2486,6 +2496,7 @@
 		   pidgin/pixmaps/emotes/none/Makefile
 		   pidgin/pixmaps/emotes/small/16/Makefile
 		   pidgin/plugins/Makefile
+		   pidgin/plugins/adiumthemes/Makefile
 		   pidgin/plugins/cap/Makefile
 		   pidgin/plugins/disco/Makefile
 		   pidgin/plugins/gestures/Makefile
--- a/libpurple/blist.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/blist.c	Mon Sep 12 02:42:41 2011 +0000
@@ -956,12 +956,6 @@
 		ops->update(purplebuddylist, node);
 }
 
-void
-purple_blist_update_buddy_icon(PurpleBuddy *buddy)
-{
-	purple_blist_update_node_icon((PurpleBlistNode *)buddy);
-}
-
 /*
  * TODO: Maybe remove the call to this from server.c and call it
  * from oscar.c and toc.c instead?
@@ -1743,11 +1737,6 @@
 	return (PurpleGroup *)(((PurpleBlistNode *)contact)->parent);
 }
 
-void purple_contact_set_alias(PurpleContact *contact, const char *alias)
-{
-	purple_blist_alias_contact(contact,alias);
-}
-
 const char *purple_contact_get_alias(PurpleContact* contact)
 {
 	g_return_val_if_fail(contact != NULL, NULL);
@@ -2375,26 +2364,6 @@
 	return NULL;
 }
 
-const char *purple_buddy_get_local_alias(PurpleBuddy *buddy)
-{
-	PurpleContact *c;
-
-	g_return_val_if_fail(buddy != NULL, NULL);
-
-	/* Search for an alias for the buddy. In order of precedence: */
-	/* The buddy alias */
-	if (buddy->alias != NULL)
-		return buddy->alias;
-
-	/* The contact alias */
-	c = purple_buddy_get_contact(buddy);
-	if ((c != NULL) && (c->alias != NULL))
-		return c->alias;
-
-	/* The buddy's user name (i.e. no alias) */
-	return buddy->name;
-}
-
 const char *purple_chat_get_name(PurpleChat *chat)
 {
 	char *ret = NULL;
--- a/libpurple/blist.h	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/blist.h	Mon Sep 12 02:42:41 2011 +0000
@@ -454,21 +454,9 @@
  * Updates a node's custom icon.
  *
  * @param node  The PurpleBlistNode whose custom icon has changed.
- *
- * @since 2.5.0
  */
 void purple_blist_update_node_icon(PurpleBlistNode *node);
 
-#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_BLIST_C_)
-/**
- * Updates a buddy's icon.
- *
- * @param buddy  The buddy whose buddy icon has changed
- * @deprecated Use purple_blist_update_node_icon() instead.
- */
-void purple_blist_update_buddy_icon(PurpleBuddy *buddy);
-#endif
-
 /**
  * Renames a buddy in the buddy list.
  *
@@ -774,18 +762,6 @@
  */
 PurpleBuddy *purple_contact_get_priority_buddy(PurpleContact *contact);
 
-#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_BLIST_C_)
-/**
- * Sets the alias for a contact.
- *
- * @param contact  The contact
- * @param alias    The alias to set, or NULL to unset
- *
- * @deprecated Use purple_blist_alias_contact() instead.
- */
-void purple_contact_set_alias(PurpleContact *contact, const char *alias);
-#endif
-
 /**
  * Gets the alias for a contact.
  *
@@ -876,19 +852,6 @@
  */
 const char *purple_buddy_get_contact_alias(PurpleBuddy *buddy);
 
-#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_BLIST_C_)
-/**
- * Returns the correct alias for this user, ignoring server aliases.  Used
- * when a user-recognizable name is required.  In order: buddy's alias; buddy's
- * contact alias; buddy's user name.
- *
- * @param buddy  The buddy whose alias will be returned.
- * @return       The appropriate name or alias.
- * @deprecated   Try purple_buddy_get_alias(), if server aliases are okay.
- */
-const char *purple_buddy_get_local_alias(PurpleBuddy *buddy);
-#endif
-
 /**
  * Returns the correct name to display for a buddy. In order of precedence:
  * the buddy's alias; the buddy's server alias; the buddy's contact alias;
--- a/libpurple/conversation.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/conversation.c	Mon Sep 12 02:42:41 2011 +0000
@@ -1379,16 +1379,6 @@
 }
 
 GList *
-purple_conv_chat_set_users(PurpleConvChat *chat, GList *users)
-{
-	g_return_val_if_fail(chat != NULL, NULL);
-
-	chat->in_room = users;
-
-	return users;
-}
-
-GList *
 purple_conv_chat_get_users(const PurpleConvChat *chat)
 {
 	g_return_val_if_fail(chat != NULL, NULL);
--- a/libpurple/conversation.h	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/conversation.h	Mon Sep 12 02:42:41 2011 +0000
@@ -1080,22 +1080,6 @@
 PurpleConversation *purple_conv_chat_get_conversation(const PurpleConvChat *chat);
 
 /**
- * Sets the list of users in the chat room.
- *
- * @note Calling this function will not update the display of the users.
- *       Please use purple_conv_chat_add_user(), purple_conv_chat_add_users(),
- *       purple_conv_chat_remove_user(), and purple_conv_chat_remove_users() instead.
- *
- * @param chat  The chat.
- * @param users The list of users.
- *
- * @return The list passed.
- *
- * @deprecated This function will be removed in 3.0.0.  You shouldn't be using it anyway.
- */
-GList *purple_conv_chat_set_users(PurpleConvChat *chat, GList *users);
-
-/**
  * Returns a list of users in the chat room.  The members of the list
  * are PurpleConvChatBuddy objects.
  *
--- a/libpurple/dbus-analyze-functions.py	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/dbus-analyze-functions.py	Mon Sep 12 02:42:41 2011 +0000
@@ -31,6 +31,11 @@
     "purple_account_unregister",
     "purple_connection_new_unregister",
 
+    # Similar to the above, again
+    "purple_menu_action_new",
+    "purple_menu_action_set_callback",
+    "purple_menu_action_get_callback",
+
     # These functions are excluded because they involve setting arbitrary
     # data via pointers for protocols and UIs.  This just won't work.
     "purple_blist_get_ui_data",
--- a/libpurple/plugins/perl/common/BuddyList.xs	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/plugins/perl/common/BuddyList.xs	Mon Sep 12 02:42:41 2011 +0000
@@ -82,11 +82,6 @@
 purple_contact_get_priority_buddy(contact)
 	Purple::BuddyList::Contact contact
 
-void
-purple_contact_set_alias(contact, alias)
-	Purple::BuddyList::Contact contact
-	const char * alias
-
 const char *
 purple_contact_get_alias(contact)
 	Purple::BuddyList::Contact contact
@@ -200,10 +195,6 @@
 	Purple::Status old_status
 
 void
-purple_blist_update_buddy_icon(buddy)
-	Purple::BuddyList::Buddy buddy
-
-void
 purple_blist_rename_buddy(buddy, name)
 	Purple::BuddyList::Buddy buddy
 	const char * name
@@ -430,9 +421,5 @@
 	Purple::BuddyList::Buddy buddy
 
 const char *
-purple_buddy_get_local_alias(buddy)
-	Purple::BuddyList::Buddy buddy
-
-const char *
 purple_buddy_get_alias(buddy)
 	Purple::BuddyList::Buddy buddy
--- a/libpurple/plugins/perl/common/Conversation.xs	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/plugins/perl/common/Conversation.xs	Mon Sep 12 02:42:41 2011 +0000
@@ -338,24 +338,6 @@
 	Purple::Conversation::Chat chat
 
 void
-purple_conv_chat_set_users(chat, users)
-	Purple::Conversation::Chat chat
-	SV * users
-PREINIT:
-	GList *l, *t_GL;
-	int i, t_len;
-PPCODE:
-	t_GL = NULL;
-	t_len = av_len((AV *)SvRV(users));
-
-	for (i = 0; i <= t_len; i++)
-		t_GL = g_list_append(t_GL, SvPVutf8_nolen(*av_fetch((AV *)SvRV(users), i, 0)));
-
-	for (l = purple_conv_chat_set_users(chat, t_GL); l != NULL; l = l->next) {
-		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::ListEntry")));
-	}
-
-void
 purple_conv_chat_get_users(chat)
 	Purple::Conversation::Chat chat
 PREINIT:
--- a/libpurple/protocols/bonjour/bonjour.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Mon Sep 12 02:42:41 2011 +0000
@@ -525,7 +525,6 @@
 	NULL,                                                    /* keepalive */
 	NULL,                                                    /* register_user */
 	NULL,                                                    /* get_cb_info */
-	NULL,                                                    /* get_cb_away */
 	NULL,                                                    /* alias_buddy */
 	bonjour_group_buddy,                                     /* group_buddy */
 	bonjour_rename_group,                                    /* rename_group */
--- a/libpurple/protocols/gg/gg.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/protocols/gg/gg.c	Mon Sep 12 02:42:41 2011 +0000
@@ -2696,7 +2696,6 @@
 	ggp_keepalive,			/* keepalive */
 	ggp_register_user,		/* register_user */
 	NULL,				/* get_cb_info */
-	NULL,				/* get_cb_away */
 	NULL,				/* alias_buddy */
 	NULL,				/* group_buddy */
 	NULL,				/* rename_group */
--- a/libpurple/protocols/irc/irc.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/protocols/irc/irc.c	Mon Sep 12 02:42:41 2011 +0000
@@ -959,7 +959,6 @@
 	irc_keepalive,		/* keepalive */
 	NULL,					/* register_user */
 	NULL,					/* get_cb_info */
-	NULL,					/* get_cb_away */
 	NULL,					/* alias_buddy */
 	NULL,					/* group_buddy */
 	NULL,					/* rename_group */
--- a/libpurple/protocols/jabber/libxmpp.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Mon Sep 12 02:42:41 2011 +0000
@@ -98,7 +98,6 @@
 	jabber_keepalive,				/* keepalive */
 	jabber_register_account,		/* register_user */
 	NULL,							/* get_cb_info */
-	NULL,							/* get_cb_away */
 	jabber_roster_alias_change,		/* alias_buddy */
 	jabber_roster_group_change,		/* group_buddy */
 	jabber_roster_group_rename,		/* rename_group */
--- a/libpurple/protocols/msn/msn.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/protocols/msn/msn.c	Mon Sep 12 02:42:41 2011 +0000
@@ -2971,7 +2971,6 @@
 	msn_keepalive,                      /* keepalive */
 	NULL,                               /* register_user */
 	NULL,                               /* get_cb_info */
-	NULL,                               /* get_cb_away */
 	msn_alias_buddy,                    /* alias_buddy */
 	msn_group_buddy,                    /* group_buddy */
 	msn_rename_group,                   /* rename_group */
--- a/libpurple/protocols/mxit/mxit.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/protocols/mxit/mxit.c	Mon Sep 12 02:42:41 2011 +0000
@@ -716,7 +716,6 @@
 	mxit_keepalive,			/* keepalive */
 	mxit_register,			/* register_user */
 	NULL,					/* get_cb_info */
-	NULL,					/* get_cb_away */
 	mxit_buddy_alias,		/* alias_buddy				[roster.c] */
 	mxit_buddy_group,		/* group_buddy				[roster.c] */
 	mxit_rename_group,		/* rename_group				[roster.c] */
--- a/libpurple/protocols/myspace/myspace.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Mon Sep 12 02:42:41 2011 +0000
@@ -3052,7 +3052,6 @@
 	NULL,              /* keepalive */
 	NULL,              /* register_user */
 	NULL,              /* get_cb_info */
-	NULL,              /* get_cb_away */
 	NULL,              /* alias_buddy */
 	NULL,              /* group_buddy */
 	NULL,              /* rename_group */
--- a/libpurple/protocols/novell/novell.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/protocols/novell/novell.c	Mon Sep 12 02:42:41 2011 +0000
@@ -3515,7 +3515,6 @@
 	novell_keepalive,			/* keepalive */
 	NULL,						/* register_user */
 	NULL,						/* get_cb_info */
-	NULL,						/* get_cb_away */
 	novell_alias_buddy,			/* alias_buddy */
 	novell_group_buddy,			/* group_buddy */
 	novell_rename_group,		/* rename_group */
--- a/libpurple/protocols/null/nullprpl.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/protocols/null/nullprpl.c	Mon Sep 12 02:42:41 2011 +0000
@@ -1101,7 +1101,6 @@
   NULL,                                /* keepalive */
   nullprpl_register_user,              /* register_user */
   nullprpl_get_cb_info,                /* get_cb_info */
-  NULL,                                /* get_cb_away */
   nullprpl_alias_buddy,                /* alias_buddy */
   nullprpl_group_buddy,                /* group_buddy */
   nullprpl_rename_group,               /* rename_group */
--- a/libpurple/protocols/oscar/libaim.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/protocols/oscar/libaim.c	Mon Sep 12 02:42:41 2011 +0000
@@ -70,7 +70,6 @@
 	oscar_keepalive,		/* keepalive */
 	NULL,					/* register_user */
 	NULL,					/* get_cb_info */
-	NULL,					/* get_cb_away */
 	oscar_alias_buddy,		/* alias_buddy */
 	oscar_move_buddy,		/* group_buddy */
 	oscar_rename_group,		/* rename_group */
--- a/libpurple/protocols/oscar/libicq.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/protocols/oscar/libicq.c	Mon Sep 12 02:42:41 2011 +0000
@@ -79,7 +79,6 @@
 	oscar_keepalive,		/* keepalive */
 	NULL,					/* register_user */
 	NULL,					/* get_cb_info */
-	NULL,					/* get_cb_away */
 	oscar_alias_buddy,		/* alias_buddy */
 	oscar_move_buddy,		/* group_buddy */
 	oscar_rename_group,		/* rename_group */
--- a/libpurple/protocols/sametime/sametime.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/protocols/sametime/sametime.c	Mon Sep 12 02:42:41 2011 +0000
@@ -5195,7 +5195,6 @@
   .keepalive                 = mw_prpl_keepalive,
   .register_user             = NULL,
   .get_cb_info               = NULL,
-  .get_cb_away               = NULL,
   .alias_buddy               = mw_prpl_alias_buddy,
   .group_buddy               = mw_prpl_group_buddy,
   .rename_group              = mw_prpl_rename_group,
--- a/libpurple/protocols/silc/silc.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/protocols/silc/silc.c	Mon Sep 12 02:42:41 2011 +0000
@@ -2093,7 +2093,6 @@
 	silcpurple_keepalive,			/* keepalive */
 	NULL,					/* register_user */
 	NULL,					/* get_cb_info */
-	NULL,					/* get_cb_away */
 	NULL,					/* alias_buddy */
 	NULL,					/* group_buddy */
 	NULL,					/* rename_group */
--- a/libpurple/protocols/simple/simple.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/protocols/simple/simple.c	Mon Sep 12 02:42:41 2011 +0000
@@ -2087,7 +2087,6 @@
 	simple_keep_alive,		/* keepalive */
 	NULL,					/* register_user */
 	NULL,					/* get_cb_info */
-	NULL,					/* get_cb_away */
 	NULL,					/* alias_buddy */
 	NULL,					/* group_buddy */
 	NULL,					/* rename_group */
--- a/libpurple/protocols/yahoo/libyahoo.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/protocols/yahoo/libyahoo.c	Mon Sep 12 02:42:41 2011 +0000
@@ -235,7 +235,6 @@
 	yahoo_keepalive,
 	NULL, /* register_user */
 	NULL, /* get_cb_info */
-	NULL, /* get_cb_away */
 	yahoo_update_alias, /* alias_buddy */
 	yahoo_change_buddys_group,
 	yahoo_rename_group,
--- a/libpurple/protocols/yahoo/libyahoojp.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/protocols/yahoo/libyahoojp.c	Mon Sep 12 02:42:41 2011 +0000
@@ -131,7 +131,6 @@
 	yahoo_keepalive,
 	NULL, /* register_user */
 	NULL, /* get_cb_info */
-	NULL, /* get_cb_away */
 	yahoo_update_alias, /* alias_buddy */
 	yahoo_change_buddys_group,
 	yahoo_rename_group,
--- a/libpurple/protocols/zephyr/zephyr.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/protocols/zephyr/zephyr.c	Mon Sep 12 02:42:41 2011 +0000
@@ -2894,7 +2894,6 @@
 	NULL,					/* keepalive -- Not necessary*/
 	NULL,					/* register_user -- Not supported*/
 	NULL,					/* XXX get_cb_info */
-	NULL,					/* get_cb_away */
 	NULL,					/* alias_buddy */
 	NULL,					/* group_buddy */
 	NULL,					/* rename_group */
--- a/libpurple/proxy.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/proxy.c	Mon Sep 12 02:42:41 2011 +0000
@@ -2408,17 +2408,6 @@
 	return connect_data;
 }
 
-PurpleProxyConnectData *
-purple_proxy_connect_socks5(void *handle, PurpleProxyInfo *gpi,
-						  const char *host, int port,
-						  PurpleProxyConnectFunction connect_cb,
-						  gpointer data)
-{
-	return purple_proxy_connect_socks5_account(NULL, handle, gpi,
-						  host, port, connect_cb, data);
-}
-
-
 /* This is called when we connect to the SOCKS5 proxy server (through any
  * relevant account proxy)
  */
--- a/libpurple/proxy.h	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/proxy.h	Mon Sep 12 02:42:41 2011 +0000
@@ -307,35 +307,6 @@
 			const char *host, int port,
 			PurpleProxyConnectFunction connect_cb, gpointer data);
 
-#if !(defined PURPLE_DISABLE_DEPRECATED) || (defined _PURPLE_PROXY_C_)
-/**
- * Makes a connection through a SOCKS5 proxy.
- *
- * @param handle     A handle that should be associated with this
- *                   connection attempt.  The handle can be used
- *                   to cancel the connection attempt using the
- *                   purple_proxy_connect_cancel_with_handle()
- *                   function.
- * @param gpi        The PurpleProxyInfo specifying the proxy settings
- * @param host       The destination host.
- * @param port       The destination port.
- * @param connect_cb The function to call when the connection is
- *                   established.  If the connection failed then
- *                   fd will be -1 and error message will be set
- *                   to something descriptive (hopefully).
- * @param data       User-defined data.
- *
- * @return NULL if there was an error, or a reference to an
- *         opaque data structure that can be used to cancel
- *         the pending connection, if needed.
- * @deprecated Use purple_proxy_connect_socks5_account instead
- */
-PurpleProxyConnectData *purple_proxy_connect_socks5(void *handle,
-			PurpleProxyInfo *gpi,
-			const char *host, int port,
-			PurpleProxyConnectFunction connect_cb, gpointer data);
-#endif
-
 /**
  * Cancel an in-progress connection attempt.  This should be called
  * by the PRPL if the user disables an account while it is still
--- a/libpurple/prpl.h	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/prpl.h	Mon Sep 12 02:42:41 2011 +0000
@@ -448,11 +448,6 @@
 	 * @deprecated Use #PurplePluginProtocolInfo.get_info instead.
 	 */
 	void (*get_cb_info)(PurpleConnection *, int, const char *who);
-	/**
-	 * @deprecated Use #PurplePluginProtocolInfo.get_cb_real_name and
-	 *             #PurplePluginProtocolInfo.status_text instead.
-	 */
-	void (*get_cb_away)(PurpleConnection *, int, const char *who);
 
 	/** save/store buddy's alias on server list/roster */
 	void (*alias_buddy)(PurpleConnection *, const char *who,
--- a/libpurple/util.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/util.c	Mon Sep 12 02:42:41 2011 +0000
@@ -113,8 +113,7 @@
 	return act->label;
 }
 
-PurpleCallback *
-purple_menu_action_get_callback(const PurpleMenuAction *act)
+PurpleCallback purple_menu_action_get_callback(const PurpleMenuAction *act)
 {
 	g_return_val_if_fail(act != NULL, NULL);
 
--- a/libpurple/util.h	Sun Sep 11 04:19:01 2011 +0000
+++ b/libpurple/util.h	Mon Sep 12 02:42:41 2011 +0000
@@ -103,7 +103,7 @@
  *
  * @return The callback function.
  */
-PurpleCallback *purple_menu_action_get_callback(const PurpleMenuAction *act);
+PurpleCallback purple_menu_action_get_callback(const PurpleMenuAction *act);
 
 /**
  * Returns the data stored in the PurpleMenuAction.
--- a/pidgin/Makefile.am	Sun Sep 11 04:19:01 2011 +0000
+++ b/pidgin/Makefile.am	Mon Sep 12 02:42:41 2011 +0000
@@ -83,9 +83,11 @@
 	gtkstatusbox.c \
 	gtkthemes.c \
 	gtkutils.c \
+	gtkwebview.c \
 	gtkwhiteboard.c \
 	minidialog.c \
-	pidgintooltip.c
+	pidgintooltip.c \
+	smileyparser.c
 
 pidgin_headers = \
 	gtkaccount.h \
@@ -133,10 +135,12 @@
 	pidginstock.h \
 	gtkthemes.h \
 	gtkutils.h \
+	gtkwebview.h \
 	gtkwhiteboard.h \
 	minidialog.h \
 	pidgintooltip.h \
-	pidgin.h
+	pidgin.h \
+	smileyparser.h
 
 pidginincludedir=$(includedir)/pidgin
 pidgininclude_HEADERS = \
@@ -155,6 +159,7 @@
 	$(INTLLIBS) \
 	$(GTKSPELL_LIBS) \
 	$(LIBXML_LIBS) \
+	$(WEBKIT_LIBS) \
 	$(GTK_LIBS) \
 	$(top_builddir)/libpurple/libpurple.la
 
@@ -178,6 +183,7 @@
 	$(DBUS_CFLAGS) \
 	$(GTKSPELL_CFLAGS) \
 	$(LIBXML_CFLAGS) \
+	$(WEBKIT_CFLAGS) \
 	$(INTGG_CFLAGS)
 endif  # ENABLE_GTK
 
--- a/pidgin/gtkconv.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/pidgin/gtkconv.c	Mon Sep 12 02:42:41 2011 +0000
@@ -69,6 +69,7 @@
 #include "gtkprivacy.h"
 #include "gtkthemes.h"
 #include "gtkutils.h"
+#include "gtkwebview.h"
 #include "pidginstock.h"
 #include "pidgintooltip.h"
 
@@ -209,7 +210,7 @@
 static const GdkColor *get_nick_color(PidginConversation *gtkconv, const char *name)
 {
 	static GdkColor col;
-	GtkStyle *style = gtk_widget_get_style(gtkconv->imhtml);
+	GtkStyle *style = gtk_widget_get_style(gtkconv->webview);
 	float scale;
 
 	col = nick_colors[g_str_hash(name) % nbr_nick_colors];
@@ -436,8 +437,9 @@
 	PidginConversation *gtkconv = NULL;
 
 	gtkconv = PIDGIN_CONVERSATION(conv);
-	if (gtkconv)
-		gtk_imhtml_clear(GTK_IMHTML(gtkconv->imhtml));
+
+	if (PIDGIN_CONVERSATION(conv))
+		webkit_web_view_load_html_string(WEBKIT_WEB_VIEW(gtkconv->webview), "", "");
 }
 
 static PurpleCmdRet
@@ -1045,32 +1047,7 @@
 static void
 savelog_writefile_cb(void *user_data, const char *filename)
 {
-	PurpleConversation *conv = (PurpleConversation *)user_data;
-	FILE *fp;
-	const char *name;
-	char **lines;
-	gchar *text;
-
-	if ((fp = g_fopen(filename, "w+")) == NULL) {
-		purple_notify_error(PIDGIN_CONVERSATION(conv), NULL, _("Unable to open file."), NULL);
-		return;
-	}
-
-	name = purple_conversation_get_name(conv);
-	fprintf(fp, "<html>\n<head>\n");
-	fprintf(fp, "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n");
-	fprintf(fp, "<title>%s</title>\n</head>\n<body>\n", name);
-	fprintf(fp, _("<h1>Conversation with %s</h1>\n"), name);
-
-	lines = gtk_imhtml_get_markup_lines(
-		GTK_IMHTML(PIDGIN_CONVERSATION(conv)->imhtml));
-	text = g_strjoinv("<br>\n", lines);
-	fprintf(fp, "%s", text);
-	g_free(text);
-	g_strfreev(lines);
-
-	fprintf(fp, "\n</body>\n</html>\n");
-	fclose(fp);
+	/* TODO: I don't know how to support this using webkit yet. */
 }
 
 /*
@@ -1579,32 +1556,6 @@
 }
 
 static void
-menu_chat_get_away_cb(GtkWidget *w, PidginConversation *gtkconv)
-{
-	PurpleConversation *conv = gtkconv->active_conv;
-	PurplePluginProtocolInfo *prpl_info = NULL;
-	PurpleConnection *gc;
-	char *who;
-
-	gc  = purple_conversation_get_gc(conv);
-	who = g_object_get_data(G_OBJECT(w), "user_data");
-
-	if (gc != NULL) {
-		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
-
-		/*
-		 * May want to expand this to work similarly to menu_info_cb?
-		 */
-
-		if (prpl_info->get_cb_away != NULL)
-		{
-			prpl_info->get_cb_away(gc,
-				purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)), who);
-		}
-	}
-}
-
-static void
 menu_chat_add_remove_cb(GtkWidget *w, PidginConversation *gtkconv)
 {
 	PurpleConversation *conv = gtkconv->active_conv;
@@ -1627,7 +1578,7 @@
 static GtkTextMark *
 get_mark_for_user(PidginConversation *gtkconv, const char *who)
 {
-	GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml));
+	GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->webview));
 	char *tmp = g_strconcat("user:", who, NULL);
 	GtkTextMark *mark = gtk_text_buffer_get_mark(buf, tmp);
 
@@ -1638,6 +1589,8 @@
 static void
 menu_last_said_cb(GtkWidget *w, PidginConversation *gtkconv)
 {
+/* FIXME: This doesn't work yet, of course... */
+#if 0
 	GtkTextMark *mark;
 	const char *who;
 
@@ -1648,6 +1601,7 @@
 		gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(gtkconv->imhtml), mark, 0.1, FALSE, 0, 0);
 	else
 		g_return_if_reached();
+#endif
 }
 
 static GtkWidget *
@@ -1735,16 +1689,6 @@
 			g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free);
 	}
 
-	if (prpl_info && prpl_info->get_cb_away) {
-		button = pidgin_new_item_from_stock(menu, _("Get Away Message"), PIDGIN_STOCK_AWAY,
-					G_CALLBACK(menu_chat_get_away_cb), PIDGIN_CONVERSATION(conv), 0, 0, NULL);
-
-		if (gc == NULL)
-			gtk_widget_set_sensitive(button, FALSE);
-		else
-			g_object_set_data_full(G_OBJECT(button), "user_data", g_strdup(who), g_free);
-	}
-
 	if (!is_me && prpl_info && !(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) {
 		if ((buddy = purple_find_buddy(conv->account, who)) != NULL)
 			button = pidgin_new_item_from_stock(menu, _("Remove"), GTK_STOCK_REMOVE,
@@ -1862,10 +1806,13 @@
 		chat_do_im(gtkconv, who);
 	} else if (event->button == 2 && event->type == GDK_BUTTON_PRESS) {
 		/* Move to user's anchor */
+/* FIXME: This isn't implemented yet. */
+#if 0
 		GtkTextMark *mark = get_mark_for_user(gtkconv, who);
 
 		if(mark != NULL)
 			gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(gtkconv->imhtml), mark, 0.1, FALSE, 0, 0);
+#endif
 	} else if (event->button == 3 && event->type == GDK_BUTTON_PRESS) {
 		GtkWidget *menu = create_chat_menu (conv, who, gc);
 		gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
@@ -1942,8 +1889,8 @@
 		GtkWidget *from;
 		GtkWidget *to;
 	} transitions[] = {
-		{gtkconv->entry, gtkconv->imhtml},
-		{gtkconv->imhtml, chat ? gtkconv->u.chat->list : gtkconv->entry},
+		{gtkconv->entry, gtkconv->webview},
+		{gtkconv->webview, chat ? gtkconv->u.chat->list : gtkconv->entry},
 		{chat ? gtkconv->u.chat->list : NULL, gtkconv->entry},
 		{NULL, NULL}
 	}, *ptr;
@@ -2199,14 +2146,20 @@
 			break;
 
 		case GDK_Page_Up:
- 		case GDK_KP_Page_Up:
+		case GDK_KP_Page_Up:
+/* FIXME: Write this. */
+#if 0
 			gtk_imhtml_page_up(GTK_IMHTML(gtkconv->imhtml));
+#endif
 			return TRUE;
 			break;
 
 		case GDK_Page_Down:
- 		case GDK_KP_Page_Down:
+		case GDK_KP_Page_Down:
+/* FIXME: Write this. */
+#if 0
 			gtk_imhtml_page_down(GTK_IMHTML(gtkconv->imhtml));
+#endif
 			return TRUE;
 			break;
 
@@ -2316,7 +2269,7 @@
 	entry = GTK_IMHTML(gtkconv->entry);
 	protocol_name = purple_account_get_protocol_name(conv->account);
 	gtk_imhtml_set_protocol_name(entry, protocol_name);
-	gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml), protocol_name);
+	/* FIXME: gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml), protocol_name); */
 
 	if (!(conv->features & PURPLE_CONNECTION_HTML))
 		gtk_imhtml_clear_formatting(GTK_IMHTML(gtkconv->entry));
@@ -3252,11 +3205,11 @@
 	if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
 		chat = purple_blist_find_chat(conv->account, conv->name);
 
-		if ((chat == NULL) && (gtkconv->imhtml != NULL)) {
-			chat = g_object_get_data(G_OBJECT(gtkconv->imhtml), "transient_chat");
-		}
-
-		if ((chat == NULL) && (gtkconv->imhtml != NULL)) {
+		if ((chat == NULL) && (gtkconv->webview != NULL)) {
+			chat = g_object_get_data(G_OBJECT(gtkconv->webview), "transient_chat");
+		}
+
+		if ((chat == NULL) && (gtkconv->webview != NULL)) {
 			GHashTable *components;
 			PurpleAccount *account = purple_conversation_get_account(conv);
 			PurplePlugin *prpl = purple_find_prpl(purple_account_get_protocol_id(account));
@@ -3274,7 +3227,7 @@
 			chat = purple_chat_new(conv->account, NULL, components);
 			purple_blist_node_set_flags((PurpleBlistNode *)chat,
 					PURPLE_BLIST_NODE_FLAG_NO_SAVE);
-			g_object_set_data_full(G_OBJECT(gtkconv->imhtml), "transient_chat",
+			g_object_set_data_full(G_OBJECT(gtkconv->webview), "transient_chat",
 					chat, (GDestroyNotify)purple_blist_remove_chat);
 		}
 	} else {
@@ -3286,15 +3239,15 @@
 		/* gotta remain bug-compatible :( libpurple < 2.0.2 didn't handle
 		 * removing "isolated" buddy nodes well */
 		if (purple_version_check(2, 0, 2) == NULL) {
-			if ((buddy == NULL) && (gtkconv->imhtml != NULL)) {
-				buddy = g_object_get_data(G_OBJECT(gtkconv->imhtml), "transient_buddy");
+			if ((buddy == NULL) && (gtkconv->webview != NULL)) {
+				buddy = g_object_get_data(G_OBJECT(gtkconv->webview), "transient_buddy");
 			}
 
-			if ((buddy == NULL) && (gtkconv->imhtml != NULL)) {
+			if ((buddy == NULL) && (gtkconv->webview != NULL)) {
 				buddy = purple_buddy_new(conv->account, conv->name, NULL);
 				purple_blist_node_set_flags((PurpleBlistNode *)buddy,
 						PURPLE_BLIST_NODE_FLAG_NO_SAVE);
-				g_object_set_data_full(G_OBJECT(gtkconv->imhtml), "transient_buddy",
+				g_object_set_data_full(G_OBJECT(gtkconv->webview), "transient_buddy",
 						buddy, (GDestroyNotify)purple_buddy_destroy);
 			}
 		}
@@ -3705,6 +3658,8 @@
 static void
 update_typing_message(PidginConversation *gtkconv, const char *message)
 {
+	/* FIXME: this is not handled at all */
+#if 0
 	GtkTextBuffer *buffer;
 	GtkTextMark *stmark, *enmark;
 
@@ -3737,6 +3692,7 @@
 		gtk_text_buffer_get_end_iter(buffer, &iter);
 		gtk_text_buffer_create_mark(buffer, "typing-notification-end", &iter, TRUE);
 	}
+#endif
 }
 
 static void
@@ -3978,7 +3934,8 @@
 						continue;
 
 					account = purple_buddy_get_account(buddy);
-					if (purple_account_is_connected(account) || account == gtkconv->active_conv->account)
+					/* FIXME: */
+					if (purple_account_is_connected(account) /*|| account == gtkconv->active_conv->account*/)
 					{
 						/* Use the PurplePresence to get unique buddies. */
 						PurplePresence *presence = purple_buddy_get_presence(buddy);
@@ -4090,10 +4047,13 @@
 	g_free(tmp);
 
 	if (is_me) {
+#if 0
+		/* FIXME: No tags in webkit stuff, yet. */
 		GtkTextTag *tag = gtk_text_tag_table_lookup(
-				gtk_text_buffer_get_tag_table(GTK_IMHTML(gtkconv->imhtml)->text_buffer),
+				gtk_text_buffer_get_tag_table(GTK_IMHTML(gtkconv->webview)->text_buffer),
 				"send-name");
 		g_object_get(tag, "foreground-gdk", &color, NULL);
+#endif
 	} else {
 		GtkTextTag *tag;
 		if ((tag = get_buddy_tag(conv, name, 0, FALSE)))
@@ -4657,7 +4617,7 @@
 	GdkRectangle oneline;
 	int height, diff;
 	int pad_top, pad_inside, pad_bottom;
-	int total_height = (gtkconv->imhtml->allocation.height + gtkconv->entry->allocation.height);
+	int total_height = (gtkconv->webview->allocation.height + gtkconv->entry->allocation.height);
 	int max_height = total_height / 2;
 	int min_lines = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/minimum_entry_lines");
 	int min_height;
@@ -4897,13 +4857,13 @@
 	if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
 		node = (PurpleBlistNode*)(purple_blist_find_chat(conv->account, conv->name));
 		if (!node)
-			node = g_object_get_data(G_OBJECT(gtkconv->imhtml), "transient_chat");
+			node = g_object_get_data(G_OBJECT(gtkconv->webview), "transient_chat");
 	} else {
 		node = (PurpleBlistNode*)(purple_find_buddy(conv->account, conv->name));
 #if 0
 		/* Using the transient blist nodes to show the tooltip doesn't quite work yet. */
 		if (!node)
-			node = g_object_get_data(G_OBJECT(gtkconv->imhtml), "transient_buddy");
+			node = g_object_get_data(G_OBJECT(gtkconv->webview), "transient_buddy");
 #endif
 	}
 
@@ -4918,7 +4878,7 @@
 {
 	gtk_widget_modify_base(gtkconv->quickfind.entry, GTK_STATE_NORMAL, NULL);
 
-	gtk_imhtml_search_clear(GTK_IMHTML(gtkconv->imhtml));
+	webkit_web_view_unmark_text_matches(WEBKIT_WEB_VIEW(gtkconv->webview));
 	gtk_widget_hide_all(gtkconv->quickfind.container);
 
 	gtk_widget_grab_focus(gtkconv->entry);
@@ -4931,7 +4891,7 @@
 	switch (event->keyval) {
 		case GDK_Return:
 		case GDK_KP_Enter:
-			if (gtk_imhtml_search_find(GTK_IMHTML(gtkconv->imhtml), gtk_entry_get_text(GTK_ENTRY(entry)))) {
+			if (webkit_web_view_search_text(WEBKIT_WEB_VIEW(gtkconv->webview), gtk_entry_get_text(GTK_ENTRY(entry)), FALSE, TRUE, TRUE)) {
 				gtk_widget_modify_base(gtkconv->quickfind.entry, GTK_STATE_NORMAL, NULL);
 			} else {
 				GdkColor col;
@@ -4984,7 +4944,7 @@
 static GtkWidget *
 setup_common_pane(PidginConversation *gtkconv)
 {
-	GtkWidget *vbox, *frame, *imhtml_sw, *event_box;
+	GtkWidget *vbox, *frame, *webview_sw, *event_box;
 	GtkCellRenderer *rend;
 	GtkTreePath *path;
 	PurpleConversation *conv = gtkconv->active_conv;
@@ -5080,9 +5040,10 @@
 	gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv->infopane), rend, "pixbuf", CONV_EMBLEM_COLUMN, NULL);
 	g_object_set(rend, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL);
 
-	/* Setup the gtkimhtml widget */
-	frame = pidgin_create_imhtml(FALSE, &gtkconv->imhtml, NULL, &imhtml_sw);
-	gtk_widget_set_size_request(gtkconv->imhtml, -1, 0);
+	/* Setup the webkit widget */
+	frame = pidgin_create_webview(FALSE, &gtkconv->webview, NULL, &webview_sw);
+	gtk_widget_set_size_request(gtkconv->webview, -1, 0);
+
 	if (chat) {
 		GtkWidget *hpaned;
 
@@ -5100,19 +5061,16 @@
 	} else {
 		gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
 	}
-	gtk_widget_show(frame);
-
-	gtk_widget_set_name(gtkconv->imhtml, "pidgin_conv_imhtml");
-	gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml),TRUE);
-	g_object_set_data(G_OBJECT(gtkconv->imhtml), "gtkconv", gtkconv);
-
-	g_object_set(G_OBJECT(imhtml_sw), "vscrollbar-policy", GTK_POLICY_ALWAYS, NULL);
-
-	g_signal_connect_after(G_OBJECT(gtkconv->imhtml), "button_press_event",
+	gtk_widget_show_all(frame);
+
+	gtk_widget_set_name(gtkconv->webview, "pidgin_conv_webview");
+	g_object_set_data(G_OBJECT(gtkconv->webview), "gtkconv", gtkconv);
+
+	g_signal_connect_after(G_OBJECT(gtkconv->webview), "button_press_event",
 	                       G_CALLBACK(entry_stop_rclick_cb), NULL);
-	g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_press_event",
+	g_signal_connect(G_OBJECT(gtkconv->webview), "key_press_event",
 	                 G_CALLBACK(refocus_entry_cb), gtkconv);
-	g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_release_event",
+	g_signal_connect(G_OBJECT(gtkconv->webview), "key_release_event",
 	                 G_CALLBACK(refocus_entry_cb), gtkconv);
 
 	pidgin_conv_setup_quickfind(gtkconv, vbox);
@@ -5353,6 +5311,8 @@
 
 static void set_typing_font(GtkWidget *widget, GtkStyle *style, PidginConversation *gtkconv)
 {
+/* FIXME */
+#if 0
 	static PangoFontDescription *font_desc = NULL;
 	static GdkColor *color = NULL;
 	static gboolean enable = TRUE;
@@ -5383,6 +5343,7 @@
 	}
 
 	g_signal_handlers_disconnect_by_func(G_OBJECT(widget), set_typing_font, gtkconv);
+#endif
 }
 
 /**************************************************************************
@@ -5424,9 +5385,6 @@
 	}
 	pane = setup_common_pane(gtkconv);
 
-	gtk_imhtml_set_format_functions(GTK_IMHTML(gtkconv->imhtml),
-			gtk_imhtml_get_format_functions(GTK_IMHTML(gtkconv->imhtml)) | GTK_IMHTML_IMAGE);
-
 	if (pane == NULL) {
 		if (conv_type == PURPLE_CONV_TYPE_CHAT)
 			g_free(gtkconv->u.chat);
@@ -5449,7 +5407,7 @@
 	                  GTK_DEST_DEFAULT_DROP,
 	                  te, sizeof(te) / sizeof(GtkTargetEntry),
 	                  GDK_ACTION_COPY);
-	gtk_drag_dest_set(gtkconv->imhtml, 0,
+	gtk_drag_dest_set(gtkconv->webview, 0,
 	                  te, sizeof(te) / sizeof(GtkTargetEntry),
 	                  GDK_ACTION_COPY);
 
@@ -5461,12 +5419,12 @@
 	                 G_CALLBACK(ignore_middle_click), NULL);
 	g_signal_connect(G_OBJECT(pane), "drag_data_received",
 	                 G_CALLBACK(conv_dnd_recv), gtkconv);
-	g_signal_connect(G_OBJECT(gtkconv->imhtml), "drag_data_received",
+	g_signal_connect(G_OBJECT(gtkconv->webview), "drag_data_received",
 	                 G_CALLBACK(conv_dnd_recv), gtkconv);
 	g_signal_connect(G_OBJECT(gtkconv->entry), "drag_data_received",
 	                 G_CALLBACK(conv_dnd_recv), gtkconv);
 
-	g_signal_connect(gtkconv->imhtml, "style-set", G_CALLBACK(set_typing_font), gtkconv);
+	g_signal_connect(gtkconv->webview, "style-set", G_CALLBACK(set_typing_font), gtkconv);
 
 	/* Setup the container for the tab. */
 	gtkconv->tab_cont = tab_cont = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
@@ -5496,10 +5454,6 @@
 	else
 		gtk_widget_hide(gtkconv->infopane_hbox);
 
-	gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml),
-		purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/show_timestamps"));
-	gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml),
-								 purple_account_get_protocol_name(conv->account));
 
 	g_signal_connect_swapped(G_OBJECT(pane), "focus",
 	                         G_CALLBACK(gtk_widget_grab_focus),
@@ -5512,7 +5466,7 @@
 
 	if (nick_colors == NULL) {
 		nbr_nick_colors = NUM_NICK_COLORS;
-		nick_colors = generate_nick_colors(&nbr_nick_colors, gtk_widget_get_style(gtkconv->imhtml)->base[GTK_STATE_NORMAL]);
+		nick_colors = generate_nick_colors(&nbr_nick_colors, gtk_widget_get_style(gtkconv->webview)->base[GTK_STATE_NORMAL]);
 	}
 
 	if (conv->features & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY)
@@ -5737,6 +5691,8 @@
 static GtkTextTag *get_buddy_tag(PurpleConversation *conv, const char *who, PurpleMessageFlags flag,
 		gboolean create)
 {
+/* FIXME */
+#if 0
 	PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
 	GtkTextTag *buddytag;
 	gchar *str;
@@ -5770,6 +5726,8 @@
 	g_free(str);
 
 	return buddytag;
+#endif
+	return NULL;
 }
 
 static void pidgin_conv_calculate_newday(PidginConversation *gtkconv, time_t mtime)
@@ -5828,8 +5786,6 @@
 	PurpleAccount *account;
 	int gtk_font_options = 0;
 	int gtk_font_options_all = 0;
-	int max_scrollback_lines;
-	int line_count;
 	char buf2[BUF_LONG];
 	gboolean show_date;
 	char *mdate;
@@ -5840,8 +5796,6 @@
 	PurpleConversationType type;
 	char *displaying;
 	gboolean plugin_return;
-	char *bracket;
-	int tag_count = 0;
 	gboolean is_rtl_message = FALSE;
 
 	g_return_if_fail(conv != NULL);
@@ -5899,56 +5853,9 @@
 	}
 	length = strlen(displaying) + 1;
 
-	/* Awful hack to work around GtkIMHtml's inefficient rendering of messages with lots of formatting changes.
-	 * If a message has over 100 '<' characters, strip formatting before appending it. Hopefully nobody actually
-	 * needs that much formatting, anyway.
-	 */
-	for (bracket = strchr(displaying, '<'); bracket && *(bracket + 1); bracket = strchr(bracket + 1, '<'))
-		tag_count++;
-
-	if (tag_count > 100) {
-		char *tmp = displaying;
-		displaying = purple_markup_strip_html(tmp);
-		g_free(tmp);
-	}
-
-	line_count = gtk_text_buffer_get_line_count(
-			gtk_text_view_get_buffer(GTK_TEXT_VIEW(
-				gtkconv->imhtml)));
-
-	max_scrollback_lines = purple_prefs_get_int(
-		PIDGIN_PREFS_ROOT "/conversations/scrollback_lines");
-	/* If we're sitting at more than 100 lines more than the
-	   max scrollback, trim down to max scrollback */
-	if (max_scrollback_lines > 0
-			&& line_count > (max_scrollback_lines + 100)) {
-		GtkTextBuffer *text_buffer = gtk_text_view_get_buffer(
-			GTK_TEXT_VIEW(gtkconv->imhtml));
-		GtkTextIter start, end;
-
-		gtk_text_buffer_get_start_iter(text_buffer, &start);
-		gtk_text_buffer_get_iter_at_line(text_buffer, &end,
-			(line_count - max_scrollback_lines));
-		gtk_imhtml_delete(GTK_IMHTML(gtkconv->imhtml), &start, &end);
-	}
-
-	if (type == PURPLE_CONV_TYPE_CHAT)
-	{
-		/* Create anchor for user */
-		GtkTextIter iter;
-		char *tmp = g_strconcat("user:", name, NULL);
-
-		gtk_text_buffer_get_end_iter(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)), &iter);
-		gtk_text_buffer_create_mark(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)),
-								tmp, &iter, TRUE);
-		g_free(tmp);
-	}
-
-	if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/use_smooth_scrolling"))
-		gtk_font_options_all |= GTK_IMHTML_USE_SMOOTHSCROLLING;
-
-	if (gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml))))
-		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR>", gtk_font_options_all | GTK_IMHTML_NO_SCROLL);
+	/* if the buffer is not empty add a <br> */
+	if (!gtk_webview_is_empty(GTK_WEBVIEW(gtkconv->webview)))
+		gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), "<br />");
 
 	/* First message in a conversation. */
 	if (gtkconv->newday == 0)
@@ -5999,32 +5906,32 @@
 	if (!(flags & PURPLE_MESSAGE_RECV) && (conv->features & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY))
 	{
 		/* We want to see our own smileys. Need to revert it after send*/
-		pidgin_themes_smiley_themeize_custom(gtkconv->imhtml);
+		pidgin_themes_smiley_themeize_custom(gtkconv->webview);
 	}
 
 	/* TODO: These colors should not be hardcoded so log.c can use them */
 	if (flags & PURPLE_MESSAGE_RAW) {
-		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), message, gtk_font_options_all);
+		gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), message);
 	} else if (flags & PURPLE_MESSAGE_SYSTEM) {
 		g_snprintf(buf2, sizeof(buf2),
-			   "<FONT %s><FONT SIZE=\"2\"><!--%s --></FONT><B>%s</B></FONT>",
+			   "<font %s><font size=\"2\"><span class='timestamp'>%s</span></font><b>%s</b></font>",
 			   sml_attrib ? sml_attrib : "", mdate, displaying);
 
-		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all);
+		gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), buf2);
 
 	} else if (flags & PURPLE_MESSAGE_ERROR) {
 		g_snprintf(buf2, sizeof(buf2),
-			   "<FONT COLOR=\"#ff0000\"><FONT %s><FONT SIZE=\"2\"><!--%s --></FONT><B>%s</B></FONT></FONT>",
+			   "<font color=\"#ff0000\"><font %s><font size=\"2\"><span class='timestamp'>%s</span> </font><b>%s</b></font></font>",
 			   sml_attrib ? sml_attrib : "", mdate, displaying);
 
-		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all);
+		gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), buf2);
 
 	} else if (flags & PURPLE_MESSAGE_NO_LOG) {
 		g_snprintf(buf2, BUF_LONG,
-			   "<B><FONT %s COLOR=\"#777777\">%s</FONT></B>",
+			   "<b><font %s color=\"#777777\">%s</font></b>",
 			   sml_attrib ? sml_attrib : "", displaying);
 
-		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all);
+		gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), buf2);
 	} else {
 		char *new_message = g_memdup(displaying, length);
 		char *alias_escaped = (alias ? g_markup_escape_text(alias, strlen(alias)) : g_strdup(""));
@@ -6034,11 +5941,6 @@
 		int tag_end_offset = 0;
 		const char *tagname = NULL;
 
-		GtkTextIter start, end;
-		GtkTextMark *mark;
-		GtkTextTag *tag;
-		GtkTextBuffer *buffer = GTK_IMHTML(gtkconv->imhtml)->text_buffer;
-
 		/* Enforce direction on alias */
 		if (is_rtl_message)
 			str_embed_direction_chars(&alias_escaped);
@@ -6099,55 +6001,41 @@
 
 		g_free(alias_escaped);
 
+		/* FIXME: */
+#if 0
 		if (tagname)
 			tag = gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table(buffer), tagname);
 		else
 			tag = get_buddy_tag(conv, name, flags, TRUE);
 
 		if (GTK_IMHTML(gtkconv->imhtml)->show_comments) {
+		{
 			/* The color for the timestamp has to be set in the font-tags, unfortunately.
 			 * Applying the nick-tag to timestamps would work, but that can make it
 			 * bold. I thought applying the "comment" tag again, which has "weight" set
 			 * to PANGO_WEIGHT_NORMAL, would remove the boldness. But it doesn't. So
 			 * this will have to do. I don't terribly like it.  -- sadrul */
-			const char *color = get_text_tag_color(tag);
+			/* const char *color = get_text_tag_color(tag); */
 			g_snprintf(buf2, BUF_LONG, "<FONT %s%s%s SIZE=\"2\"><!--%s --></FONT>",
 					color ? "COLOR=\"" : "", color ? color : "", color ? "\"" : "", mdate);
-			gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all | GTK_IMHTML_NO_SCROLL);
-		}
-
-		gtk_text_buffer_get_end_iter(buffer, &end);
-		mark = gtk_text_buffer_create_mark(buffer, NULL, &end, TRUE);
-
-		g_snprintf(buf2, BUF_LONG, "<FONT %s>%s</FONT> ", sml_attrib ? sml_attrib : "", str);
-		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), buf2, gtk_font_options_all | GTK_IMHTML_NO_SCROLL);
-
-		gtk_text_buffer_get_end_iter(buffer, &end);
-		gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
-		gtk_text_buffer_apply_tag(buffer, tag, &start, &end);
-		gtk_text_buffer_delete_mark(buffer, mark);
+			gtk_webview_append_html (GTK_WEBVIEW(gtkconv->webview), buf2);
+		}
+#endif
+		g_snprintf(buf2, BUF_LONG, "<font %s>%s</font> ", sml_attrib ? sml_attrib : "", str);
+		gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), buf2);
 
 		g_free(str);
 
-		if(gc){
+		if (gc) {
 			char *pre = g_strdup_printf("<font %s>", sml_attrib ? sml_attrib : "");
 			char *post = "</font>";
-			int pre_len = strlen(pre);
-			int post_len = strlen(post);
-
-			with_font_tag = g_malloc(length + pre_len + post_len + 1);
-
-			strcpy(with_font_tag, pre);
-			memcpy(with_font_tag + pre_len, new_message, length);
-			strcpy(with_font_tag + pre_len + length, post);
-
-			length += pre_len + post_len;
+			with_font_tag = g_strdup_printf("%s%s%s", pre, new_message, post);
 			g_free(pre);
 		} else
 			with_font_tag = g_memdup(new_message, length);
 
-		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml),
-							 with_font_tag, gtk_font_options | gtk_font_options_all);
+		gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview),
+							 with_font_tag);
 
 		g_free(with_font_tag);
 		g_free(new_message);
@@ -6177,7 +6065,7 @@
 	if (!(flags & PURPLE_MESSAGE_RECV) && (conv->features & PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY))
 	{
 		/* Restore the smiley-data */
-		pidgin_themes_smiley_themeize(gtkconv->imhtml);
+		pidgin_themes_smiley_themeize(gtkconv->webview);
 	}
 
 	purple_signal_emit(pidgin_conversations_get_handle(),
@@ -6423,6 +6311,13 @@
 }
 
 static gboolean
+add_custom_smiley_for_webview(GtkWebView *webview, const char *sml, const char *smile)
+{
+	/* FIXME: Smileys need to be added to webkit stuff */
+	return TRUE;
+}
+
+static gboolean
 pidgin_conv_custom_smiley_add(PurpleConversation *conv, const char *smile, gboolean remote)
 {
 	PidginConversation *gtkconv;
@@ -6449,7 +6344,7 @@
 		}
 	}
 
-	if (!add_custom_smiley_for_imhtml(GTK_IMHTML(gtkconv->imhtml), sml, smile))
+	if (!add_custom_smiley_for_webview(GTK_WEBVIEW(gtkconv->webview), sml, smile))
 		return FALSE;
 
 	if (!remote)	/* If it's a local custom smiley, then add it for the entry */
@@ -6463,6 +6358,8 @@
 pidgin_conv_custom_smiley_write(PurpleConversation *conv, const char *smile,
                                       const guchar *data, gsize size)
 {
+/* FIXME */
+#if 0
 	PidginConversation *gtkconv;
 	GtkIMHtmlSmiley *smiley;
 	const char *sml;
@@ -6497,11 +6394,14 @@
 		g_object_unref(G_OBJECT(smiley->loader));
 		smiley->loader = gdk_pixbuf_loader_new();
 	}
+#endif
 }
 
 static void
 pidgin_conv_custom_smiley_close(PurpleConversation *conv, const char *smile)
 {
+/* FIXME*/
+#if 0
 	PidginConversation *gtkconv;
 	GtkIMHtmlSmiley *smiley;
 	const char *sml;
@@ -6538,6 +6438,7 @@
 		g_object_unref(G_OBJECT(smiley->loader));
 		smiley->loader = gdk_pixbuf_loader_new();
 	}
+#endif
 }
 
 static void
@@ -6811,7 +6712,7 @@
 	}
 
 	if (fields & PIDGIN_CONV_SMILEY_THEME)
-		pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->imhtml);
+		pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->webview);
 
 	if ((fields & PIDGIN_CONV_COLORIZE_TITLE) ||
 			(fields & PIDGIN_CONV_SET_TITLE) ||
@@ -7389,8 +7290,11 @@
 		        GTK_CHECK_MENU_ITEM(win->menu.show_timestamps),
 		        (gboolean)GPOINTER_TO_INT(value));
 
+/* FIXME: Use WebKit version of this. */
+#if 0
 		gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml),
 			(gboolean)GPOINTER_TO_INT(value));
+#endif
 	}
 }
 
@@ -7791,7 +7695,7 @@
 	while (gtkconv->attach.current && count < 100) {  /* XXX: 100 is a random value here */
 		PurpleConvMessage *msg = gtkconv->attach.current->data;
 		if (!im && when && when < msg->when) {
-			gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR><HR>", 0);
+			gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), "<BR><HR>");
 			g_object_set_data(G_OBJECT(gtkconv->entry), "attach-start-time", NULL);
 		}
 		pidgin_conv_write_conv(msg->conv, msg->who, msg->alias, msg->what, msg->flags, msg->when);
@@ -7826,7 +7730,7 @@
 			PurpleConvMessage *msg = msgs->data;
 			pidgin_conv_write_conv(msg->conv, msg->who, msg->alias, msg->what, msg->flags, msg->when);
 		}
-		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR><HR>", 0);
+		gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), "<BR><HR>");
 		g_object_set_data(G_OBJECT(gtkconv->entry), "attach-start-time", NULL);
 	}
 
--- a/pidgin/gtkconv.h	Sun Sep 11 04:19:01 2011 +0000
+++ b/pidgin/gtkconv.h	Mon Sep 12 02:42:41 2011 +0000
@@ -95,7 +95,7 @@
 	GtkWidget *tabby;
 	GtkWidget *menu_tabby;
 
-	GtkWidget *imhtml;
+	GtkWidget *webview;
 	GtkTextBuffer *entry_buffer;
 	GtkWidget *entry;
 	gboolean auto_resize;   /* this is set to TRUE if the conversation
--- a/pidgin/gtkdialogs.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/pidgin/gtkdialogs.c	Mon Sep 12 02:42:41 2011 +0000
@@ -39,10 +39,9 @@
 
 #include "gtkblist.h"
 #include "gtkdialogs.h"
-#include "gtkimhtml.h"
-#include "gtkimhtmltoolbar.h"
 #include "gtklog.h"
 #include "gtkutils.h"
+#include "gtkwebview.h"
 #include "pidginstock.h"
 
 static GList *dialogwindows = NULL;
@@ -422,9 +421,8 @@
 static GtkWidget *
 pidgin_build_help_dialog(const char *title, const char *role, GString *string)
 {
-	GtkWidget *win, *vbox, *frame, *logo, *imhtml, *button;
+	GtkWidget *win, *vbox, *frame, *logo, *webview, *button;
 	GdkPixbuf *pixbuf;
-	GtkTextIter iter;
 	AtkObject *obj;
 	char *filename, *tmp;
 
@@ -451,13 +449,15 @@
 	g_free(tmp);
 	gtk_box_pack_start(GTK_BOX(vbox), logo, FALSE, FALSE, 0);
 
-	frame = pidgin_create_imhtml(FALSE, &imhtml, NULL, NULL);
+	frame = pidgin_create_webview(FALSE, &webview, NULL, NULL);
+	/* FIXME: Compile now and fix it later when we have a proper replacement for this function
 	gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml), GTK_IMHTML_ALL ^ GTK_IMHTML_SMILEY);
+	*/
 	gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
 
-	gtk_imhtml_append_text(GTK_IMHTML(imhtml), string->str, GTK_IMHTML_NO_SCROLL);
-	gtk_text_buffer_get_start_iter(gtk_text_view_get_buffer(GTK_TEXT_VIEW(imhtml)), &iter);
-	gtk_text_buffer_place_cursor(gtk_text_view_get_buffer(GTK_TEXT_VIEW(imhtml)), &iter);
+	gtk_webview_append_html(GTK_WEBVIEW(webview), string->str);
+	/* FIXME: This doesn't seem to stay at the top. */
+	webkit_web_view_move_cursor(WEBKIT_WEB_VIEW(webview), GTK_MOVEMENT_BUFFER_ENDS, -1);
 
 	button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE,
 	                G_CALLBACK(destroy_win), win);
--- a/pidgin/gtkdocklet.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/pidgin/gtkdocklet.c	Mon Sep 12 02:42:41 2011 +0000
@@ -715,6 +715,7 @@
 {
 	static GtkWidget *menu = NULL;
 	GtkWidget *menuitem;
+	GtkMenuPositionFunc pos_func = gtk_status_icon_position_menu;
 
 	if (menu) {
 		gtk_widget_destroy(menu);
@@ -790,10 +791,11 @@
 #ifdef _WIN32
 	g_signal_connect(menu, "leave-notify-event", G_CALLBACK(docklet_menu_leave_enter), NULL);
 	g_signal_connect(menu, "enter-notify-event", G_CALLBACK(docklet_menu_leave_enter), NULL);
+	pos_func = NULL;
 #endif
 	gtk_widget_show_all(menu);
 	gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
-		       gtk_status_icon_position_menu,
+		       pos_func,
 		       docklet, 0, gtk_get_current_event_time());
 }
 
@@ -856,6 +858,7 @@
 	return FALSE;
 }
 
+#ifndef _WIN32
 static gboolean
 docklet_gtk_embed_timeout_cb(gpointer data)
 {
@@ -888,6 +891,7 @@
 	return TRUE;
 #endif
 }
+#endif
 
 #if GTK_CHECK_VERSION(2,12,0)
 static gboolean
@@ -1003,6 +1007,7 @@
 	 */
 	if (!recreate) {
 		pidgin_docklet_embedded();
+#ifndef _WIN32
 #if GTK_CHECK_VERSION(2,12,0)
 		if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/docklet/gtk/embedded")) {
 			embed_timeout = purple_timeout_add_seconds(LONG_EMBED_TIMEOUT, docklet_gtk_embed_timeout_cb, NULL);
@@ -1012,6 +1017,7 @@
 #else
 		embed_timeout = purple_timeout_add_seconds(SHORT_EMBED_TIMEOUT, docklet_gtk_embed_timeout_cb, NULL);
 #endif
+#endif
 	}
 
 	purple_debug_info("docklet", "GTK+ created\n");
--- a/pidgin/gtklog.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/pidgin/gtklog.c	Mon Sep 12 02:42:41 2011 +0000
@@ -35,9 +35,9 @@
 
 #include "pidginstock.h"
 #include "gtkblist.h"
-#include "gtkimhtml.h"
 #include "gtklog.h"
 #include "gtkutils.h"
+#include "gtkwebview.h"
 
 static GHashTable *log_viewers = NULL;
 static void populate_log_tree(PidginLogViewer *lv);
@@ -130,7 +130,7 @@
 		populate_log_tree(lv);
 		g_free(lv->search);
 		lv->search = NULL;
-		gtk_imhtml_search_clear(GTK_IMHTML(lv->imhtml));
+		webkit_web_view_unmark_text_matches(WEBKIT_WEB_VIEW(lv->web_view));
 		select_first_log(lv);
 		return;
 	}
@@ -138,7 +138,7 @@
 	if (lv->search != NULL && !strcmp(lv->search, search_term))
 	{
 		/* Searching for the same term acts as "Find Next" */
-		gtk_imhtml_search_find(GTK_IMHTML(lv->imhtml), lv->search);
+		webkit_web_view_search_text(WEBKIT_WEB_VIEW(lv->web_view), lv->search, FALSE, TRUE, TRUE);
 		return;
 	}
 
@@ -148,7 +148,7 @@
 	lv->search = g_strdup(search_term);
 
 	gtk_tree_store_clear(lv->treestore);
-	gtk_imhtml_clear(GTK_IMHTML(lv->imhtml));
+	webkit_web_view_open(WEBKIT_WEB_VIEW(lv->web_view), "about:blank"); /* clear the view */
 
 	for (logs = lv->logs; logs != NULL; logs = logs->next) {
 		char *read = purple_log_read((PurpleLog*)logs->data, NULL);
@@ -419,8 +419,9 @@
 static gboolean search_find_cb(gpointer data)
 {
 	PidginLogViewer *viewer = data;
-	gtk_imhtml_search_find(GTK_IMHTML(viewer->imhtml), viewer->search);
-	g_object_steal_data(G_OBJECT(viewer->entry), "search-find-cb");
+	webkit_web_view_mark_text_matches(WEBKIT_WEB_VIEW(viewer->web_view), viewer->search, FALSE, 0);
+	webkit_web_view_set_highlight_text_matches(WEBKIT_WEB_VIEW(viewer->web_view), TRUE);
+	webkit_web_view_search_text(WEBKIT_WEB_VIEW(viewer->web_view), viewer->search, FALSE, TRUE, TRUE);
 	return FALSE;
 }
 
@@ -461,23 +462,16 @@
 	read = purple_log_read(log, &flags);
 	viewer->flags = flags;
 
-	gtk_imhtml_clear(GTK_IMHTML(viewer->imhtml));
-	gtk_imhtml_set_protocol_name(GTK_IMHTML(viewer->imhtml),
-	                            purple_account_get_protocol_name(log->account));
+	webkit_web_view_open(WEBKIT_WEB_VIEW(viewer->web_view), "about:blank");
 
 	purple_signal_emit(pidgin_log_get_handle(), "log-displaying", viewer, log);
 
-	gtk_imhtml_append_text(GTK_IMHTML(viewer->imhtml), read,
-			       GTK_IMHTML_NO_COMMENTS | GTK_IMHTML_NO_TITLE | GTK_IMHTML_NO_SCROLL |
-			       ((flags & PURPLE_LOG_READ_NO_NEWLINE) ? GTK_IMHTML_NO_NEWLINE : 0));
+	webkit_web_view_load_html_string(WEBKIT_WEB_VIEW(viewer->web_view), read, "");
 	g_free(read);
 
 	if (viewer->search != NULL) {
-		guint source;
-		gtk_imhtml_search_clear(GTK_IMHTML(viewer->imhtml));
-		source = g_idle_add(search_find_cb, viewer);
-		g_object_set_data_full(G_OBJECT(viewer->entry), "search-find-cb",
-		                       GINT_TO_POINTER(source), (GDestroyNotify)g_source_remove);
+		webkit_web_view_unmark_text_matches(WEBKIT_WEB_VIEW(viewer->web_view));
+		g_idle_add(search_find_cb, viewer);
 	}
 
 	pidgin_clear_cursor(viewer->window);
@@ -620,7 +614,7 @@
 	col = gtk_tree_view_column_new_with_attributes ("time", rend, "markup", 0, NULL);
 	gtk_tree_view_append_column (GTK_TREE_VIEW(lv->treeview), col);
 	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (lv->treeview), FALSE);
-	gtk_paned_add1(GTK_PANED(pane), 
+	gtk_paned_add1(GTK_PANED(pane),
 		pidgin_make_scrollable(lv->treeview, GTK_POLICY_NEVER, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1));
 
 	populate_log_tree(lv);
@@ -655,9 +649,9 @@
 	gtk_paned_add2(GTK_PANED(pane), vbox);
 
 	/* Viewer ************/
-	frame = pidgin_create_imhtml(FALSE, &lv->imhtml, NULL, NULL);
-	gtk_widget_set_name(lv->imhtml, "pidgin_log_imhtml");
-	gtk_widget_set_size_request(lv->imhtml, 320, 200);
+	frame = pidgin_create_webview(FALSE, &lv->web_view, NULL, NULL);
+	gtk_widget_set_name(lv->web_view, "pidgin_log_web_view");
+	gtk_widget_set_size_request(lv->web_view, 320, 200);
 	gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
 	gtk_widget_show(frame);
 
--- a/pidgin/gtklog.h	Sun Sep 11 04:19:01 2011 +0000
+++ b/pidgin/gtklog.h	Mon Sep 12 02:42:41 2011 +0000
@@ -43,7 +43,7 @@
 	GtkWidget        *window;    /**< The viewer's window                      */
 	GtkTreeStore     *treestore; /**< The treestore containing said logs       */
 	GtkWidget        *treeview;  /**< The treeview representing said treestore */
-	GtkWidget        *imhtml;    /**< The imhtml to display said logs          */
+	GtkWidget        *web_view;  /**< The webkit web view to display said logs */
 	GtkWidget        *entry;     /**< The search entry, in which search terms
 	                              *   are entered                              */
 	PurpleLogReadFlags flags;      /**< The most recently used log flags         */
--- a/pidgin/gtknotify.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/pidgin/gtknotify.c	Mon Sep 12 02:42:41 2011 +0000
@@ -36,10 +36,10 @@
 #include "util.h"
 
 #include "gtkblist.h"
-#include "gtkimhtml.h"
 #include "gtknotify.h"
 #include "gtkpounce.h"
 #include "gtkutils.h"
+#include "gtkwebview.h"
 
 typedef struct
 {
@@ -810,21 +810,6 @@
 	return FALSE;
 }
 
-static GtkIMHtmlOptions
-notify_imhtml_options(void)
-{
-	GtkIMHtmlOptions options = 0;
-
-	if (!purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/show_incoming_formatting"))
-		options |= GTK_IMHTML_NO_COLOURS | GTK_IMHTML_NO_FONTS | GTK_IMHTML_NO_SIZES;
-
-	options |= GTK_IMHTML_NO_COMMENTS;
-	options |= GTK_IMHTML_NO_TITLE;
-	options |= GTK_IMHTML_NO_NEWLINE;
-	options |= GTK_IMHTML_NO_SCROLL;
-	return options;
-}
-
 static void *
 pidgin_notify_formatted(const char *title, const char *primary,
 						  const char *secondary, const char *text)
@@ -833,7 +818,7 @@
 	GtkWidget *vbox;
 	GtkWidget *label;
 	GtkWidget *button;
-	GtkWidget *imhtml;
+	GtkWidget *web_view;
 	GtkWidget *frame;
 	char label_text[2048];
 	char *linked_text, *primary_esc, *secondary_esc;
@@ -869,12 +854,10 @@
 	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
 	gtk_widget_show(label);
 
-	/* Add the imhtml */
-	frame = pidgin_create_imhtml(FALSE, &imhtml, NULL, NULL);
-	gtk_widget_set_name(imhtml, "pidgin_notify_imhtml");
-	gtk_imhtml_set_format_functions(GTK_IMHTML(imhtml),
-			gtk_imhtml_get_format_functions(GTK_IMHTML(imhtml)) | GTK_IMHTML_IMAGE);
-	gtk_widget_set_size_request(imhtml, 300, 250);
+	/* Add the webview */
+	frame = pidgin_create_webview(FALSE, &web_view, NULL, NULL);
+	gtk_widget_set_name(web_view, "pidgin_notify_webview");
+	gtk_widget_set_size_request(web_view, 300, 250);
 	gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
 	gtk_widget_show(frame);
 
@@ -889,10 +872,10 @@
 
 	/* Make sure URLs are clickable */
 	linked_text = purple_markup_linkify(text);
-	gtk_imhtml_append_text(GTK_IMHTML(imhtml), linked_text, notify_imhtml_options());
+	webkit_web_view_load_html_string(WEBKIT_WEB_VIEW(web_view), linked_text, "");
 	g_free(linked_text);
 
-	g_object_set_data(G_OBJECT(window), "info-widget", imhtml);
+	g_object_set_data(G_OBJECT(window), "webview-widget", web_view);
 
 	/* Show the window */
 	pidgin_auto_parent_window(window);
@@ -1147,10 +1130,9 @@
 	info = purple_notify_user_info_get_text_with_newline(user_info, "<br />");
 	pinfo = g_hash_table_lookup(userinfo, key);
 	if (pinfo != NULL) {
-		GtkIMHtml *imhtml = g_object_get_data(G_OBJECT(pinfo->window), "info-widget");
+		GtkWidget *webview = g_object_get_data(G_OBJECT(pinfo->window), "webview-widget");
 		char *linked_text = purple_markup_linkify(info);
-		gtk_imhtml_clear(imhtml);
-		gtk_imhtml_append_text(imhtml, linked_text, notify_imhtml_options());
+		gtk_webview_load_html_string_with_imgstore(GTK_WEBVIEW(webview), linked_text);
 		g_free(linked_text);
 		g_free(key);
 		ui_handle = pinfo->window;
@@ -1650,7 +1632,7 @@
 	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
 	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
-	gtk_box_pack_start(GTK_BOX(vbox), 
+	gtk_box_pack_start(GTK_BOX(vbox),
 		pidgin_make_scrollable(spec_dialog->treeview, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS, GTK_SHADOW_IN, -1, -1),
 		TRUE, TRUE, 2);
 
--- a/pidgin/gtkthemes.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/pidgin/gtkthemes.c	Mon Sep 12 02:42:41 2011 +0000
@@ -278,6 +278,8 @@
 		if (*i == '[' && strchr(i, ']') && load) {
 			struct smiley_list *child = g_new0(struct smiley_list, 1);
 			child->sml = g_strndup(i+1, strchr(i, ']') - i - 1);
+			child->files = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
 			if (theme->list)
 				list->next = child;
 			else
@@ -320,6 +322,7 @@
 				} else {
 					GtkIMHtmlSmiley *smiley = gtk_imhtml_smiley_create(sfile, l, hidden, 0);
 					list->smileys = g_slist_prepend(list->smileys, smiley);
+					g_hash_table_insert (list->files, g_strdup(l), g_strdup(sfile));
 				}
 				while (isspace(*i))
 					i++;
@@ -358,7 +361,6 @@
 
 			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_custom(PIDGIN_CONVERSATION(conv)->entry);
 			}
 		}
--- a/pidgin/gtkthemes.h	Sun Sep 11 04:19:01 2011 +0000
+++ b/pidgin/gtkthemes.h	Mon Sep 12 02:42:41 2011 +0000
@@ -29,6 +29,7 @@
 struct smiley_list {
 	char *sml;
 	GSList *smileys;
+	GHashTable *files; /**< map from smiley shortcut to filename */
 	struct smiley_list *next;
 };
 
--- a/pidgin/gtkutils.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/pidgin/gtkutils.c	Mon Sep 12 02:42:41 2011 +0000
@@ -67,6 +67,7 @@
 #include "pidginstock.h"
 #include "gtkthemes.h"
 #include "gtkutils.h"
+#include "gtkwebview.h"
 #include "pidgin/minidialog.h"
 
 typedef struct {
@@ -276,6 +277,69 @@
 	return frame;
 }
 
+GtkWidget *
+pidgin_create_webview(gboolean editable, GtkWidget **webview_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret)
+{
+	GtkWidget *frame;
+	GtkWidget *webview;
+	GtkWidget *sep;
+	GtkWidget *sw;
+	GtkWidget *toolbar = NULL;
+	GtkWidget *vbox;
+
+	frame = gtk_frame_new(NULL);
+	gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
+
+	vbox = gtk_vbox_new(FALSE, 0);
+	gtk_container_add(GTK_CONTAINER(frame), vbox);
+	gtk_widget_show(vbox);
+
+	if (editable) {
+		toolbar = gtk_imhtmltoolbar_new();
+		gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0);
+		gtk_widget_show(toolbar);
+
+		sep = gtk_hseparator_new();
+		gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 0);
+		g_signal_connect_swapped(G_OBJECT(toolbar), "show", G_CALLBACK(gtk_widget_show), sep);
+		g_signal_connect_swapped(G_OBJECT(toolbar), "hide", G_CALLBACK(gtk_widget_hide), sep);
+		gtk_widget_show(sep);
+	}
+
+	webview = gtk_webview_new();
+#if 0
+	/* FIXME: Don't have editable webview yet. */
+	gtk_webview_set_editable(GTK_WEBVIEW(webview), editable);
+#endif
+#ifdef USE_GTKSPELL
+	if (editable && purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/spellcheck"))
+		pidgin_setup_gtkspell(GTK_TEXT_VIEW(webview));
+#endif
+	gtk_widget_show(webview);
+
+	if (editable) {
+		gtk_imhtmltoolbar_attach(GTK_IMHTMLTOOLBAR(toolbar), webview);
+		gtk_imhtmltoolbar_associate_smileys(GTK_IMHTMLTOOLBAR(toolbar), "default");
+	}
+
+	sw = pidgin_make_scrollable(webview, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC, GTK_SHADOW_NONE, -1, -1);
+	gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
+
+	gtk_webview_set_vadjustment(GTK_WEBVIEW(webview),
+			gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(sw)));
+
+	if (webview_ret != NULL)
+		*webview_ret = webview;
+
+	if (editable && (toolbar_ret != NULL))
+		*toolbar_ret = toolbar;
+
+	if (sw_ret != NULL)
+		*sw_ret = sw;
+
+	return frame;
+}
+
 void
 pidgin_set_sensitive_if_input(GtkWidget *entry, GtkWidget *dialog)
 {
--- a/pidgin/gtkutils.h	Sun Sep 11 04:19:01 2011 +0000
+++ b/pidgin/gtkutils.h	Mon Sep 12 02:42:41 2011 +0000
@@ -109,6 +109,28 @@
 GtkWidget *pidgin_create_imhtml(gboolean editable, GtkWidget **imhtml_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret);
 
 /**
+ * Create an GtkWebView widget and associated GtkIMHtmlToolbar widget.  This
+ * function puts both widgets in a nice GtkFrame.  They're separated by an
+ * attractive GtkSeparator.
+ * FIXME: There is no editable GtkWebView yet.
+ *
+ * @param editable @c TRUE if this webview should be editable.  If this is
+ *        @c FALSE, then the toolbar will NOT be created.  If this webview
+ *        should be read-only at first, but may become editable later, then
+ *        pass in @c TRUE here and then manually call gtk_webview_set_editable()
+ *        later.
+ * @param webview_ret A pointer to a pointer to a GtkWidget.  This pointer
+ *        will be set to the webview when this function exits.
+ * @param toolbar_ret A pointer to a pointer to a GtkWidget.  If editable is
+ *        TRUE then this will be set to the toolbar when this function exits.
+ *        Otherwise this will be set to @c NULL.
+ * @param sw_ret This will be filled with a pointer to the scrolled window
+ *        widget which contains the webview.
+ * @return The GtkFrame containing the toolbar and webview.
+ */
+GtkWidget *pidgin_create_webview(gboolean editable, GtkWidget **webview_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret);
+
+/**
  * Creates a small button
  *
  * @param  image   A button image.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtkwebview.c	Mon Sep 12 02:42:41 2011 +0000
@@ -0,0 +1,419 @@
+/*
+ * @file gtkwebview.c GTK+ WebKitWebView wrapper class.
+ * @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
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <string.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <JavaScriptCore/JavaScript.h>
+
+#include "util.h"
+#include "gtkwebview.h"
+#include "imgstore.h"
+
+static WebKitWebViewClass *parent_class = NULL;
+
+struct GtkWebViewPriv {
+	GHashTable *images; /**< a map from id to temporary file for the image */
+	gboolean    empty;  /**< whether anything has been appended **/
+
+	/* JS execute queue */
+	GQueue *js_queue;
+	gboolean is_loading;
+	GtkAdjustment *vadj;
+	guint scroll_src;
+	GTimer *scroll_time;
+};
+
+GtkWidget *
+gtk_webview_new(void)
+{
+	GtkWebView* ret = GTK_WEBVIEW(g_object_new(gtk_webview_get_type(), NULL));
+	return GTK_WIDGET(ret);
+}
+
+static char *
+get_image_filename_from_id(GtkWebView* view, int id)
+{
+	char *filename = NULL;
+	FILE *file;
+	PurpleStoredImage* img;
+
+	if (!view->priv->images)
+		view->priv->images = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
+
+	filename = (char *)g_hash_table_lookup(view->priv->images, GINT_TO_POINTER(id));
+	if (filename)
+		return filename;
+
+	/* else get from img store */
+	file = purple_mkstemp(&filename, TRUE);
+
+	img = purple_imgstore_find_by_id(id);
+
+	fwrite(purple_imgstore_get_data(img), purple_imgstore_get_size(img), 1, file);
+	g_hash_table_insert(view->priv->images, GINT_TO_POINTER(id), filename);
+	fclose(file);
+	return filename;
+}
+
+static void
+clear_single_image(gpointer key, gpointer value, gpointer userdata)
+{
+	g_unlink((char *)value);
+}
+
+static void
+clear_images(GtkWebView *view)
+{
+	if (!view->priv->images)
+		return;
+	g_hash_table_foreach(view->priv->images, clear_single_image, NULL);
+	g_hash_table_unref(view->priv->images);
+}
+
+/*
+ * Replace all <img id=""> tags with <img src="">. I hoped to never
+ * write any HTML parsing code, but I'm forced to do this, until
+ * purple changes the way it works.
+ */
+static char *
+replace_img_id_with_src(GtkWebView *view, const char *html)
+{
+	GString *buffer = g_string_sized_new(strlen(html));
+	const char* cur = html;
+	char *id;
+	int nid;
+
+	while (*cur) {
+		const char *img = strstr(cur, "<img");
+		if (!img) {
+			g_string_append(buffer, cur);
+			break;
+		} else
+			g_string_append_len(buffer, cur, img - cur);
+
+		cur = strstr(img, "/>");
+		if (!cur)
+			cur = strstr(img, ">");
+
+		if (!cur) { /* invalid html? */
+			g_string_printf(buffer, "%s", html);
+			break;
+		}
+
+		if (strstr(img, "src=") || !strstr(img, "id=")) {
+			g_string_printf(buffer, "%s", html);
+			break;
+		}
+
+		/*
+		 * if this is valid HTML, then I can be sure that it
+		 * has an id= and does not have an src=, since
+		 * '=' cannot appear in parameters.
+		 */
+
+		id = strstr(img, "id=") + 3;
+
+		/* *id can't be \0, since a ">" appears after this */
+		if (isdigit(*id))
+			nid = atoi(id);
+		else
+			nid = atoi(id + 1);
+
+		/* let's dump this, tag and then dump the src information */
+		g_string_append_len(buffer, img, cur - img);
+
+		g_string_append_printf(buffer, " src='file://%s' ", get_image_filename_from_id(view, nid));
+	}
+
+	return g_string_free(buffer, FALSE);
+}
+
+static void
+gtk_webview_finalize(GObject *view)
+{
+	gpointer temp;
+
+	while ((temp = g_queue_pop_head(GTK_WEBVIEW(view)->priv->js_queue)))
+		g_free(temp);
+	g_queue_free(GTK_WEBVIEW(view)->priv->js_queue);
+
+	clear_images(GTK_WEBVIEW(view));
+	g_free(GTK_WEBVIEW(view)->priv);
+	G_OBJECT_CLASS(parent_class)->finalize(G_OBJECT(view));
+}
+
+static void
+gtk_webview_class_init(GtkWebViewClass *klass, gpointer userdata)
+{
+	parent_class = g_type_class_ref(webkit_web_view_get_type());
+	G_OBJECT_CLASS(klass)->finalize = gtk_webview_finalize;
+}
+
+static gboolean
+webview_link_clicked(WebKitWebView *view,
+		      WebKitWebFrame *frame,
+		      WebKitNetworkRequest *request,
+		      WebKitWebNavigationAction *navigation_action,
+		      WebKitWebPolicyDecision *policy_decision)
+{
+	const gchar *uri;
+	WebKitWebNavigationReason reason;
+
+	uri = webkit_network_request_get_uri(request);
+	reason = webkit_web_navigation_action_get_reason(navigation_action);
+
+	if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
+		/* the gtk imhtml way was to create an idle cb, not sure
+		 * why, so right now just using purple_notify_uri directly */
+		purple_notify_uri(NULL, uri);
+	} else
+		webkit_web_policy_decision_use(policy_decision);
+
+	return TRUE;
+}
+
+static gboolean
+process_js_script_queue(GtkWebView *view)
+{
+	char *script;
+	if (view->priv->is_loading)
+		return FALSE; /* we will be called when loaded */
+	if (!view->priv->js_queue || g_queue_is_empty(view->priv->js_queue))
+		return FALSE; /* nothing to do! */
+
+	script = g_queue_pop_head(view->priv->js_queue);
+	webkit_web_view_execute_script(WEBKIT_WEB_VIEW(view), script);
+	g_free(script);
+
+	return TRUE; /* there may be more for now */
+}
+
+static void
+webview_load_started(WebKitWebView *view,
+		      WebKitWebFrame *frame,
+		      gpointer userdata)
+{
+	/* is there a better way to test for is_loading? */
+	GTK_WEBVIEW(view)->priv->is_loading = TRUE;
+}
+
+static void
+webview_load_finished(WebKitWebView *view,
+		       WebKitWebFrame *frame,
+		       gpointer userdata)
+{
+	GTK_WEBVIEW(view)->priv->is_loading = FALSE;
+	g_idle_add((GSourceFunc)process_js_script_queue, view);
+}
+
+void
+gtk_webview_safe_execute_script(GtkWebView *view, const char *script)
+{
+	g_queue_push_tail(view->priv->js_queue, g_strdup(script));
+	g_idle_add((GSourceFunc)process_js_script_queue, view);
+}
+
+static void
+gtk_webview_init(GtkWebView *view, gpointer userdata)
+{
+	view->priv = g_new0(struct GtkWebViewPriv, 1);
+	g_signal_connect(view, "navigation-policy-decision-requested",
+			  G_CALLBACK(webview_link_clicked),
+			  view);
+
+	g_signal_connect(view, "load-started",
+			  G_CALLBACK(webview_load_started),
+			  view);
+
+	g_signal_connect(view, "load-finished",
+			  G_CALLBACK(webview_load_finished),
+			  view);
+
+	view->priv->empty = TRUE;
+	view->priv->js_queue = g_queue_new();
+}
+
+
+void
+gtk_webview_load_html_string_with_imgstore(GtkWebView *view, const char *html)
+{
+	char *html_imged;
+
+	clear_images(view);
+	html_imged = replace_img_id_with_src(view, html);
+	webkit_web_view_load_html_string(WEBKIT_WEB_VIEW(view), html_imged, "file:///");
+	g_free(html_imged);
+}
+
+char *
+gtk_webview_quote_js_string(const char *text)
+{
+	GString *str = g_string_new("\"");
+	const char *cur = text;
+
+	while (cur && *cur) {
+		switch (*cur) {
+			case '\\':
+				g_string_append(str, "\\\\");
+				break;
+			case '\"':
+				g_string_append(str, "\\\"");
+				break;
+			case '\r':
+				g_string_append(str, "<br/>");
+				break;
+			case '\n':
+				break;
+			default:
+				g_string_append_c(str, *cur);
+		}
+		cur++;
+	}
+	g_string_append_c(str, '"');
+	return g_string_free(str, FALSE);
+}
+
+void
+gtk_webview_set_vadjustment(GtkWebView *webview, GtkAdjustment *vadj)
+{
+	webview->priv->vadj = vadj;
+}
+
+/* this is a "hack", my plan is to eventually handle this
+ * correctly using a signals and a plugin: the plugin will have
+ * the information as to what javascript function to call. It seems
+ * wrong to hardcode that here.
+ */
+void
+gtk_webview_append_html(GtkWebView *view, const char *html)
+{
+	char *escaped = gtk_webview_quote_js_string(html);
+	char *script = g_strdup_printf("document.write(%s)", escaped);
+	webkit_web_view_execute_script(WEBKIT_WEB_VIEW(view), script);
+	view->priv->empty = FALSE;
+	gtk_webview_scroll_to_end(view, TRUE);
+	g_free(script);
+	g_free(escaped);
+}
+
+gboolean
+gtk_webview_is_empty(GtkWebView *view)
+{
+	return view->priv->empty;
+}
+
+#define MAX_SCROLL_TIME 0.4 /* seconds */
+#define SCROLL_DELAY 33 /* milliseconds */
+
+/*
+ * Smoothly scroll a WebView.
+ *
+ * @return TRUE if the window needs to be scrolled further, FALSE if we're at the bottom.
+ */
+static gboolean
+smooth_scroll_cb(gpointer data)
+{
+	struct GtkWebViewPriv *priv = data;
+	GtkAdjustment *adj = priv->vadj;
+	gdouble max_val = adj->upper - adj->page_size;
+	gdouble scroll_val = gtk_adjustment_get_value(adj) + ((max_val - gtk_adjustment_get_value(adj)) / 3);
+
+	g_return_val_if_fail(priv->scroll_time != NULL, FALSE);
+
+	if (g_timer_elapsed(priv->scroll_time, NULL) > MAX_SCROLL_TIME || scroll_val >= max_val) {
+		/* time's up. jump to the end and kill the timer */
+		gtk_adjustment_set_value(adj, max_val);
+		g_timer_destroy(priv->scroll_time);
+		priv->scroll_time = NULL;
+		g_source_remove(priv->scroll_src);
+		priv->scroll_src = 0;
+		return FALSE;
+	}
+
+	/* scroll by 1/3rd the remaining distance */
+	gtk_adjustment_set_value(adj, scroll_val);
+	return TRUE;
+}
+
+static gboolean
+scroll_idle_cb(gpointer data)
+{
+	struct GtkWebViewPriv *priv = data;
+	GtkAdjustment *adj = priv->vadj;
+	if (adj) {
+		gtk_adjustment_set_value(adj, adj->upper - adj->page_size);
+	}
+	priv->scroll_src = 0;
+	return FALSE;
+}
+
+void
+gtk_webview_scroll_to_end(GtkWebView *webview, gboolean smooth)
+{
+	struct GtkWebViewPriv *priv = webview->priv;
+	if (priv->scroll_time)
+		g_timer_destroy(priv->scroll_time);
+	if (priv->scroll_src)
+		g_source_remove(priv->scroll_src);
+	if(smooth) {
+		priv->scroll_time = g_timer_new();
+		priv->scroll_src = g_timeout_add_full(G_PRIORITY_LOW, SCROLL_DELAY, smooth_scroll_cb, priv, NULL);
+	} else {
+		priv->scroll_time = NULL;
+		priv->scroll_src = g_idle_add_full(G_PRIORITY_LOW, scroll_idle_cb, priv, NULL);
+	}
+}
+
+GType
+gtk_webview_get_type(void)
+{
+	static GType mview_type = 0;
+	if (G_UNLIKELY(mview_type == 0)) {
+		static const GTypeInfo mview_info = {
+			sizeof(GtkWebViewClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) gtk_webview_class_init,
+			NULL,
+			NULL,
+			sizeof(GtkWebView),
+			0,
+			(GInstanceInitFunc) gtk_webview_init,
+			NULL
+		};
+		mview_type = g_type_register_static(webkit_web_view_get_type(),
+				"GtkWebView", &mview_info, 0);
+	}
+	return mview_type;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtkwebview.h	Mon Sep 12 02:42:41 2011 +0000
@@ -0,0 +1,147 @@
+/**
+ * @file gtkwebview.h Wrapper over the Gtk WebKitWebView component
+ * @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_WEBVIEW_H_
+#define _PIDGIN_WEBVIEW_H_
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <webkit/webkit.h>
+
+#include "notify.h"
+
+#define GTK_TYPE_WEBVIEW            (gtk_webview_get_type())
+#define GTK_WEBVIEW(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_WEBVIEW, GtkWebView))
+#define GTK_WEBVIEW_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_WEBVIEW, GtkWebViewClass))
+#define GTK_IS_WEBVIEW(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), GTK_TYPE_WEBVIEW))
+#define GTK_IS_WEBVIEW_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass), GTK_TYPE_WEBVIEW))
+
+
+struct GtkWebViewPriv;
+
+struct _GtkWebView
+{
+	WebKitWebView webkit_web_view;
+
+	/*< private >*/
+	struct GtkWebViewPriv *priv;
+};
+
+typedef struct _GtkWebView GtkWebView;
+
+struct _GtkWebViewClass
+{
+	WebKitWebViewClass parent;
+};
+
+typedef struct _GtkWebViewClass GtkWebViewClass;
+
+
+/**
+ * Returns the GType for a GtkWebView widget
+ *
+ * @return the GType for GtkWebView widget
+ */
+GType gtk_webview_get_type(void);
+
+/**
+ * Create a new GtkWebView object
+ *
+ * @return a GtkWidget corresponding to the GtkWebView object
+ */
+GtkWidget *gtk_webview_new(void);
+
+/**
+ * Set the vertical adjustment for the GtkWebView.
+ *
+ * @param webview  The GtkWebView.
+ * @param vadj     The GtkAdjustment that control the webview.
+ */
+void gtk_webview_set_vadjustment(GtkWebView *webview, GtkAdjustment *vadj);
+
+/**
+ * A very basic routine to append html, which can be considered
+ * equivalent to a "document.write" using JavaScript.
+ *
+ * @param webview The GtkWebView object
+ * @param markup  The html markup to append
+ */
+void gtk_webview_append_html(GtkWebView *webview, const char *markup);
+
+/**
+ * Rather than use webkit_webview_load_string, this routine
+ * parses and displays the <img id=?> tags that make use of the
+ * Pidgin imgstore.
+ *
+ * @param webview The GtkWebView object
+ * @param html    The HTML content to load
+ */
+void gtk_webview_load_html_string_with_imgstore(GtkWebView *webview, const char *html);
+
+/**
+ * FIXME: (To be changed, right now it just tests whether an append has been
+ * called since the last clear or since the Widget was created. So it
+ * does not test for load_string's called in between.
+ *
+ * @param webview The GtkWebView object
+ *
+ * @return gboolean indicating whether the webview is empty.
+ */
+gboolean gtk_webview_is_empty(GtkWebView *webview);
+
+/**
+ * Execute the JavaScript only after the webkit_webview_load_string
+ * loads completely. We also guarantee that the scripts are executed
+ * in the order they are called here. This is useful to avoid race
+ * conditions when calling JS functions immediately after opening the
+ * page.
+ *
+ * @param webview the GtkWebView object
+ * @param script   the script to execute
+ */
+void gtk_webview_safe_execute_script(GtkWebView *webview, const char *script);
+
+/**
+ * A convenience routine to quote a string for use as a JavaScript
+ * string. For instance, "hello 'world'" becomes "'hello \\'world\\''"
+ *
+ * @param str The string to escape and quote
+ *
+ * @return the quoted string.
+ */
+char *gtk_webview_quote_js_string(const char *str);
+
+/**
+ * Scrolls the Webview to the end of its contents.
+ *
+ * @param webview The GtkWebView.
+ * @param smooth   A boolean indicating if smooth scrolling should be used.
+ */
+void gtk_webview_scroll_to_end(GtkWebView *webview, gboolean smooth);
+
+#endif /* _PIDGIN_WEBVIEW_H_ */
+
--- a/pidgin/plugins/Makefile.am	Sun Sep 11 04:19:01 2011 +0000
+++ b/pidgin/plugins/Makefile.am	Mon Sep 12 02:42:41 2011 +0000
@@ -1,4 +1,4 @@
-DIST_SUBDIRS = cap disco gestures gevolution musicmessaging perl ticker
+DIST_SUBDIRS = adiumthemes cap disco gestures gevolution musicmessaging perl ticker
 
 if BUILD_GEVOLUTION
 GEVOLUTION_DIR = gevolution
@@ -16,9 +16,6 @@
 PERL_DIR = perl
 endif
 
-if ENABLE_GESTURES
-GESTURE_DIR = gestures
-endif
 
 SUBDIRS = \
 	$(CAP_DIR) \
@@ -27,7 +24,8 @@
 	$(MUSICMESSAGING_DIR) \
 	$(PERL_DIR) \
 	disco \
-	ticker
+	ticker \
+	adiumthemes
 
 plugindir = $(libdir)/pidgin
 
@@ -38,15 +36,12 @@
 gtkbuddynote_la_LDFLAGS     = -module -avoid-version
 history_la_LDFLAGS          = -module -avoid-version
 iconaway_la_LDFLAGS         = -module -avoid-version
-markerline_la_LDFLAGS       = -module -avoid-version
 notify_la_LDFLAGS           = -module -avoid-version
 pidginrc_la_LDFLAGS         = -module -avoid-version
 relnot_la_LDFLAGS           = -module -avoid-version
 sendbutton_la_LDFLAGS       = -module -avoid-version
 spellchk_la_LDFLAGS         = -module -avoid-version
 themeedit_la_LDFLAGS        = -module -avoid-version
-timestamp_la_LDFLAGS        = -module -avoid-version
-timestamp_format_la_LDFLAGS = -module -avoid-version
 vvconfig_la_LDFLAGS         = -module -avoid-version
 xmppconsole_la_LDFLAGS      = -module -avoid-version
 
@@ -58,15 +53,12 @@
 	gtkbuddynote.la     \
 	history.la          \
 	iconaway.la         \
-	markerline.la       \
 	notify.la           \
 	pidginrc.la         \
 	relnot.la           \
 	sendbutton.la       \
 	spellchk.la         \
 	themeedit.la         \
-	timestamp.la        \
-	timestamp_format.la \
 	xmppconsole.la
 
 if USE_VV
@@ -84,16 +76,12 @@
 gtkbuddynote_la_SOURCES     = gtkbuddynote.c
 history_la_SOURCES          = history.c
 iconaway_la_SOURCES         = iconaway.c
-markerline_la_SOURCES       = markerline.c
 notify_la_SOURCES           = notify.c
 pidginrc_la_SOURCES         = pidginrc.c
 relnot_la_SOURCES           = relnot.c
 sendbutton_la_SOURCES       = sendbutton.c
 spellchk_la_SOURCES         = spellchk.c
 themeedit_la_SOURCES        = themeedit.c themeedit-icon.c themeedit-icon.h
-timestamp_la_SOURCES        = timestamp.c
-timestamp_format_la_SOURCES = timestamp_format.c
-vvconfig_la_SOURCES         = vvconfig.c
 xmppconsole_la_SOURCES      = xmppconsole.c
 
 convcolors_la_LIBADD        = $(GTK_LIBS)
@@ -103,16 +91,12 @@
 gtkbuddynote_la_LIBADD      = $(GTK_LIBS)
 history_la_LIBADD           = $(GTK_LIBS)
 iconaway_la_LIBADD          = $(GTK_LIBS)
-markerline_la_LIBADD        = $(GTK_LIBS)
 notify_la_LIBADD            = $(GTK_LIBS)
 pidginrc_la_LIBADD          = $(GTK_LIBS)
 relnot_la_LIBADD            = $(GLIB_LIBS)
 sendbutton_la_LIBADD        = $(GTK_LIBS)
 spellchk_la_LIBADD          = $(GTK_LIBS)
 themeedit_la_LIBADD         = $(GTK_LIBS)
-timestamp_la_LIBADD         = $(GTK_LIBS)
-timestamp_format_la_LIBADD  = $(GTK_LIBS)
-vvconfig_la_LIBADD          = $(GTK_LIBS) $(GSTREAMER_LIBS)
 xmppconsole_la_LIBADD       = $(GTK_LIBS)
 
 endif # PLUGINS
@@ -136,6 +120,7 @@
 	-I$(top_srcdir)/pidgin \
 	$(DEBUG_CFLAGS) \
 	$(GTK_CFLAGS) \
+	$(WEBKIT_CFLAGS) \
 	$(GSTREAMER_CFLAGS) \
 	$(PLUGIN_CFLAGS)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/plugins/adiumthemes/Makefile.am	Mon Sep 12 02:42:41 2011 +0000
@@ -0,0 +1,30 @@
+
+webkittemplatedir = $(datadir)/pidgin/webkit
+webkittemplate_DATA = Template.html
+
+webkitdir = $(libdir)/pidgin
+
+webkit_la_LDFLAGS = -module -avoid-version
+
+EXTRA_DIST = $(webkittemplate_DATA)
+
+if PLUGINS
+
+webkit_LTLIBRARIES = webkit.la
+
+webkit_la_SOURCES = webkit.c \
+	message-style.h \
+	message-style.c
+
+endif
+
+webkit_la_LIBADD = $(GTK_LIBS) $(WEBKIT_LIBS)
+
+AM_CPPFLAGS = \
+	-DDATADIR=\"$(datadir)\" \
+	-I$(top_srcdir)/libpurple \
+	-I$(top_builddir)/libpurple \
+	-I$(top_srcdir)/pidgin \
+	$(DEBUG_CFLAGS) \
+	$(GTK_CFLAGS) \
+	$(WEBKIT_CFLAGS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/plugins/adiumthemes/Template.html	Mon Sep 12 02:42:41 2011 +0000
@@ -0,0 +1,164 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+	<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+	<base href="%@">
+	<script type="text/ecmascript" defer="defer">
+	
+		//Appending new content to the message view
+		function appendMessage(html) {
+			shouldScroll = nearBottom();
+		
+			//Remove any existing insertion point
+			insert = document.getElementById("insert");
+			if(insert) insert.parentNode.removeChild(insert);
+
+			//Append the new message to the bottom of our chat block
+			chat = document.getElementById("Chat");
+			range = document.createRange();
+			range.selectNode(chat);
+			documentFragment = range.createContextualFragment(html);
+			chat.appendChild(documentFragment);
+			
+			alignChat(shouldScroll);
+		}
+		function appendMessageNoScroll(html) {
+			//Remove any existing insertion point
+			insert = document.getElementById("insert");
+			if(insert) insert.parentNode.removeChild(insert);
+
+			//Append the new message to the bottom of our chat block
+			chat = document.getElementById("Chat");
+			range = document.createRange();
+			range.selectNode(chat);
+			documentFragment = range.createContextualFragment(html);
+			chat.appendChild(documentFragment);
+		}
+		function appendNextMessage(html){
+			shouldScroll = nearBottom();
+
+			//Locate the insertion point
+			insert = document.getElementById("insert");
+		
+			//make new node
+			range = document.createRange();
+			range.selectNode(insert.parentNode);
+			newNode = range.createContextualFragment(html);
+
+			//swap
+			insert.parentNode.replaceChild(newNode,insert);
+			
+			alignChat(shouldScroll);
+		}
+		function appendNextMessageNoScroll(html){
+			//Locate the insertion point
+			insert = document.getElementById("insert");
+		
+			//make new node
+			range = document.createRange();
+			range.selectNode(insert.parentNode);
+			newNode = range.createContextualFragment(html);
+
+			//swap
+			insert.parentNode.replaceChild(newNode,insert);
+		}
+		
+		//Auto-scroll to bottom.  Use nearBottom to determine if a scrollToBottom is desired.
+		function nearBottom() {
+			return ( document.body.scrollTop >= ( document.body.offsetHeight - ( window.innerHeight * 1.2 ) ) );
+		}
+		function scrollToBottom() {
+			document.body.scrollTop = document.body.offsetHeight;
+		}
+
+		//Dynamically exchange the active stylesheet
+		function setStylesheet( id, url ) {
+			code = "<style id=\"" + id + "\" type=\"text/css\" media=\"screen,print\">";
+			if( url.length ) code += "@import url( \"" + url + "\" );";
+			code += "</style>";
+			range = document.createRange();
+			head = document.getElementsByTagName( "head" ).item(0);
+			range.selectNode( head );
+			documentFragment = range.createContextualFragment( code );
+			head.removeChild( document.getElementById( id ) );
+			head.appendChild( documentFragment );
+		}
+		
+		//Swap an image with its alt-tag text on click, or expand/unexpand an attached image
+		document.onclick = imageCheck;
+		function imageCheck() {		
+			node = event.target;
+			if(node.tagName == 'IMG' && !client.zoomImage(node) && node.alt) {
+				a = document.createElement('a');
+				a.setAttribute('onclick', 'imageSwap(this)');
+				a.setAttribute('src', node.getAttribute('src'));
+				a.className = node.className;
+				text = document.createTextNode(node.alt);
+				a.appendChild(text);
+				node.parentNode.replaceChild(a, node);
+			}
+		}
+
+		function imageSwap(node) {
+			shouldScroll = nearBottom();
+
+			//Swap the image/text
+			img = document.createElement('img');
+			img.setAttribute('src', node.getAttribute('src'));
+			img.setAttribute('alt', node.firstChild.nodeValue);
+			img.className = node.className;
+			node.parentNode.replaceChild(img, node);
+			
+			alignChat(shouldScroll);
+		}
+		
+		//Align our chat to the bottom of the window.  If true is passed, view will also be scrolled down
+		function alignChat(shouldScroll) {
+			var windowHeight = window.innerHeight;
+			
+			if (windowHeight > 0) {
+				var contentElement = document.getElementById('Chat');
+				var contentHeight = contentElement.offsetHeight;
+				if (windowHeight - contentHeight > 0) {
+					contentElement.style.position = 'relative';
+					contentElement.style.top = (windowHeight - contentHeight) + 'px';
+				} else {
+					contentElement.style.position = 'static';
+				}
+			}
+			
+			if (shouldScroll) scrollToBottom();
+		}
+		
+		function windowDidResize(){
+			alignChat(true/*nearBottom()*/); //nearBottom buggy with inactive tabs
+		}
+		
+		window.onresize = windowDidResize;
+	</script>
+	
+	<style type="text/css">
+		.actionMessageUserName:before { content:"*"; }
+		.actionMessageBody:after { content:"*"; }
+		*{ word-wrap:break-word; }
+		img.scaledToFitImage { height:auto; width:100%; }
+	</style>
+	
+	<!-- This style is shared by all variants. !-->
+	<style id="baseStyle" type="text/css" media="screen,print">	
+		%@
+	</style>
+	
+	<!-- Although we call this mainStyle for legacy reasons, it's actually the variant style !-->
+	<style id="mainStyle" type="text/css" media="screen,print">	
+		@import url( "%@" );
+	</style>
+
+</head>
+<body onload="alignChat(true);" style="==bodyBackground==">
+%@
+<div id="Chat">
+</div>
+%@
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/plugins/adiumthemes/message-style.c	Mon Sep 12 02:42:41 2011 +0000
@@ -0,0 +1,429 @@
+/* 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 "message-style.h"
+
+#include <string.h>
+
+#include <glib.h>
+
+#include <debug.h>
+#include <util.h>
+
+static void
+glist_free_all_string(GList *list)
+{
+	for (; list; list = g_list_delete_link(list, list))
+		g_free(list->data);
+}
+
+static PidginMessageStyle *
+pidgin_message_style_new(const char *styledir)
+{
+	PidginMessageStyle *ret = g_new0(PidginMessageStyle, 1);
+
+	ret->ref_counter = 1;
+	ret->style_dir = g_strdup(styledir);
+
+	return ret;
+}
+
+void
+pidgin_message_style_unref(PidginMessageStyle *style)
+{
+	if (!style)
+		return;
+	g_assert (style->ref_counter > 0);
+
+	style->ref_counter--;
+	if (style->ref_counter)
+		return;
+
+	g_free(style->cf_bundle_name);
+	g_free(style->cf_bundle_identifier);
+	g_free(style->cf_bundle_get_info_string);
+	g_free(style->default_font_family);
+	g_free(style->default_background_color);
+	g_free(style->image_mask);
+	g_free(style->default_variant);
+
+	g_free(style->style_dir);
+	g_free(style->template_path);
+
+	g_free(style->template_html);
+	g_free(style->incoming_content_html);
+	g_free(style->outgoing_content_html);
+	g_free(style->outgoing_next_content_html);
+	g_free(style->status_html);
+	g_free(style->basestyle_css);
+
+	g_free(style);
+}
+
+void
+pidgin_message_style_save_state(const PidginMessageStyle *style)
+{
+	char *prefname = g_strdup_printf("/plugins/gtk/adiumthemes/%s", style->cf_bundle_identifier);
+	char *variant = g_strdup_printf("%s/variant", prefname);
+
+	purple_debug_info("webkit", "saving state with variant %s\n", style->variant);
+	purple_prefs_add_none(prefname);
+	purple_prefs_add_string(variant, "");
+	purple_prefs_set_string(variant, style->variant);
+
+	g_free(prefname);
+	g_free(variant);
+}
+
+static void
+pidgin_message_style_load_state(PidginMessageStyle *style)
+{
+	char *prefname = g_strdup_printf("/plugins/gtk/adiumthemes/%s", style->cf_bundle_identifier);
+	char *variant = g_strdup_printf("%s/variant", prefname);
+
+	const char* value = purple_prefs_get_string(variant);
+	gboolean changed = !style->variant || !g_str_equal(style->variant, value);
+
+	g_free(style->variant);
+	style->variant = g_strdup(value);
+
+	if (changed)
+		pidgin_message_style_read_info_plist(style, style->variant);
+
+	g_free(prefname);
+	g_free(variant);
+}
+
+
+static gboolean
+parse_info_plist_key_value(xmlnode* key, gpointer destination, const char* expected)
+{
+	xmlnode *val = key->next;
+
+	for (; val && val->type != XMLNODE_TYPE_TAG; val = val->next)
+		;
+	if (!val)
+		return FALSE;
+
+	if (expected == NULL || g_str_equal(expected, "string")) {
+		char **dest = (char **)destination;
+		if (!g_str_equal(val->name, "string"))
+			return FALSE;
+		if (*dest)
+			g_free(*dest);
+		*dest = xmlnode_get_data_unescaped(val);
+	} else if (g_str_equal(expected, "integer")) {
+		int *dest = (int *)destination;
+		char *value = xmlnode_get_data_unescaped(val);
+
+		if (!g_str_equal(val->name, "integer"))
+			return FALSE;
+		*dest = atoi(value);
+		g_free(value);
+	} else if (g_str_equal(expected, "boolean")) {
+		gboolean *dest = (gboolean *)destination;
+		if (g_str_equal(val->name, "true"))
+			*dest = TRUE;
+		else if (g_str_equal(val->name, "false"))
+			*dest = FALSE;
+		else
+			return FALSE;
+	} else return FALSE;
+
+	return TRUE;
+}
+
+static gboolean
+str_for_key(const char *key, const char *found, const char *variant)
+{
+	if (g_str_equal(key, found))
+		return TRUE;
+	if (!variant)
+		return FALSE;
+	return (g_str_has_prefix(found, key)
+		&& g_str_has_suffix(found, variant)
+		&& strlen(found) == strlen(key) + strlen(variant) + 1);
+}
+
+/**
+ * Info.plist should be re-read every time the variant changes, this is because
+ * the keys that take precedence depend on the value of the current variant.
+ */
+void
+pidgin_message_style_read_info_plist(PidginMessageStyle *style, const char *variant)
+{
+	/* note that if a variant is used the option:VARIANTNAME takes precedence */
+	char *contents = g_build_filename(style->style_dir, "Contents", NULL);
+	xmlnode *plist = xmlnode_from_file(contents, "Info.plist", "Info.plist", "webkit"), *iter;
+	xmlnode *dict = xmlnode_get_child(plist, "dict");
+
+	g_assert (dict);
+	for (iter = xmlnode_get_child(dict, "key"); iter; iter = xmlnode_get_next_twin(iter)) {
+		char* key = xmlnode_get_data_unescaped(iter);
+		gboolean pr = TRUE;
+
+		if (g_str_equal("MessageViewVersion", key))
+			pr = parse_info_plist_key_value(iter, &style->message_view_version, "integer");
+		else if (g_str_equal("CFBundleName", key))
+			pr = parse_info_plist_key_value(iter, &style->cf_bundle_name, "string");
+		else if (g_str_equal("CFBundleIdentifier", key))
+			pr = parse_info_plist_key_value(iter, &style->cf_bundle_identifier, "string");
+		else if (g_str_equal("CFBundleGetInfoString", key))
+			pr = parse_info_plist_key_value(iter, &style->cf_bundle_get_info_string, "string");
+		else if (str_for_key("DefaultFontFamily", key, variant))
+			pr = parse_info_plist_key_value(iter, &style->default_font_family, "string");
+		else if (str_for_key("DefaultFontSize", key, variant))
+			pr = parse_info_plist_key_value(iter, &style->default_font_size, "integer");
+		else if (str_for_key("ShowsUserIcons", key, variant))
+			pr = parse_info_plist_key_value(iter, &style->shows_user_icons, "boolean");
+		else if (str_for_key("DisableCombineConsecutive", key, variant))
+			pr = parse_info_plist_key_value(iter, &style->disable_combine_consecutive, "boolean");
+		else if (str_for_key("DefaultBackgroundIsTransparent", key, variant))
+			pr = parse_info_plist_key_value(iter, &style->default_background_is_transparent, "boolean");
+		else if (str_for_key("DisableCustomBackground", key, variant))
+			pr = parse_info_plist_key_value(iter, &style->disable_custom_background, "boolean");
+		else if (str_for_key("DefaultBackgroundColor", key, variant))
+			pr = parse_info_plist_key_value(iter, &style->default_background_color, "string");
+		else if (str_for_key("AllowTextColors", key, variant))
+			pr = parse_info_plist_key_value(iter, &style->allow_text_colors, "integer");
+		else if (str_for_key("ImageMask", key, variant))
+			pr = parse_info_plist_key_value(iter, &style->image_mask, "string");
+
+		if (!pr)
+			purple_debug_warning("webkit", "Failed to parse key %s\n", key);
+		g_free(key);
+	}
+
+	xmlnode_free(plist);
+}
+
+PidginMessageStyle *
+pidgin_message_style_load(const char *styledir)
+{
+	/*
+	 * the loading process described:
+	 *
+	 * First we load all the style .html files, etc.
+	 * The we load any config options that have been stored for
+	 * this variant.
+	 * Then we load the Info.plist, for the currently decided variant.
+	 * At this point, if we find that variants exist, yet
+	 * we don't have a variant selected, we choose DefaultVariant
+	 * and if that does not exist, we choose the first one in the
+	 * directory.
+	 */
+	char *file;
+	PidginMessageStyle *style = NULL;
+
+	style = pidgin_message_style_new(styledir);
+
+	/* load all other files */
+
+	/* The template path can either come from the theme, or can
+	 * be stock Template.html that comes with the plugin */
+	style->template_path = g_build_filename(styledir, "Contents", "Resources", "Template.html", NULL);
+
+	if (!g_file_test(style->template_path, G_FILE_TEST_EXISTS)) {
+		g_free(style->template_path);
+		style->template_path = g_build_filename(DATADIR, "pidgin", "webkit", "Template.html", NULL);
+	}
+
+	if (!g_file_get_contents(style->template_path, &style->template_html, NULL, NULL)) {
+		purple_debug_error("webkit", "Could not locate a Template.html (%s)\n", style->template_path);
+		pidgin_message_style_unref(style);
+		return NULL;
+	}
+
+	file = g_build_filename(styledir, "Contents", "Resources", "Status.html", NULL);
+	if (!g_file_get_contents(file, &style->status_html, NULL, NULL)) {
+		purple_debug_info("webkit", "%s could not find Resources/Status.html", styledir);
+		pidgin_message_style_unref(style);
+		g_free(file);
+		return NULL;
+	}
+	g_free(file);
+
+	file = g_build_filename(styledir, "Contents", "Resources", "main.css", NULL);
+	if (!g_file_get_contents(file, &style->basestyle_css, NULL, NULL))
+		style->basestyle_css = g_strdup("");
+	g_free(file);
+
+	file = g_build_filename(styledir, "Contents", "Resources", "Header.html", NULL);
+	if (!g_file_get_contents(file, &style->header_html, NULL, NULL))
+		style->header_html = g_strdup("");
+	g_free(file);
+
+	file = g_build_filename(styledir, "Contents", "Resources", "Footer.html", NULL);
+	if (!g_file_get_contents(file, &style->footer_html, NULL, NULL))
+		style->footer_html = g_strdup("");
+	g_free(file);
+
+	file = g_build_filename(styledir, "Contents", "Resources", "Incoming", "Content.html", NULL);
+	if (!g_file_get_contents(file, &style->incoming_content_html, NULL, NULL)) {
+		purple_debug_info("webkit", "%s did not have a Incoming/Content.html\n", styledir);
+		pidgin_message_style_unref(style);
+		g_free(file);
+		return NULL;
+	}
+	g_free(file);
+
+
+	/* according to the spec, the following are optional files */
+	file = g_build_filename(styledir, "Contents", "Resources", "Incoming", "NextContent.html", NULL);
+	if (!g_file_get_contents(file, &style->incoming_next_content_html, NULL, NULL)) {
+		style->incoming_next_content_html = g_strdup(style->incoming_content_html);
+	}
+	g_free(file);
+
+	file = g_build_filename(styledir, "Contents", "Resources", "Outgoing", "Content.html", NULL);
+	if (!g_file_get_contents(file, &style->outgoing_content_html, NULL, NULL)) {
+		style->outgoing_content_html = g_strdup(style->incoming_content_html);
+	}
+	g_free(file);
+
+	file = g_build_filename(styledir, "Contents", "Resources", "Outgoing", "NextContent.html", NULL);
+	if (!g_file_get_contents(file, &style->outgoing_next_content_html, NULL, NULL)) {
+		style->outgoing_next_content_html = g_strdup(style->outgoing_content_html);
+	}
+
+	pidgin_message_style_read_info_plist(style, NULL);
+	pidgin_message_style_load_state(style);
+
+	/* non variant dependent Info.plist checks */
+	if (style->message_view_version < 3) {
+		purple_debug_info("webkit", "%s is a legacy style (version %d) and will not be loaded\n", style->cf_bundle_name, style->message_view_version);
+		pidgin_message_style_unref(style);
+		return NULL;
+	}
+
+	if (!style->variant)
+	{
+		GList *variants = pidgin_message_style_get_variants(style);
+
+		if (variants)
+			pidgin_message_style_set_variant(style, variants->data);
+
+		glist_free_all_string(variants);
+	}
+
+	return style;
+}
+
+PidginMessageStyle *
+pidgin_message_style_copy(const PidginMessageStyle *style)
+{
+	PidginMessageStyle *ret = pidgin_message_style_new(style->style_dir);
+
+	ret->variant = g_strdup(style->variant);
+	ret->message_view_version = style->message_view_version;
+	ret->cf_bundle_name = g_strdup(style->cf_bundle_name);
+	ret->cf_bundle_identifier = g_strdup(style->cf_bundle_identifier);
+	ret->cf_bundle_get_info_string = g_strdup(style->cf_bundle_get_info_string);
+	ret->default_font_family = g_strdup(style->default_font_family);
+	ret->default_font_size = style->default_font_size;
+	ret->shows_user_icons = style->shows_user_icons;
+	ret->disable_combine_consecutive = style->disable_combine_consecutive;
+	ret->default_background_is_transparent = style->default_background_is_transparent;
+	ret->disable_custom_background = style->disable_custom_background;
+	ret->default_background_color = g_strdup(style->default_background_color);
+	ret->allow_text_colors = style->allow_text_colors;
+	ret->image_mask = g_strdup(style->image_mask);
+	ret->default_variant = g_strdup(style->default_variant);
+
+	ret->template_path = g_strdup(style->template_path);
+	ret->template_html = g_strdup(style->template_html);
+	ret->header_html = g_strdup(style->header_html);
+	ret->footer_html = g_strdup(style->footer_html);
+	ret->incoming_content_html = g_strdup(style->incoming_content_html);
+	ret->outgoing_content_html = g_strdup(style->outgoing_content_html);
+	ret->incoming_next_content_html = g_strdup(style->incoming_next_content_html);
+	ret->outgoing_next_content_html = g_strdup(style->outgoing_next_content_html);
+	ret->status_html = g_strdup(style->status_html);
+	ret->basestyle_css = g_strdup(style->basestyle_css);
+	return ret;
+}
+
+void
+pidgin_message_style_set_variant(PidginMessageStyle *style, const char *variant)
+{
+	/* I'm not going to test whether this variant is valid! */
+	g_free(style->variant);
+	style->variant = g_strdup(variant);
+
+	pidgin_message_style_read_info_plist(style, variant);
+
+	/* todo, the style has "changed". Ideally, I would like to use signals at this point. */
+}
+
+char *
+pidgin_message_style_get_variant(PidginMessageStyle *style)
+{
+	return g_strdup(style->variant);
+}
+
+/**
+ * Get a list of variants supported by the style.
+ */
+GList*
+pidgin_message_style_get_variants(PidginMessageStyle *style)
+{
+	GList *ret = NULL;
+	GDir *variants;
+	const char *css_file;
+	char *css;
+	char *variant_dir;
+
+	g_assert(style->style_dir);
+	variant_dir = g_build_filename(style->style_dir, "Contents", "Resources", "Variants", NULL);
+
+	variants = g_dir_open(variant_dir, 0, NULL);
+	if (!variants)
+		return NULL;
+
+	while ((css_file = g_dir_read_name(variants)) != NULL) {
+		if (!g_str_has_suffix(css_file, ".css"))
+			continue;
+
+		css = g_strndup(css_file, strlen(css_file) - 4);
+		ret = g_list_append(ret, css);
+	}
+
+	g_dir_close(variants);
+	g_free(variant_dir);
+
+	ret = g_list_sort(ret, (GCompareFunc)g_strcmp0);
+	return ret;
+}
+
+char *
+pidgin_message_style_get_css(PidginMessageStyle *style)
+{
+	if (!style->variant) {
+		return g_build_filename(style->style_dir, "Contents", "Resources", "main.css", NULL);
+	} else {
+		char *file = g_strdup_printf("%s.css", style->variant);
+		char *ret = g_build_filename(style->style_dir, "Contents", "Resources", "Variants",  file, NULL);
+		g_free(file);
+		return ret;
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/plugins/adiumthemes/message-style.h	Mon Sep 12 02:42:41 2011 +0000
@@ -0,0 +1,81 @@
+/* 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 <glib.h>
+
+/*
+ * I'm going to allow a different style for each PidginConversation.
+ * This way I can do two things: 1) change the theme on the fly and not
+ * change existing themes, and 2) Use a different theme for IMs and
+ * chats.
+ */
+typedef struct _PidginMessageStyle {
+	int     ref_counter;
+
+	/* current config options */
+	char     *variant; /* allowed to be NULL if there are no variants */
+
+	/* Info.plist keys that change with Variant */
+
+	/* Static Info.plist keys */
+	int      message_view_version;
+	char     *cf_bundle_name;
+	char     *cf_bundle_identifier;
+	char     *cf_bundle_get_info_string;
+	char     *default_font_family;
+	int      default_font_size;
+	gboolean shows_user_icons;
+	gboolean disable_combine_consecutive;
+	gboolean default_background_is_transparent;
+	gboolean disable_custom_background;
+	char     *default_background_color;
+	gboolean allow_text_colors;
+	char     *image_mask;
+	char     *default_variant;
+
+	/* paths */
+	char    *style_dir;
+	char    *template_path;
+
+	/* caches */
+	char    *template_html;
+	char    *header_html;
+	char    *footer_html;
+	char    *incoming_content_html;
+	char    *outgoing_content_html;
+	char    *incoming_next_content_html;
+	char    *outgoing_next_content_html;
+	char    *status_html;
+	char    *basestyle_css;
+} PidginMessageStyle;
+
+PidginMessageStyle *pidgin_message_style_load(const char *styledir);
+PidginMessageStyle *pidgin_message_style_copy(const PidginMessageStyle *style);
+void pidgin_message_style_save_state(const PidginMessageStyle *style);
+void pidgin_message_style_unref(PidginMessageStyle *style);
+void pidgin_message_style_read_info_plist(PidginMessageStyle *style, const char *variant);
+char *pidgin_message_style_get_variant(PidginMessageStyle *style);
+GList *pidgin_message_style_get_variants(PidginMessageStyle *style);
+void pidgin_message_style_set_variant(PidginMessageStyle *style, const char *variant);
+
+char *pidgin_message_style_get_css(PidginMessageStyle *style);
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/plugins/adiumthemes/webkit.c	Mon Sep 12 02:42:41 2011 +0000
@@ -0,0 +1,863 @@
+/* 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
+ *
+ */
+
+#define PLUGIN_ID		"gtk-webview-adium-ims"
+#define PLUGIN_NAME		"webview-adium-ims"
+
+/*
+ * A lot of this was originally written by Sean Egan, but I think I've
+ * rewrote enough to replace the author for now.
+ */
+#define PLUGIN_AUTHOR		"Arnold Noronha <arnstein87@gmail.com>"
+#define PURPLE_PLUGINS		"Hell yeah"
+
+/* System headers */
+#include <string.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#include <webkit/webkit.h>
+
+/* Purple headers */
+#include <conversation.h>
+#include <debug.h>
+#include <internal.h>
+#include <notify.h>
+#include <util.h>
+#include <version.h>
+
+/* Pidgin headers */
+#include <gtkconv.h>
+#include <gtkplugin.h>
+#include <gtkwebview.h>
+#include <smileyparser.h>
+
+#include <libxml/xmlreader.h>
+
+#include "message-style.h"
+/* GObject data keys */
+#define MESSAGE_STYLE_KEY "message-style"
+
+static char  *cur_style_dir = NULL;
+static void  *handle = NULL;
+
+static inline char *
+get_absolute_path(const char *path)
+{
+	if (g_path_is_absolute(path))
+		return g_strdup(path);
+	else {
+		char *cwd, *ret;
+		cwd = g_get_current_dir();
+		ret = g_build_filename(cwd, path, NULL);
+		g_free(cwd);
+		return ret;
+	}
+}
+
+static void webkit_on_webview_destroy(GtkObject* obj, gpointer data);
+
+static void *
+webkit_plugin_get_handle(void)
+{
+	if (handle)
+		return handle;
+	else
+		return (handle = g_malloc(1));
+}
+
+static void
+webkit_plugin_free_handle(void)
+{
+	purple_signals_disconnect_by_handle(handle);
+	g_free(handle);
+}
+
+static char *
+replace_message_tokens(
+	const char *text,
+	gsize len,
+	PurpleConversation *conv,
+	const char *name,
+	const char *alias,
+	const char *message,
+	PurpleMessageFlags flags,
+	time_t mtime)
+{
+	GString *str = g_string_new_len(NULL, len);
+	const char *cur = text;
+	const char *prev = cur;
+
+	while ((cur = strchr(cur, '%'))) {
+		const char *replace = NULL;
+		char *fin = NULL;
+
+		if (!strncmp(cur, "%message%", strlen("%message%"))) {
+			replace = message;
+		} else if (!strncmp(cur, "%messageClasses%", strlen("%messageClasses%"))) {
+			replace = flags & PURPLE_MESSAGE_SEND ? "outgoing" :
+				  flags & PURPLE_MESSAGE_RECV ? "incoming" : "event";
+		} else if (!strncmp(cur, "%time", strlen("%time"))) {
+			char *format = NULL;
+			if (*(cur + strlen("%time")) == '{') {
+				const char *start = cur + strlen("%time") + 1;
+				char *end = strstr(start, "}%");
+				if (!end) /* Invalid string */
+					continue;
+				format = g_strndup(start, end - start);
+				fin = end + 1;
+			}
+			replace = purple_utf8_strftime(format ? format : "%X", NULL);
+			g_free(format);
+		} else if (!strncmp(cur, "%userIconPath%", strlen("%userIconPath%"))) {
+			if (flags & PURPLE_MESSAGE_SEND) {
+				if (purple_account_get_bool(conv->account, "use-global-buddyicon", TRUE)) {
+					replace = purple_prefs_get_path(PIDGIN_PREFS_ROOT "/accounts/buddyicon");
+				} else {
+					PurpleStoredImage *img = purple_buddy_icons_find_account_icon(conv->account);
+					replace = purple_imgstore_get_filename(img);
+				}
+				if (replace == NULL || !g_file_test(replace, G_FILE_TEST_EXISTS)) {
+					replace = g_build_filename("Outgoing", "buddy_icon.png", NULL);
+				}
+			} else if (flags & PURPLE_MESSAGE_RECV) {
+				PurpleBuddyIcon *icon = purple_conv_im_get_icon(PURPLE_CONV_IM(conv));
+				replace = purple_buddy_icon_get_full_path(icon);
+				if (replace == NULL || !g_file_test(replace, G_FILE_TEST_EXISTS)) {
+					replace = g_build_filename("Incoming", "buddy_icon.png", NULL);
+				}
+			}
+
+		} else if (!strncmp(cur, "%senderScreenName%", strlen("%senderScreenName%"))) {
+			replace = name;
+		} else if (!strncmp(cur, "%sender%", strlen("%sender%"))) {
+			replace = alias;
+		} else if (!strncmp(cur, "%service%", strlen("%service%"))) {
+			replace = purple_account_get_protocol_name(conv->account);
+		} else {
+			cur++;
+			continue;
+		}
+
+		/* Here we have a replacement to make */
+		g_string_append_len(str, prev, cur - prev);
+		g_string_append(str, replace);
+
+		/* And update the pointers */
+		if (fin) {
+			prev = cur = fin + 1;
+		} else {
+			prev = cur = strchr(cur + 1, '%') + 1;
+		}
+
+	}
+
+	/* And wrap it up */
+	g_string_append(str, prev);
+	return g_string_free(str, FALSE);
+}
+
+static char *
+replace_header_tokens(char *text, gsize len, PurpleConversation *conv)
+{
+	GString *str = g_string_new_len(NULL, len);
+	char *cur = text;
+	char *prev = cur;
+
+	if (text == NULL)
+		return NULL;
+
+	while ((cur = strchr(cur, '%'))) {
+		const char *replace = NULL;
+		char *fin = NULL;
+
+		if (!strncmp(cur, "%chatName%", strlen("%chatName%"))) {
+			replace = conv->name;
+		} else if (!strncmp(cur, "%sourceName%", strlen("%sourceName%"))) {
+			replace = purple_account_get_alias(conv->account);
+			if (replace == NULL)
+				replace = purple_account_get_username(conv->account);
+		} else if (!strncmp(cur, "%destinationName%", strlen("%destinationName%"))) {
+			PurpleBuddy *buddy = purple_find_buddy(conv->account, conv->name);
+			if (buddy) {
+				replace = purple_buddy_get_alias(buddy);
+			} else {
+				replace = conv->name;
+			}
+		} else if (!strncmp(cur, "%incomingIconPath%", strlen("%incomingIconPath%"))) {
+			PurpleBuddyIcon *icon = purple_conv_im_get_icon(PURPLE_CONV_IM(conv));
+			replace = purple_buddy_icon_get_full_path(icon);
+		} else if (!strncmp(cur, "%outgoingIconPath%", strlen("%outgoingIconPath%"))) {
+		} else if (!strncmp(cur, "%timeOpened", strlen("%timeOpened"))) {
+			char *format = NULL;
+			if (*(cur + strlen("%timeOpened")) == '{') {
+				char *start = cur + strlen("%timeOpened") + 1;
+				char *end = strstr(start, "}%");
+				if (!end) /* Invalid string */
+					continue;
+				format = g_strndup(start, end - start);
+				fin = end + 1;
+			}
+			replace = purple_utf8_strftime(format ? format : "%X", NULL);
+			g_free(format);
+		} else {
+			continue;
+		}
+
+		/* Here we have a replacement to make */
+		g_string_append_len(str, prev, cur - prev);
+		g_string_append(str, replace);
+
+		/* And update the pointers */
+		if (fin) {
+			prev = cur = fin + 1;
+		} else {
+			prev = cur = strchr(cur + 1, '%') + 1;
+		}
+	}
+
+	/* And wrap it up */
+	g_string_append(str, prev);
+	return g_string_free(str, FALSE);
+}
+
+static char *
+replace_template_tokens(PidginMessageStyle *style, char *text, int len, char *header, char *footer)
+{
+	GString *str = g_string_new_len(NULL, len);
+
+	char **ms = g_strsplit(text, "%@", 6);
+	char *base = NULL;
+	char *csspath = pidgin_message_style_get_css(style);
+	if (ms[0] == NULL || ms[1] == NULL || ms[2] == NULL || ms[3] == NULL || ms[4] == NULL || ms[5] == NULL) {
+		g_strfreev(ms);
+		g_string_free(str, TRUE);
+		return NULL;
+	}
+
+	g_string_append(str, ms[0]);
+	g_string_append(str, "file://");
+	base = g_build_filename(style->style_dir, "Contents", "Resources", "Template.html", NULL);
+	g_string_append(str, base);
+	g_free(base);
+
+	g_string_append(str, ms[1]);
+
+	g_string_append(str, style->basestyle_css);
+
+	g_string_append(str, ms[2]);
+
+	g_string_append(str, "file://");
+	g_string_append(str, csspath);
+
+	g_string_append(str, ms[3]);
+	if (header)
+		g_string_append(str, header);
+	g_string_append(str, ms[4]);
+	if (footer)
+		g_string_append(str, footer);
+	g_string_append(str, ms[5]);
+
+	g_strfreev(ms);
+	g_free(csspath);
+	return g_string_free(str, FALSE);
+}
+
+static GtkWidget *
+get_webkit(PurpleConversation *conv)
+{
+	PidginConversation *gtkconv;
+	gtkconv = PIDGIN_CONVERSATION(conv);
+	if (!gtkconv)
+		return NULL;
+	else
+		return gtkconv->webview;
+}
+
+static void
+set_theme_webkit_settings(WebKitWebView *webview, PidginMessageStyle *style)
+{
+	WebKitWebSettings *settings;
+
+	g_object_get(G_OBJECT(webview), "settings", &settings, NULL);
+	if (style->default_font_family)
+		g_object_set(G_OBJECT(settings), "default-font-family", style->default_font_family, NULL);
+
+	if (style->default_font_size)
+		g_object_set(G_OBJECT(settings), "default-font-size", GINT_TO_POINTER(style->default_font_size), NULL);
+
+	/* this does not work :( */
+	webkit_web_view_set_transparent(webview, style->default_background_is_transparent);
+}
+
+/*
+ * The style specification says that if the conversation is a group
+ * chat then the <div id="Chat"> element will be given a class
+ * 'groupchat'. I can't add another '%@' in Template.html because
+ * that breaks style-specific Template.html's. I have to either use libxml
+ * or conveniently play with WebKit's javascript engine. The javascript
+ * engine should work, but it's not an identical behavior.
+ */
+static void
+webkit_set_groupchat(GtkWebView *webview)
+{
+	gtk_webview_safe_execute_script(webview, "document.getElementById('Chat').className = 'groupchat'");
+}
+
+
+/**
+ * Called when either a new PurpleConversation is created
+ * or when a PidginConversation changes its active PurpleConversation
+ * This will not change the theme if the theme is already set.
+ * (This is to prevent accidental theme changes if a new
+ * PurpleConversation gets added.
+ *
+ * FIXME: it's not at all clear to me as to how
+ * Adium themes handle the case when the PurpleConversation
+ * changes.
+ */
+static void
+init_theme_for_webkit(PurpleConversation *conv, char *style_dir)
+{
+	GtkWidget *webkit = PIDGIN_CONVERSATION(conv)->webview;
+	char *header, *footer;
+	char *template;
+
+	char* basedir;
+	char* baseuri;
+	PidginMessageStyle *style, *oldStyle;
+	PidginMessageStyle *copy;
+
+	oldStyle = g_object_get_data(G_OBJECT(webkit), MESSAGE_STYLE_KEY);
+	if (oldStyle)
+		return;
+
+	purple_debug_info("webkit", "loading %s\n", style_dir);
+	style = pidgin_message_style_load(style_dir);
+	g_assert(style);
+	g_assert(style->template_html); /* debugging test? */
+
+	basedir = g_build_filename(style->style_dir, "Contents", "Resources", "Template.html", NULL);
+	baseuri = g_strdup_printf("file://%s", basedir);
+	header = replace_header_tokens(style->header_html, strlen(style->header_html), conv);
+	g_assert(style);
+	footer = replace_header_tokens(style->footer_html, strlen(style->footer_html), conv);
+	template = replace_template_tokens(style, style->template_html, strlen(style->template_html) + strlen(style->header_html), header, footer);
+
+	g_assert(template);
+
+	purple_debug_info("webkit", "template: %s\n", template);
+
+	set_theme_webkit_settings(WEBKIT_WEB_VIEW(webkit), style);
+	webkit_web_view_load_string(WEBKIT_WEB_VIEW(webkit), template, "text/html", "UTF-8", baseuri);
+
+	copy = pidgin_message_style_copy(style);
+	g_object_set_data(G_OBJECT(webkit), MESSAGE_STYLE_KEY, copy);
+
+	pidgin_message_style_unref(style);
+	/* I need to unref this style when the webkit object destroys */
+	g_signal_connect(G_OBJECT(webkit), "destroy", G_CALLBACK(webkit_on_webview_destroy), copy);
+
+	if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT)
+		webkit_set_groupchat(GTK_WEBVIEW(webkit));
+	g_free(basedir);
+	g_free(baseuri);
+	g_free(header);
+	g_free(footer);
+	g_free(template);
+}
+
+
+/* restore the non theme version of the conversation window */
+static void
+finalize_theme_for_webkit(PurpleConversation *conv)
+{
+	GtkWidget *webview = PIDGIN_CONVERSATION(conv)->webview;
+	PidginMessageStyle *style = g_object_get_data(G_OBJECT(webview), MESSAGE_STYLE_KEY);
+
+	webkit_web_view_load_string(WEBKIT_WEB_VIEW(webview), "", "text/html", "UTF-8", "");
+
+	g_object_set_data(G_OBJECT(webview), MESSAGE_STYLE_KEY, NULL);
+	pidgin_message_style_unref(style);
+}
+
+static void
+webkit_on_webview_destroy(GtkObject *object, gpointer data)
+{
+	pidgin_message_style_unref((PidginMessageStyle *)data);
+	g_object_set_data(G_OBJECT(object), MESSAGE_STYLE_KEY, NULL);
+}
+
+static gboolean
+webkit_on_displaying_im_msg(PurpleAccount *account,
+						 const char* name,
+						 char **pmessage,
+						 PurpleConversation *conv,
+						 PurpleMessageFlags flags,
+						 gpointer data)
+{
+	GtkWidget *webkit;
+	char *message = *pmessage;
+	const char *alias = name; /* FIXME: signal doesn't give me alias */
+	char *stripped;
+	char *message_html;
+	char *msg;
+	char *escape;
+	char *script;
+	char *func = "appendMessage";
+	char *smileyed;
+	time_t mtime = time(NULL); /* FIXME: this should come from the write_conv calback, but the signal doesn't pass this to me */
+
+	PurpleMessageFlags old_flags = GPOINTER_TO_INT(purple_conversation_get_data(conv, "webkit-lastflags"));
+	PidginMessageStyle *style;
+
+	webkit = get_webkit(conv);
+	stripped = g_strdup(message);
+
+	style = g_object_get_data(G_OBJECT(webkit), MESSAGE_STYLE_KEY);
+	g_assert(style);
+
+	if (flags & PURPLE_MESSAGE_SEND && old_flags & PURPLE_MESSAGE_SEND) {
+		message_html = style->outgoing_next_content_html;
+		func = "appendNextMessage";
+	} else if (flags & PURPLE_MESSAGE_SEND) {
+		message_html = style->outgoing_content_html;
+	} else if (flags & PURPLE_MESSAGE_RECV && old_flags & PURPLE_MESSAGE_RECV) {
+		message_html = style->incoming_next_content_html;
+		func = "appendNextMessage";
+	} else if (flags & PURPLE_MESSAGE_RECV) {
+		message_html = style->incoming_content_html;
+	} else {
+		message_html = style->status_html;
+	}
+	purple_conversation_set_data(conv, "webkit-lastflags", GINT_TO_POINTER(flags));
+
+	smileyed = smiley_parse_markup(stripped, conv->account->protocol_id);
+	msg = replace_message_tokens(message_html, 0, conv, name, alias, smileyed, flags, mtime);
+	escape = gtk_webview_quote_js_string(msg);
+	script = g_strdup_printf("%s(%s)", func, escape);
+
+	purple_debug_info("webkit", "JS: %s\n", script);
+	gtk_webview_safe_execute_script(GTK_WEBVIEW(webkit), script);
+
+	g_free(script);
+	g_free(smileyed);
+	g_free(msg);
+	g_free(stripped);
+	g_free(escape);
+
+	return TRUE; /* GtkConv should not handle this IM */
+}
+
+static gboolean
+webkit_on_displaying_chat_msg(PurpleAccount *account,
+					       const char *who,
+					       char **message,
+					       PurpleConversation *conv,
+					       PurpleMessageFlags flags,
+					       gpointer userdata)
+{
+	/* handle exactly like an IM message for now */
+	return webkit_on_displaying_im_msg(account, who, message, conv, flags, NULL);
+}
+
+static void
+webkit_on_conversation_displayed(PidginConversation *gtkconv, gpointer data)
+{
+	init_theme_for_webkit(gtkconv->active_conv, cur_style_dir);
+}
+
+static void
+webkit_on_conversation_switched(PurpleConversation *conv, gpointer data)
+{
+	init_theme_for_webkit(conv, cur_style_dir);
+}
+
+static void
+webkit_on_conversation_hiding(PidginConversation *gtkconv, gpointer data)
+{
+	/*
+	 * I'm not sure if I need to do anything here, but let's keep
+	 * this anyway.
+	 */
+}
+
+static GList *
+get_dir_dir_list(const char *dirname)
+{
+	GList *ret = NULL;
+	GDir  *dir = g_dir_open(dirname, 0, NULL);
+	const char* subdir;
+
+	if (!dir) return NULL;
+	while ((subdir = g_dir_read_name(dir))) {
+		ret = g_list_append(ret, g_build_filename(dirname, subdir, NULL));
+	}
+
+	g_dir_close(dir);
+	return ret;
+}
+
+/**
+ * Get me a list of all the available themes specified by their
+ * directories. I don't guarrantee that these are valid themes, just
+ * that they are in the directories for themes.
+ */
+static GList *
+get_style_directory_list(void)
+{
+	char *user_dir, *user_style_dir, *global_style_dir;
+	GList *list1, *list2;
+
+	user_dir = get_absolute_path(purple_user_dir());
+
+	user_style_dir = g_build_filename(user_dir, "styles", NULL);
+	global_style_dir = g_build_filename(DATADIR, "pidgin", "styles", NULL);
+
+	list1 = get_dir_dir_list(user_style_dir);
+	list2 = get_dir_dir_list(global_style_dir);
+
+	g_free(global_style_dir);
+	g_free(user_style_dir);
+	g_free(user_dir);
+
+	return g_list_concat(list1, list2);
+}
+
+/**
+ * use heuristics or previous user options to figure out what
+ * theme to use as default in this Pidgin instance.
+ */
+static void
+style_set_default(void)
+{
+	GList *styles = get_style_directory_list(), *iter;
+	const char *stylepath = purple_prefs_get_string("/plugins/gtk/adiumthemes/stylepath");
+	g_assert(cur_style_dir == NULL);
+
+	if (stylepath && *stylepath)
+		styles = g_list_prepend(styles, g_strdup(stylepath));
+	else {
+		purple_notify_error(handle, _("Webkit themes"),
+			_("Can't find installed styles"),
+			_("Please install some theme and verify the installation path"));
+
+	}
+
+	/* pick any one that works. Note that we have first preference
+	 * for the one in the userdir */
+	for (iter = styles; iter; iter = g_list_next(iter)) {
+		PidginMessageStyle *style = pidgin_message_style_load(iter->data);
+		if (style) {
+			cur_style_dir = (char *)g_strdup(iter->data);
+			pidgin_message_style_unref(style);
+			break;
+		}
+		purple_debug_info("webkit", "Style %s is invalid\n", (char *)iter->data);
+	}
+
+	for (iter = styles; iter; iter = g_list_next(iter))
+		g_free(iter->data);
+	g_list_free(styles);
+}
+
+static gboolean
+plugin_load(PurplePlugin *plugin)
+{
+	style_set_default();
+	if (!cur_style_dir)
+		return FALSE; /* couldn't find a style */
+
+	purple_signal_connect(pidgin_conversations_get_handle(),
+			       "displaying-im-msg",
+			       webkit_plugin_get_handle(),
+			       PURPLE_CALLBACK(webkit_on_displaying_im_msg),
+			       NULL);
+
+	purple_signal_connect(pidgin_conversations_get_handle(),
+			       "displaying-chat-msg",
+			       webkit_plugin_get_handle(),
+			       PURPLE_CALLBACK(webkit_on_displaying_chat_msg),
+			       NULL);
+
+	purple_signal_connect(pidgin_conversations_get_handle(),
+			       "conversation-displayed",
+			       webkit_plugin_get_handle(),
+			       PURPLE_CALLBACK(webkit_on_conversation_displayed),
+			       NULL);
+
+	purple_signal_connect(pidgin_conversations_get_handle(),
+			       "conversation-switched",
+			       webkit_plugin_get_handle(),
+			       PURPLE_CALLBACK(webkit_on_conversation_switched),
+			       NULL);
+
+	purple_signal_connect(pidgin_conversations_get_handle(),
+			       "conversation-hiding",
+			       webkit_plugin_get_handle(),
+			       PURPLE_CALLBACK(webkit_on_conversation_hiding),
+			       NULL);
+
+	/* finally update each of the existing conversation windows */
+	{
+		GList *list = purple_get_conversations();
+		for (;list; list = g_list_next(list))
+			init_theme_for_webkit(list->data, cur_style_dir);
+
+	}
+	return TRUE;
+}
+
+static gboolean
+plugin_unload(PurplePlugin *plugin)
+{
+	GList *list;
+
+	webkit_plugin_free_handle();
+	cur_style_dir = NULL;
+	list = purple_get_conversations();
+	while (list) {
+		finalize_theme_for_webkit(list->data);
+		list = g_list_next(list);
+	}
+
+	return TRUE;
+}
+
+/*
+ * UI config code
+ */
+
+static void
+style_changed(GtkWidget *combobox, gpointer null)
+{
+	char *name = gtk_combo_box_get_active_text(GTK_COMBO_BOX(combobox));
+	GtkWidget *dialog;
+	GList *styles = get_style_directory_list(), *iter;
+
+	/* find the full path for this name, I wish I could store this info in the combobox itself. :( */
+	for (iter = styles; iter; iter = g_list_next(iter)) {
+		char *basename = g_path_get_basename(iter->data);
+		if (g_str_equal(basename, name)) {
+			g_free(basename);
+			break;
+		}
+		g_free(basename);
+	}
+
+	g_assert(iter);
+	g_free(name);
+	g_free(cur_style_dir);
+	cur_style_dir = g_strdup(iter->data);;
+	purple_prefs_set_string("/plugins/gtk/adiumthemes/stylepath", cur_style_dir);
+
+	/* inform the user that existing conversations haven't changed */
+	dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, "The style for existing conversations have not been changed. Please close and re-open the conversation for the changes to take effect.");
+	g_assert(dialog);
+	gtk_widget_show(dialog);
+	g_signal_connect_swapped(dialog, "response", G_CALLBACK(gtk_widget_destroy), dialog);
+}
+
+static GtkWidget *
+get_style_config_frame(void)
+{
+	GtkWidget *combobox = gtk_combo_box_new_text();
+	GList *styles = get_style_directory_list(), *iter;
+	int index = 0, selected = 0;
+
+	for (iter = styles; iter; iter = g_list_next(iter)) {
+		PidginMessageStyle *style = pidgin_message_style_load(iter->data);
+
+		if (style) {
+			char *text = g_path_get_basename(iter->data);
+			gtk_combo_box_append_text(GTK_COMBO_BOX(combobox), text);
+			g_free(text);
+
+			if (g_str_equal(iter->data, cur_style_dir))
+				selected = index;
+			index++;
+			pidgin_message_style_unref(style);
+		}
+	}
+	gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), selected);
+	g_signal_connect_after(G_OBJECT(combobox), "changed", G_CALLBACK(style_changed), NULL);
+	return combobox;
+}
+
+static void
+variant_update_conversation(PurpleConversation *conv)
+{
+	PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
+	WebKitWebView *webview = WEBKIT_WEB_VIEW(gtkconv->webview);
+	PidginMessageStyle *style = (PidginMessageStyle *)g_object_get_data(G_OBJECT(webview), MESSAGE_STYLE_KEY);
+	char *script;
+
+	g_assert(style);
+
+	script = g_strdup_printf("setStylesheet(\"mainStyle\",\"%s\")", pidgin_message_style_get_css(style));
+	gtk_webview_safe_execute_script(GTK_WEBVIEW(webview), script);
+
+	set_theme_webkit_settings(WEBKIT_WEB_VIEW(gtkconv->webview), style);
+	g_free(script);
+}
+
+static void
+variant_changed(GtkWidget* combobox, gpointer null)
+{
+	char *name;
+	GList *list;
+	PidginMessageStyle *style = pidgin_message_style_load(cur_style_dir);
+
+	g_assert(style);
+	name = gtk_combo_box_get_active_text(GTK_COMBO_BOX(combobox));
+	pidgin_message_style_set_variant(style, name);
+	pidgin_message_style_save_state(style);
+
+	/* update conversations */
+	list = purple_get_conversations();
+	while (list) {
+		variant_update_conversation(list->data);
+		list = g_list_next(list);
+	}
+
+	g_free(name);
+	pidgin_message_style_unref(style);
+}
+
+static GtkWidget *
+get_variant_config_frame()
+{
+	PidginMessageStyle *style = pidgin_message_style_load(cur_style_dir);
+	GList *variants = pidgin_message_style_get_variants(style), *iter;
+	char *cur_variant = pidgin_message_style_get_variant(style);
+	GtkWidget *combobox = gtk_combo_box_new_text();
+	int def = -1, index = 0;
+
+	pidgin_message_style_unref(style);
+
+	for (iter = variants; iter; iter = g_list_next(iter)) {
+		gtk_combo_box_append_text(GTK_COMBO_BOX(combobox), iter->data);
+
+		if (g_str_equal(cur_variant, iter->data))
+			def = index;
+		index ++;
+
+	}
+
+	gtk_combo_box_set_active(GTK_COMBO_BOX(combobox), def);
+	g_signal_connect(G_OBJECT(combobox), "changed", G_CALLBACK(variant_changed), NULL);
+
+	return combobox;
+}
+
+static void
+style_changed_reset_variants(GtkWidget* combobox, gpointer table)
+{
+	/* I hate to do this, I swear. But I don't know how to cleanly clean an existing combobox */
+	GtkWidget* variants = g_object_get_data(G_OBJECT(table), "variants-cbox");
+	gtk_widget_destroy(variants);
+	variants = get_variant_config_frame();
+	gtk_table_attach_defaults(GTK_TABLE(table), variants, 1, 2, 1, 2);
+	gtk_widget_show_all(GTK_WIDGET(table));
+
+	g_object_set_data(G_OBJECT(table), "variants-cbox", variants);
+}
+
+static GtkWidget*
+get_config_frame(PurplePlugin* plugin)
+{
+	GtkWidget *table = gtk_table_new(2, 2, FALSE);
+	GtkWidget *style_config = get_style_config_frame();
+	GtkWidget *variant_config = get_variant_config_frame();
+
+	gtk_table_attach_defaults(GTK_TABLE(table), gtk_label_new("Message Style"), 0, 1, 0, 1);
+	gtk_table_attach_defaults(GTK_TABLE(table), style_config, 1, 2, 0, 1);
+	gtk_table_attach_defaults(GTK_TABLE(table), gtk_label_new("Style Variant"), 0, 1, 1, 2);
+	gtk_table_attach_defaults(GTK_TABLE(table), variant_config, 1, 2, 1, 2);
+
+	g_object_set_data(G_OBJECT(table), "variants-cbox", variant_config);
+	/* to clarify, this is a second signal connected on style config */
+	g_signal_connect_after(G_OBJECT(style_config), "changed", G_CALLBACK(style_changed_reset_variants), table);
+
+	return table;
+}
+
+PidginPluginUiInfo ui_info =
+{
+        get_config_frame,
+        0, /* page_num (Reserved) */
+
+        /* padding */
+        NULL,
+        NULL,
+        NULL,
+        NULL
+};
+
+
+static PurplePluginInfo info =
+{
+	PURPLE_PLUGIN_MAGIC,		/* Magic				*/
+	PURPLE_MAJOR_VERSION,		/* Purple Major Version	*/
+	PURPLE_MINOR_VERSION,		/* Purple Minor Version	*/
+	PURPLE_PLUGIN_STANDARD,		/* plugin type			*/
+	PIDGIN_PLUGIN_TYPE,			/* ui requirement		*/
+	0,							/* flags				*/
+	NULL,						/* dependencies			*/
+	PURPLE_PRIORITY_DEFAULT,	/* priority				*/
+
+	PLUGIN_ID,					/* plugin id			*/
+	NULL,						/* name					*/
+	"0.1",					/* version				*/
+	NULL,						/* summary				*/
+	NULL,						/* description			*/
+	PLUGIN_AUTHOR,				/* author				*/
+	"http://pidgin.im",					/* website				*/
+
+	plugin_load,				/* load					*/
+	plugin_unload,				/* unload				*/
+	NULL,						/* destroy				*/
+
+	&ui_info,						/* ui_info				*/
+	NULL,						/* extra_info			*/
+	NULL,						/* prefs_info			*/
+	NULL,						/* actions				*/
+	NULL,						/* reserved 1			*/
+	NULL,						/* reserved 2			*/
+	NULL,						/* reserved 3			*/
+	NULL						/* reserved 4			*/
+};
+
+static void
+init_plugin(PurplePlugin *plugin) {
+	info.name = "Adium IMs";
+	info.summary = "Adium-like IMs with Pidgin";
+	info.description = "You can chat in Pidgin using Adium's WebKit view.";
+
+	purple_prefs_add_none("/plugins");
+	purple_prefs_add_none("/plugins/gtk");
+	purple_prefs_add_none("/plugins/gtk/adiumthemes");
+	purple_prefs_add_string("/plugins/gtk/adiumthemes/stylepath", "");
+}
+
+PURPLE_INIT_PLUGIN(webkit, init_plugin, info)
+
--- a/pidgin/plugins/history.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/pidgin/plugins/history.c	Mon Sep 12 02:42:41 2011 +0000
@@ -16,16 +16,17 @@
 #include "gtkconv.h"
 #include "gtkimhtml.h"
 #include "gtkplugin.h"
+#include "gtkwebview.h"
 
 #define HISTORY_PLUGIN_ID "gtk-history"
 
 #define HISTORY_SIZE (4 * 1024)
 
-static gboolean _scroll_imhtml_to_end(gpointer data)
+static gboolean _scroll_webview_to_end(gpointer data)
 {
-	GtkIMHtml *imhtml = data;
-	gtk_imhtml_scroll_to_end(GTK_IMHTML(imhtml), FALSE);
-	g_object_unref(G_OBJECT(imhtml));
+	GtkWebView *webview = data;
+	gtk_webview_scroll_to_end(GTK_WEBVIEW(webview), FALSE);
+	g_object_unref(G_OBJECT(webview));
 	return FALSE;
 }
 
@@ -39,9 +40,15 @@
 	guint flags;
 	char *history;
 	PidginConversation *gtkconv;
+#if 0
+	/* FIXME: WebView has no options */
 	GtkIMHtmlOptions options = GTK_IMHTML_NO_COLOURS;
+#endif
 	char *header;
+#if 0
+	/* FIXME: WebView has no protocol setting */
 	char *protocol;
+#endif
 	char *escaped_alias;
 	const char *header_date;
 
@@ -116,15 +123,21 @@
 
 	history = purple_log_read((PurpleLog*)logs->data, &flags);
 	gtkconv = PIDGIN_CONVERSATION(c);
+#if 0
+	/* FIXME: WebView has no options */
 	if (flags & PURPLE_LOG_READ_NO_NEWLINE)
 		options |= GTK_IMHTML_NO_NEWLINE;
+#endif
 
+#if 0
+	/* FIXME: WebView has no protocol setting */
 	protocol = g_strdup(gtk_imhtml_get_protocol_name(GTK_IMHTML(gtkconv->imhtml)));
 	gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml),
 			purple_account_get_protocol_name(((PurpleLog*)logs->data)->account));
+#endif
 
-	if (gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml))))
-		gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR>", options);
+	if (!gtk_webview_is_empty(GTK_WEBVIEW(gtkconv->webview)))
+		gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), "<BR>");
 
 	escaped_alias = g_markup_escape_text(alias, -1);
 
@@ -134,21 +147,24 @@
 		header_date = purple_date_format_full(localtime(&((PurpleLog *)logs->data)->time));
 
 	header = g_strdup_printf(_("<b>Conversation with %s on %s:</b><br>"), escaped_alias, header_date);
-	gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), header, options);
+	gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), header);
 	g_free(header);
 	g_free(escaped_alias);
 
 	g_strchomp(history);
-	gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), history, options);
+	gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), history);
 	g_free(history);
 
-	gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<hr>", options);
+	gtk_webview_append_html(GTK_WEBVIEW(gtkconv->webview), "<hr>");
 
+#if 0
+	/* FIXME: WebView has no protocol setting */
 	gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml), protocol);
 	g_free(protocol);
+#endif
 
-	g_object_ref(G_OBJECT(gtkconv->imhtml));
-	g_idle_add(_scroll_imhtml_to_end, gtkconv->imhtml);
+	g_object_ref(G_OBJECT(gtkconv->webview));
+	g_idle_add(_scroll_webview_to_end, gtkconv->webview);
 
 	g_list_foreach(logs, (GFunc)purple_log_free, NULL);
 	g_list_free(logs);
--- a/pidgin/plugins/notify.c	Sun Sep 11 04:19:01 2011 +0000
+++ b/pidgin/plugins/notify.c	Mon Sep 12 02:42:41 2011 +0000
@@ -303,7 +303,7 @@
 attach_signals(PurpleConversation *conv)
 {
 	PidginConversation *gtkconv = NULL;
-	GSList *imhtml_ids = NULL, *entry_ids = NULL;
+	GSList *webview_ids = NULL, *entry_ids = NULL;
 	guint id;
 
 	gtkconv = PIDGIN_CONVERSATION(conv);
@@ -322,9 +322,9 @@
 		                      G_CALLBACK(unnotify_cb), conv);
 		entry_ids = g_slist_append(entry_ids, GUINT_TO_POINTER(id));
 
-		id = g_signal_connect(G_OBJECT(gtkconv->imhtml), "focus-in-event",
+		id = g_signal_connect(G_OBJECT(gtkconv->webview), "focus-in-event",
 		                      G_CALLBACK(unnotify_cb), conv);
-		imhtml_ids = g_slist_append(imhtml_ids, GUINT_TO_POINTER(id));
+		webview_ids = g_slist_append(webview_ids, GUINT_TO_POINTER(id));
 	}
 
 	if (purple_prefs_get_bool("/plugins/gtk/X11/notify/notify_click")) {
@@ -334,9 +334,9 @@
 		                      G_CALLBACK(unnotify_cb), conv);
 		entry_ids = g_slist_append(entry_ids, GUINT_TO_POINTER(id));
 
-		id = g_signal_connect(G_OBJECT(gtkconv->imhtml), "button-press-event",
+		id = g_signal_connect(G_OBJECT(gtkconv->webview), "button-press-event",
 		                      G_CALLBACK(unnotify_cb), conv);
-		imhtml_ids = g_slist_append(imhtml_ids, GUINT_TO_POINTER(id));
+		webview_ids = g_slist_append(webview_ids, GUINT_TO_POINTER(id));
 	}
 
 	if (purple_prefs_get_bool("/plugins/gtk/X11/notify/notify_type")) {
@@ -345,7 +345,7 @@
 		entry_ids = g_slist_append(entry_ids, GUINT_TO_POINTER(id));
 	}
 
-	purple_conversation_set_data(conv, "notify-imhtml-signals", imhtml_ids);
+	purple_conversation_set_data(conv, "notify-webview-signals", webview_ids);
 	purple_conversation_set_data(conv, "notify-entry-signals", entry_ids);
 
 	return 0;
@@ -361,9 +361,9 @@
 	if (!gtkconv)
 		return;
 
-	ids = purple_conversation_get_data(conv, "notify-imhtml-signals");
+	ids = purple_conversation_get_data(conv, "notify-webview-signals");
 	for (l = ids; l != NULL; l = l->next)
-		g_signal_handler_disconnect(gtkconv->imhtml, GPOINTER_TO_INT(l->data));
+		g_signal_handler_disconnect(gtkconv->webview, GPOINTER_TO_INT(l->data));
 	g_slist_free(ids);
 
 	ids = purple_conversation_get_data(conv, "notify-entry-signals");
@@ -373,7 +373,7 @@
 
 	purple_conversation_set_data(conv, "notify-message-count", GINT_TO_POINTER(0));
 
-	purple_conversation_set_data(conv, "notify-imhtml-signals", NULL);
+	purple_conversation_set_data(conv, "notify-webview-signals", NULL);
 	purple_conversation_set_data(conv, "notify-entry-signals", NULL);
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/smileyparser.c	Mon Sep 12 02:42:41 2011 +0000
@@ -0,0 +1,169 @@
+/* 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 <gtk/gtk.h>
+#include <debug.h>
+#include "smileyparser.h"
+#include <smiley.h>
+#include <string.h>
+#include "gtkthemes.h"
+
+static char *
+get_fullpath(const char *filename)
+{
+	if (g_path_is_absolute(filename))
+		return g_strdup(filename);
+	else
+		return g_build_path(g_get_current_dir(), filename, NULL);
+}
+
+static void
+parse_for_shortcut_plaintext(const char *text, const char *shortcut, const char *file, GString *ret)
+{
+	const char *tmp = text;
+
+	for (;*tmp;) {
+		const char *end = strstr(tmp, shortcut);
+		char *path;
+		char *escaped_path;
+
+		if (end == NULL) {
+			g_string_append(ret, tmp);
+			break;
+		}
+		path = get_fullpath(file);
+		escaped_path = g_markup_escape_text(path, -1);
+
+		g_string_append_len(ret, tmp, end-tmp);
+		g_string_append_printf(ret,"<img alt='%s' src='%s' />",
+					shortcut, escaped_path);
+		g_free(path);
+		g_free(escaped_path);
+		g_assert(strlen(tmp) >= strlen(shortcut));
+		tmp = end + strlen(shortcut);
+	}
+}
+
+static char *
+parse_for_shortcut(const char *markup, const char *shortcut, const char *file)
+{
+	GString* ret = g_string_new("");
+	char *local_markup = g_strdup(markup);
+	char *escaped_shortcut = g_markup_escape_text(shortcut, -1);
+
+	char *temp = local_markup;
+
+	for (;*temp;) {
+		char *end = strchr(temp, '<');
+		char *end_of_tag;
+
+		if (!end) {
+			parse_for_shortcut_plaintext(temp, escaped_shortcut, file, ret);
+			break;
+		}
+
+		*end = 0;
+		parse_for_shortcut_plaintext(temp, escaped_shortcut, file, ret);
+		*end = '<';
+
+		/* if this is well-formed, then there should be no '>' within
+		 * the tag. TODO: handle a comment tag better :( */
+		end_of_tag = strchr(end, '>');
+		if (!end_of_tag) {
+			g_string_append(ret, end);
+			break;
+		}
+
+		g_string_append_len(ret, end, end_of_tag - end + 1);
+
+		temp = end_of_tag + 1;
+	}
+	g_free(local_markup);
+	g_free(escaped_shortcut);
+	return g_string_free(ret, FALSE);
+}
+
+static char *
+parse_for_purple_smiley(const char *markup, PurpleSmiley *smiley)
+{
+	char *file = purple_smiley_get_full_path(smiley);
+	char *ret = parse_for_shortcut(markup, purple_smiley_get_shortcut(smiley), file);
+	g_free(file);
+	return ret;
+}
+
+static char *
+parse_for_smiley_list(const char *markup, GHashTable *smileys)
+{
+	GHashTableIter iter;
+	char *key, *value;
+	char *ret = g_strdup(markup);
+
+	g_hash_table_iter_init(&iter, smileys);
+	while (g_hash_table_iter_next(&iter, (gpointer *)&key, (gpointer *)&value))
+	{
+		char *temp = parse_for_shortcut(ret, key, value);
+		g_free(ret);
+		ret = temp;
+	}
+
+	return ret;
+}
+
+char *
+smiley_parse_markup(const char *markup, const char *proto_id)
+{
+	GList *smileys = purple_smileys_get_all();
+	char *temp = g_strdup(markup), *temp2;
+	struct smiley_list *list;
+	const char *proto_name = "default";
+
+	if (proto_id != NULL) {
+		PurplePlugin *proto;
+		proto = purple_find_prpl(proto_id);
+		proto_name = proto->info->name;
+	}
+
+	/* unnecessarily slow, but lets manage for now. */
+	for (; smileys; smileys = g_list_next(smileys)) {
+		temp2 = parse_for_purple_smiley(temp, PURPLE_SMILEY(smileys->data));
+		g_free(temp);
+		temp = temp2;
+	}
+
+	/* now for each theme smiley, observe that this does look nasty */
+	if (!current_smiley_theme || !(current_smiley_theme->list)) {
+		purple_debug_warning("smiley", "theme does not exist\n");
+		return temp;
+	}
+
+	for (list = current_smiley_theme->list; list; list = list->next) {
+		if (g_str_equal(list->sml, proto_name)) {
+			temp2 = parse_for_smiley_list(temp, list->files);
+			g_free(temp);
+			temp = temp2;
+		}
+	}
+
+	return temp;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/smileyparser.h	Mon Sep 12 02:42:41 2011 +0000
@@ -0,0 +1,25 @@
+/* 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
+ *
+ */
+
+char *
+smiley_parse_markup(const char *markup, const char *sml);
+