changeset 19186:eef82b050c21

merge of 'bbeac90ad01d5059327da9e2504716614a191cab' and 'f30bfc6fc7aee19d096dd838aad5a784a7371d6c'
author Kevin Stange <kevin@simguy.net>
date Sat, 11 Aug 2007 21:08:27 +0000
parents e20619418edf (current diff) cdb0fbe5de7b (diff)
children 0ac6c0fbc102
files pidgin/sounds/Makefile.am pidgin/sounds/Makefile.mingw pidgin/sounds/alert.wav pidgin/sounds/login.wav pidgin/sounds/logout.wav pidgin/sounds/receive.wav pidgin/sounds/send.wav
diffstat 156 files changed, 2258 insertions(+), 439 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Sat Aug 11 21:08:06 2007 +0000
+++ b/COPYRIGHT	Sat Aug 11 21:08:27 2007 +0000
@@ -278,6 +278,7 @@
 Joao Luís Marques Pinto
 Aleksander Piotrowski
 Julien Pivotto
+Eric Polino <aluink@gmail.com>
 Ari Pollak
 Robey Pointer
 Eric Polino
--- a/ChangeLog	Sat Aug 11 21:08:06 2007 +0000
+++ b/ChangeLog	Sat Aug 11 21:08:27 2007 +0000
@@ -7,6 +7,7 @@
 	* Server-stored aliases for Yahoo. (John Moody)
 	* Fixed support for Yahoo! doodling.
 	* Bonjour plugin uses native Avahi instead of Howl
+	* Bonjour plugin supports Buddy Icons
 
 	Pidgin:
 	* Show current outgoing conversation formatting on the font label on
@@ -14,6 +15,9 @@
 	* Slim new redesign of conversation tabs to maximize number of
 	  conversations that can fit in a window
 
+	Finch:
+	* Sound support (Eric Polino)
+
 version 2.1.0 (07/28/2007):
 	libpurple:
 	* Core changes to allow UIs to use second-granularity for scheduling.
--- a/ChangeLog.API	Sat Aug 11 21:08:06 2007 +0000
+++ b/ChangeLog.API	Sat Aug 11 21:08:27 2007 +0000
@@ -1,5 +1,15 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+Version 2.1.1 (x/x/x):
+	libpurple:
+		Changed:
+		* PurpleAccountUiOps.request_authorize's authorize_cb and
+		  deny_cb parameters now correctly have type
+		  PurpleAccountRequestAuthorizationCb rather than GCallback.
+		  (You'll want to change your UI's implementation's signature
+		  to avoid warnings, and then remove some now-redundant casts
+		  back to the proper type.)
+
 version 2.1.0 (7/28/2007):
 	libpurple:
 		Added:
--- a/Makefile.am	Sat Aug 11 21:08:06 2007 +0000
+++ b/Makefile.am	Sat Aug 11 21:08:27 2007 +0000
@@ -42,7 +42,7 @@
 GNT_DIR=finch
 endif
 
-SUBDIRS = libpurple doc $(GNT_DIR) $(GTK_DIR) m4macros po
+SUBDIRS = libpurple doc $(GNT_DIR) $(GTK_DIR) m4macros po share
 
 docs: Doxyfile
 if HAVE_DOXYGEN
--- a/configure.ac	Sat Aug 11 21:08:06 2007 +0000
+++ b/configure.ac	Sat Aug 11 21:08:27 2007 +0000
@@ -54,9 +54,9 @@
 
 m4_define([gnt_lt_current], [1])
 m4_define([gnt_major_version], [2])
-m4_define([gnt_minor_version], [0])
+m4_define([gnt_minor_version], [1])
 m4_define([gnt_micro_version], [0])
-m4_define([gnt_version_suffix], [])
+m4_define([gnt_version_suffix], [devel])
 m4_define([gnt_version],
           [gnt_major_version.gnt_minor_version.gnt_micro_version])
 m4_define([gnt_display_version], gnt_version[]m4_ifdef([gnt_version_suffix],[gnt_version_suffix]))
@@ -2086,6 +2086,8 @@
 	AC_DEFINE(DEBUG, 1, [Define if debugging is enabled.])
 fi
 
+AM_CONDITIONAL(PURPLE_AVAILABLE, true)
+
 AC_OUTPUT([Makefile
 		   Doxyfile
 		   doc/Makefile
@@ -2164,7 +2166,6 @@
 		   pidgin/plugins/perl/Makefile
 		   pidgin/plugins/perl/common/Makefile.PL
 		   pidgin/plugins/ticker/Makefile
-		   pidgin/sounds/Makefile
 		   libpurple/example/Makefile
 		   libpurple/gconf/Makefile
 		   libpurple/purple.pc
@@ -2197,6 +2198,8 @@
 		   libpurple/protocols/zephyr/Makefile
 		   libpurple/tests/Makefile
 		   libpurple/version.h
+		   share/Makefile
+		   share/sounds/Makefile
 		   finch/Makefile
 		   finch/libgnt/Makefile
 		   finch/libgnt/gnt.pc
--- a/finch/Makefile.am	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/Makefile.am	Sat Aug 11 21:08:27 2007 +0000
@@ -60,6 +60,7 @@
 	$(GLIB_LIBS) \
 	$(LIBXML_LIBS) \
 	$(GNT_LIBS) \
+	$(GSTREAMER_LIBS) \
 	./libgnt/libgnt.la \
 	$(top_builddir)/libpurple/libpurple.la
 
@@ -77,4 +78,5 @@
 	$(GLIB_CFLAGS) \
 	$(DBUS_CFLAGS) \
 	$(LIBXML_CFLAGS) \
+	$(GSTREAMER_CFLAGS) \
 	$(GNT_CFLAGS)
--- a/finch/finch.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/finch.c	Sat Aug 11 21:08:27 2007 +0000
@@ -55,16 +55,10 @@
 	purple_debug_set_ui_ops(finch_debug_get_ui_ops());
 }
 
-/* XXX: this "leaks" a hashtable on shutdown.  I'll let
- * the finch guys decide if they want to go through the trouble
- * of properly freeing it, since their quit function doesn't
- * live in this file */
-
 static GHashTable *ui_info = NULL;
-
 static GHashTable *finch_ui_get_info()
 {
-	if(NULL == ui_info) {
+	if (ui_info == NULL) {
 		ui_info = g_hash_table_new(g_str_hash, g_str_equal);
 
 		g_hash_table_insert(ui_info, "name", (char*)_("Finch"));
@@ -74,12 +68,20 @@
 	return ui_info;
 }
 
+static void
+finch_quit(void)
+{
+	gnt_ui_uninit();
+	if (ui_info)
+		g_hash_table_destroy(ui_info);
+}
+
 static PurpleCoreUiOps core_ops =
 {
 	finch_prefs_init,
 	debug_init,
 	gnt_ui_init,
-	gnt_ui_uninit,
+	finch_quit,
 	finch_ui_get_info,
 
 	/* padding */
@@ -396,7 +398,17 @@
 	return 1;
 }
 
-int main(int argc, char **argv)
+static gboolean gnt_start(int *argc, char ***argv)
+{
+	/* Initialize the libpurple stuff */
+	if (!init_libpurple(*argc, *argv))
+		return FALSE;
+ 
+	purple_blist_show();
+	return TRUE;
+}
+
+int main(int argc, char *argv[])
 {
 	signal(SIGPIPE, SIG_IGN);
 
@@ -405,11 +417,10 @@
 	g_set_application_name(_("Finch"));
 #endif
 
-	/* Initialize the libpurple stuff */
-	if (!init_libpurple(argc, argv))
-		return 0;
- 
-	purple_blist_show();
+	gnt_init();
+
+	gnt_start(&argc, &argv);
+
 	gnt_main();
 
 #ifdef STANDALONE
--- a/finch/finch.h	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/finch.h	Sat Aug 11 21:08:27 2007 +0000
@@ -27,3 +27,4 @@
 
 #define FINCH_UI "gnt-purple"
 
+#define FINCH_PREFS_ROOT "/finch"
--- a/finch/gntaccount.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/gntaccount.c	Sat Aug 11 21:08:27 2007 +0000
@@ -912,9 +912,15 @@
 }
 
 static void *
-finch_request_authorize(PurpleAccount *account, const char *remote_user,
-					const char *id, const char *alias, const char *message, gboolean on_list,
-					GCallback auth_cb, GCallback deny_cb, void *user_data)
+finch_request_authorize(PurpleAccount *account,
+                        const char *remote_user,
+                        const char *id,
+                        const char *alias,
+                        const char *message,
+                        gboolean on_list,
+                        PurpleAccountRequestAuthorizationCb auth_cb,
+                        PurpleAccountRequestAuthorizationCb deny_cb,
+                        void *user_data)
 {
 	char *buffer;
 	PurpleConnection *gc;
@@ -941,8 +947,8 @@
 		GList *iter;
 		auth_and_add *aa = g_new(auth_and_add, 1);
 
-		aa->auth_cb = (PurpleAccountRequestAuthorizationCb)auth_cb;
-		aa->deny_cb = (PurpleAccountRequestAuthorizationCb)deny_cb;
+		aa->auth_cb = auth_cb;
+		aa->deny_cb = deny_cb;
 		aa->data = user_data;
 		aa->username = g_strdup(remote_user);
 		aa->alias = g_strdup(alias);
--- a/finch/gntconv.h	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/gntconv.h	Sat Aug 11 21:08:27 2007 +0000
@@ -30,6 +30,9 @@
 
 #include "conversation.h"
 
+/* Grabs the conv out of a PurpleConverstation */
+#define FINCH_CONV(conv) ((FinchConv *)(conv)->ui_data)
+
 /***************************************************************************
  * @name GNT Conversations API
  ***************************************************************************/
--- a/finch/gntsound.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/gntsound.c	Sat Aug 11 21:08:27 2007 +0000
@@ -23,28 +23,1052 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 #include "internal.h"
+#include "finch.h"
+
+#ifdef _WIN32
+#include <windows.h>
+#include <mmsystem.h>
+#endif
+
+#ifdef USE_GSTREAMER
+#include <gst/gst.h>
+#endif /* USE_GSTREAMER */
+
+#include "debug.h"
+#include "notify.h"
+#include "prefs.h"
+#include "sound.h"
+#include "util.h"
+
+#include "gntbox.h"
+#include "gntwindow.h"
+#include "gntcombobox.h"
+#include "gntlabel.h"
+#include "gntconv.h"
 #include "gntsound.h"
+#include "gntwidget.h"
+#include "gntentry.h"
+#include "gntcheckbox.h"
+#include "gntline.h"
+#include "gntslider.h"
+#include "gnttree.h"
+#include "gntfilesel.h"
+
+typedef struct {
+	PurpleSoundEventID id;
+	char *label;
+	char *pref;
+	char *def;
+	char *file;
+} FinchSoundEvent;
+
+typedef struct {
+	GntWidget *method;
+	GntWidget *command;
+	GntWidget *conv_focus;
+	GntWidget *while_status;
+	GntWidget *volume;
+	GntWidget *events;
+	GntWidget *window;
+	GntWidget *selector;
+
+	GntWidget *profiles;
+	GntWidget *new_profile;
+	gchar * original_profile;
+} SoundPrefDialog;
+
+#define DEFAULT_PROFILE "default"
+
+static SoundPrefDialog *pref_dialog;
+
+#define PLAY_SOUND_TIMEOUT 15000
+
+static guint mute_login_sounds_timeout = 0;
+static gboolean mute_login_sounds = FALSE;
+
+#ifdef USE_GSTREAMER
+static gboolean gst_init_failed;
+#endif /* USE_GSTREAMER */
+
+static FinchSoundEvent sounds[PURPLE_NUM_SOUNDS] = {
+	{PURPLE_SOUND_BUDDY_ARRIVE, N_("Buddy logs in"), "login", "login.wav", NULL},
+	{PURPLE_SOUND_BUDDY_LEAVE,  N_("Buddy logs out"), "logout", "logout.wav", NULL},
+	{PURPLE_SOUND_RECEIVE,      N_("Message received"), "im_recv", "receive.wav", NULL},
+	{PURPLE_SOUND_FIRST_RECEIVE, N_("Message received begins conversation"), "first_im_recv", "receive.wav", NULL},
+	{PURPLE_SOUND_SEND,         N_("Message sent"), "send_im", "send.wav", NULL},
+	{PURPLE_SOUND_CHAT_JOIN,    N_("Person enters chat"), "join_chat", "login.wav", NULL},
+	{PURPLE_SOUND_CHAT_LEAVE,   N_("Person leaves chat"), "left_chat", "logout.wav", NULL},
+	{PURPLE_SOUND_CHAT_YOU_SAY, N_("You talk in chat"), "send_chat_msg", "send.wav", NULL},
+	{PURPLE_SOUND_CHAT_SAY,     N_("Others talk in chat"), "chat_msg_recv", "receive.wav", NULL},
+	{PURPLE_SOUND_POUNCE_DEFAULT, NULL, "pounce_default", "alert.wav", NULL},
+	{PURPLE_SOUND_CHAT_NICK,    N_("Someone says your screen name in chat"), "nick_said", "alert.wav", NULL}
+};
+
+const char *
+finch_sound_get_active_profile()
+{
+	return purple_prefs_get_string(FINCH_PREFS_ROOT "/sound/actprofile");
+}
 
-const char *finch_sound_get_active_profile(void)
+/* This method creates a pref name based on the current active profile.
+ * So if "Home" is the current active profile the pref name
+ * [FINCH_PREFS_ROOT "/sound/profiles/Home/$NAME"] is created.
+ */
+static gchar *
+make_pref(const char *name)
+{
+	static char pref_string[512];
+	g_snprintf(pref_string, sizeof(pref_string),
+			FINCH_PREFS_ROOT "/sound/profiles/%s%s", finch_sound_get_active_profile(), name);
+	return pref_string;
+}
+
+
+static gboolean
+unmute_login_sounds_cb(gpointer data)
+{
+	mute_login_sounds = FALSE;
+	mute_login_sounds_timeout = 0;
+	return FALSE;
+}
+
+static gboolean
+chat_nick_matches_name(PurpleConversation *conv, const char *aname)
 {
-	return NULL;
+	PurpleConvChat *chat = NULL;
+	char *nick = NULL;
+	char *name = NULL;
+	gboolean ret = FALSE;
+	chat = purple_conversation_get_chat_data(conv);
+
+	if (chat == NULL)
+		return ret;
+
+	nick = g_strdup(purple_normalize(conv->account, chat->nick));
+	name = g_strdup(purple_normalize(conv->account, aname));
+
+	if (g_utf8_collate(nick, name) == 0)
+		ret = TRUE;
+
+	g_free(nick);
+	g_free(name);
+
+	return ret;
+}
+
+/*
+ * play a sound event for a conversation, honoring make_sound flag
+ * of conversation and checking for focus if conv_focus pref is set
+ */
+static void
+play_conv_event(PurpleConversation *conv, PurpleSoundEventID event)
+{
+	/* If we should not play the sound for some reason, then exit early */
+	if (conv != NULL)
+	{
+		FinchConv *gntconv;
+		gboolean has_focus;
+
+		gntconv = FINCH_CONV(conv);
+
+		has_focus = purple_conversation_has_focus(conv);
+
+		if (has_focus && !purple_prefs_get_bool(make_pref("/conv_focus")))
+		{
+			return;
+		}
+	}
+
+	purple_sound_play_event(event, conv ? purple_conversation_get_account(conv) : NULL);
+}
+
+static void
+buddy_state_cb(PurpleBuddy *buddy, PurpleSoundEventID event)
+{
+	purple_sound_play_event(event, purple_buddy_get_account(buddy));
+}
+
+static void
+im_msg_received_cb(PurpleAccount *account, char *sender,
+				   char *message, PurpleConversation *conv,
+				   PurpleMessageFlags flags, PurpleSoundEventID event)
+{
+	if (flags & PURPLE_MESSAGE_DELAYED)
+		return;
+
+	if (conv == NULL) {
+		purple_sound_play_event(PURPLE_SOUND_FIRST_RECEIVE, account);
+	} else {
+		play_conv_event(conv, event);
+	}
 }
 
-void finch_sound_set_active_profile(const char *name)
+static void
+im_msg_sent_cb(PurpleAccount *account, const char *receiver,
+			   const char *message, PurpleSoundEventID event)
+{
+	PurpleConversation *conv = purple_find_conversation_with_account(
+		PURPLE_CONV_TYPE_ANY, receiver, account);
+	play_conv_event(conv, event);
+}
+
+static void
+chat_buddy_join_cb(PurpleConversation *conv, const char *name,
+				   PurpleConvChatBuddyFlags flags, gboolean new_arrival,
+				   PurpleSoundEventID event)
+{
+	if (new_arrival && !chat_nick_matches_name(conv, name))
+		play_conv_event(conv, event);
+}
+
+static void
+chat_buddy_left_cb(PurpleConversation *conv, const char *name,
+				   const char *reason, PurpleSoundEventID event)
+{
+	if (!chat_nick_matches_name(conv, name))
+		play_conv_event(conv, event);
+}
+
+static void
+chat_msg_sent_cb(PurpleAccount *account, const char *message,
+				 int id, PurpleSoundEventID event)
+{
+	PurpleConnection *conn = purple_account_get_connection(account);
+	PurpleConversation *conv = NULL;
+
+	if (conn!=NULL)
+		conv = purple_find_chat(conn, id);
+
+	play_conv_event(conv, event);
+}
+
+static void
+chat_msg_received_cb(PurpleAccount *account, char *sender,
+					 char *message, PurpleConversation *conv,
+					 PurpleMessageFlags flags, PurpleSoundEventID event)
+{
+	PurpleConvChat *chat;
+
+	if (flags & PURPLE_MESSAGE_DELAYED)
+		return;
+
+	chat = purple_conversation_get_chat_data(conv);
+	g_return_if_fail(chat != NULL);
+
+	if (purple_conv_chat_is_user_ignored(chat, sender))
+		return;
+
+	if (chat_nick_matches_name(conv, sender))
+		return;
+
+	if (flags & PURPLE_MESSAGE_NICK || purple_utf8_has_word(message, chat->nick))
+		play_conv_event(conv, PURPLE_SOUND_CHAT_NICK);
+	else
+		play_conv_event(conv, event);
+}
+
+/*
+ * We mute sounds for the 10 seconds after you log in so that
+ * you don't get flooded with sounds when the blist shows all
+ * your buddies logging in.
+ */
+static void
+account_signon_cb(PurpleConnection *gc, gpointer data)
+{
+	if (mute_login_sounds_timeout != 0)
+		g_source_remove(mute_login_sounds_timeout);
+	mute_login_sounds = TRUE;
+	mute_login_sounds_timeout = purple_timeout_add_seconds(10, unmute_login_sounds_cb, NULL);
+}
+
+static void *
+finch_sound_get_handle()
+{
+	static int handle;
+
+	return &handle;
+}
+
+
+/* This gets called when the active profile changes */
+static void
+initialize_profile(const char *name, PurplePrefType type, gconstpointer val, gpointer null)
 {
+	if (purple_prefs_exists(make_pref("")))
+		return;
+
+	purple_prefs_add_none(make_pref(""));
+	purple_prefs_add_none(make_pref("/enabled"));
+	purple_prefs_add_none(make_pref("/file"));
+	purple_prefs_add_bool(make_pref("/enabled/login"), TRUE);
+	purple_prefs_add_path(make_pref("/file/login"), "");
+	purple_prefs_add_bool(make_pref("/enabled/logout"), TRUE);
+	purple_prefs_add_path(make_pref("/file/logout"), "");
+	purple_prefs_add_bool(make_pref("/enabled/im_recv"), TRUE);
+	purple_prefs_add_path(make_pref("/file/im_recv"), "");
+	purple_prefs_add_bool(make_pref("/enabled/first_im_recv"), FALSE);
+	purple_prefs_add_path(make_pref("/file/first_im_recv"), "");
+	purple_prefs_add_bool(make_pref("/enabled/send_im"), TRUE);
+	purple_prefs_add_path(make_pref("/file/send_im"), "");
+	purple_prefs_add_bool(make_pref("/enabled/join_chat"), FALSE);
+	purple_prefs_add_path(make_pref("/file/join_chat"), "");
+	purple_prefs_add_bool(make_pref("/enabled/left_chat"), FALSE);
+	purple_prefs_add_path(make_pref("/file/left_chat"), "");
+	purple_prefs_add_bool(make_pref("/enabled/send_chat_msg"), FALSE);
+	purple_prefs_add_path(make_pref("/file/send_chat_msg"), "");
+	purple_prefs_add_bool(make_pref("/enabled/chat_msg_recv"), FALSE);
+	purple_prefs_add_path(make_pref("/file/chat_msg_recv"), "");
+	purple_prefs_add_bool(make_pref("/enabled/nick_said"), FALSE);
+	purple_prefs_add_path(make_pref("/file/nick_said"), "");
+	purple_prefs_add_bool(make_pref("/enabled/pounce_default"), TRUE);
+	purple_prefs_add_path(make_pref("/file/pounce_default"), "");
+	purple_prefs_add_bool(make_pref("/conv_focus"), TRUE);
+	purple_prefs_add_bool(make_pref("/mute"), FALSE);
+	purple_prefs_add_path(make_pref("/command"), "");
+	purple_prefs_add_string(make_pref("/method"), "automatic");
+	purple_prefs_add_int(make_pref("/volume"), 50);
+}
+
+static void
+finch_sound_init(void)
+{
+	void *gnt_sound_handle = finch_sound_get_handle();
+	void *blist_handle = purple_blist_get_handle();
+	void *conv_handle = purple_conversations_get_handle();
+#ifdef USE_GSTREAMER
+	GError *error = NULL;
+#endif
+
+	purple_signal_connect(purple_connections_get_handle(), "signed-on",
+						gnt_sound_handle, PURPLE_CALLBACK(account_signon_cb),
+						NULL);
+
+	purple_prefs_add_none(FINCH_PREFS_ROOT "/sound");
+	purple_prefs_add_string(FINCH_PREFS_ROOT "/sound/actprofile", DEFAULT_PROFILE);
+	purple_prefs_add_none(FINCH_PREFS_ROOT "/sound/profiles");
+
+	purple_prefs_connect_callback(gnt_sound_handle, FINCH_PREFS_ROOT "/sound/actprofile", initialize_profile, NULL);
+	purple_prefs_trigger_callback(FINCH_PREFS_ROOT "/sound/actprofile");
+
+	
+#ifdef USE_GSTREAMER
+	purple_debug_info("sound", "Initializing sound output drivers.\n");
+	if ((gst_init_failed = !gst_init_check(NULL, NULL, &error))) {
+		purple_notify_error(NULL, _("GStreamer Failure"),
+					_("GStreamer failed to initialize."),
+					error ? error->message : "");
+		if (error) {
+			g_error_free(error);
+			error = NULL;
+		}
+	}
+#endif /* USE_GSTREAMER */
+
+	purple_signal_connect(blist_handle, "buddy-signed-on",
+						gnt_sound_handle, PURPLE_CALLBACK(buddy_state_cb),
+						GINT_TO_POINTER(PURPLE_SOUND_BUDDY_ARRIVE));
+	purple_signal_connect(blist_handle, "buddy-signed-off",
+						gnt_sound_handle, PURPLE_CALLBACK(buddy_state_cb),
+						GINT_TO_POINTER(PURPLE_SOUND_BUDDY_LEAVE));
+	purple_signal_connect(conv_handle, "received-im-msg",
+						gnt_sound_handle, PURPLE_CALLBACK(im_msg_received_cb),
+						GINT_TO_POINTER(PURPLE_SOUND_RECEIVE));
+	purple_signal_connect(conv_handle, "sent-im-msg",
+						gnt_sound_handle, PURPLE_CALLBACK(im_msg_sent_cb),
+						GINT_TO_POINTER(PURPLE_SOUND_SEND));
+	purple_signal_connect(conv_handle, "chat-buddy-joined",
+						gnt_sound_handle, PURPLE_CALLBACK(chat_buddy_join_cb),
+						GINT_TO_POINTER(PURPLE_SOUND_CHAT_JOIN));
+	purple_signal_connect(conv_handle, "chat-buddy-left",
+						gnt_sound_handle, PURPLE_CALLBACK(chat_buddy_left_cb),
+						GINT_TO_POINTER(PURPLE_SOUND_CHAT_LEAVE));
+	purple_signal_connect(conv_handle, "sent-chat-msg",
+						gnt_sound_handle, PURPLE_CALLBACK(chat_msg_sent_cb),
+						GINT_TO_POINTER(PURPLE_SOUND_CHAT_YOU_SAY));
+	purple_signal_connect(conv_handle, "received-chat-msg",
+						gnt_sound_handle, PURPLE_CALLBACK(chat_msg_received_cb),
+						GINT_TO_POINTER(PURPLE_SOUND_CHAT_SAY));
+}
+
+static void
+finch_sound_uninit(void)
+{
+#ifdef USE_GSTREAMER
+	if (!gst_init_failed)
+		gst_deinit();
+#endif
+
+	purple_signals_disconnect_by_handle(finch_sound_get_handle());
 }
 
-GList *finch_sound_get_profiles(void)
+#ifdef USE_GSTREAMER
+static gboolean
+bus_call (GstBus *bus, GstMessage *msg, gpointer data)
+{
+	GstElement *play = data;
+	GError *err = NULL;
+
+	switch (GST_MESSAGE_TYPE (msg)) {
+	case GST_MESSAGE_EOS:
+		gst_element_set_state(play, GST_STATE_NULL);
+		gst_object_unref(GST_OBJECT(play));
+		break;
+	case GST_MESSAGE_ERROR:
+		gst_message_parse_error(msg, &err, NULL);
+		purple_debug_error("gstreamer", "%s\n", err->message);
+		g_error_free(err);
+		break;
+	case GST_MESSAGE_WARNING:
+		gst_message_parse_warning(msg, &err, NULL);
+		purple_debug_warning("gstreamer", "%s\n", err->message);
+		g_error_free(err);
+		break;
+	default:
+		break;
+	}
+	return TRUE;
+}
+#endif
+
+static void
+finch_sound_play_file(const char *filename)
+{
+	const char *method;
+#ifdef USE_GSTREAMER
+	float volume;
+	char *uri;
+	GstElement *sink = NULL;
+	GstElement *play = NULL;
+	GstBus *bus = NULL;
+#endif
+	if (purple_prefs_get_bool(make_pref("/mute")))
+		return;
+
+	method = purple_prefs_get_string(make_pref("/method"));
+
+	if (!strcmp(method, "none")) {
+		return;
+	} else if (!strcmp(method, "beep")) {
+		beep();
+		return;
+	}
+
+	if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
+		purple_debug_error("gntsound", "sound file (%s) does not exist.\n", filename);
+		return;
+	}
+
+#ifndef _WIN32
+	if (!strcmp(method, "custom")) {
+		const char *sound_cmd;
+		char *command;
+		char *esc_filename;
+		GError *error = NULL;
+
+		sound_cmd = purple_prefs_get_path(make_pref("/command"));
+
+		if (!sound_cmd || *sound_cmd == '\0') {
+			purple_debug_error("gntsound",
+					 "'Command' sound method has been chosen, "
+					 "but no command has been set.");
+			return;
+		}
+
+		esc_filename = g_shell_quote(filename);
+
+		if (strstr(sound_cmd, "%s"))
+			command = purple_strreplace(sound_cmd, "%s", esc_filename);
+		else
+			command = g_strdup_printf("%s %s", sound_cmd, esc_filename);
+
+		if (!g_spawn_command_line_async(command, &error)) {
+			purple_debug_error("gntsound", "sound command could not be launched: %s\n", error->message);
+			g_error_free(error);
+		}
+
+		g_free(esc_filename);
+		g_free(command);
+		return;
+	}
+#ifdef USE_GSTREAMER
+	if (gst_init_failed)  /* Perhaps do beep instead? */
+		return;
+	volume = (float)(CLAMP(purple_prefs_get_int(make_pref("/volume")), 0, 100)) / 50;
+	if (!strcmp(method, "automatic")) {
+		if (purple_running_gnome()) {
+			sink = gst_element_factory_make("gconfaudiosink", "sink");
+		}
+		if (!sink)
+			sink = gst_element_factory_make("autoaudiosink", "sink");
+		if (!sink) {
+			purple_debug_error("sound", "Unable to create GStreamer audiosink.\n");
+			return;
+		}
+	} else if (!strcmp(method, "esd")) {
+		sink = gst_element_factory_make("esdsink", "sink");
+		if (!sink) {
+			purple_debug_error("sound", "Unable to create GStreamer audiosink.\n");
+			return;
+		}
+	} else if (!strcmp(method, "alsa")) {
+		sink = gst_element_factory_make("alsasink", "sink");
+		if (!sink) {
+			purple_debug_error("sound", "Unable to create GStreamer audiosink.\n");
+			return;
+		}
+	} else {
+		purple_debug_error("sound", "Unknown sound method '%s'\n", method);
+		return;
+	}
+
+	play = gst_element_factory_make("playbin", "play");
+	
+	if (play == NULL) {
+		return;
+	}
+	
+	uri = g_strdup_printf("file://%s", filename);
+
+	g_object_set(G_OBJECT(play), "uri", uri,
+		                     "volume", volume,
+		                     "audio-sink", sink, NULL);
+
+	bus = gst_pipeline_get_bus(GST_PIPELINE(play));
+	gst_bus_add_watch(bus, bus_call, play);
+
+	gst_element_set_state(play, GST_STATE_PLAYING);
+
+	gst_object_unref(bus);
+	g_free(uri);
+
+#else /* USE_GSTREAMER */
+	beep();
+	return;
+#endif /* USE_GSTREAMER */
+#else /* _WIN32 */
+	purple_debug_info("sound", "Playing %s\n", filename);
+
+	if (G_WIN32_HAVE_WIDECHAR_API ()) {
+		wchar_t *wc_filename = g_utf8_to_utf16(filename,
+				-1, NULL, NULL, NULL);
+		if (!PlaySoundW(wc_filename, NULL, SND_ASYNC | SND_FILENAME))
+			purple_debug(PURPLE_DEBUG_ERROR, "sound", "Error playing sound.\n");
+		g_free(wc_filename);
+	} else {
+		char *l_filename = g_locale_from_utf8(filename,
+				-1, NULL, NULL, NULL);
+		if (!PlaySoundA(l_filename, NULL, SND_ASYNC | SND_FILENAME))
+			purple_debug(PURPLE_DEBUG_ERROR, "sound", "Error playing sound.\n");
+		g_free(l_filename);
+	}
+#endif /* _WIN32 */
+}
+
+static void
+finch_sound_play_event(PurpleSoundEventID event)
 {
-	return NULL;
+	char *enable_pref;
+	char *file_pref;
+	if ((event == PURPLE_SOUND_BUDDY_ARRIVE) && mute_login_sounds)
+		return;
+
+	if (event >= PURPLE_NUM_SOUNDS) {
+		purple_debug_error("sound", "got request for unknown sound: %d\n", event);
+		return;
+	}
+
+	enable_pref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/enabled/%s",
+			finch_sound_get_active_profile(),
+			sounds[event].pref);
+	file_pref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/file/%s", finch_sound_get_active_profile(), sounds[event].pref);
+
+	/* check NULL for sounds that don't have an option, ie buddy pounce */
+	if (purple_prefs_get_bool(enable_pref)) {
+		char *filename = g_strdup(purple_prefs_get_path(file_pref));
+		if (!filename || !strlen(filename)) {
+			g_free(filename);
+			/* XXX Consider creating a constant for "sounds/purple" to be shared with Pidgin */
+			filename = g_build_filename(DATADIR, "sounds", "purple", sounds[event].def, NULL);
+		}
+
+		purple_sound_play_file(filename, NULL);
+		g_free(filename);
+	}
+
+	g_free(enable_pref);
+	g_free(file_pref);
+}
+
+GList *
+finch_sound_get_profiles()
+{
+	GList *list = NULL, *iter;
+	iter = purple_prefs_get_children_names(FINCH_PREFS_ROOT "/sound/profiles");
+	while (iter) {
+		list = g_list_append(list, g_strdup(strrchr(iter->data, '/') + 1));
+		g_free(iter->data);
+		iter = g_list_delete_link(iter, iter);
+	}
+	return list;
+}
+
+/* This will also create it if it doesn't exist */
+void
+finch_sound_set_active_profile(const char *name)
+{
+	purple_prefs_set_string(FINCH_PREFS_ROOT "/sound/actprofile", name);
+}
+
+static gboolean
+finch_sound_profile_exists(const char *name)
+{
+	gchar * tmp;
+	gboolean ret = purple_prefs_exists(tmp = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s", name));
+	g_free(tmp);
+	return ret;
+}
+
+static void
+save_cb(GntWidget *button, gpointer win)
+{
+	GList * itr;
+
+	purple_prefs_set_string(make_pref("/method"), gnt_combo_box_get_selected_data(GNT_COMBO_BOX(pref_dialog->method)));
+	purple_prefs_set_path(make_pref("/command"), gnt_entry_get_text(GNT_ENTRY(pref_dialog->command)));
+	purple_prefs_set_bool(make_pref("/conv_focus"), gnt_check_box_get_checked(GNT_CHECK_BOX(pref_dialog->conv_focus)));
+	purple_prefs_set_int("/purple/sound/while_status", GPOINTER_TO_INT(gnt_combo_box_get_selected_data(GNT_COMBO_BOX(pref_dialog->while_status))));
+	purple_prefs_set_int(make_pref("/volume"), gnt_slider_get_value(GNT_SLIDER(pref_dialog->volume)));
+
+	for (itr = gnt_tree_get_rows(GNT_TREE(pref_dialog->events)); itr; itr = itr->next) {
+		FinchSoundEvent * event = &sounds[GPOINTER_TO_INT(itr->data)];
+		char * filepref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/file/%s", finch_sound_get_active_profile(), event->pref);
+		char * boolpref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/enabled/%s", finch_sound_get_active_profile(), event->pref);
+		purple_prefs_set_bool(boolpref, gnt_tree_get_choice(GNT_TREE(pref_dialog->events), itr->data));
+		purple_prefs_set_path(filepref, event->file ? event->file : "");
+		g_free(filepref);
+		g_free(boolpref);
+	}
+	gnt_widget_destroy(GNT_WIDGET(win));
+}
+
+static void
+file_cb(GntFileSel *w, const char *path, const char *file, gpointer data)
+{
+	FinchSoundEvent *event = data;
+
+	g_free(event->file);
+	event->file = g_strdup(path);
+
+	gnt_tree_change_text(GNT_TREE(pref_dialog->events), GINT_TO_POINTER(event->id), 1, file);
+	gnt_tree_set_choice(GNT_TREE(pref_dialog->events), GINT_TO_POINTER(event->id), TRUE);
+	
+	gnt_widget_destroy(GNT_WIDGET(w));
+}
+
+static void
+test_cb(GntWidget *button, gpointer null)
+{
+	PurpleSoundEventID id = GPOINTER_TO_INT(gnt_tree_get_selection_data(GNT_TREE(pref_dialog->events)));
+	FinchSoundEvent * event = &sounds[id];
+	char *enabled, *file, *tmpfile;
+	gboolean temp_value;
+
+	enabled = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/enabled/%s",
+			finch_sound_get_active_profile(), event->pref);
+	file = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/file/%s",
+			finch_sound_get_active_profile(), event->pref);
+
+	temp_value = purple_prefs_get_bool(enabled);
+	tmpfile = g_strdup(purple_prefs_get_string(file));
+
+	purple_prefs_set_string(file, event->file);
+	if (!temp_value) purple_prefs_set_bool(enabled, TRUE);
+
+	purple_sound_play_event(id, NULL);
+
+	if (!temp_value) purple_prefs_set_bool(enabled, FALSE);
+	purple_prefs_set_string(file, tmpfile);
+
+	g_free(enabled);
+	g_free(file);
+	g_free(tmpfile);
+}
+
+static void
+reset_cb(GntWidget *button, gpointer null)
+{
+	/* Don't dereference this pointer ! */
+	gpointer key = gnt_tree_get_selection_data(GNT_TREE(pref_dialog->events)); 
+
+	FinchSoundEvent * event = &sounds[GPOINTER_TO_INT(key)];
+	g_free(event->file);
+	event->file = NULL;
+	gnt_tree_change_text(GNT_TREE(pref_dialog->events), key, 1, _("(default)"));
+}
+
+
+static void
+choose_cb(GntWidget *button, gpointer null)
+{
+	GntWidget *w = gnt_file_sel_new();
+	GntFileSel *sel = GNT_FILE_SEL(w);
+	PurpleSoundEventID id = GPOINTER_TO_INT(gnt_tree_get_selection_data(GNT_TREE(pref_dialog->events)));
+	FinchSoundEvent * event = &sounds[id];
+	char *path = NULL;
+
+	gnt_box_set_title(GNT_BOX(w), _("Select Sound File ..."));
+	gnt_file_sel_set_current_location(sel,
+			(event && event->file) ? (path = g_path_get_dirname(event->file))
+				: purple_home_dir());
+
+	g_signal_connect_swapped(G_OBJECT(sel->cancel), "activate", G_CALLBACK(gnt_widget_destroy), sel);
+	g_signal_connect(G_OBJECT(sel), "file_selected", G_CALLBACK(file_cb), event);
+	g_signal_connect_swapped(G_OBJECT(sel), "destroy", G_CALLBACK(g_nullify_pointer), &pref_dialog->selector);
+
+	/* If there's an already open file-selector, close that one. */
+	if (pref_dialog->selector)
+		gnt_widget_destroy(pref_dialog->selector);
+
+	pref_dialog->selector = w;
+
+	gnt_widget_show(w);
+	g_free(path);
 }
 
-PurpleSoundUiOps *finch_sound_get_ui_ops(void)
+static void
+release_pref_dialog(GntBindable *data, gpointer null)
+{
+	GList * itr;
+	for (itr = gnt_tree_get_rows(GNT_TREE(pref_dialog->events)); itr; itr = itr->next) {
+		PurpleSoundEventID id = GPOINTER_TO_INT(itr->data);
+		FinchSoundEvent * e = &sounds[id];
+		g_free(e->file);
+		e->file = NULL;
+	}
+	if (pref_dialog->selector)
+		gnt_widget_destroy(pref_dialog->selector);
+	g_free(pref_dialog);
+	pref_dialog = NULL;
+}
+
+static void
+load_pref_window(const char * profile)
 {
-	return NULL;
+	gint i;
+
+	finch_sound_set_active_profile(profile);
+
+	gnt_combo_box_set_selected(GNT_COMBO_BOX(pref_dialog->method), (gchar *)purple_prefs_get_string(make_pref("/method")));
+
+	gnt_entry_set_text(GNT_ENTRY(pref_dialog->command), purple_prefs_get_path(make_pref("/command")));
+
+	gnt_check_box_set_checked(GNT_CHECK_BOX(pref_dialog->conv_focus), purple_prefs_get_bool(make_pref("/conv_focus")));
+
+	gnt_combo_box_set_selected(GNT_COMBO_BOX(pref_dialog->while_status), GINT_TO_POINTER(purple_prefs_get_int("/purple" "/sound/while_status")));
+
+	gnt_slider_set_value(GNT_SLIDER(pref_dialog->volume), CLAMP(purple_prefs_get_int(make_pref("/volume")), 0, 100));
+
+	for (i = 0; i < PURPLE_NUM_SOUNDS; i++) {
+		FinchSoundEvent * event = &sounds[i];
+		gchar *boolpref;
+		gchar *filepref, *basename = NULL;
+		const char * profile = finch_sound_get_active_profile();
+
+		filepref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/file/%s", profile, event->pref);
+
+		g_free(event->file);
+		event->file = g_strdup(purple_prefs_get_path(filepref));
+
+		g_free(filepref);
+		if (event->label == NULL) {
+			continue;
+		}
+
+		boolpref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s/enabled/%s", profile, event->pref);
+
+		gnt_tree_change_text(GNT_TREE(pref_dialog->events), GINT_TO_POINTER(i), 0, event->label);
+		gnt_tree_change_text(GNT_TREE(pref_dialog->events), GINT_TO_POINTER(i), 1,
+				event->file[0] ? (basename = g_path_get_basename(event->file)) : _("(default)"));
+		g_free(basename);
+
+		gnt_tree_set_choice(GNT_TREE(pref_dialog->events), GINT_TO_POINTER(i), purple_prefs_get_bool(boolpref));
+
+		g_free(boolpref);
+	}
+
+	gnt_tree_set_selected(GNT_TREE(pref_dialog->profiles), (gchar *)finch_sound_get_active_profile());
+
+	gnt_widget_draw(pref_dialog->window);
+}
+
+static void
+reload_pref_window(const char *profile)
+{
+	if (!strcmp(profile, finch_sound_get_active_profile()))
+		return;
+	load_pref_window(profile);
+}
+
+static void
+prof_del_cb(GntWidget *button, gpointer null)
+{
+	const char * profile = gnt_entry_get_text(GNT_ENTRY(pref_dialog->new_profile));
+	gchar * pref;
+
+	if (!strcmp(profile, DEFAULT_PROFILE))
+		return;
+
+	pref = g_strdup_printf(FINCH_PREFS_ROOT "/sound/profiles/%s", profile);
+	purple_prefs_remove(pref);
+	g_free(pref);
+
+	if (!strcmp(pref_dialog->original_profile, profile)) {
+		g_free(pref_dialog->original_profile);
+		pref_dialog->original_profile = g_strdup(DEFAULT_PROFILE);
+	}
+
+	if(!strcmp(profile, finch_sound_get_active_profile()))
+		reload_pref_window(DEFAULT_PROFILE);
+
+	gnt_tree_remove(GNT_TREE(pref_dialog->profiles), (gchar *) profile);
+}
+
+static void
+prof_add_cb(GntButton *button, GntEntry * entry)
+{
+	const char * profile = gnt_entry_get_text(entry);
+	GntTreeRow * row;
+	if (!finch_sound_profile_exists(profile)) {
+		gpointer key = g_strdup(profile);
+		row = gnt_tree_create_row(GNT_TREE(pref_dialog->profiles), profile);
+		gnt_tree_add_row_after(GNT_TREE(pref_dialog->profiles), key,
+				row,
+				NULL, NULL);
+		gnt_entry_set_text(entry, "");
+		gnt_tree_set_selected(GNT_TREE(pref_dialog->profiles), key);
+		finch_sound_set_active_profile(key);
+	} else 
+		reload_pref_window(profile);
+}
+
+static void
+prof_load_cb(GntTree *tree, gpointer oldkey, gpointer newkey, gpointer null)
+{
+	reload_pref_window(newkey);
+}
+
+static void
+cancel_cb(GntButton *button, gpointer win)
+{
+	finch_sound_set_active_profile(pref_dialog->original_profile);
+	gnt_widget_destroy(GNT_WIDGET(win));
 }
 
-void finch_sounds_show_all(void)
+void
+finch_sounds_show_all(void)
 {
+	GntWidget *box, *tmpbox, *splitbox, *cmbox, *slider;
+	GntWidget *entry;
+	GntWidget *chkbox;
+	GntWidget *button;
+	GntWidget *label;
+	GntWidget *tree;
+	GntWidget *win;
+
+	gint i;
+	GList *itr, *list;
+
+	if (pref_dialog) {
+		gnt_window_present(pref_dialog->window);
+		return;
+	}
+
+	pref_dialog = g_new0(SoundPrefDialog, 1);
+
+	pref_dialog->original_profile = g_strdup(finch_sound_get_active_profile());
+
+	pref_dialog->window = win = gnt_window_box_new(FALSE, TRUE);
+	gnt_box_set_pad(GNT_BOX(win), 0);
+	gnt_box_set_toplevel(GNT_BOX(win), TRUE);
+	gnt_box_set_title(GNT_BOX(win), _("Sound Preferences"));
+	gnt_box_set_fill(GNT_BOX(win), TRUE);
+	gnt_box_set_alignment(GNT_BOX(win), GNT_ALIGN_MID);
+
+	/* Profiles */
+	splitbox = gnt_hbox_new(FALSE);
+	gnt_box_set_pad(GNT_BOX(splitbox), 0);
+	gnt_box_set_alignment(GNT_BOX(splitbox), GNT_ALIGN_TOP);
+
+	box = gnt_vbox_new(FALSE);
+	gnt_box_set_pad(GNT_BOX(box), 0);
+	gnt_box_add_widget(GNT_BOX(box), gnt_label_new_with_format(_("Profiles"), GNT_TEXT_FLAG_BOLD));
+	pref_dialog->profiles = tree = gnt_tree_new();
+	gnt_tree_set_hash_fns(GNT_TREE(tree), g_str_hash, g_str_equal, g_free);
+	gnt_tree_set_compare_func(GNT_TREE(tree), (GCompareFunc)g_ascii_strcasecmp);
+	g_signal_connect(G_OBJECT(tree), "selection-changed", G_CALLBACK(prof_load_cb), NULL);
+
+	itr = list = finch_sound_get_profiles();
+	for (; itr; itr = itr->next) {
+		/* Do not free itr->data. It's the stored as a key for the tree, and will
+		 * be freed when the tree is destroyed. */
+		gnt_tree_add_row_after(GNT_TREE(tree), itr->data,
+				gnt_tree_create_row(GNT_TREE(tree), itr->data), NULL, NULL);
+	}
+	g_list_free(list);
+
+	gnt_box_add_widget(GNT_BOX(box), tree);
+
+	pref_dialog->new_profile = entry = gnt_entry_new("");
+	gnt_box_add_widget(GNT_BOX(box), entry);
+
+	tmpbox = gnt_hbox_new(FALSE);
+	button = gnt_button_new("Add");
+	g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(prof_add_cb), entry);
+	gnt_box_add_widget(GNT_BOX(tmpbox), button);
+	button = gnt_button_new("Delete");
+	g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(prof_del_cb), NULL);
+	gnt_box_add_widget(GNT_BOX(tmpbox), button);
+	gnt_box_add_widget(GNT_BOX(box), tmpbox);
+	gnt_box_add_widget(GNT_BOX(splitbox), box);
+
+	gnt_box_add_widget(GNT_BOX(splitbox), gnt_vline_new());
+
+	/* Sound method */
+
+	box = gnt_vbox_new(FALSE);
+	gnt_box_set_pad(GNT_BOX(box), 0);
+
+	pref_dialog->method = cmbox = gnt_combo_box_new();
+	gnt_tree_set_hash_fns(GNT_TREE(GNT_COMBO_BOX(cmbox)->dropdown), g_str_hash, g_str_equal, NULL);
+	gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), "automatic", _("Automatic"));
+	gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), "alsa", "ALSA");
+	gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), "esd", "ESD");
+	gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), "beep", _("Console Beep"));
+	gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), "custom", _("Command"));
+	gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), "nosound", _("No Sound"));
+
+	label = gnt_label_new_with_format(_("Sound Method"), GNT_TEXT_FLAG_BOLD);
+	gnt_box_add_widget(GNT_BOX(box), label); 
+	tmpbox = gnt_hbox_new(TRUE);
+	gnt_box_set_fill(GNT_BOX(tmpbox), FALSE);
+	gnt_box_set_pad(GNT_BOX(tmpbox), 0);
+	gnt_box_add_widget(GNT_BOX(tmpbox), gnt_label_new(_("Method: ")));
+	gnt_box_add_widget(GNT_BOX(tmpbox), cmbox);
+	gnt_box_add_widget(GNT_BOX(box), tmpbox); 
+
+	tmpbox = gnt_hbox_new(TRUE);
+	gnt_box_set_pad(GNT_BOX(tmpbox), 0);
+	gnt_box_set_fill(GNT_BOX(tmpbox), FALSE);
+	gnt_box_add_widget(GNT_BOX(tmpbox), gnt_label_new(_("Sound Command\n(%s for filename)")));
+	pref_dialog->command = entry = gnt_entry_new("");
+	gnt_box_add_widget(GNT_BOX(tmpbox), entry);
+	gnt_box_add_widget(GNT_BOX(box), tmpbox);
+
+	gnt_box_add_widget(GNT_BOX(box), gnt_line_new(FALSE));
+
+	/* Sound options */
+	gnt_box_add_widget(GNT_BOX(box), gnt_label_new_with_format(_("Sound Options"), GNT_TEXT_FLAG_BOLD)); 
+	pref_dialog->conv_focus = chkbox = gnt_check_box_new(_("Sounds when conversation has focus"));
+	gnt_box_add_widget(GNT_BOX(box), chkbox);
+
+	tmpbox = gnt_hbox_new(TRUE);
+	gnt_box_set_pad(GNT_BOX(tmpbox), 0);
+	gnt_box_set_fill(GNT_BOX(tmpbox), FALSE);
+	gnt_box_add_widget(GNT_BOX(tmpbox), gnt_label_new("Enable Sounds:"));
+	pref_dialog->while_status = cmbox = gnt_combo_box_new();
+	gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), GINT_TO_POINTER(3), _("Always"));
+	gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), GINT_TO_POINTER(1), _("Only when available"));
+	gnt_combo_box_add_data(GNT_COMBO_BOX(cmbox), GINT_TO_POINTER(2), _("Only when not available"));
+	gnt_box_add_widget(GNT_BOX(tmpbox), cmbox);
+	gnt_box_add_widget(GNT_BOX(box), tmpbox);
+
+	tmpbox = gnt_hbox_new(TRUE);
+	gnt_box_set_pad(GNT_BOX(tmpbox), 0);
+	gnt_box_set_fill(GNT_BOX(tmpbox), FALSE);
+	gnt_box_add_widget(GNT_BOX(tmpbox), gnt_label_new(_("Volume(0-100):")));
+
+	pref_dialog->volume = slider = gnt_slider_new(FALSE, 100, 0);
+	gnt_slider_set_step(GNT_SLIDER(slider), 5);
+	label = gnt_label_new("");
+	gnt_slider_reflect_label(GNT_SLIDER(slider), GNT_LABEL(label));
+	gnt_box_set_pad(GNT_BOX(tmpbox), 1);
+	gnt_box_add_widget(GNT_BOX(tmpbox), slider);
+	gnt_box_add_widget(GNT_BOX(tmpbox), label);
+	gnt_box_add_widget(GNT_BOX(box), tmpbox);
+	gnt_box_add_widget(GNT_BOX(splitbox), box);
+
+	gnt_box_add_widget(GNT_BOX(win), splitbox);
+
+	gnt_box_add_widget(GNT_BOX(win), gnt_hline_new());
+
+	/* Sound events */
+	gnt_box_add_widget(GNT_BOX(win), gnt_label_new_with_format(_("Sound Events"), GNT_TEXT_FLAG_BOLD)); 
+	pref_dialog->events = tree = gnt_tree_new_with_columns(2);
+	gnt_tree_set_column_titles(GNT_TREE(tree), _("Event"), _("File"));
+	gnt_tree_set_show_title(GNT_TREE(tree), TRUE);
+
+	for (i = 0; i < PURPLE_NUM_SOUNDS; i++) {
+		FinchSoundEvent * event = &sounds[i];
+		
+		if (event->label == NULL) {
+			continue;
+		}
+
+		gnt_tree_add_choice(GNT_TREE(tree), GINT_TO_POINTER(i),
+			gnt_tree_create_row(GNT_TREE(tree), "", ""),
+			NULL, NULL);
+	}
+
+	gnt_tree_adjust_columns(GNT_TREE(tree));
+	gnt_box_add_widget(GNT_BOX(win), tree);
+
+	box = gnt_hbox_new(FALSE);
+	button = gnt_button_new(_("Test"));
+	g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(test_cb), NULL);
+	gnt_box_add_widget(GNT_BOX(box), button);
+	button = gnt_button_new(_("Reset"));
+	g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(reset_cb), NULL);
+	gnt_box_add_widget(GNT_BOX(box), button);
+	button = gnt_button_new(_("Choose..."));
+	g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(choose_cb), NULL);
+	gnt_box_add_widget(GNT_BOX(box), button);
+	gnt_box_add_widget(GNT_BOX(win), box);
+
+	gnt_box_add_widget(GNT_BOX(win), gnt_line_new(FALSE));
+
+	/* Add new stuff before this */
+	box = gnt_hbox_new(FALSE);
+	gnt_box_set_fill(GNT_BOX(box), TRUE);
+	button = gnt_button_new(_("Save"));
+	g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(save_cb), win);
+	gnt_box_add_widget(GNT_BOX(box), button);
+	button = gnt_button_new(_("Cancel"));
+	g_signal_connect(G_OBJECT(button), "activate", G_CALLBACK(cancel_cb), win);
+	gnt_box_add_widget(GNT_BOX(box), button);
+	gnt_box_add_widget(GNT_BOX(win), box);
+
+	g_signal_connect(G_OBJECT(win), "destroy", G_CALLBACK(release_pref_dialog), NULL);
+
+	load_pref_window(finch_sound_get_active_profile());
+
+	gnt_widget_show(win);
+}	
+
+static PurpleSoundUiOps sound_ui_ops =
+{
+	finch_sound_init,
+	finch_sound_uninit,
+	finch_sound_play_file,
+	finch_sound_play_event,
+	NULL,
+	NULL,
+	NULL,
+	NULL
+};
+
+PurpleSoundUiOps *
+finch_sound_get_ui_ops(void)
+{
+	return &sound_ui_ops;
 }
 
--- a/finch/gntui.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/gntui.c	Sat Aug 11 21:08:27 2007 +0000
@@ -35,6 +35,7 @@
 #include "gntprefs.h"
 #include "gntrequest.h"
 #include "gntstatus.h"
+#include "gntsound.h"
 
 #include <prefs.h>
 
@@ -58,6 +59,9 @@
 	finch_blist_init();
 	purple_blist_set_ui_ops(finch_blist_get_ui_ops());
 
+	/* Initialize sound */
+	purple_sound_set_ui_ops(finch_sound_get_ui_ops());
+
 	/* Now the conversations */
 	finch_conversation_init();
 	purple_conversations_set_ui_ops(finch_conv_get_ui_ops());
@@ -80,6 +84,7 @@
 	gnt_register_action(_("Debug Window"), finch_debug_window_show);
 	gnt_register_action(_("File Transfers"), finch_xfer_dialog_show);
 	gnt_register_action(_("Plugins"), finch_plugins_show_all);
+	gnt_register_action(_("Sounds"), finch_sounds_show_all);
 	gnt_register_action(_("Preferences"), finch_prefs_show_all);
 	gnt_register_action(_("Statuses"), finch_savedstatus_show_all);
 
--- a/finch/libgnt/configure.ac	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/libgnt/configure.ac	Sat Aug 11 21:08:27 2007 +0000
@@ -26,7 +26,7 @@
 
 m4_define([gnt_lt_current], [1])
 m4_define([gnt_major_version], [2])
-m4_define([gnt_minor_version], [0])
+m4_define([gnt_minor_version], [1])
 m4_define([gnt_micro_version], [0])
 m4_define([gnt_version_suffix], [devel])
 m4_define([gnt_version],
@@ -36,6 +36,7 @@
 AC_INIT([libgnt], [gnt_display_version], [devel@pidgin.im])
 AC_CANONICAL_SYSTEM
 AM_CONFIG_HEADER(config.h)
+AC_CONFIG_AUX_DIR([.])
 AM_INIT_AUTOMAKE(AC_PACKAGE_NAME, AC_PACKAGE_VERSION)
 
 GNT_MAJOR_VERSION=gnt_major_version
@@ -314,6 +315,8 @@
 	AC_DEFINE(NO_LIBXML, 1, [Do not have libxml2.])
 fi
 
+AM_CONDITIONAL(PURPLE_AVAILABLE, false)
+
 AC_OUTPUT([Makefile
            gnt.pc
            wms/Makefile
--- a/finch/libgnt/gnt.h	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/libgnt/gnt.h	Sat Aug 11 21:08:27 2007 +0000
@@ -157,4 +157,3 @@
 		void (*callback)(int status, gpointer data), gpointer data);
 
 gboolean gnt_is_refugee(void);
-
--- a/finch/libgnt/gntbindable.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/libgnt/gntbindable.c	Sat Aug 11 21:08:27 2007 +0000
@@ -20,13 +20,182 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
+#include <string.h>
+
 #include "gntbindable.h"
 #include "gntstyle.h"
 #include "gnt.h"
 #include "gntutils.h"
+#include "gnttextview.h"
+#include "gnttree.h"
+#include "gntbox.h"
+#include "gntbutton.h"
+#include "gntwindow.h"
+#include "gntlabel.h"
 
 static GObjectClass *parent_class = NULL;
 
+static struct
+{
+	char * okeys;         /* Old keystrokes */
+	char * keys;          /* New Keystrokes being bound to the action */
+	GntBindableClass * klass; /* Class of the object that's getting keys rebound */
+	char * name;          /* The name of the action */
+	GList * params;       /* The list of paramaters */
+} rebind_info;
+
+static void 
+gnt_bindable_free_rebind_info()
+{
+	g_free(rebind_info.name);
+	g_free(rebind_info.keys);
+	g_free(rebind_info.okeys);
+}
+
+static void
+gnt_bindable_rebinding_cancel(GntWidget *button, gpointer data)
+{
+	gnt_bindable_free_rebind_info();
+	gnt_widget_destroy(GNT_WIDGET(data));
+}
+
+static void
+gnt_bindable_rebinding_rebind(GntWidget *button, gpointer data)
+{
+	if (rebind_info.keys) {
+		gnt_bindable_register_binding(rebind_info.klass,
+				NULL,
+				rebind_info.okeys,
+				rebind_info.params);
+		gnt_bindable_register_binding(rebind_info.klass,
+				rebind_info.name,
+				rebind_info.keys,
+				rebind_info.params);
+	}
+	gnt_bindable_free_rebind_info();
+	gnt_widget_destroy(GNT_WIDGET(data));
+}
+
+static gboolean
+gnt_bindable_rebinding_grab_key(GntBindable *bindable, const char *text, gpointer data)
+{
+	GntTextView *textview = GNT_TEXT_VIEW(data);
+	char *new_text;
+	const char *tmp;
+
+	if (text && *text) {
+		/* Rebinding tab or enter for something is probably not that great an idea */
+		if (!strcmp(text, GNT_KEY_CTRL_I) || !strcmp(text, GNT_KEY_ENTER)) {
+			return FALSE;
+		}
+		
+		tmp = gnt_key_lookup(text);
+		new_text = g_strdup_printf("KEY: \"%s\"", tmp);
+		gnt_text_view_clear(textview);
+		gnt_text_view_append_text_with_flags(textview, new_text, GNT_TEXT_FLAG_NORMAL);
+		g_free(new_text);
+
+		g_free(rebind_info.keys);
+		rebind_info.keys = g_strdup(text);
+
+		return TRUE;
+	}
+	return FALSE;
+} 
+static void
+gnt_bindable_rebinding_activate(GntBindable *data, gpointer bindable)
+{
+	const char *widget_name = g_type_name(G_OBJECT_TYPE(bindable));
+	char *keys;
+	GntWidget *key_textview;
+	GntWidget *label;
+	GntWidget *bind_button, *cancel_button;
+	GntWidget *button_box;
+	GList *current_row_data;
+	char *tmp;
+	GntWidget *win = gnt_window_new();
+	GntTree *tree = GNT_TREE(data);
+	GntWidget *vbox = gnt_box_new(FALSE, TRUE);
+
+	rebind_info.klass = GNT_BINDABLE_GET_CLASS(bindable);
+
+	current_row_data = gnt_tree_get_selection_text_list(tree);
+	rebind_info.name = g_strdup(g_list_nth_data(current_row_data, 1));
+
+	keys = gnt_tree_get_selection_data(tree);
+	rebind_info.okeys = g_strdup(gnt_key_translate(keys));
+
+	rebind_info.params = NULL;
+
+	g_list_foreach(current_row_data, (GFunc)g_free, NULL);
+	g_list_free(current_row_data);
+
+	gnt_box_set_alignment(GNT_BOX(vbox), GNT_ALIGN_MID);
+
+	gnt_box_set_title(GNT_BOX(win), "Key Capture");
+
+	tmp = g_strdup_printf("Type the new bindings for %s in a %s.", rebind_info.name, widget_name);
+	label = gnt_label_new(tmp);
+	g_free(tmp);
+	gnt_box_add_widget(GNT_BOX(vbox), label);
+
+	tmp = g_strdup_printf("KEY: \"%s\"", keys);
+	key_textview = gnt_text_view_new();
+	gnt_widget_set_size(key_textview, key_textview->priv.x, 2);
+	gnt_text_view_append_text_with_flags(GNT_TEXT_VIEW(key_textview), tmp, GNT_TEXT_FLAG_NORMAL);
+	g_free(tmp);
+	gnt_widget_set_name(key_textview, "keystroke");
+	gnt_box_add_widget(GNT_BOX(vbox), key_textview);
+
+	g_signal_connect(G_OBJECT(win), "key_pressed", G_CALLBACK(gnt_bindable_rebinding_grab_key), key_textview);
+
+	button_box = gnt_box_new(FALSE, FALSE);
+	
+	bind_button = gnt_button_new("BIND");
+	gnt_widget_set_name(bind_button, "bind");
+	gnt_box_add_widget(GNT_BOX(button_box), bind_button);
+	
+	cancel_button = gnt_button_new("Cancel");
+	gnt_widget_set_name(cancel_button, "cancel");
+	gnt_box_add_widget(GNT_BOX(button_box), cancel_button);
+	
+	g_signal_connect(G_OBJECT(bind_button), "activate", G_CALLBACK(gnt_bindable_rebinding_rebind), win);
+	g_signal_connect(G_OBJECT(cancel_button), "activate", G_CALLBACK(gnt_bindable_rebinding_cancel), win);
+	
+	gnt_box_add_widget(GNT_BOX(vbox), button_box);
+
+	gnt_box_add_widget(GNT_BOX(win), vbox);
+	gnt_widget_show(win);
+}
+
+typedef struct
+{
+	GHashTable *hash;
+	GntTree *tree;
+} BindingView;
+
+static void
+add_binding(gpointer key, gpointer value, gpointer data)
+{
+	BindingView *bv = data;
+	GntBindableActionParam *act = value;
+	const char *name = g_hash_table_lookup(bv->hash, act->action);
+	if (name && *name) {
+		const char *k = gnt_key_lookup(key);
+		if (!k)
+			k = key;
+		gnt_tree_add_row_after(bv->tree, (gpointer)k,
+				gnt_tree_create_row(bv->tree, k, name), NULL, NULL);
+	}
+}
+
+static void
+add_action(gpointer key, gpointer value, gpointer data)
+{
+	BindingView *bv = data;
+	g_hash_table_insert(bv->hash, value, key);
+}
+
 static void
 gnt_bindable_class_init(GntBindableClass *klass)
 {
@@ -88,7 +257,7 @@
 {
 	static GType type = 0;
 
-	if(type == 0) {
+	if (type == 0) {
 		static const GTypeInfo info = {
 			sizeof(GntBindableClass),
 			(GBaseInitFunc)duplicate_hashes,	/* base_init		*/
@@ -251,4 +420,55 @@
 	g_free(param);
 }
 
+GntBindable * gnt_bindable_bindings_view(GntBindable *bind)
+{
+	GntBindable *tree = GNT_BINDABLE(gnt_tree_new_with_columns(2));
+	GntBindableClass *klass = GNT_BINDABLE_CLASS(GNT_BINDABLE_GET_CLASS(bind));
+	GHashTable *hash = g_hash_table_new(g_direct_hash, g_direct_equal);
+	BindingView bv = {hash, GNT_TREE(tree)};
 
+	gnt_tree_set_compare_func(bv.tree, (GCompareFunc)g_utf8_collate);
+	g_hash_table_foreach(klass->actions, add_action, &bv);
+	g_hash_table_foreach(klass->bindings, add_binding, &bv);
+	if (GNT_TREE(tree)->list == NULL) {
+		gnt_widget_destroy(GNT_WIDGET(tree));
+		tree = NULL;
+	} else
+		gnt_tree_adjust_columns(bv.tree);
+	g_hash_table_destroy(hash);
+
+	return tree;
+}
+
+static void
+reset_binding_window(GntBindableClass *window, gpointer k)
+{
+	GntBindableClass *klass = GNT_BINDABLE_CLASS(k);
+	klass->help_window = NULL;
+}
+
+gboolean
+gnt_bindable_build_help_window(GntBindable *bindable)
+{
+	GntWidget *tree;
+	GntBindableClass *klass = GNT_BINDABLE_GET_CLASS(bindable);
+	char *title;
+
+	tree = GNT_WIDGET(gnt_bindable_bindings_view(bindable));
+
+	klass->help_window = GNT_BINDABLE(gnt_window_new());
+	title = g_strdup_printf("Bindings for %s", g_type_name(G_OBJECT_TYPE(bindable)));
+	gnt_box_set_title(GNT_BOX(klass->help_window), title);
+	if (tree) {
+		g_signal_connect(G_OBJECT(tree), "activate", G_CALLBACK(gnt_bindable_rebinding_activate), bindable);
+		gnt_box_add_widget(GNT_BOX(klass->help_window), tree);
+	} else
+		gnt_box_add_widget(GNT_BOX(klass->help_window), gnt_label_new("This widget has no customizable bindings."));
+
+	g_signal_connect(G_OBJECT(klass->help_window), "destroy", G_CALLBACK(reset_binding_window), klass);
+	gnt_widget_show(GNT_WIDGET(klass->help_window));
+	g_free(title);
+
+	return TRUE;
+}
+
--- a/finch/libgnt/gntbindable.h	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/libgnt/gntbindable.h	Sat Aug 11 21:08:27 2007 +0000
@@ -57,7 +57,8 @@
 	GHashTable *actions;  /* name -> Action */
 	GHashTable *bindings; /* key -> ActionParam */
 
-	void (*gnt_reserved1)(void);
+	GntBindable * help_window;
+
 	void (*gnt_reserved2)(void);
 	void (*gnt_reserved3)(void);
 	void (*gnt_reserved4)(void);
@@ -150,6 +151,31 @@
  */
 gboolean gnt_bindable_perform_action_named(GntBindable *bindable, const char *name, ...);
 
+/**
+* Returns a GntTree populated with "key" -> "binding" for the widget.
+*/
+/**
+* 
+* @param widget
+*
+* @return
+*/
+GntBindable * gnt_bindable_bindings_view(GntBindable *bind);
+
+/**
+ *
+ * Builds a window that list the key bindings for a GntBindable object.  From this window a user can select a listing to rebind a new key for the given action.
+ *
+ */
+/**
+ * 
+ * @param bindable
+ *	
+ * @return
+ */
+
+gboolean gnt_bindable_build_help_window(GntBindable *bindable);
+
 G_END_DECLS
 
 #endif /* GNT_BINDABLE_H */
--- a/finch/libgnt/gntcombobox.h	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/libgnt/gntcombobox.h	Sat Aug 11 21:08:27 2007 +0000
@@ -69,7 +69,8 @@
 G_BEGIN_DECLS
 
 /**
- * 
+ *
+ * Get the GType for GntComboBox
  *
  * @return
  */
@@ -77,44 +78,55 @@
 
 /**
  * 
+ * Create a new GntComboBox
  *
- * @return
+ * @return A new GntComboBox
  */
 GntWidget * gnt_combo_box_new(void);
 
 /**
  * 
- * @param box
- * @param key
- * @param text
+ * Add an entry
+ *
+ * @param box The GntComboBox
+ * @param key The data
+ * @param text The text to display
  */
 void gnt_combo_box_add_data(GntComboBox *box, gpointer key, const char *text);
 
 /**
+ *
+ * Remove an entry
  * 
- * @param box
- * @param key
+ * @param box The GntComboBox
+ * @param key The data to be removed
  */
 void gnt_combo_box_remove(GntComboBox *box, gpointer key);
 
 /**
  * 
- * @param box
+ * Remove all entries
+ *
+ * @param box The GntComboBox
  */
 void gnt_combo_box_remove_all(GntComboBox *box);
 
 /**
  * 
- * @param box
+ * Get the data that is currently selected
  *
- * @return
+ * @param box The GntComboBox
+ *
+ * @return The data of the currently selected entry
  */
 gpointer gnt_combo_box_get_selected_data(GntComboBox *box);
 
 /**
  * 
- * @param box
- * @param key
+ * Set the current selection to a specific entry
+ *
+ * @param box The GntComboBox
+ * @param key The data to be set to
  */
 void gnt_combo_box_set_selected(GntComboBox *box, gpointer key);
 
--- a/finch/libgnt/gntfilesel.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/libgnt/gntfilesel.c	Sat Aug 11 21:08:27 2007 +0000
@@ -355,6 +355,7 @@
 		} else if (strcmp(str, "..") == 0) {
 			gnt_tree_set_selected(tree, dir);
 		}
+		gnt_bindable_perform_action_named(GNT_BINDABLE(tree), "end-search", NULL);
 		g_free(dir);
 		g_free(str);
 		g_free(path);
@@ -495,10 +496,11 @@
 	if (!sel->multiselect)
 		return FALSE;
 	tree = sel->dirsonly ? sel->dirs : sel->files;
-	if (!gnt_widget_has_focus(tree))
+	if (!gnt_widget_has_focus(tree) ||
+			gnt_tree_is_searching(GNT_TREE(tree)))
 		return FALSE;
 
-	file = gnt_tree_get_selection_data(sel->dirsonly ? GNT_TREE(sel->dirs) : GNT_TREE(sel->files));
+	file = gnt_tree_get_selection_data(GNT_TREE(tree));
 
 	str = gnt_file_sel_get_selected_file(sel);
 	if ((find = g_list_find_custom(sel->tags, str, (GCompareFunc)g_utf8_collate)) != NULL) {
@@ -526,7 +528,8 @@
 	if (!sel->multiselect)
 		return FALSE;
 	tree = sel->dirsonly ? sel->dirs : sel->files;
-	if (!gnt_widget_has_focus(tree))
+	if (!gnt_widget_has_focus(tree) ||
+			gnt_tree_is_searching(GNT_TREE(tree)))
 		return FALSE;
 
 	g_list_foreach(sel->tags, (GFunc)g_free, NULL);
@@ -547,6 +550,9 @@
 	if (!gnt_widget_has_focus(sel->dirs) &&
 			!gnt_widget_has_focus(sel->files))
 		return FALSE;
+	if (gnt_tree_is_searching(GNT_TREE(sel->dirs)) ||
+			gnt_tree_is_searching(GNT_TREE(sel->files)))
+		return FALSE;
 
 	path = g_build_filename(sel->current, "..", NULL);
 	dir = g_path_get_basename(sel->current);
--- a/finch/libgnt/gntline.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/libgnt/gntline.c	Sat Aug 11 21:08:27 2007 +0000
@@ -24,6 +24,12 @@
 
 enum
 {
+	PROP_0,
+	PROP_VERTICAL
+};
+
+enum
+{
 	SIGS = 1,
 };
 
@@ -65,14 +71,57 @@
 }
 
 static void
+gnt_line_set_property(GObject *obj, guint prop_id, const GValue *value,
+		GParamSpec *spec)
+{
+	GntLine *line = GNT_LINE(obj);
+	switch (prop_id) {
+		case PROP_VERTICAL:
+			line->vertical = g_value_get_boolean(value);
+			if (line->vertical) {
+				GNT_WIDGET_SET_FLAGS(line, GNT_WIDGET_GROW_Y);
+			} else {
+				GNT_WIDGET_SET_FLAGS(line, GNT_WIDGET_GROW_X);
+			}
+			break;
+		default:
+			break;
+	}
+}
+
+static void
+gnt_line_get_property(GObject *obj, guint prop_id, GValue *value,
+		GParamSpec *spec)
+{
+	GntLine *line = GNT_LINE(obj);
+	switch (prop_id) {
+		case PROP_VERTICAL:
+			g_value_set_boolean(value, line->vertical);
+			break;
+		default:
+			break;
+	}
+}
+
+static void
 gnt_line_class_init(GntLineClass *klass)
 {
+	GObjectClass *gclass = G_OBJECT_CLASS(klass);
 	parent_class = GNT_WIDGET_CLASS(klass);
 	parent_class->draw = gnt_line_draw;
 	parent_class->map = gnt_line_map;
 	parent_class->size_request = gnt_line_size_request;
 
-	GNTDEBUG;
+	gclass->set_property = gnt_line_set_property;
+	gclass->get_property = gnt_line_get_property;
+	g_object_class_install_property(gclass,
+			PROP_VERTICAL,
+			g_param_spec_boolean("vertical", "Vertical",
+				"Whether it's a vertical line or a horizontal one.",
+				TRUE,
+				G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
+			)
+		);
 }
 
 static void
@@ -118,20 +167,7 @@
 
 GntWidget *gnt_line_new(gboolean vertical)
 {
-	GntWidget *widget = g_object_new(GNT_TYPE_LINE, NULL);
-	GntLine *line = GNT_LINE(widget);
-
-	line->vertical = vertical;
-
-	if (vertical)
-	{
-		GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_GROW_Y);
-	}
-	else
-	{
-		GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_GROW_X);
-	}
-
+	GntWidget *widget = g_object_new(GNT_TYPE_LINE, "vertical", vertical, NULL);
 	return widget;
 }
 
--- a/finch/libgnt/gntmain.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/libgnt/gntmain.c	Sat Aug 11 21:08:27 2007 +0000
@@ -252,6 +252,18 @@
 	if (HOLDING_ESCAPE)
 		keys[0] = '\033';
 	k = keys;
+
+#if 0
+	/* I am not sure what's happening here. If this actually does something,
+	 * then this needs to go in gnt_keys_refine. */
+	if (*k < 0) { /* Alt not sending ESC* */
+		*(k + 1) = 128 - *k;
+		*k = 27;
+		*(k + 2) = 0;
+		rd++;
+	}
+#endif
+
 	while (rd) {
 		char back;
 		int p;
--- a/finch/libgnt/gntmenu.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/libgnt/gntmenu.c	Sat Aug 11 21:08:27 2007 +0000
@@ -247,11 +247,14 @@
 	int current = menu->selected;
 
 	if (menu->submenu) {
-		do menu = menu->submenu; while (menu->submenu);
-		return (gnt_widget_key_pressed(GNT_WIDGET(menu), text));
+		GntMenu *sub = menu;
+		do sub = sub->submenu; while (sub->submenu);
+		if (gnt_widget_key_pressed(GNT_WIDGET(sub), text))
+			return TRUE;
 	}
 
-	if (text[0] == 27 && text[1] == 0) {
+	if ((text[0] == 27 && text[1] == 0) ||
+			(menu->type != GNT_MENU_TOPLEVEL && strcmp(text, GNT_KEY_LEFT) == 0)) {
 		/* Escape closes menu */
 		GntMenu *par = menu->parentmenu;
 		if (par != NULL) {
@@ -271,11 +274,17 @@
 			menu->selected++;
 			if (menu->selected >= g_list_length(menu->list))
 				menu->selected = 0;
-		} else if (strcmp(text, GNT_KEY_ENTER) == 0) {
+		} else if (strcmp(text, GNT_KEY_ENTER) == 0 ||
+				strcmp(text, GNT_KEY_DOWN) == 0) {
 			gnt_widget_activate(widget);
 		}
 
 		if (current != menu->selected) {
+			GntMenu *sub = menu->submenu;
+			while (sub) {
+				gnt_widget_hide(GNT_WIDGET(sub));
+				sub = sub->submenu;
+			}
 			gnt_widget_draw(widget);
 			return TRUE;
 		}
@@ -283,6 +292,12 @@
 		if (text[1] == '\0') {
 			if (check_for_trigger(menu, text[0]))
 				return TRUE;
+		} else if (strcmp(text, GNT_KEY_RIGHT) == 0) {
+			GntMenuItem *item = gnt_tree_get_selection_data(GNT_TREE(menu));
+			if (item && item->submenu) {
+				menuitem_activate(menu, item);
+				return TRUE;
+			}
 		}
 		return org_key_pressed(widget, text);
 	}
--- a/finch/libgnt/gntutils.h	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/libgnt/gntutils.h	Sat Aug 11 21:08:27 2007 +0000
@@ -40,6 +40,7 @@
  */
 void gnt_util_get_text_bound(const char *text, int *width, int *height);
 
+/* excluding *end */
 /**
  * Get the onscreen width of a string, or a substring.
  *
--- a/finch/libgnt/gntwm.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/libgnt/gntwm.c	Sat Aug 11 21:08:27 2007 +0000
@@ -39,6 +39,8 @@
 #include "gntmarshal.h"
 #include "gnt.h"
 #include "gntbox.h"
+#include "gntbutton.h"
+#include "gntentry.h"
 #include "gntlabel.h"
 #include "gntmenu.h"
 #include "gnttextview.h"
@@ -84,6 +86,7 @@
 static time_t last_active_time;
 static gboolean idle_update;
 static GList *act = NULL; /* list of WS with unseen activitiy */
+static gboolean ignore_keys = FALSE;
 
 static GList *
 g_list_bring_to_front(GList *list, gpointer data)
@@ -485,35 +488,6 @@
 	return TRUE;
 }
 
-static gboolean
-help_for_widget(GntBindable *bindable, GList *null)
-{
-	GntWM *wm = GNT_WM(bindable);
-	GntWidget *widget, *tree, *win, *active;
-	char *title;
-
-	if (!wm->cws->ordered)
-		return TRUE;
-
-	widget = wm->cws->ordered->data;
-	if (!GNT_IS_BOX(widget))
-		return TRUE;
-	active = GNT_BOX(widget)->active;
-
-	tree = gnt_widget_bindings_view(active);
-	win = gnt_window_new();
-	title = g_strdup_printf("Bindings for %s", g_type_name(G_OBJECT_TYPE(active)));
-	gnt_box_set_title(GNT_BOX(win), title);
-	if (tree)
-		gnt_box_add_widget(GNT_BOX(win), tree);
-	else
-		gnt_box_add_widget(GNT_BOX(win), gnt_label_new("This widget has no customizable bindings."));
-
-	gnt_widget_show(win);
-
-	return TRUE;
-}
-
 static void
 destroy__list(GntWidget *widget, GntWM *wm)
 {
@@ -843,6 +817,7 @@
 shift_right(GntBindable *bindable, GList *null)
 {
 	GntWM *wm = GNT_WM(bindable);
+	
 	if (wm->_list.window)
 		return TRUE;
 
@@ -1138,6 +1113,74 @@
 	return TRUE;
 }
 
+static gboolean
+ignore_keys_start(GntBindable *bindable, GList *n)
+{
+	GntWM *wm = GNT_WM(bindable);
+
+	if(!wm->menu && !wm->_list.window && wm->mode == GNT_KP_MODE_NORMAL){
+		ignore_keys = TRUE;
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static gboolean
+ignore_keys_end(GntBindable *bindable, GList *n)
+{
+	return ignore_keys ? !(ignore_keys = FALSE) : FALSE;
+}
+
+static gboolean
+help_for_bindable(GntWM *wm, GntBindable *bindable)
+{
+	gboolean ret = TRUE;
+	GntBindableClass *klass = GNT_BINDABLE_GET_CLASS(bindable);
+ 
+	if (klass->help_window) {
+		gnt_wm_raise_window(wm, GNT_WIDGET(klass->help_window));
+	} else {
+		ret =  gnt_bindable_build_help_window(bindable);
+	}
+	return ret;
+}
+
+static gboolean
+help_for_wm(GntBindable *bindable, GList *null)
+{
+	return help_for_bindable(GNT_WM(bindable),bindable);
+}
+
+static gboolean
+help_for_window(GntBindable *bindable, GList *null)
+{
+	GntWM *wm = GNT_WM(bindable);
+	GntWidget *widget;
+
+	if(!wm->cws->ordered)
+		return FALSE;
+
+	widget = wm->cws->ordered->data;
+
+	return help_for_bindable(wm,GNT_BINDABLE(widget));
+}
+
+static gboolean
+help_for_widget(GntBindable *bindable, GList *null)
+{
+	GntWM *wm = GNT_WM(bindable);
+	GntWidget *widget;
+
+	if (!wm->cws->ordered)
+		return TRUE;
+
+	widget = wm->cws->ordered->data;
+	if (!GNT_IS_BOX(widget))
+		return TRUE;
+
+	return help_for_bindable(wm, GNT_BINDABLE(GNT_BOX(widget)->active));
+}
+
 static void
 accumulate_windows(gpointer window, gpointer node, gpointer p)
 {
@@ -1321,8 +1364,16 @@
 				"\033" "T", NULL);
 	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "workspace-list", workspace_list,
 				"\033" "s", NULL);
-	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "toggle-clipboard",
-				toggle_clipboard, "\033" "C", NULL);
+	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "toggle-clipboard", toggle_clipboard,
+				"\033" "C", NULL);
+	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "help-for-wm", help_for_wm,
+				"\033" "\\", NULL);
+	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "help-for-window", help_for_window,
+				"\033" "|", NULL);
+	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "ignore-keys-start", ignore_keys_start, 
+				GNT_KEY_CTRL_G, NULL);
+	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "ignore-keys-end", ignore_keys_end, 
+				"\033" GNT_KEY_CTRL_G, NULL);
 
 	gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), GNT_BINDABLE_CLASS(klass));
 
@@ -1709,6 +1760,14 @@
 	keys = gnt_bindable_remap_keys(GNT_BINDABLE(wm), keys);
 
 	idle_update = TRUE;
+	if(ignore_keys){
+		if(keys && !strcmp(keys, "\033" GNT_KEY_CTRL_G)){
+			if(gnt_bindable_perform_action_key(GNT_BINDABLE(wm), keys)){
+				return TRUE;
+			}
+		}
+		return wm->cws->ordered ? gnt_widget_key_pressed(GNT_WIDGET(wm->cws->ordered->data), keys) : FALSE;
+	}
 
 	if (gnt_bindable_perform_action_key(GNT_BINDABLE(wm), keys)) {
 		return TRUE;
@@ -1927,7 +1986,7 @@
 
 	if (!node)
 		return;
-	
+
 	if (widget != wm->_list.window && !GNT_IS_MENU(widget) &&
 				wm->cws->ordered->data != widget) {
 		GntWidget *w = wm->cws->ordered->data;
@@ -2008,3 +2067,4 @@
 	wm->event_stack = set;
 }
 
+
--- a/finch/libgnt/pygnt/example/rss/gntrss-ui.py	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/libgnt/pygnt/example/rss/gntrss-ui.py	Sat Aug 11 21:08:27 2007 +0000
@@ -73,6 +73,7 @@
         if first:
             self.set_active(first)
             self.set_selected(first)
+        return True
 
     def __init__(self):
         self.active = None
--- a/finch/libgnt/pygnt/example/rss/gntrss.py	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/libgnt/pygnt/example/rss/gntrss.py	Sat Aug 11 21:08:27 2007 +0000
@@ -55,8 +55,7 @@
             item['date'] = self.date = time.ctime()
             self.date_parsed = feedparser._parse_date(self.date)
 
-        self.title = item['title']
-        sum = item['summary']
+        self.title = item['title'].encode('utf8')
         self.summary = item['summary'].encode('utf8')
         self.link = item['link']
         self.parent = parent
@@ -79,7 +78,7 @@
 gobject.type_register(FeedItem)
 
 def item_hash(item):
-    return str(item['date'] + item['title'])
+    return str(item['title'])
 
 """
 The Feed class. It will update the 'link', 'title', 'desc' and 'items'
@@ -171,6 +170,7 @@
             self.set_property('unread', unread)
 
         for hv in tmp:
+            self.items.remove(tmp[hv])
             tmp[hv].remove()
             "Also notify the UI about the count change"
 
--- a/finch/libgnt/wms/Makefile.am	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/libgnt/wms/Makefile.am	Sat Aug 11 21:08:27 2007 +0000
@@ -1,10 +1,16 @@
+if PURPLE_AVAILABLE
+# These custom wms depend on libpurple
+purple_wms = s.la irssi.la
+else
+purple_wms =
+endif
+
 s_la_LDFLAGS             = -module -avoid-version
 irssi_la_LDFLAGS         = -module -avoid-version
 
 plugin_LTLIBRARIES = \
-	s.la \
-	irssi.la
-
+	$(purple_wms)
+ 
 plugindir = $(libdir)/gnt
 
 irssi_la_SOURCES = irssi.c
--- a/finch/libgnt/wms/s.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/finch/libgnt/wms/s.c	Sat Aug 11 21:08:27 2007 +0000
@@ -15,6 +15,10 @@
 
 #define TYPE_S				(s_get_gtype())
 
+#ifdef _S
+#undef _S
+#endif
+
 typedef struct _S
 {
 	GntWM inherit;
--- a/libpurple/account.h	Sat Aug 11 21:08:06 2007 +0000
+++ b/libpurple/account.h	Sat Aug 11 21:08:27 2007 +0000
@@ -88,8 +88,8 @@
 	                           const char *alias,
 	                           const char *message,
 	                           gboolean on_list,
-	                           GCallback authorize_cb,
-	                           GCallback deny_cb,
+	                           PurpleAccountRequestAuthorizationCb authorize_cb,
+	                           PurpleAccountRequestAuthorizationCb deny_cb,
 	                           void *user_data);
 
 	/** Close a pending request for authorization.  \a ui_handle is a handle
--- a/libpurple/log.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/libpurple/log.c	Sat Aug 11 21:08:27 2007 +0000
@@ -718,9 +718,9 @@
 		if (tmp < start)
 			g_string_append_len(newmsg, tmp, start - tmp);
 
-		idstr = g_datalist_get_data(&attributes, "id");
+		if ((idstr = g_datalist_get_data(&attributes, "id")) != NULL)
+			imgid = atoi(idstr);
 
-		imgid = atoi(idstr);
 		if (imgid != 0)
 		{
 			FILE *image_file;
@@ -735,6 +735,7 @@
 			if (image == NULL)
 			{
 				/* This should never happen. */
+				/* This *does* happen for failed Direct-IMs -DAA */
 				g_string_free(newmsg, TRUE);
 				g_return_val_if_reached((char *)msg);
 			}
--- a/libpurple/prefs.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/libpurple/prefs.c	Sat Aug 11 21:08:27 2007 +0000
@@ -1341,7 +1341,6 @@
 		list = g_list_append(list, g_strdup_printf("%s%s%s", name, sep, child->name));
 	}
 	return list;
-
 }
 
 void
--- a/libpurple/protocols/bonjour/bonjour.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Sat Aug 11 21:08:27 2007 +0000
@@ -138,6 +138,8 @@
 		return;
 	}
 
+	bonjour_dns_sd_update_buddy_icon(bd->dns_sd_data);
+
 	/* Create a group for bonjour buddies */
 	bonjour_group = purple_group_new(BONJOUR_GROUP_NAME);
 	purple_blist_add_group(bonjour_group, NULL);
@@ -283,17 +285,33 @@
 	bb->conversation = NULL;
 }
 
+static
+void bonjour_set_buddy_icon(PurpleConnection *conn, PurpleStoredImage *img)
+{
+	BonjourData *bd = conn->proto_data;
+	bonjour_dns_sd_update_buddy_icon(bd->dns_sd_data);
+}
+
+
 static char *
 bonjour_status_text(PurpleBuddy *buddy)
 {
-	PurplePresence *presence;
+	const PurplePresence *presence;
+	const PurpleStatus *status;
+	const char *message;
+	gchar *ret = NULL;
 
 	presence = purple_buddy_get_presence(buddy);
+	status = purple_presence_get_active_status(presence);
 
-	if (purple_presence_is_online(presence) && !purple_presence_is_available(presence))
-		return g_strdup(_("Away"));
+	message = purple_status_get_attr_string(status, "message");
 
-	return NULL;
+	if (message != NULL) {
+		ret = g_markup_escape_text(message, -1);
+		purple_util_chrreplace(ret, '\n', ' ');
+	}
+
+	return ret;
 }
 
 static void
@@ -301,6 +319,7 @@
 {
 	PurplePresence *presence;
 	PurpleStatus *status;
+	BonjourBuddy *bb = buddy->proto_data;
 	const char *status_description;
 	const char *message;
 
@@ -318,6 +337,23 @@
 	purple_notify_user_info_add_pair(user_info, _("Status"), status_description);
 	if (message != NULL)
 		purple_notify_user_info_add_pair(user_info, _("Message"), message);
+
+	/* Only show first/last name if there is a nickname set (to avoid duplication) */
+	if (bb->nick != NULL) {
+		if (bb->first != NULL)
+			purple_notify_user_info_add_pair(user_info, _("First name"), bb->first);
+		if (bb->first != NULL)
+			purple_notify_user_info_add_pair(user_info, _("Last name"), bb->last);
+	}
+
+	if (bb->email != NULL)
+		purple_notify_user_info_add_pair(user_info, _("E-Mail"), bb->email);
+
+	if (bb->AIM != NULL)
+		purple_notify_user_info_add_pair(user_info, _("AIM Account"), bb->AIM);
+
+	if (bb->jid!= NULL)
+		purple_notify_user_info_add_pair(user_info, _("XMPP Account"), bb->jid);
 }
 
 static gboolean
@@ -339,10 +375,9 @@
 	OPT_PROTO_NO_PASSWORD,
 	NULL,                                                    /* user_splits */
 	NULL,                                                    /* protocol_options */
-	/* {"png", 0, 0, 96, 96, 0, PURPLE_ICON_SCALE_DISPLAY}, */ /* icon_spec */
-	NO_BUDDY_ICONS, /* not yet */                            /* icon_spec */
+	{"png,gif,jpeg", 0, 0, 96, 96, 65535, PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */
 	bonjour_list_icon,                                       /* list_icon */
-	NULL,													 /* list_emblem */
+	NULL,                                                    /* list_emblem */
 	bonjour_status_text,                                     /* status_text */
 	bonjour_tooltip_text,                                    /* tooltip_text */
 	bonjour_status_types,                                    /* status_types */
@@ -384,7 +419,7 @@
 	NULL,                                                    /* buddy_free */
 	bonjour_convo_closed,                                    /* convo_closed */
 	NULL,                                                    /* normalize */
-	NULL,                                                    /* set_buddy_icon */
+	bonjour_set_buddy_icon,                                  /* set_buddy_icon */
 	NULL,                                                    /* remove_group */
 	NULL,                                                    /* get_cb_real_name */
 	NULL,                                                    /* set_chat_topic */
@@ -412,11 +447,11 @@
 	PURPLE_PLUGIN_MAGIC,
 	PURPLE_MAJOR_VERSION,
 	PURPLE_MINOR_VERSION,
-	PURPLE_PLUGIN_PROTOCOL,                             /**< type           */
+	PURPLE_PLUGIN_PROTOCOL,                           /**< type           */
 	NULL,                                             /**< ui_requirement */
 	0,                                                /**< flags          */
 	NULL,                                             /**< dependencies   */
-	PURPLE_PRIORITY_DEFAULT,                            /**< priority       */
+	PURPLE_PRIORITY_DEFAULT,                          /**< priority       */
 
 	"prpl-bonjour",                                   /**< id             */
 	"Bonjour",                                        /**< name           */
@@ -426,10 +461,10 @@
 	                                                  /**  description    */
 	N_("Bonjour Protocol Plugin"),
 	NULL,                                             /**< author         */
-	PURPLE_WEBSITE,                                     /**< homepage       */
+	PURPLE_WEBSITE,                                   /**< homepage       */
 
 	NULL,                                             /**< load           */
-	plugin_unload,                                             /**< unload         */
+	plugin_unload,                                    /**< unload         */
 	NULL,                                             /**< destroy        */
 
 	NULL,                                             /**< ui_info        */
@@ -533,7 +568,7 @@
 	{
 		default_firstname = g_strndup(fullname, splitpoint - fullname);
 		tmp = &splitpoint[1];
-		
+
 		/* The last name may be followed by a comma and additional data.
 		 * Only use the last name itself.
 		 */
--- a/libpurple/protocols/bonjour/buddy.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.c	Sat Aug 11 21:08:27 2007 +0000
@@ -18,6 +18,7 @@
 #include <stdlib.h>
 
 #include "internal.h"
+#include "cipher.h"
 #include "buddy.h"
 #include "account.h"
 #include "blist.h"
@@ -41,6 +42,26 @@
 	return buddy;
 }
 
+#define _B_CLR(x) g_free(x); x = NULL;
+
+void clear_bonjour_buddy_values(BonjourBuddy *buddy) {
+
+	_B_CLR(buddy->first)
+	_B_CLR(buddy->email);
+	_B_CLR(buddy->ext);
+	_B_CLR(buddy->jid);
+	_B_CLR(buddy->last);
+	_B_CLR(buddy->msg);
+	_B_CLR(buddy->nick);
+	_B_CLR(buddy->node);
+	_B_CLR(buddy->phsh);
+	_B_CLR(buddy->status);
+	_B_CLR(buddy->vc);
+	_B_CLR(buddy->ver);
+	_B_CLR(buddy->AIM);
+
+}
+
 void
 set_bonjour_buddy_value(BonjourBuddy* buddy, const char *record_key, const char *value, uint32_t len){
 	gchar **fld = NULL;
@@ -106,11 +127,10 @@
 	PurpleBuddy *buddy;
 	PurpleGroup *group;
 	PurpleAccount *account = bonjour_buddy->account;
-	const char *status_id, *first, *last, *old_hash, *new_hash;
-	gchar *alias = NULL;
+	const char *status_id, *old_hash, *new_hash;
 
 	/* Translate between the Bonjour status and the Purple status */
-	if (g_ascii_strcasecmp("dnd", bonjour_buddy->status) == 0)
+	if (bonjour_buddy->status != NULL && g_ascii_strcasecmp("dnd", bonjour_buddy->status) == 0)
 		status_id = BONJOUR_STATUS_ID_AWAY;
 	else
 		status_id = BONJOUR_STATUS_ID_AVAILABLE;
@@ -138,15 +158,21 @@
 	}
 
 	/* Create the alias for the buddy using the first and the last name */
-	first = bonjour_buddy->first;
-	last = bonjour_buddy->last;
-	if ((first && *first) || (last && *last))
-		alias = g_strdup_printf("%s%s%s",
-					(first && *first ? first : ""),
-					(first && *first && last && *last ? " " : ""),
-					(last && *last ? last : ""));
-	serv_got_alias(purple_account_get_connection(account), buddy->name, alias);
-	g_free(alias);
+	if (bonjour_buddy->nick)
+		serv_got_alias(purple_account_get_connection(account), buddy->name, bonjour_buddy->nick);
+	else {
+		gchar *alias = NULL;
+		const char *first, *last;
+		first = bonjour_buddy->first;
+		last = bonjour_buddy->last;
+		if ((first && *first) || (last && *last))
+			alias = g_strdup_printf("%s%s%s",
+						(first && *first ? first : ""),
+						(first && *first && last && *last ? " " : ""),
+						(last && *last ? last : ""));
+		serv_got_alias(purple_account_get_connection(account), buddy->name, alias);
+		g_free(alias);
+	}
 
 	/* Set the user's status */
 	if (bonjour_buddy->msg != NULL)
@@ -166,12 +192,46 @@
 	new_hash = (bonjour_buddy->phsh && *(bonjour_buddy->phsh)) ? bonjour_buddy->phsh : NULL;
 	if (new_hash && (!old_hash || strcmp(old_hash, new_hash) != 0)) {
 		/* Look up the new icon data */
+		/* TODO: Make sure the hash assigned to the retrieved buddy icon is the same
+		 * as what we looked up. */
 		bonjour_dns_sd_retrieve_buddy_icon(bonjour_buddy);
-	} else
+	} else if (!new_hash)
 		purple_buddy_icons_set_for_user(account, buddy->name, NULL, 0, NULL);
 }
 
 /**
+ * We got the buddy icon data; deal with it
+ */
+void bonjour_buddy_got_buddy_icon(BonjourBuddy *buddy, gconstpointer data, gsize len) {
+	/* Recalculate the hash instead of using the current phsh to make sure it is accurate for the icon. */
+	int i;
+	gchar *enc;
+	char *p, hash[41];
+	unsigned char hashval[20];
+
+	if (data == NULL || len == 0)
+		return;
+
+	enc = purple_base64_encode(data, len);
+
+	purple_cipher_digest_region("sha1", data,
+				    len, sizeof(hashval),
+				    hashval, NULL);
+
+	p = hash;
+	for(i=0; i<20; i++, p+=2)
+		snprintf(p, 3, "%02x", hashval[i]);
+
+	purple_debug_info("bonjour", "Got buddy icon for %s icon hash='%s' phsh='%s'.\n", buddy->name,
+			  hash, buddy->phsh ? buddy->phsh : "(null)");
+
+	purple_buddy_icons_set_for_user(buddy->account, buddy->name,
+		g_memdup(data, len), len, hash);
+
+	g_free(enc);
+}
+
+/**
  * Deletes a buddy from memory.
  */
 void
@@ -179,8 +239,6 @@
 {
 	g_free(buddy->name);
 	g_free(buddy->ip);
-	g_free(buddy->full_service_name);
-
 	g_free(buddy->first);
 	g_free(buddy->phsh);
 	g_free(buddy->status);
--- a/libpurple/protocols/bonjour/buddy.h	Sat Aug 11 21:08:06 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.h	Sat Aug 11 21:08:27 2007 +0000
@@ -29,7 +29,6 @@
 	gchar *name;
 	/* TODO: Remove and just use the hostname */
 	gchar *ip;
-	gchar *full_service_name;
 	gint port_p2pj;
 
 	gchar *first;
@@ -76,9 +75,15 @@
 BonjourBuddy *bonjour_buddy_new(const gchar *name, PurpleAccount *account);
 
 /**
+ * Clear any existing values from the buddy.
+ * This is called before updating so that we can notice removals
+ */
+void clear_bonjour_buddy_values(BonjourBuddy *buddy);
+
+/**
  * Sets a value in the BonjourBuddy struct, destroying the old value
  */
-void set_bonjour_buddy_value(BonjourBuddy* buddy, const char *record_key, const char *value, uint32_t len);
+void set_bonjour_buddy_value(BonjourBuddy *buddy, const char *record_key, const char *value, uint32_t len);
 
 /**
  * Check if all the compulsory buddy data is present.
@@ -91,6 +96,11 @@
 void bonjour_buddy_add_to_purple(BonjourBuddy *buddy);
 
 /**
+ * We got the buddy icon data; deal with it
+ */
+void bonjour_buddy_got_buddy_icon(BonjourBuddy *buddy, gconstpointer data, gsize len);
+
+/**
  * Deletes a buddy from memory.
  */
 void bonjour_buddy_delete(BonjourBuddy *buddy);
--- a/libpurple/protocols/bonjour/issues.txt	Sat Aug 11 21:08:06 2007 +0000
+++ b/libpurple/protocols/bonjour/issues.txt	Sat Aug 11 21:08:27 2007 +0000
@@ -3,6 +3,5 @@
 ==========================================
 
 * Status changes don't work
-* Avatars
 * File transfers
 * Typing notifications
--- a/libpurple/protocols/bonjour/mdns_avahi.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_avahi.c	Sat Aug 11 21:08:27 2007 +0000
@@ -19,6 +19,7 @@
 #include "mdns_interface.h"
 #include "debug.h"
 #include "buddy.h"
+#include "bonjour.h"
 
 #include <avahi-client/client.h>
 #include <avahi-client/lookup.h>
@@ -32,14 +33,25 @@
 #include <avahi-glib/glib-malloc.h>
 #include <avahi-glib/glib-watch.h>
 
+/* For some reason, this is missing from the Avahi type defines */
+#ifndef AVAHI_DNS_TYPE_NULL
+#define AVAHI_DNS_TYPE_NULL 0x0A
+#endif
+
 /* data used by avahi bonjour implementation */
 typedef struct _avahi_session_impl_data {
 	AvahiClient *client;
 	AvahiGLibPoll *glib_poll;
 	AvahiServiceBrowser *sb;
 	AvahiEntryGroup *group;
+	AvahiEntryGroup *buddy_icon_group;
 } AvahiSessionImplData;
 
+typedef struct _avahi_buddy_impl_data {
+	AvahiServiceResolver *resolver;
+	AvahiRecordBrowser *buddy_icon_rec_browser;
+} AvahiBuddyImplData;
+
 static void
 _resolver_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol,
 		  AvahiResolverEvent event, const char *name, const char *type, const char *domain,
@@ -59,11 +71,14 @@
 		case AVAHI_RESOLVER_FAILURE:
 			purple_debug_error("bonjour", "_resolve_callback - Failure: %s\n",
 				avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
+			avahi_service_resolver_free(r);
 			break;
 		case AVAHI_RESOLVER_FOUND:
 			/* create a buddy record */
 			buddy = bonjour_buddy_new(name, account);
 
+			((AvahiBuddyImplData *)buddy->mdns_impl_data)->resolver = r;
+
 			/* Get the ip as a string */
 			buddy->ip = g_malloc(AVAHI_ADDRESS_STR_MAX);
 			avahi_address_snprint(buddy->ip, AVAHI_ADDRESS_STR_MAX, a);
@@ -71,6 +86,7 @@
 			buddy->port_p2pj = port;
 
 			/* Obtain the parameters from the text_record */
+			clear_bonjour_buddy_values(buddy);
 			l = txt;
 			while (l != NULL) {
 				ret = avahi_string_list_get_pair(l, &key, &value, &size);
@@ -95,7 +111,6 @@
 			purple_debug_info("bonjour", "Unrecognized Service Resolver event: %d.\n", event);
 	}
 
-	avahi_service_resolver_free(r);
 }
 
 static void
@@ -145,6 +160,30 @@
 }
 
 static void
+_buddy_icon_group_cb(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
+	BonjourDnsSd *data = userdata;
+	AvahiSessionImplData *idata = data->mdns_impl_data;
+
+	g_return_if_fail(g == idata->buddy_icon_group || idata->buddy_icon_group == NULL);
+
+	switch(state) {
+		case AVAHI_ENTRY_GROUP_ESTABLISHED:
+			purple_debug_info("bonjour", "Successfully registered buddy icon data.\n");
+		case AVAHI_ENTRY_GROUP_COLLISION:
+			purple_debug_error("bonjour", "Collision registering buddy icon data.\n");
+			break;
+		case AVAHI_ENTRY_GROUP_FAILURE:
+			purple_debug_error("bonjour", "Error registering buddy icon data: %s\n.",
+				avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
+			break;
+		case AVAHI_ENTRY_GROUP_UNCOMMITED:
+		case AVAHI_ENTRY_GROUP_REGISTERING:
+			break;
+	}
+
+}
+
+static void
 _entry_group_cb(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
 	AvahiSessionImplData *idata = userdata;
 
@@ -170,6 +209,31 @@
 
 }
 
+static void
+_buddy_icon_record_cb(AvahiRecordBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol,
+		      AvahiBrowserEvent event, const char *name, uint16_t clazz, uint16_t type,
+		      const void *rdata, size_t size, AvahiLookupResultFlags flags, void *userdata) {
+	BonjourBuddy *buddy = userdata;
+	AvahiBuddyImplData *idata = buddy->mdns_impl_data;
+
+	switch (event) {
+		case AVAHI_BROWSER_NEW:
+			bonjour_buddy_got_buddy_icon(buddy, rdata, size);
+			break;
+		case AVAHI_BROWSER_REMOVE:
+		case AVAHI_BROWSER_CACHE_EXHAUSTED:
+		case AVAHI_BROWSER_ALL_FOR_NOW:
+		case AVAHI_BROWSER_FAILURE:
+			purple_debug_error("bonjour", "Error rerieving buddy icon record: %s\n",
+				avahi_strerror(avahi_client_errno(avahi_record_browser_get_client(b))));
+			break;
+	}
+
+	/* Stop listening */
+	avahi_record_browser_free(idata->buddy_icon_rec_browser);
+	idata->buddy_icon_rec_browser = NULL;
+}
+
 /****************************
  * mdns_interface functions *
  ****************************/
@@ -203,10 +267,8 @@
 	return TRUE;
 }
 
-gboolean _mdns_publish(BonjourDnsSd *data, PublishType type) {
+gboolean _mdns_publish(BonjourDnsSd *data, PublishType type, GSList *records) {
 	int publish_result = 0;
-	char portstring[6];
-	const char *jid, *aim, *email;
 	AvahiSessionImplData *idata = data->mdns_impl_data;
 	AvahiStringList *lst = NULL;
 
@@ -223,44 +285,11 @@
 		}
 	}
 
-	/* Convert the port to a string */
-	snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj);
-
-	jid = purple_account_get_string(data->account, "jid", NULL);
-	aim = purple_account_get_string(data->account, "AIM", NULL);
-	email = purple_account_get_string(data->account, "email", NULL);
-
-	/* We should try to follow XEP-0174, but some clients have "issues", so we humor them.
-	 * See http://telepathy.freedesktop.org/wiki/SalutInteroperability
-	 */
-
-	/* Needed by iChat */
-	lst = avahi_string_list_add_pair(lst,"txtvers", "1");
-	/* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
-	lst = avahi_string_list_add_pair(lst, "1st", data->first);
-	/* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
-	lst = avahi_string_list_add_pair(lst, "last", data->last);
-	/* Needed by Adium */
-	lst = avahi_string_list_add_pair(lst, "port.p2pj", portstring);
-	/* Needed by iChat, Gaim/Pidgin <= 2.0.1 */
-	lst = avahi_string_list_add_pair(lst, "status", data->status);
-	/* Currently always set to "!" since we don't support AV and wont ever be in a conference */
-	lst = avahi_string_list_add_pair(lst, "vc", data->vc);
-	lst = avahi_string_list_add_pair(lst, "ver", VERSION);
-	if (email != NULL && *email != '\0')
-		lst = avahi_string_list_add_pair(lst, "email", email);
-	if (jid != NULL && *jid != '\0')
-		lst = avahi_string_list_add_pair(lst, "jid", jid);
-	/* Nonstandard, but used by iChat */
-	if (aim != NULL && *aim != '\0')
-		lst = avahi_string_list_add_pair(lst, "AIM", aim);
-	if (data->msg != NULL && *data->msg != '\0')
-		lst = avahi_string_list_add_pair(lst, "msg", data->msg);
-	if (data->phsh != NULL && *data->phsh != '\0')
-		lst = avahi_string_list_add_pair(lst, "phsh", data->phsh);
-
-	/* TODO: ext, nick, node */
-
+	while (records) {
+		PurpleKeyValuePair *kvp = records->data;
+		lst = avahi_string_list_add_pair(lst, kvp->key, kvp->value);
+		records = records->next;
+	}
 
 	/* Publish the service */
 	switch (type) {
@@ -290,7 +319,8 @@
 		return FALSE;
 	}
 
-	if ((publish_result = avahi_entry_group_commit(idata->group)) < 0) {
+	if (type == PUBLISH_START
+			&& (publish_result = avahi_entry_group_commit(idata->group)) < 0) {
 		purple_debug_error("bonjour",
 			"Failed to commit " ICHAT_SERVICE " service. Error: %s\n",
 			avahi_strerror(publish_result));
@@ -317,9 +347,71 @@
 	return TRUE;
 }
 
-/* This is done differently than with Howl/Apple Bonjour */
-guint _mdns_register_to_mainloop(BonjourDnsSd *data) {
-	return 0;
+gboolean _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len) {
+	AvahiSessionImplData *idata = data->mdns_impl_data;
+
+	if (idata == NULL || idata->client == NULL)
+		return FALSE;
+
+	if (avatar_data != NULL) {
+		gboolean new_group = FALSE;
+		gchar *svc_name;
+		int ret;
+		AvahiPublishFlags flags = 0;
+
+		if (idata->buddy_icon_group == NULL) {
+			purple_debug_info("bonjour", "Setting new buddy icon.\n");
+			new_group = TRUE;
+
+			idata->buddy_icon_group = avahi_entry_group_new(idata->client,
+				_buddy_icon_group_cb, data);
+		} else {
+			purple_debug_info("bonjour", "Updating existing buddy icon.\n");
+			flags |= AVAHI_PUBLISH_UPDATE;
+		}
+
+		if (idata->buddy_icon_group == NULL) {
+			purple_debug_error("bonjour",
+				"Unable to initialize the buddy icon group (%s).\n",
+				avahi_strerror(avahi_client_errno(idata->client)));
+			return FALSE;
+		}
+
+		svc_name = g_strdup_printf("%s." ICHAT_SERVICE "local",
+				purple_account_get_username(data->account));
+
+		ret = avahi_entry_group_add_record(idata->buddy_icon_group, AVAHI_IF_UNSPEC,
+			AVAHI_PROTO_UNSPEC, flags, svc_name,
+			AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_NULL, 120, avatar_data, avatar_len);
+
+		g_free(svc_name);
+
+		if (ret < 0) {
+			purple_debug_error("bonjour",
+				"Failed to register buddy icon. Error: %s\n", avahi_strerror(ret));
+			if (new_group) {
+				avahi_entry_group_free(idata->buddy_icon_group);
+				idata->buddy_icon_group = NULL;
+			}
+			return FALSE;
+		}
+
+		if (new_group && (ret = avahi_entry_group_commit(idata->buddy_icon_group)) < 0) {
+			purple_debug_error("bonjour",
+				"Failed to commit buddy icon group. Error: %s\n", avahi_strerror(ret));
+			if (new_group) {
+				avahi_entry_group_free(idata->buddy_icon_group);
+				idata->buddy_icon_group = NULL;
+			}
+			return FALSE;
+		}
+	} else if (idata->buddy_icon_group != NULL) {
+		purple_debug_info("bonjour", "Removing existing buddy icon.\n");
+		avahi_entry_group_free(idata->buddy_icon_group);
+		idata->buddy_icon_group = NULL;
+	}
+
+	return TRUE;
 }
 
 void _mdns_stop(BonjourDnsSd *data) {
@@ -340,10 +432,50 @@
 }
 
 void _mdns_init_buddy(BonjourBuddy *buddy) {
+	buddy->mdns_impl_data = g_new0(AvahiBuddyImplData, 1);
 }
 
 void _mdns_delete_buddy(BonjourBuddy *buddy) {
+	AvahiBuddyImplData *idata = buddy->mdns_impl_data;
+
+	g_return_if_fail(idata != NULL);
+
+	if (idata->buddy_icon_rec_browser != NULL)
+		avahi_record_browser_free(idata->buddy_icon_rec_browser);
+
+	if (idata->resolver != NULL)
+		avahi_service_resolver_free(idata->resolver);
+
+	g_free(idata);
+
+	buddy->mdns_impl_data = NULL;
 }
 
-void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) {
+void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy) {
+	PurpleConnection *conn = purple_account_get_connection(buddy->account);
+	BonjourData *bd = conn->proto_data;
+	AvahiSessionImplData *session_idata = bd->dns_sd_data->mdns_impl_data;
+	AvahiBuddyImplData *idata = buddy->mdns_impl_data;
+	gchar *name;
+
+	g_return_if_fail(idata != NULL);
+
+	if (idata->buddy_icon_rec_browser != NULL)
+		avahi_record_browser_free(idata->buddy_icon_rec_browser);
+
+	purple_debug_info("bonjour", "Retrieving buddy icon for '%s'.\n", buddy->name);
+
+	name = g_strdup_printf("%s." ICHAT_SERVICE "local", buddy->name);
+	idata->buddy_icon_rec_browser = avahi_record_browser_new(session_idata->client, AVAHI_IF_UNSPEC,
+		AVAHI_PROTO_UNSPEC, name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_NULL, 0,
+		_buddy_icon_record_cb, buddy);
+	g_free(name);
+
+	if (!idata->buddy_icon_rec_browser) {
+		purple_debug_error("bonjour",
+			"Unable to initialize buddy icon record browser.  Error: %s\n.",
+			avahi_strerror(avahi_client_errno(session_idata->client)));
+	}
+
 }
+
--- a/libpurple/protocols/bonjour/mdns_common.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_common.c	Sat Aug 11 21:08:27 2007 +0000
@@ -17,30 +17,27 @@
 #include <string.h>
 
 #include "internal.h"
+#include "cipher.h"
+#include "debug.h"
+
 #include "mdns_common.h"
 #include "mdns_interface.h"
 #include "bonjour.h"
 #include "buddy.h"
-#include "debug.h"
 
 
 /**
  * Allocate space for the dns-sd data.
  */
-BonjourDnsSd *
-bonjour_dns_sd_new()
-{
+BonjourDnsSd * bonjour_dns_sd_new() {
 	BonjourDnsSd *data = g_new0(BonjourDnsSd, 1);
-
 	return data;
 }
 
 /**
  * Deallocate the space of the dns-sd data.
  */
-void
-bonjour_dns_sd_free(BonjourDnsSd *data)
-{
+void bonjour_dns_sd_free(BonjourDnsSd *data) {
 	g_free(data->first);
 	g_free(data->last);
 	g_free(data->phsh);
@@ -50,12 +47,90 @@
 	g_free(data);
 }
 
+static GSList *generate_presence_txt_records(BonjourDnsSd *data) {
+	GSList *ret = NULL;
+	PurpleKeyValuePair *kvp;
+	char portstring[6];
+	const char *jid, *aim, *email;
+
+	/* Convert the port to a string */
+	snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj);
+
+	jid = purple_account_get_string(data->account, "jid", NULL);
+	aim = purple_account_get_string(data->account, "AIM", NULL);
+	email = purple_account_get_string(data->account, "email", NULL);
+
+#define _M_ADD_R(k, v) \
+	kvp = g_new0(PurpleKeyValuePair, 1); \
+	kvp->key = g_strdup(k); \
+	kvp->value = g_strdup(v); \
+	ret = g_slist_prepend(ret, kvp); \
+
+	/* We should try to follow XEP-0174, but some clients have "issues", so we humor them.
+	 * See http://telepathy.freedesktop.org/wiki/SalutInteroperability
+	 */
+
+	/* Needed by iChat */
+	_M_ADD_R("txtvers", "1")
+	/* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
+	_M_ADD_R("1st", data->first)
+	/* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
+	_M_ADD_R("last", data->last)
+	/* Needed by Adium */
+	_M_ADD_R("port.p2pj", portstring)
+	/* Needed by iChat, Gaim/Pidgin <= 2.0.1 */
+	_M_ADD_R("status", data->status)
+	_M_ADD_R("node", "libpurple")
+	_M_ADD_R("ver", VERSION)
+	/* Currently always set to "!" since we don't support AV and wont ever be in a conference */
+	_M_ADD_R("vc", data->vc)
+	if (email != NULL && *email != '\0') {
+		_M_ADD_R("email", email)
+	}
+	if (jid != NULL && *jid != '\0') {
+		_M_ADD_R("jid", jid)
+	}
+	/* Nonstandard, but used by iChat */
+	if (aim != NULL && *aim != '\0') {
+		_M_ADD_R("AIM", aim)
+	}
+	if (data->msg != NULL && *data->msg != '\0') {
+		_M_ADD_R("msg", data->msg)
+	}
+	if (data->phsh != NULL && *data->phsh != '\0') {
+		_M_ADD_R("phsh", data->phsh)
+	}
+
+	/* TODO: ext, nick */
+	return ret;
+}
+
+static void free_presence_txt_records(GSList *lst) {
+	PurpleKeyValuePair *kvp;
+	while(lst) {
+		kvp = lst->data;
+		g_free(kvp->key);
+		g_free(kvp->value);
+		g_free(kvp);
+		lst = g_slist_remove(lst, lst->data);
+	}
+}
+
+static gboolean publish_presence(BonjourDnsSd *data, PublishType type) {
+	GSList *txt_records;
+	gboolean ret;
+
+	txt_records = generate_presence_txt_records(data);
+	ret = _mdns_publish(data, type, txt_records);
+	free_presence_txt_records(txt_records);
+
+	return ret;
+}
+
 /**
  * Send a new dns-sd packet updating our status.
  */
-void
-bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message)
-{
+void bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message) {
 	g_free(data->status);
 	g_free(data->msg);
 
@@ -63,26 +138,78 @@
 	data->msg = g_strdup(status_message);
 
 	/* Update our text record with the new status */
-	_mdns_publish(data, PUBLISH_UPDATE); /* <--We must control the errors */
+	publish_presence(data, PUBLISH_UPDATE);
+}
+
+/**
+ * Retrieve the buddy icon blob
+ */
+void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) {
+	_mdns_retrieve_buddy_icon(buddy);
+}
+
+void bonjour_dns_sd_update_buddy_icon(BonjourDnsSd *data) {
+	PurpleStoredImage *img;
+
+	if ((img = purple_buddy_icons_find_account_icon(data->account))) {
+		gconstpointer avatar_data;
+		gsize avatar_len;
+
+		avatar_data = purple_imgstore_get_data(img);
+		avatar_len = purple_imgstore_get_size(img);
+
+		if (_mdns_set_buddy_icon_data(data, avatar_data, avatar_len)) {
+			int i;
+			gchar *enc;
+			char *p, hash[41];
+			unsigned char hashval[20];
+
+			enc = purple_base64_encode(avatar_data, avatar_len);
+
+			purple_cipher_digest_region("sha1", avatar_data,
+						    avatar_len, sizeof(hashval),
+						    hashval, NULL);
+
+			p = hash;
+			for(i=0; i<20; i++, p+=2)
+				snprintf(p, 3, "%02x", hashval[i]);
+
+			g_free(data->phsh);
+			data->phsh = g_strdup(hash);
+
+			g_free(enc);
+
+			/* Update our TXT record */
+			publish_presence(data, PUBLISH_UPDATE);
+		}
+
+		purple_imgstore_unref(img);
+	} else {
+		/* We need to do this regardless of whether data->phsh is set so that we
+		 * cancel any icons that are currently in the process of being set */
+		_mdns_set_buddy_icon_data(data, NULL, 0);
+		if (data->phsh != NULL) {
+			/* Clear the buddy icon */
+			g_free(data->phsh);
+			data->phsh = NULL;
+			/* Update our TXT record */
+			publish_presence(data, PUBLISH_UPDATE);
+		}
+	}
 }
 
 /**
  * Advertise our presence within the dns-sd daemon and start browsing
  * for other bonjour peers.
  */
-gboolean
-bonjour_dns_sd_start(BonjourDnsSd *data)
-{
-	PurpleConnection *gc;
-
-	gc = purple_account_get_connection(data->account);
+gboolean bonjour_dns_sd_start(BonjourDnsSd *data) {
 
 	/* Initialize the dns-sd data and session */
 	if (!_mdns_init_session(data))
 		return FALSE;
 
 	/* Publish our bonjour IM client at the mDNS daemon */
-	if (!_mdns_publish(data, PUBLISH_START))
+	if (!publish_presence(data, PUBLISH_START))
 		return FALSE;
 
 	/* Advise the daemon that we are waiting for connections */
@@ -91,11 +218,6 @@
 		return FALSE;
 	}
 
-
-	/* Get the socket that communicates with the mDNS daemon and bind it to a */
-	/* callback that will handle the dns_sd packets */
-	gc->inpa = _mdns_register_to_mainloop(data);
-
 	return TRUE;
 }
 
@@ -103,14 +225,6 @@
  * Unregister the "_presence._tcp" service at the mDNS daemon.
  */
 
-void
-bonjour_dns_sd_stop(BonjourDnsSd *data)
-{
-	PurpleConnection *gc;
-
+void bonjour_dns_sd_stop(BonjourDnsSd *data) {
 	_mdns_stop(data);
-
-	gc = purple_account_get_connection(data->account);
-	if (gc->inpa > 0)
-		purple_input_remove(gc->inpa);
 }
--- a/libpurple/protocols/bonjour/mdns_common.h	Sat Aug 11 21:08:06 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_common.h	Sat Aug 11 21:08:27 2007 +0000
@@ -42,6 +42,11 @@
 void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy);
 
 /**
+ * Deal with a buddy icon update
+ */
+void bonjour_dns_sd_update_buddy_icon(BonjourDnsSd *data);
+
+/**
  * Advertise our presence within the dns-sd daemon and start
  * browsing for other bonjour peers.
  */
--- a/libpurple/protocols/bonjour/mdns_howl.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_howl.c	Sat Aug 11 21:08:27 2007 +0000
@@ -26,6 +26,7 @@
 typedef struct _howl_impl_data {
 	sw_discovery session;
 	sw_discovery_oid session_id;
+	guint session_handler;
 } HowlSessionImplData;
 
 static sw_result HOWL_API
@@ -84,6 +85,7 @@
 	/* Obtain the parameters from the text_record */
 	if ((text_record_len > 0) && (text_record) && (*text_record != '\0'))
 	{
+		clear_bonjour_buddy_values(buddy);
 		sw_text_record_iterator_init(&iterator, text_record, text_record_len);
 		while (sw_text_record_iterator_next(iterator, key, (sw_octet *)value, &value_length) == SW_OKAY)
 			set_bonjour_buddy_value(buddy, key, value, value_length);
@@ -192,11 +194,9 @@
 }
 
 
-gboolean _mdns_publish(BonjourDnsSd *data, PublishType type) {
+gboolean _mdns_publish(BonjourDnsSd *data, PublishType type, GSList *records) {
 	sw_text_record dns_data;
 	sw_result publish_result = SW_OKAY;
-	char portstring[6];
-	const char *jid, *aim, *email;
 	HowlSessionImplData *idata = data->mdns_impl_data;
 
 	g_return_val_if_fail(idata != NULL, FALSE);
@@ -207,43 +207,11 @@
 		return FALSE;
 	}
 
-	/* Convert the port to a string */
-	snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj);
-
-	jid = purple_account_get_string(data->account, "jid", NULL);
-	aim = purple_account_get_string(data->account, "AIM", NULL);
-	email = purple_account_get_string(data->account, "email", NULL);
-
-	/* We should try to follow XEP-0174, but some clients have "issues", so we humor them.
-	 * See http://telepathy.freedesktop.org/wiki/SalutInteroperability
-	 */
-
-	/* Needed by iChat */
-	sw_text_record_add_key_and_string_value(dns_data, "txtvers", "1");
-	/* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
-	sw_text_record_add_key_and_string_value(dns_data, "1st", data->first);
-	/* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
-	sw_text_record_add_key_and_string_value(dns_data, "last", data->last);
-	/* Needed by Adium */
-	sw_text_record_add_key_and_string_value(dns_data, "port.p2pj", portstring);
-	/* Needed by iChat, Gaim/Pidgin <= 2.0.1 */
-	sw_text_record_add_key_and_string_value(dns_data, "status", data->status);
-	/* Currently always set to "!" since we don't support AV and wont ever be in a conference */
-	sw_text_record_add_key_and_string_value(dns_data, "vc", data->vc);
-	sw_text_record_add_key_and_string_value(dns_data, "ver", VERSION);
-	if (email != NULL && *email != '\0')
-		sw_text_record_add_key_and_string_value(dns_data, "email", email);
-	if (jid != NULL && *jid != '\0')
-		sw_text_record_add_key_and_string_value(dns_data, "jid", jid);
-	/* Nonstandard, but used by iChat */
-	if (aim != NULL && *aim != '\0')
-		sw_text_record_add_key_and_string_value(dns_data, "AIM", aim);
-	if (data->msg != NULL && *data->msg != '\0')
-		sw_text_record_add_key_and_string_value(dns_data, "msg", data->msg);
-	if (data->phsh != NULL && *data->phsh != '\0')
-		sw_text_record_add_key_and_string_value(dns_data, "phsh", data->phsh);
-
-	/* TODO: ext, nick, node */
+	while (records) {
+		PurpleKeyValuePair *kvp = records->data;
+		sw_text_record_add_key_and_string_value(dns_data, kvp->key, kvp->value);
+		records = records->next;
+	}
 
 	/* Publish the service */
 	switch (type) {
@@ -276,17 +244,18 @@
 
 	g_return_val_if_fail(idata != NULL, FALSE);
 
-	return (sw_discovery_browse(idata->session, 0, ICHAT_SERVICE, NULL, _browser_reply,
-				    data->account, &session_id) == SW_OKAY);
+	if (sw_discovery_browse(idata->session, 0, ICHAT_SERVICE, NULL, _browser_reply,
+				    data->account, &session_id) == SW_OKAY) {
+		idata->session_handler = purple_input_add(sw_discovery_socket(idata->session),
+				PURPLE_INPUT_READ, _mdns_handle_event, idata->session);
+		return TRUE;
+	}
+
+	return FALSE;
 }
 
-guint _mdns_register_to_mainloop(BonjourDnsSd *data) {
-	HowlSessionImplData *idata = data->mdns_impl_data;
-
-	g_return_val_if_fail(idata != NULL, 0);
-
-	return purple_input_add(sw_discovery_socket(idata->session),
-				PURPLE_INPUT_READ, _mdns_handle_event, idata->session);
+gboolean _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len) {
+	return FALSE;
 }
 
 void _mdns_stop(BonjourDnsSd *data) {
@@ -297,6 +266,8 @@
 
 	sw_discovery_cancel(idata->session, idata->session_id);
 
+	purple_input_remove(idata->session_handler);
+
 	/* TODO: should this really be g_free()'d ??? */
 	g_free(idata->session);
 
@@ -311,5 +282,7 @@
 void _mdns_delete_buddy(BonjourBuddy *buddy) {
 }
 
-void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) {
+void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy) {
 }
+
+
--- a/libpurple/protocols/bonjour/mdns_interface.h	Sat Aug 11 21:08:06 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_interface.h	Sat Aug 11 21:08:27 2007 +0000
@@ -22,19 +22,18 @@
 
 gboolean _mdns_init_session(BonjourDnsSd *data);
 
-gboolean _mdns_publish(BonjourDnsSd *data, PublishType type);
+gboolean _mdns_publish(BonjourDnsSd *data, PublishType type, GSList *records);
 
 gboolean _mdns_browse(BonjourDnsSd *data);
 
-guint _mdns_register_to_mainloop(BonjourDnsSd *data);
+void _mdns_stop(BonjourDnsSd *data);
 
-void _mdns_stop(BonjourDnsSd *data);
+gboolean _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len);
 
 void _mdns_init_buddy(BonjourBuddy *buddy);
 
 void _mdns_delete_buddy(BonjourBuddy *buddy);
 
-/* This doesn't quite belong here, but there really isn't any shared functionality */
-void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy);
+void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy);
 
 #endif
--- a/libpurple/protocols/bonjour/mdns_win32.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_win32.c	Sat Aug 11 21:08:27 2007 +0000
@@ -21,12 +21,14 @@
 #include "mdns_interface.h"
 #include "dns_sd_proxy.h"
 #include "dnsquery.h"
+#include "mdns_common.h"
 
 
 /* data structure for the resolve callback */
 typedef struct _ResolveCallbackArgs {
 	DNSServiceRef resolver;
 	guint resolver_handler;
+	gchar *full_service_name;
 
 	PurpleDnsQueryData *query;
 
@@ -35,10 +37,12 @@
 
 /* data used by win32 bonjour implementation */
 typedef struct _win32_session_impl_data {
-	DNSServiceRef advertisement;
-	DNSServiceRef browser;
+	DNSServiceRef presence_svc;
+	DNSServiceRef browser_svc;
+	DNSRecordRef buddy_icon_rec;
 
-	guint advertisement_handler; /* hack... windows bonjour is broken, so we have to have this */
+	guint presence_handler;
+	guint browser_handler;
 } Win32SessionImplData;
 
 typedef struct _win32_buddy_impl_data {
@@ -60,6 +64,7 @@
 	uint8_t txt_len;
 	int i;
 
+	clear_bonjour_buddy_values(buddy);
 	for (i = 0; buddy_TXT_records[i] != NULL; i++) {
 		txt_entry = TXTRecordGetValuePtr(record_len, record, buddy_TXT_records[i], &txt_len);
 		if (txt_entry != NULL)
@@ -68,7 +73,7 @@
 }
 
 static void DNSSD_API
-_mdns_text_record_query_callback(DNSServiceRef DNSServiceRef, DNSServiceFlags flags,
+_mdns_record_query_callback(DNSServiceRef DNSServiceRef, DNSServiceFlags flags,
 	uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *fullname,
 	uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void *rdata,
 	uint32_t ttl, void *context)
@@ -90,8 +95,7 @@
 
 			g_return_if_fail(idata != NULL);
 
-			purple_buddy_icons_set_for_user(buddy->account, buddy->name,
-							g_memdup(rdata, rdlen), rdlen, buddy->phsh);
+			bonjour_buddy_got_buddy_icon(buddy, rdata, rdlen);
 
 			/* We've got what we need; stop listening */
 			purple_input_remove(idata->null_query_handler);
@@ -120,14 +124,16 @@
 
 		/* finally, set up the continuous txt record watcher, and add the buddy to purple */
 
-		if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->txt_query, 0, 0, buddy->full_service_name,
-				kDNSServiceType_TXT, kDNSServiceClass_IN, _mdns_text_record_query_callback, buddy)) {
-			int fd = DNSServiceRefSockFD(idata->txt_query);
-			idata->txt_query_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, idata->txt_query);
+		if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->txt_query, kDNSServiceFlagsLongLivedQuery,
+				kDNSServiceInterfaceIndexAny, args->full_service_name, kDNSServiceType_TXT,
+				kDNSServiceClass_IN, _mdns_record_query_callback, buddy)) {
+
+			purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", buddy->name, buddy->ip, buddy->port_p2pj);
+
+			idata->txt_query_handler = purple_input_add(DNSServiceRefSockFD(idata->txt_query),
+				PURPLE_INPUT_READ, _mdns_handle_event, idata->txt_query);
 
 			bonjour_buddy_add_to_purple(buddy);
-
-			purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", buddy->name, buddy->ip, buddy->port_p2pj);
 		} else
 			bonjour_buddy_delete(buddy);
 
@@ -138,6 +144,7 @@
 
 	/* free the remaining args memory */
 	purple_dnsquery_destroy(args->query);
+	g_free(args->full_service_name);
 	g_free(args);
 }
 
@@ -165,13 +172,14 @@
 		_mdns_parse_text_record(args->buddy, txtRecord, txtLen);
 
 		/* set more arguments, and start the host resolver */
-		args->buddy->full_service_name = g_strdup(fullname);
+		args->full_service_name = g_strdup(fullname);
 
 		if (!(args->query =
 			purple_dnsquery_a(hosttarget, port, _mdns_resolve_host_callback, args)))
 		{
 			purple_debug_error("bonjour", "service resolver - host resolution failed.\n");
 			bonjour_buddy_delete(args->buddy);
+			g_free(args->full_service_name);
 			g_free(args);
 		}
 	}
@@ -180,11 +188,11 @@
 
 static void DNSSD_API
 _mdns_service_register_callback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode,
-    const char *name, const char *regtype, const char *domain, void *context)
-{
-	/* we don't actually care about anything said in this callback - this is only here because Bonjour for windows is broken */
+				const char *name, const char *regtype, const char *domain, void *context) {
+
+	/* TODO: deal with collision */
 	if (kDNSServiceErr_NoError != errorCode)
-		purple_debug_error("bonjour", "service advertisement - callback error.\n");
+		purple_debug_error("bonjour", "service advertisement - callback error (%d).\n", errorCode);
 	else
 		purple_debug_info("bonjour", "service advertisement - callback.\n");
 }
@@ -235,61 +243,23 @@
 	return TRUE;
 }
 
-gboolean _mdns_publish(BonjourDnsSd *data, PublishType type) {
+gboolean _mdns_publish(BonjourDnsSd *data, PublishType type, GSList *records) {
 	TXTRecordRef dns_data;
-	char portstring[6];
 	gboolean ret = TRUE;
-	const char *jid, *aim, *email;
-	DNSServiceErrorType set_ret;
+	DNSServiceErrorType set_ret = kDNSServiceErr_NoError;
 	Win32SessionImplData *idata = data->mdns_impl_data;
 
 	g_return_val_if_fail(idata != NULL, FALSE);
 
 	TXTRecordCreate(&dns_data, 256, NULL);
 
-	/* Convert the port to a string */
-	snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj);
-
-	jid = purple_account_get_string(data->account, "jid", NULL);
-	aim = purple_account_get_string(data->account, "AIM", NULL);
-	email = purple_account_get_string(data->account, "email", NULL);
-
-	/* We should try to follow XEP-0174, but some clients have "issues", so we humor them.
-	 * See http://telepathy.freedesktop.org/wiki/SalutInteroperability
-	 */
-
-	/* Needed by iChat */
-	set_ret = TXTRecordSetValue(&dns_data, "txtvers", 1, "1");
-	/* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
-	if (set_ret == kDNSServiceErr_NoError)
-		set_ret = TXTRecordSetValue(&dns_data, "1st", strlen(data->first), data->first);
-	/* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */
-	if (set_ret == kDNSServiceErr_NoError)
-		set_ret = TXTRecordSetValue(&dns_data, "last", strlen(data->last), data->last);
-	/* Needed by Adium */
-	if (set_ret == kDNSServiceErr_NoError)
-		set_ret = TXTRecordSetValue(&dns_data, "port.p2pj", strlen(portstring), portstring);
-	/* Needed by iChat, Gaim/Pidgin <= 2.0.1 */
-	if (set_ret == kDNSServiceErr_NoError)
-		set_ret = TXTRecordSetValue(&dns_data, "status", strlen(data->status), data->status);
-	if (set_ret == kDNSServiceErr_NoError)
-		set_ret = TXTRecordSetValue(&dns_data, "ver", strlen(VERSION), VERSION);
-	/* Currently always set to "!" since we don't support AV and wont ever be in a conference */
-	if (set_ret == kDNSServiceErr_NoError)
-		set_ret = TXTRecordSetValue(&dns_data, "vc", strlen(data->vc), data->vc);
-	if (set_ret == kDNSServiceErr_NoError && email != NULL && *email != '\0')
-		set_ret = TXTRecordSetValue(&dns_data, "email", strlen(email), email);
-	if (set_ret == kDNSServiceErr_NoError && jid != NULL && *jid != '\0')
-		set_ret = TXTRecordSetValue(&dns_data, "jid", strlen(jid), jid);
-	/* Nonstandard, but used by iChat */
-	if (set_ret == kDNSServiceErr_NoError && aim != NULL && *aim != '\0')
-		set_ret = TXTRecordSetValue(&dns_data, "AIM", strlen(aim), aim);
-	if (set_ret == kDNSServiceErr_NoError && data->msg != NULL && *data->msg != '\0')
-		set_ret = TXTRecordSetValue(&dns_data, "msg", strlen(data->msg), data->msg);
-	if (set_ret == kDNSServiceErr_NoError && data->phsh != NULL && *data->phsh != '\0')
-		set_ret = TXTRecordSetValue(&dns_data, "phsh", strlen(data->phsh), data->phsh);
-
-	/* TODO: ext, nick, node */
+	while (records) {
+		PurpleKeyValuePair *kvp = records->data;
+		set_ret = TXTRecordSetValue(&dns_data, kvp->key, strlen(kvp->value), kvp->value);
+		if (set_ret != kDNSServiceErr_NoError)
+			break;
+		records = records->next;
+	}
 
 	if (set_ret != kDNSServiceErr_NoError) {
 		purple_debug_error("bonjour", "Unable to allocate memory for text record.\n");
@@ -301,14 +271,15 @@
 
 		switch (type) {
 			case PUBLISH_START:
-				purple_debug_info("bonjour", "Registering service on port %d\n", data->port_p2pj);
-				err = DNSServiceRegister(&idata->advertisement, 0, 0, purple_account_get_username(data->account), ICHAT_SERVICE,
+				purple_debug_info("bonjour", "Registering presence on port %d\n", data->port_p2pj);
+				err = DNSServiceRegister(&idata->presence_svc, 0, 0, purple_account_get_username(data->account), ICHAT_SERVICE,
 					NULL, NULL, htons(data->port_p2pj), TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data),
 					_mdns_service_register_callback, NULL);
 				break;
 
 			case PUBLISH_UPDATE:
-				err = DNSServiceUpdateRecord(idata->advertisement, NULL, 0, TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data), 0);
+				purple_debug_info("bonjour", "Updating presence.\n");
+				err = DNSServiceUpdateRecord(idata->presence_svc, NULL, 0, TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data), 0);
 				break;
 		}
 
@@ -316,9 +287,12 @@
 			purple_debug_error("bonjour", "Failed to publish presence service.\n");
 			ret = FALSE;
 		} else if (type == PUBLISH_START) {
-			/* hack: Bonjour on windows is broken. We don't care about the callback but we have to listen anyway */
-			gint fd = DNSServiceRefSockFD(idata->advertisement);
-			idata->advertisement_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, idata->advertisement);
+			/* We need to do this because according to the Apple docs:
+			 * "the client is responsible for ensuring that DNSServiceProcessResult() is called
+			 * whenever there is a reply from the daemon - the daemon may terminate its connection
+			 * with a client that does not process the daemon's responses */
+			idata->presence_handler = purple_input_add(DNSServiceRefSockFD(idata->presence_svc),
+				PURPLE_INPUT_READ, _mdns_handle_event, idata->presence_svc);
 		}
 	}
 
@@ -332,37 +306,64 @@
 
 	g_return_val_if_fail(idata != NULL, FALSE);
 
-	return (DNSServiceBrowse(&idata->browser, 0, 0, ICHAT_SERVICE, NULL,
+	if (DNSServiceBrowse(&idata->browser_svc, 0, 0, ICHAT_SERVICE, NULL,
 				 _mdns_service_browse_callback, data->account)
-			== kDNSServiceErr_NoError);
-}
+			== kDNSServiceErr_NoError) {
+		idata->browser_handler = purple_input_add(DNSServiceRefSockFD(idata->browser_svc),
+			PURPLE_INPUT_READ, _mdns_handle_event, idata->browser_svc);
+		return TRUE;
+	}
 
-guint _mdns_register_to_mainloop(BonjourDnsSd *data) {
-	Win32SessionImplData *idata = data->mdns_impl_data;
-
-	g_return_val_if_fail(idata != NULL, 0);
-
-	return purple_input_add(DNSServiceRefSockFD(idata->browser),
-				PURPLE_INPUT_READ, _mdns_handle_event, idata->browser);
+	return FALSE;
 }
 
 void _mdns_stop(BonjourDnsSd *data) {
 	Win32SessionImplData *idata = data->mdns_impl_data;
 
-	if (idata == NULL || idata->advertisement == NULL || idata->browser == NULL)
+	if (idata == NULL)
 		return;
 
-	/* hack: for win32, we need to stop listening to the advertisement pipe too */
-	purple_input_remove(idata->advertisement_handler);
+	if (idata->presence_svc != NULL) {
+		purple_input_remove(idata->presence_handler);
+		DNSServiceRefDeallocate(idata->presence_svc);
+	}
 
-	DNSServiceRefDeallocate(idata->advertisement);
-	DNSServiceRefDeallocate(idata->browser);
+	if (idata->browser_svc != NULL) {
+		purple_input_remove(idata->browser_handler);
+		DNSServiceRefDeallocate(idata->browser_svc);
+	}
 
 	g_free(idata);
 
 	data->mdns_impl_data = NULL;
 }
 
+gboolean _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len) {
+	Win32SessionImplData *idata = data->mdns_impl_data;
+	DNSServiceErrorType err = kDNSServiceErr_NoError;
+
+	g_return_val_if_fail(idata != NULL, FALSE);
+
+	if (avatar_data != NULL && idata->buddy_icon_rec == NULL) {
+		purple_debug_info("bonjour", "Setting new buddy icon.\n");
+		err = DNSServiceAddRecord(idata->presence_svc, &idata->buddy_icon_rec,
+			0, kDNSServiceType_NULL, avatar_len, avatar_data, 0);
+	} else if (avatar_data != NULL) {
+		purple_debug_info("bonjour", "Updating existing buddy icon.\n");
+		err = DNSServiceUpdateRecord(idata->presence_svc, idata->buddy_icon_rec,
+			0, avatar_len, avatar_data, 0);
+	} else if (idata->buddy_icon_rec != NULL) {
+		purple_debug_info("bonjour", "Removing existing buddy icon.\n");
+		DNSServiceRemoveRecord(idata->presence_svc, idata->buddy_icon_rec, 0);
+		idata->buddy_icon_rec = NULL;
+	}
+
+	if (err != kDNSServiceErr_NoError)
+		purple_debug_error("bonjour", "Error (%d) setting buddy icon record.\n", err);
+
+	return (err == kDNSServiceErr_NoError);
+}
+
 void _mdns_init_buddy(BonjourBuddy *buddy) {
 	buddy->mdns_impl_data = g_new0(Win32BuddyImplData, 1);
 }
@@ -387,8 +388,9 @@
 	buddy->mdns_impl_data = NULL;
 }
 
-void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) {
+void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy) {
 	Win32BuddyImplData *idata = buddy->mdns_impl_data;
+	char svc_name[kDNSServiceMaxDomainName];
 
 	g_return_if_fail(idata != NULL);
 
@@ -400,10 +402,11 @@
 		idata->null_query = NULL;
 	}
 
-	if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->null_query, 0, 0, buddy->full_service_name,
-			kDNSServiceType_NULL, kDNSServiceClass_IN, _mdns_text_record_query_callback, buddy)) {
-		int fd = DNSServiceRefSockFD(idata->null_query);
-		idata->null_query_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, idata->null_query);
+	DNSServiceConstructFullName(svc_name, buddy->name, ICHAT_SERVICE, "local");
+	if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->null_query, 0, kDNSServiceInterfaceIndexAny, svc_name,
+			kDNSServiceType_NULL, kDNSServiceClass_IN, _mdns_record_query_callback, buddy)) {
+		idata->null_query_handler = purple_input_add(DNSServiceRefSockFD(idata->null_query),
+			PURPLE_INPUT_READ, _mdns_handle_event, idata->null_query);
 	}
 
 }
--- a/libpurple/protocols/jabber/auth.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/libpurple/protocols/jabber/auth.c	Sat Aug 11 21:08:27 2007 +0000
@@ -296,7 +296,7 @@
 					purple_request_yes_no(js->gc, _("Plaintext Authentication"),
 							_("Plaintext Authentication"),
 							msg,
-							2, js->gc->account, NULL, NULL, NULL,
+							2, js->gc->account, NULL, NULL, js->gc->account,
 							allow_cyrus_plaintext_auth,
 							disallow_plaintext_auth);
 					g_free(msg);
--- a/libpurple/protocols/jabber/jabber.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Sat Aug 11 21:08:27 2007 +0000
@@ -1718,7 +1718,7 @@
 static PurpleCmdRet jabber_cmd_chat_role(PurpleConversation *conv,
 		const char *cmd, char **args, char **error, void *data)
 {
-	JabberChat *chat;
+	JabberChat *chat = jabber_chat_find_by_conv(conv);
 
 	if (!chat || !args || !args[0] || !args[1])
 		return PURPLE_CMD_RET_FAILED;
@@ -1731,8 +1731,6 @@
 		return PURPLE_CMD_RET_FAILED;
 	}
 
-	chat = jabber_chat_find_by_conv(conv);
-
 	if (!jabber_chat_role_user(chat, args[0], args[1])) {
 		*error = g_strdup_printf(_("Unable to set role \"%s\" for user: %s"),
 		                         args[1], args[0]);
--- a/pidgin/Makefile.am	Sat Aug 11 21:08:06 2007 +0000
+++ b/pidgin/Makefile.am	Sat Aug 11 21:08:27 2007 +0000
@@ -65,7 +65,7 @@
 pkgconfigdir = $(libdir)/pkgconfig
 pkgconfig_DATA = pidgin.pc
 
-SUBDIRS = pixmaps plugins sounds
+SUBDIRS = pixmaps plugins
 
 bin_PROGRAMS = pidgin
 
--- a/pidgin/gtkaccount.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/pidgin/gtkaccount.c	Sat Aug 11 21:08:27 2007 +0000
@@ -2546,9 +2546,15 @@
 }
 
 static void *
-pidgin_accounts_request_authorization(PurpleAccount *account, const char *remote_user,
-					const char *id, const char *alias, const char *message, gboolean on_list,
-					GCallback auth_cb, GCallback deny_cb, void *user_data)
+pidgin_accounts_request_authorization(PurpleAccount *account,
+                                      const char *remote_user,
+                                      const char *id,
+                                      const char *alias,
+                                      const char *message,
+                                      gboolean on_list,
+                                      PurpleAccountRequestAuthorizationCb auth_cb,
+                                      PurpleAccountRequestAuthorizationCb deny_cb,
+                                      void *user_data)
 {
 	char *buffer;
 	PurpleConnection *gc;
@@ -2574,8 +2580,8 @@
 
 	if (!on_list) {
 		struct auth_and_add *aa = g_new0(struct auth_and_add, 1);
-		aa->auth_cb = (PurpleAccountRequestAuthorizationCb)auth_cb;
-		aa->deny_cb = (PurpleAccountRequestAuthorizationCb)deny_cb;
+		aa->auth_cb = auth_cb;
+		aa->deny_cb = deny_cb;
 		aa->data = user_data;
 		aa->username = g_strdup(remote_user);
 		aa->alias = g_strdup(alias);
--- a/pidgin/gtkconv.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/pidgin/gtkconv.c	Sat Aug 11 21:08:27 2007 +0000
@@ -8337,11 +8337,6 @@
 	if (gdk_window_get_state(w->window) & GDK_WINDOW_STATE_MAXIMIZED)
 		return FALSE;
 	
-	/* don't save if nothing changed */
-	if (x == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/x") &&
-			y == purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/y"))
-		return FALSE; /* carry on normally */
-		
 	/* don't save off-screen positioning */
 	if (x + event->width < 0 ||
 	    y + event->height < 0 ||
@@ -8389,10 +8384,10 @@
 static void
 pidgin_conv_restore_position(PidginWindow *win) {
 	pidgin_conv_set_position_size(win,
-			purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/x"),
-			purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/y"),
-			purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/width"),
-			purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/height"));
+		purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/x"),
+		purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/y"),
+		purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/width"),
+		purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/height"));
 }
 
 PidginWindow *
@@ -8545,6 +8540,20 @@
 		                              gtkconv->tab_cont));
 }
 
+static gboolean
+close_button_left_cb(GtkWidget *widget, GdkEventCrossing *event, GtkLabel *label)
+{
+	gtk_label_set_markup(label, "×");
+	return FALSE;
+}
+
+static gboolean
+close_button_entered_cb(GtkWidget *widget, GdkEventCrossing *event, GtkLabel *label)
+{
+	gtk_label_set_markup(label, "<b>×</b>");
+	return FALSE;
+}
+
 void
 pidgin_conv_window_add_gtkconv(PidginWindow *win, PidginConversation *gtkconv)
 {
@@ -8567,15 +8576,17 @@
 	/* Close button. */
 	gtkconv->close = gtk_event_box_new();
 	gtk_event_box_set_visible_window(GTK_EVENT_BOX(gtkconv->close), FALSE);
-	close_image = gtk_label_new(NULL);
-	gtk_label_set_markup(GTK_LABEL(close_image),"<b>×</b>");
+	gtk_widget_set_events(gtkconv->close, GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
+	close_image = gtk_label_new("×");
+	g_signal_connect(G_OBJECT(gtkconv->close), "enter-notify-event", G_CALLBACK(close_button_entered_cb), close_image);
+	g_signal_connect(G_OBJECT(gtkconv->close), "leave-notify-event", G_CALLBACK(close_button_left_cb), close_image);
 	gtk_widget_show(close_image);
 	gtk_container_add(GTK_CONTAINER(gtkconv->close), close_image);
 	gtk_tooltips_set_tip(gtkconv->tooltips, gtkconv->close,
 	                     _("Close conversation"), NULL);
 
 	g_signal_connect(G_OBJECT(gtkconv->close), "button-press-event",
-	                 G_CALLBACK(close_conv_cb), gtkconv);
+			 G_CALLBACK(close_conv_cb), gtkconv);
 
 #if !GTK_CHECK_VERSION(2,6,0)
 	/*
@@ -8596,7 +8607,7 @@
 	gtkconv->tab_label = gtk_label_new(tmp_lab = purple_conversation_get_title(conv));
 
 	gtkconv->menu_tabby = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
-	gtkconv->menu_label = gtk_label_new(purple_conversation_get_title(gtkconv->active_conv));
+	gtkconv->menu_label = gtk_label_new(tmp_lab);
 	gtk_box_pack_start(GTK_BOX(gtkconv->menu_tabby), gtkconv->menu_icon, FALSE, FALSE, 0);
 
 	gtk_widget_show_all(gtkconv->menu_icon);
@@ -8606,7 +8617,6 @@
 	gtk_misc_set_alignment(GTK_MISC(gtkconv->menu_label), 0, 0);
 
 	gtk_widget_show(gtkconv->menu_tabby);
-	gtk_widget_set_size_request(gtkconv->menu_tabby, 0, -1);
 
 	if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM)
 		pidgin_conv_update_buddy_icon(conv);
@@ -8621,9 +8631,6 @@
 	if (pidgin_conv_window_get_gtkconv_count(win) == 1) {
 		/* Er, bug in notebooks? Switch to the page manually. */
 		gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), 0);
-
-		gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), 
-					   purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/tabs"));
 	} else {
 		gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), TRUE);
 	}
@@ -8722,6 +8729,12 @@
 					   !tabs_side && !angle && pidgin_conv_window_get_gtkconv_count(win) > 1, 
 					   TRUE, GTK_PACK_START);
 
+	if (pidgin_conv_window_get_gtkconv_count(win) == 1) 
+		gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook),
+                                           !purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/im/show_buddy_icons") ||  
+                                           purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/tab_side") == GTK_POS_LEFT ||
+                                           purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/tab_side") == GTK_POS_RIGHT);
+
 	/* show the widgets */
 /*	gtk_widget_show(gtkconv->icon); */
 	gtk_widget_show(gtkconv->tab_label);
@@ -8745,12 +8758,6 @@
 
 	gtk_notebook_remove_page(GTK_NOTEBOOK(win->notebook), index);
 
-	/* go back to tabless */
-	if (pidgin_conv_window_get_gtkconv_count(win) <= 2) {
-		gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), 
-  					   purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/tabs"));
-	}
-
 	win->gtkconvs = g_list_remove(win->gtkconvs, gtkconv);
 
 	if (win->gtkconvs && win->gtkconvs->next == NULL)
@@ -9029,6 +9036,7 @@
 	PidginWindow *win;
 
 	win = pidgin_conv_window_new();
+
 	g_signal_connect(G_OBJECT(win->window), "configure_event", 
 			G_CALLBACK(gtk_conv_configure_cb), NULL);
 
--- a/pidgin/gtkdocklet.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/pidgin/gtkdocklet.c	Sat Aug 11 21:08:27 2007 +0000
@@ -121,7 +121,7 @@
 	/* determine if any ims have unseen messages */
 	convs = get_pending_list(DOCKLET_TOOLTIP_LINE_LIMIT);
 
-	if (!strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/docklet/show"), "always")) {
+	if (!strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/docklet/show"), "pending")) {
 		if (convs && ui_ops->create && !visible) {
 			g_list_free(convs);
 			ui_ops->create();
@@ -636,7 +636,7 @@
 
 	purple_prefs_add_none(PIDGIN_PREFS_ROOT "/docklet");
 	purple_prefs_add_bool(PIDGIN_PREFS_ROOT "/docklet/blink", FALSE);
-	purple_prefs_add_string(PIDGIN_PREFS_ROOT "/docklet/show", "pending");
+	purple_prefs_add_string(PIDGIN_PREFS_ROOT "/docklet/show", "always");
 	purple_prefs_connect_callback(docklet_handle, PIDGIN_PREFS_ROOT "/docklet/show",
 				    docklet_show_pref_changed_cb, NULL);
 
--- a/pidgin/gtkimhtmltoolbar.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Sat Aug 11 21:08:27 2007 +0000
@@ -939,7 +939,7 @@
 		*y -= widget->allocation.height;
 }
 
-static void pidgin_menu_clicked(GtkWidget *button, GdkEventButton *event, GtkMenu *menu)
+static void pidgin_menu_clicked(GtkWidget *button, GtkMenu *menu)
 {
 	gtk_widget_show_all(GTK_WIDGET(menu));
 	gtk_menu_popup(menu, NULL, NULL, menu_position_func, button, 0, gtk_get_current_event_time());
@@ -1188,7 +1188,8 @@
 		gtk_container_foreach(GTK_CONTAINER(menuitem), (GtkCallback)enable_markup, NULL);
 	}
 
-	g_signal_connect(G_OBJECT(font_button), "button-press-event", G_CALLBACK(pidgin_menu_clicked), font_menu);
+	g_signal_connect_swapped(G_OBJECT(font_button), "button-press-event", G_CALLBACK(gtk_widget_activate), font_button);
+	g_signal_connect(G_OBJECT(font_button), "activate", G_CALLBACK(pidgin_menu_clicked), font_menu);
 	g_signal_connect(G_OBJECT(font_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), font_button);
 
 	/* Sep */
@@ -1229,7 +1230,8 @@
 	g_signal_connect(G_OBJECT(toolbar->link), "notify::sensitive",
 			G_CALLBACK(button_sensitiveness_changed), menuitem);
 
-	g_signal_connect(G_OBJECT(insert_button), "button-press-event", G_CALLBACK(pidgin_menu_clicked), insert_menu);
+	g_signal_connect_swapped(G_OBJECT(insert_button), "button-press-event", G_CALLBACK(gtk_widget_activate), insert_button);
+	g_signal_connect(G_OBJECT(insert_button), "activate", G_CALLBACK(pidgin_menu_clicked), insert_menu);
 	g_signal_connect(G_OBJECT(insert_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), insert_button);
 	toolbar->sml = NULL;
 }
--- a/pidgin/gtknotify.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/pidgin/gtknotify.c	Sat Aug 11 21:08:27 2007 +0000
@@ -274,6 +274,7 @@
 
 	gtk_label_set_markup(GTK_LABEL(label), label_text);
 	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+	gtk_label_set_selectable(GTK_LABEL(label), TRUE);
 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
 	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
 
@@ -609,6 +610,7 @@
 
 	gtk_label_set_markup(GTK_LABEL(label), label_text);
 	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+	gtk_label_set_selectable(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_widget_show(label);
@@ -626,6 +628,7 @@
 	button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
 	gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0);
 	gtk_widget_show(button);
+	gtk_widget_grab_focus(button);
 
 	g_signal_connect_swapped(G_OBJECT(button), "clicked",
 							 G_CALLBACK(gtk_widget_destroy), window);
--- a/pidgin/gtksound.c	Sat Aug 11 21:08:06 2007 +0000
+++ b/pidgin/gtksound.c	Sat Aug 11 21:08:27 2007 +0000
@@ -543,7 +543,8 @@
 		char *filename = g_strdup(purple_prefs_get_path(file_pref));
 		if(!filename || !strlen(filename)) {
 			g_free(filename);
-			filename = g_build_filename(DATADIR, "sounds", "pidgin", sounds[event].def, NULL);
+			/* XXX Consider creating a constant for "sounds/purple" to be shared with Finch */
+			filename = g_build_filename(DATADIR, "sounds", "purple", sounds[event].def, NULL);
 		}
 
 		purple_sound_play_file(filename, NULL);
Binary file pidgin/pixmaps/emotes/default/22/act-up.png has changed
Binary file pidgin/pixmaps/emotes/default/22/alien.png has changed
Binary file pidgin/pixmaps/emotes/default/22/angel.png has changed
Binary file pidgin/pixmaps/emotes/default/22/angry.png has changed
Binary file pidgin/pixmaps/emotes/default/22/arrogant.png has changed
Binary file pidgin/pixmaps/emotes/default/22/at-wits-end.png has changed
Binary file pidgin/pixmaps/emotes/default/22/bashful.png has changed
Binary file pidgin/pixmaps/emotes/default/22/beat-up.png has changed
Binary file pidgin/pixmaps/emotes/default/22/beauty.png has changed
Binary file pidgin/pixmaps/emotes/default/22/blowkiss.png has changed
Binary file pidgin/pixmaps/emotes/default/22/bye.png has changed
Binary file pidgin/pixmaps/emotes/default/22/call-me.png has changed
Binary file pidgin/pixmaps/emotes/default/22/clap.png has changed
Binary file pidgin/pixmaps/emotes/default/22/confused.png has changed
Binary file pidgin/pixmaps/emotes/default/22/crying.png has changed
Binary file pidgin/pixmaps/emotes/default/22/curl-lip.png has changed
Binary file pidgin/pixmaps/emotes/default/22/curse.png has changed
Binary file pidgin/pixmaps/emotes/default/22/cute.png has changed
Binary file pidgin/pixmaps/emotes/default/22/dance.png has changed
Binary file pidgin/pixmaps/emotes/default/22/dazed.png has changed
Binary file pidgin/pixmaps/emotes/default/22/desire.png has changed
Binary file pidgin/pixmaps/emotes/default/22/devil.png has changed
Binary file pidgin/pixmaps/emotes/default/22/disapointed.png has changed
Binary file pidgin/pixmaps/emotes/default/22/disdain.png has changed
Binary file pidgin/pixmaps/emotes/default/22/doh.png has changed
Binary file pidgin/pixmaps/emotes/default/22/dont-know.png has changed
Binary file pidgin/pixmaps/emotes/default/22/drool.png has changed
Binary file pidgin/pixmaps/emotes/default/22/eat.png has changed
Binary file pidgin/pixmaps/emotes/default/22/embarrassed.png has changed
Binary file pidgin/pixmaps/emotes/default/22/excruciating.png has changed
Binary file pidgin/pixmaps/emotes/default/22/eyeroll.png has changed
Binary file pidgin/pixmaps/emotes/default/22/fingers-crossed.png has changed
Binary file pidgin/pixmaps/emotes/default/22/foot-in-mouth.png has changed
Binary file pidgin/pixmaps/emotes/default/22/freaked-out.png has changed
Binary file pidgin/pixmaps/emotes/default/22/glasses-cool.png has changed
Binary file pidgin/pixmaps/emotes/default/22/glasses-nerdy.png has changed
Binary file pidgin/pixmaps/emotes/default/22/go-away.png has changed
Binary file pidgin/pixmaps/emotes/default/22/handshake.png has changed
Binary file pidgin/pixmaps/emotes/default/22/highfive.png has changed
Binary file pidgin/pixmaps/emotes/default/22/hug-left.png has changed
Binary file pidgin/pixmaps/emotes/default/22/hug-right.png has changed
Binary file pidgin/pixmaps/emotes/default/22/hypnotized.png has changed
Binary file pidgin/pixmaps/emotes/default/22/in-love.png has changed
Binary file pidgin/pixmaps/emotes/default/22/jump.png has changed
Binary file pidgin/pixmaps/emotes/default/22/kiss.png has changed
Binary file pidgin/pixmaps/emotes/default/22/lashes.png has changed
Binary file pidgin/pixmaps/emotes/default/22/laugh.png has changed
Binary file pidgin/pixmaps/emotes/default/22/lying.png has changed
Binary file pidgin/pixmaps/emotes/default/22/mean.png has changed
Binary file pidgin/pixmaps/emotes/default/22/meeting.png has changed
Binary file pidgin/pixmaps/emotes/default/22/moneymouth.png has changed
Binary file pidgin/pixmaps/emotes/default/22/nailbiting.png has changed
Binary file pidgin/pixmaps/emotes/default/22/neutral.png has changed
Binary file pidgin/pixmaps/emotes/default/22/on-the-phone.png has changed
Binary file pidgin/pixmaps/emotes/default/22/party.png has changed
Binary file pidgin/pixmaps/emotes/default/22/pissed-off.png has changed
Binary file pidgin/pixmaps/emotes/default/22/pray.png has changed
Binary file pidgin/pixmaps/emotes/default/22/question.png has changed
Binary file pidgin/pixmaps/emotes/default/22/quiet.png has changed
Binary file pidgin/pixmaps/emotes/default/22/rotfl.png has changed
Binary file pidgin/pixmaps/emotes/default/22/sad.png has changed
Binary file pidgin/pixmaps/emotes/default/22/sarcastic.png has changed
Binary file pidgin/pixmaps/emotes/default/22/secret.png has changed
Binary file pidgin/pixmaps/emotes/default/22/shame.png has changed
Binary file pidgin/pixmaps/emotes/default/22/shock.png has changed
Binary file pidgin/pixmaps/emotes/default/22/shout.png has changed
Binary file pidgin/pixmaps/emotes/default/22/shut-mouth.png has changed
Binary file pidgin/pixmaps/emotes/default/22/sick.png has changed
Binary file pidgin/pixmaps/emotes/default/22/silly.png has changed
Binary file pidgin/pixmaps/emotes/default/22/skywalker.png has changed
Binary file pidgin/pixmaps/emotes/default/22/sleepy.png has changed
Binary file pidgin/pixmaps/emotes/default/22/smile-big.png has changed
Binary file pidgin/pixmaps/emotes/default/22/smile.png has changed
Binary file pidgin/pixmaps/emotes/default/22/smirk.png has changed
Binary file pidgin/pixmaps/emotes/default/22/snicker.png has changed
Binary file pidgin/pixmaps/emotes/default/22/soldier.png has changed
Binary file pidgin/pixmaps/emotes/default/22/starving.png has changed
Binary file pidgin/pixmaps/emotes/default/22/struggle.png has changed
Binary file pidgin/pixmaps/emotes/default/22/sweat.png has changed
Binary file pidgin/pixmaps/emotes/default/22/teeth.png has changed
Binary file pidgin/pixmaps/emotes/default/22/terror.png has changed
Binary file pidgin/pixmaps/emotes/default/22/thinking.png has changed
Binary file pidgin/pixmaps/emotes/default/22/time-out.png has changed
Binary file pidgin/pixmaps/emotes/default/22/tongue.png has changed
Binary file pidgin/pixmaps/emotes/default/22/tremble.png has changed
Binary file pidgin/pixmaps/emotes/default/22/vampire.png has changed
Binary file pidgin/pixmaps/emotes/default/22/victory.png has changed
Binary file pidgin/pixmaps/emotes/default/22/waiting.png has changed
Binary file pidgin/pixmaps/emotes/default/22/weep.png has changed
Binary file pidgin/pixmaps/emotes/default/22/wilt.png has changed
Binary file pidgin/pixmaps/emotes/default/22/wink.png has changed
Binary file pidgin/pixmaps/emotes/default/22/yawn.png has changed
--- a/pidgin/sounds/Makefile.am	Sat Aug 11 21:08:06 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-soundsdir =	$(datadir)/sounds/pidgin
-sounds_DATA =	\
-		alert.wav \
-		login.wav \
-		logout.wav \
-		receive.wav \
-		send.wav
-
-EXTRA_DIST = \
-		Makefile.mingw \
-                $(sounds_DATA)
-
--- a/pidgin/sounds/Makefile.mingw	Sat Aug 11 21:08:06 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-#
-# Makefile.mingw
-#
-# Description: Makefile for win32 (mingw) version of Pidgin sounds
-#
-
-PIDGIN_TREE_TOP := ../..
-include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
-
-datadir := $(PIDGIN_INSTALL_DIR)
-include ./Makefile.am
-
-.PHONY: install
-
-install:
-	if test '$(sounds_DATA)'; then \
-	  mkdir -p $(soundsdir); \
-	  cp $(sounds_DATA) $(soundsdir); \
-	fi;
-
Binary file pidgin/sounds/alert.wav has changed
Binary file pidgin/sounds/login.wav has changed
Binary file pidgin/sounds/logout.wav has changed
Binary file pidgin/sounds/receive.wav has changed
Binary file pidgin/sounds/send.wav has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/Makefile.am	Sat Aug 11 21:08:27 2007 +0000
@@ -0,0 +1,2 @@
+
+SUBDIRS = sounds
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/sounds/Makefile.am	Sat Aug 11 21:08:27 2007 +0000
@@ -0,0 +1,12 @@
+soundsdir =	$(datadir)/sounds/purple
+sounds_DATA =	\
+		alert.wav \
+		login.wav \
+		logout.wav \
+		receive.wav \
+		send.wav
+
+EXTRA_DIST = \
+		Makefile.mingw \
+                $(sounds_DATA)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/share/sounds/Makefile.mingw	Sat Aug 11 21:08:27 2007 +0000
@@ -0,0 +1,20 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for win32 (mingw) version of Pidgin sounds
+#
+
+PIDGIN_TREE_TOP := ../..
+include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
+
+datadir := $(PIDGIN_INSTALL_DIR)
+include ./Makefile.am
+
+.PHONY: install
+
+install:
+	if test '$(sounds_DATA)'; then \
+	  mkdir -p $(soundsdir); \
+	  cp $(sounds_DATA) $(soundsdir); \
+	fi;
+
Binary file share/sounds/alert.wav has changed
Binary file share/sounds/login.wav has changed
Binary file share/sounds/logout.wav has changed
Binary file share/sounds/receive.wav has changed
Binary file share/sounds/send.wav has changed