changeset 19098:ac904659104f

propagate from branch 'im.pidgin.pidgin.2.1.0' (head 121873f517c4c5e2d65c3a1cdd152694834d7db0) to branch 'im.pidgin.soc.2007.finchfeat' (head c079bc8ba4b62373e63c0923c45007d2eac37aa2)
author Eric Polino <aluink@pidgin.im>
date Tue, 12 Jun 2007 21:21:37 +0000
parents 9018b785ef73 (diff) d7cd0afd3c36 (current diff)
children a1ac8b05ecdb
files COPYRIGHT finch/libgnt/gnt.h finch/libgnt/gntbindable.h finch/libgnt/gntbox.c finch/libgnt/gntkeys.c finch/libgnt/gntkeys.h finch/libgnt/gntmain.c finch/libgnt/gntmenu.c finch/libgnt/gntstyle.c finch/libgnt/gntstyle.h finch/libgnt/gnttree.c finch/libgnt/gntutils.c finch/libgnt/gntutils.h finch/libgnt/gntwidget.c finch/libgnt/gntwm.c libpurple/protocols/bonjour/dns_sd.c libpurple/protocols/bonjour/dns_sd.h
diffstat 204 files changed, 17694 insertions(+), 4013 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Tue Jun 12 13:54:04 2007 +0000
+++ b/COPYRIGHT	Tue Jun 12 21:21:37 2007 +0000
@@ -52,6 +52,7 @@
 Jeremy Brooks
 Jonathan Brossard
 Philip Brown
+Norbert Buchmuller
 Sean Burke
 Thomas Butter
 Trevor Caira
@@ -89,6 +90,7 @@
 Jeramey Crawford
 Michael Culbertson
 Steven Danna
+Chris Davies
 Martijn Dekker
 Vinicius Depizzol
 Philip Derrin
@@ -145,6 +147,7 @@
 Charlie Gordon
 Ryan C. Gordon
 Miah Gregory
+David Grohmann
 Christian Hammond
 Erick Hamness
 Fred Hampton
@@ -225,6 +228,7 @@
 Lalo Martins
 John Matthews
 Simo Mattila
+Michal Matyska
 Ryan McCabe
 Peter McCurdy
 Kurt McKee
@@ -265,6 +269,7 @@
 Ted Percival
 Eduardo Pérez
 Matt Perry
+Nathan Peterson
 Celso Pinto
 Joao Luís Marques Pinto
 Aleksander Piotrowski
--- a/ChangeLog	Tue Jun 12 13:54:04 2007 +0000
+++ b/ChangeLog	Tue Jun 12 21:21:37 2007 +0000
@@ -1,5 +1,23 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version 2.1.0 (??/??/????):
+	libpurple:
+	* Core changes to allow UIs to use second-granularity for scheduling.
+	  Pidgin and Finch, which use the glib event loop, were changed to use
+	  g_timeout_add_seconds() on glib >= 2.14 when possible.  This allows
+	  glib to better group our longer timers to increase power efficiency.
+	  (Arjan van de Ven with Intel Corporation)
+	* No longer linkifies screennames containing @ signs in join/part
+	  notifications in chats
+	* With the HTML logger, images in conversations are now saved.
+	  NOTE: Saved images are not yet displayed when loading logs.
+
+	Pidgin:
+	* Ensure only one copy of Pidgin is running with a given configuration
+	  directory.  The net effect of this is that trying to start Pidgin a
+	  second time will raise the buddy list.  (Gabriel Schulhof)
+	* Undo capability in the conversation window
+
 version 2.0.2 (??/??/????):
 	Pidgin:
 	* Added a custom conversation font option to preferences
@@ -13,6 +31,7 @@
 	* Remove MSN's random "Authorization Failed" dialogs
 	* Fix MSN to correctly detect incorrect passwords and disable the account
 	* Get User Info on MSN is now more reliable & accurate
+	* Updated SILC protocol to support SILC Toolkit 1.1 (Pekka Riikonen)
 
 	Finch:
 	* Auto account reconnecting
--- a/ChangeLog.API	Tue Jun 12 13:54:04 2007 +0000
+++ b/ChangeLog.API	Tue Jun 12 21:21:37 2007 +0000
@@ -1,5 +1,80 @@
 Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul
 
+version 2.1.0 (??/??/????):
+	libpurple:
+		Added:
+		* purple-remote: added getstatus command
+		* conversation-extended-menu signal (See Doxygen docs)
+		* OPT_PROTO_SLASH_COMMANDS_NATIVE protocol option to indicate that
+		  slash commands are "native" to the protocol
+		* PURPLE_MESSAGE_NO_LINKIFY message flag to indicate that the message
+		  should not be auto-linkified
+		* PurpleEventLoopUiOps.timeout_add_seconds
+		    UIs can now use better scheduling for whole-second timers.  For
+		    example, clients based on the glib event loop can now use
+		    g_timeout_add_seconds.
+		* gtk_imhtml_setup_entry
+		* pidgin_create_window
+		* purple_blist_node_get_type
+		* purple_conversation_do_command
+		* purple_conversation_get_extended_menu
+		* purple_core_ensure_single_instance
+		    This is for UIs to use to ensure only one copy is running.
+		* purple_dbus_is_owner
+		* purple_dbusify_const_GList
+		* purple_dbusify_const_GSList
+		* purple_const_GList_to_array
+		* purple_const_GSList_to_array
+		* purple_image_data_calculate_filename
+		* pidgin_retrieve_user_info, shows immediate feedback when getting
+		  information about a user.
+		* purple_timeout_add_seconds
+		    Callers should prefer this to purple_timeout_add for timers
+		    longer than 1 second away.  Be aware of the rounding, though.
+		* purple_timeout_add_seconds
+		    Callers should prefer this to purple_timeout_add for timers
+		    longer than 1 second away.  Be aware of the rounding, though.
+		* purple_xfer_get_remote_user
+		* gtk_imhtml_animation_new
+		    Can be used for inserting an animated image into an IMHTML.
+
+		Changed:
+		* Mark some return types const:
+			* purple_accounts_get_all
+			* purple_connections_get_all
+			* purple_connections_get_connecting
+			* purple_conv_chat_get_ignored
+			* purple_conv_chat_get_users
+			* purple_get_chats
+			* purple_get_conversations
+			* purple_get_ims
+			* purple_notify_user_info_get_entries
+
+		Deprecated:
+		* purple_dbusify_GList:  Use purple_dbusify_const_GList (and
+		  g_list_free if needed) if depending on 2.1.0 is okay.
+		* purple_dbusify_GSList:  Use purple_dbusify_const_GSList (and
+		  g_slist_free if needed) if depending on 2.1.0 is okay..
+		* purple_GList_to_array:  Use purple_const_GList_to_array (and
+		  g_list_free if needed) if depending on 2.1.0 is okay..
+		* purple_GSList_to_array:  Use purple_const_GSList_to_array (and
+		  g_slist_free if needed) if depending on 2.1.0 is okay..
+
+	Pidgin:
+		Changed:
+		* pidgin_append_menu_action returns the menuitem added to the menu.
+		* pidgin_separator returns the separator added to the menu.
+
+	Finch:
+		Added:
+		* finch_retrieve_user_info
+
+version 2.0.2 (6/14/2007):
+	Pidgin:
+		Deprecated:
+		* pidgin_dialogs_alias_contact:  This will be removed in 3.0.0
+		  unless there is sufficient demand to keep it.
+
 version 2.0.0 (5/3/2007):
 	Please note all functions, defines, and data structures have been
 	re-namespaced to match the new names of Pidgin, Finch, and libpurple.
--- a/ChangeLog.win32	Tue Jun 12 13:54:04 2007 +0000
+++ b/ChangeLog.win32	Tue Jun 12 21:21:37 2007 +0000
@@ -1,4 +1,7 @@
-version 2.0.1 (??/??/????):
+version 2.0.2 (??/??/????):
+	* Add Bonjour protocol support thanks to Chris Davies. This requires
+	  Apple Bonjour for Windows from:
+	  http://www.apple.com/support/downloads/bonjourforwindows.html
 
 version 2.0.0 (5/3/2007):
 	* URI Handler support added via `pidgin.exe --protocolhandler=`
--- a/config.h.mingw	Tue Jun 12 13:54:04 2007 +0000
+++ b/config.h.mingw	Tue Jun 12 21:21:37 2007 +0000
@@ -344,9 +344,6 @@
 /* Define to the version of this package. */
 /* #define PACKAGE_VERSION "2.0.0dev" */
 
-/* Define to make assertions fatal (useful for debugging). */
-/* #define PURPLE_FATAL_ASSERTS 1 */
-
 /* Define if plugins are enabled. */
 #define PURPLE_PLUGINS 1
 
--- a/configure.ac	Tue Jun 12 13:54:04 2007 +0000
+++ b/configure.ac	Tue Jun 12 21:21:37 2007 +0000
@@ -43,10 +43,10 @@
 #
 # Make sure to update finch/libgnt/configure.ac with libgnt version changes.
 #
-m4_define([purple_lt_current], [0])
+m4_define([purple_lt_current], [1])
 m4_define([purple_major_version], [2])
-m4_define([purple_minor_version], [0])
-m4_define([purple_micro_version], [2])
+m4_define([purple_minor_version], [1])
+m4_define([purple_micro_version], [0])
 m4_define([purple_version_suffix], [devel])
 m4_define([purple_version],
           [purple_major_version.purple_minor_version.purple_micro_version])
@@ -55,7 +55,7 @@
 m4_define([gnt_lt_current], [0])
 m4_define([gnt_major_version], [1])
 m4_define([gnt_minor_version], [0])
-m4_define([gnt_micro_version], [2])
+m4_define([gnt_micro_version], [1])
 m4_define([gnt_version_suffix], [devel])
 m4_define([gnt_version],
           [gnt_major_version.gnt_minor_version.gnt_micro_version])
@@ -569,14 +569,15 @@
 AC_ARG_ENABLE(gstreamer,
 	[AC_HELP_STRING([--disable-gstreamer], [compile without GStreamer audio support])],
 	enable_gst="$enableval", enable_gst="yes")
-PKG_CHECK_MODULES(GSTREAMER, [gstreamer-0.10], , [
-	AC_MSG_RESULT(no)
-	enable_gst="no"
-])
 if test "x$enable_gst" != "xno"; then
-	AC_DEFINE(USE_GSTREAMER, 1, [Use GStreamer for playing sounds])
-	AC_SUBST(GSTREAMER_CFLAGS)
-	AC_SUBST(GSTREAMER_LIBS)
+	PKG_CHECK_MODULES(GSTREAMER, [gstreamer-0.10], [
+		AC_DEFINE(USE_GSTREAMER, 1, [Use GStreamer for playing sounds])
+		AC_SUBST(GSTREAMER_CFLAGS)
+		AC_SUBST(GSTREAMER_LIBS)
+	], [
+		AC_MSG_RESULT(no)
+		enable_gst="no"
+	])
 fi
 
 dnl #######################################################################
@@ -646,13 +647,14 @@
 AC_ARG_WITH(silc-libs, [AC_HELP_STRING([--with-silc-libs=DIR], [compile the SILC plugin against the SILC libs in DIR])], [ac_silc_libs="$withval"], [ac_silc_libs="no"])
 SILC_CFLAGS=""
 SILC_LIBS=""
+have_silc="no"
 if test -n "$with_silc_includes" || test -n "$with_silc_libs"; then
 	silc_manual_check="yes"
 else
 	silc_manual_check="no"
 fi
 if test "x$silc_manual_check" = "xno"; then
-	PKG_CHECK_MODULES(SILC, silcclient, [
+	PKG_CHECK_MODULES(SILC, [silcclient >= 1.1], [
 		have_silc="yes"
 		silcincludes="yes"
 		silcclient="yes"
@@ -660,16 +662,26 @@
 		AC_MSG_RESULT(no)
 		have_silc="no"
 	])
-	dnl If silcclient.pc wasn't found, check for just silc.pc
 	if test "x$have_silc" = "xno"; then
-		PKG_CHECK_MODULES(SILC, silc, [
+		PKG_CHECK_MODULES(SILC, silcclient, [
 			have_silc="yes"
-			silcincludes="yes"
-			silcclient="yes"
+			silc10includes="yes"
+			silc10client="yes"
 		], [
 			AC_MSG_RESULT(no)
 			have_silc="no"
 		])
+		dnl If silcclient.pc wasn't found, check for just silc.pc
+		if test "x$have_silc" = "xno"; then
+			PKG_CHECK_MODULES(SILC, silc, [
+				have_silc="yes"
+				silc10includes="yes"
+				silc10client="yes"
+			], [
+				AC_MSG_RESULT(no)
+				have_silc="no"
+			])
+		fi
 	fi
 else
 	if test "$ac_silc_includes" != "no"; then
@@ -677,7 +689,7 @@
 	fi
 	CPPFLAGS_save="$CPPFLAGS"
 	CPPFLAGS="$CPPFLAGS $SILC_CFLAGS"
-	AC_CHECK_HEADER(silcincludes.h, [silcincludes=yes])
+	AC_CHECK_HEADER(silc.h, [silcincludes=yes])
 	CPPFLAGS="$CPPFLAGS_save"
 
 	if test "$ac_silc_libs" != "no"; then
@@ -685,11 +697,28 @@
 	fi
 	SILC_LIBS="$SILC_LIBS -lsilc -lsilcclient -lpthread $LIBDL"
 	AC_CHECK_LIB(silcclient, silc_client_init, [silcclient=yes], , $SILC_LIBS)
+
+	if test "x$silcincludes" = "xyes" -a "x$silcclient" = "xyes"; then
+		have_silc="yes"
+	else
+		CPPFLAGS_save="$CPPFLAGS"
+		CPPFLAGS="$CPPFLAGS $SILC_CFLAGS"
+		AC_CHECK_HEADER(silcincludes.h, [silc10includes=yes])
+		CPPFLAGS="$CPPFLAGS_save"
+
+		SILC_LIBS="$SILC_LIBS -lsilc -lsilcclient -lpthread $LIBDL"
+		AC_CHECK_LIB(silcclient, silc_client_init, [silc10client=yes], , $SILC_LIBS)
+		if test "x$silc10includes" = "xyes" -a "x$silc10client" = "xyes"; then
+			have_silc="yes"
+		fi
+	fi
 fi
 AC_SUBST(SILC_LIBS)
 AC_SUBST(SILC_CFLAGS)
 dnl SILC Toolkit >= 1.0.1 has a new MIME API
 if test "x$silcclient" = "xyes"; then
+	AC_DEFINE(HAVE_SILCMIME_H, 1, [Define if we have silcmime.h])
+elif test "x$silc10client" = "xyes"; then
 	CPPFLAGS_save="$CPPFLAGS"
 	CPPFLAGS="$CPPFLAGS $SILC_CFLAGS"
 		AC_MSG_CHECKING(for silcmime.h)
@@ -794,7 +823,10 @@
 	STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/bonjour//'`
 fi
 if test "x$silcincludes" != "xyes" -o "x$silcclient" != "xyes"; then
-	STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/silc//'`
+	STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/silc/silc10/'`
+fi
+if test "x$silc10includes" != "xyes" -o "x$silc10client" != "xyes"; then
+	STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/silc10//'`
 fi
 AC_SUBST(STATIC_PRPLS)
 STATIC_LINK_LIBS=
@@ -802,13 +834,24 @@
 load_proto=
 for i in $STATIC_PRPLS ; do
 	dnl Ugly special case for "libsilcpurple.a":
-	if test "x$i" = "xsilc"; then
-		STATIC_LINK_LIBS="$STATIC_LINK_LIBS protocols/$i/lib${i}purple.a"
+	dnl ... and Ugly special case for multi-protocol oscar
+	if test \( "x$i" = "xoscar" -o "x$i" = "xaim" -o "x$i" = "xicq" \) -a "x$static_oscar" != "xyes"; then
+		STATIC_LINK_LIBS="$STATIC_LINK_LIBS \$(top_builddir)/libpurple/protocols/oscar/liboscar.a"
+		extern_init="$extern_init extern gboolean purple_init_aim_plugin();"
+		extern_init="$extern_init extern gboolean purple_init_icq_plugin();"
+		load_proto="$load_proto purple_init_aim_plugin();"
+		load_proto="$load_proto purple_init_icq_plugin();"
 	else
-		STATIC_LINK_LIBS="$STATIC_LINK_LIBS protocols/$i/lib$i.a"
+		if test "x$i" = "xsilc"; then
+			STATIC_LINK_LIBS="$STATIC_LINK_LIBS \$(top_builddir)/libpurple/protocols/$i/lib${i}purple.a"
+		elif test "x$i" = "xsilc10"; then
+			STATIC_LINK_LIBS="$STATIC_LINK_LIBS \$(top_builddir)/libpurple/protocols/$i/libsilcpurple.a"
+		else
+			STATIC_LINK_LIBS="$STATIC_LINK_LIBS \$(top_builddir)/libpurple/protocols/$i/lib$i.a"
+		fi
+		extern_init="$extern_init extern gboolean purple_init_${i}_plugin();"
+		load_proto="$load_proto purple_init_${i}_plugin();"
 	fi
-	extern_init="$extern_init extern gboolean purple_init_${i}_plugin();"
-	load_proto="$load_proto purple_init_${i}_plugin();"
 	case $i in
 		bonjour)	static_bonjour=yes ;;
 		gg)			static_gg=yes ;;
@@ -822,6 +865,7 @@
 		qq)			static_qq=yes ;;
 		sametime)	static_sametime=yes ;;
 		silc)		static_silc=yes ;;
+		silc10)		static_silc=yes ;;
 		simple)		static_simple=yes ;;
 		toc)		static_toc=yes ;;
 		yahoo)		static_yahoo=yes ;;
@@ -838,7 +882,7 @@
 AM_CONDITIONAL(STATIC_OSCAR, test "x$static_oscar" = "xyes")
 AM_CONDITIONAL(STATIC_QQ, test "x$static_qq" = "xyes")
 AM_CONDITIONAL(STATIC_SAMETIME, test "x$static_sametime" = "xyes" -a "x$have_meanwhile" = "xyes")
-AM_CONDITIONAL(STATIC_SILC, test "x$static_silc" = "xyes" -a "x$silcincludes" = "xyes" -a "x$silcclient" = "xyes")
+AM_CONDITIONAL(STATIC_SILC, test "x$static_silc" = "xyes" -a "x$have_silc" = "xyes")
 AM_CONDITIONAL(STATIC_SIMPLE, test "x$static_simple" = "xyes")
 AM_CONDITIONAL(STATIC_TOC, test "x$static_toc" = "xyes")
 AM_CONDITIONAL(STATIC_YAHOO, test "x$static_yahoo" = "xyes")
@@ -858,7 +902,10 @@
 	DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/bonjour//'`
 fi
 if test "x$silcincludes" != "xyes" -o "x$silcclient" != "xyes"; then
-	DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/silc//'`
+	DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/silc/silc10/'`
+fi
+if test "x$silc10includes" != "xyes" -o "x$silc10client" != "xyes"; then
+	DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/silc10//'`
 fi
 AC_SUBST(DYNAMIC_PRPLS)
 for i in $DYNAMIC_PRPLS ; do
@@ -875,6 +922,7 @@
 		qq)			dynamic_qq=yes ;;
 		sametime)	dynamic_sametime=yes ;;
 		silc)		dynamic_silc=yes ;;
+		silc10)		dynamic_silc=yes ;;
 		simple)		dynamic_simple=yes ;;
 		toc)		dynamic_toc=yes ;;
 		yahoo)		dynamic_yahoo=yes ;;
@@ -891,7 +939,7 @@
 AM_CONDITIONAL(DYNAMIC_OSCAR, test "x$dynamic_oscar" = "xyes")
 AM_CONDITIONAL(DYNAMIC_QQ, test "x$dynamic_qq" = "xyes")
 AM_CONDITIONAL(DYNAMIC_SAMETIME, test "x$dynamic_sametime" = "xyes" -a "x$have_meanwhile" = "xyes")
-AM_CONDITIONAL(DYNAMIC_SILC, test "x$dynamic_silc" = "xyes" -a "x$silcincludes" = "xyes" -a "x$silcclient" = "xyes")
+AM_CONDITIONAL(DYNAMIC_SILC, test "x$dynamic_silc" = "xyes" -a "x$have_silc" = "xyes")
 AM_CONDITIONAL(DYNAMIC_SIMPLE, test "x$dynamic_simple" = "xyes")
 AM_CONDITIONAL(DYNAMIC_TOC, test "x$dynamic_toc" = "xyes")
 AM_CONDITIONAL(DYNAMIC_YAHOO, test "x$dynamic_yahoo" = "xyes")
@@ -2090,6 +2138,7 @@
 		   libpurple/protocols/qq/Makefile
 		   libpurple/protocols/sametime/Makefile
 		   libpurple/protocols/silc/Makefile
+		   libpurple/protocols/silc10/Makefile
 		   libpurple/protocols/simple/Makefile
 		   libpurple/protocols/toc/Makefile
 		   libpurple/protocols/yahoo/Makefile
--- a/doc/conversation-signals.dox	Tue Jun 12 13:54:04 2007 +0000
+++ b/doc/conversation-signals.dox	Tue Jun 12 21:21:37 2007 +0000
@@ -29,6 +29,7 @@
   @signal chat-joined
   @signal chat-left
   @signal chat-topic-changed
+  @signal conversation-extended-menu
  @endsignals
 
  @signaldef writing-im-msg
@@ -417,5 +418,15 @@
   @param topic The new topic.
  @endsignaldef
 
+ @signaldef conversation-extended-menu
+  @signalproto
+void (*conversation_extended_menu)(PurpleConversation *conv, GList **list);
+  @endsignalproto
+  @signaldesc
+   Emitted when the UI requests a list of plugin actions for a
+   conversation.
+  @param conv   The conversation.
+  @param list   A pointer to the list of actions.
+ @endsignaldef
 */
 // vim: syntax=c tw=75 et
--- a/doc/finch.1.in	Tue Jun 12 13:54:04 2007 +0000
+++ b/doc/finch.1.in	Tue Jun 12 21:21:37 2007 +0000
@@ -109,6 +109,15 @@
 .B Ctrl \+ o
 Bring up the menu (if there is one) for a window. Note that currently only the
 buddylist has a menu.
+.TP
+.B Alt \+ Shift \+ .
+Switch to the next workspace
+.TP
+.B Alt \+ Shift \+ ,
+Switch to the previous workspace
+.TP
+.B Alt \+ s
+Show the workspace list
 
 .SH FILES
 \fI~/.gntrc\fR: configuration file for gnt applications.
@@ -134,6 +143,33 @@
 .br
 
 .br
+# Workspaces are created simply by adding Workspace-X groups as follows:
+.br
+[Workspace-1]
+.br
+name = blist
+.br
+# window-names specifies that windows with these semi-colon separated names are placed
+into this workspace
+.br
+window-names = buddylist;debug-window
+.br
+
+.br
+[Workspace-2]
+.br
+name = IM
+.br
+window-names = conversation-window
+.br
+# window-titles specifies that windows with these semi-colon separated titles are placed
+into this workspace. These are matched as substrings. Window titles take precedence over
+names.
+.br
+window-titles = Preferences;Pounce
+.br
+
+.br
 [colors]
 .br
 # The RGB values range in [0, 1000]
--- a/doc/funniest_home_convos.txt	Tue Jun 12 13:54:04 2007 +0000
+++ b/doc/funniest_home_convos.txt	Tue Jun 12 21:21:37 2007 +0000
@@ -471,4 +471,6 @@
 14:08 <elb> "... yes"
 14:08 <elb> I mean, what do you say
 14:08 <Robot101> elb: was their nick "idi"?
-          
+
+19:23 <-- elb has quit (K-lined)
+
--- a/finch/finch.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/finch.c	Tue Jun 12 21:21:37 2007 +0000
@@ -156,11 +156,15 @@
 	gnt_input_add,
 	g_source_remove,
 	NULL, /* input_get_error */
+#if GLIB_CHECK_VERSION(2,14,0)
+	g_timeout_add_seconds,
+#else
+	NULL,
+#endif
 
 	/* padding */
 	NULL,
 	NULL,
-	NULL,
 	NULL
 };
 
--- a/finch/gntaccount.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/gntaccount.c	Tue Jun 12 21:21:37 2007 +0000
@@ -31,6 +31,7 @@
 #include <gntlabel.h>
 #include <gntline.h>
 #include <gnttree.h>
+#include <gntwindow.h>
 
 #include <account.h>
 #include <accountopt.h>
@@ -40,6 +41,7 @@
 #include <request.h>
 
 #include "gntaccount.h"
+#include "gntblist.h"
 #include "finch.h"
 
 #include <string.h>
@@ -280,7 +282,11 @@
 
 		if (dialog->account)
 		{
-			s = strrchr(username, purple_account_user_split_get_separator(split));
+			if(purple_account_user_split_get_reverse(split))
+				s = strrchr(username, purple_account_user_split_get_separator(split));
+			else
+				s = strchr(username, purple_account_user_split_get_separator(split));
+
 			if (s != NULL)
 			{
 				*s = '\0';
@@ -635,7 +641,7 @@
 
 void finch_accounts_show_all()
 {
-	GList *iter;
+	const GList *iter;
 	GntWidget *box, *button;
 
 	if (accounts.window)
@@ -728,7 +734,7 @@
 
 void finch_accounts_init()
 {
-	GList *iter;
+	const GList *iter;
 
 	purple_signal_connect(purple_accounts_get_handle(), "account-added",
 			finch_accounts_get_handle(), PURPLE_CALLBACK(account_added_callback),
@@ -743,12 +749,18 @@
 			finch_accounts_get_handle(),
 			PURPLE_CALLBACK(account_abled_cb), GINT_TO_POINTER(TRUE));
 
-	for (iter = purple_accounts_get_all(); iter; iter = iter->next) {
-		if (purple_account_get_enabled(iter->data, FINCH_UI))
-			break;
+	iter = purple_accounts_get_all();
+	if (iter) {
+		for (; iter; iter = iter->next) {
+			if (purple_account_get_enabled(iter->data, FINCH_UI))
+				break;
+		}
+		if (!iter)
+			finch_accounts_show_all();
+	} else {
+		edit_account(NULL);
+		finch_accounts_show_all();
 	}
-	if (!iter)
-		finch_accounts_show_all();
 }
 
 void finch_accounts_uninit()
@@ -819,7 +831,7 @@
 {
 	PurpleConnection *gc = purple_account_get_connection(data->account);
 
-	if (g_list_find(purple_connections_get_all(), gc))
+	if (g_list_find((GList *)purple_connections_get_all(), gc))
 	{
 		purple_blist_request_add_buddy(data->account, data->username,
 									 NULL, data->alias);
@@ -865,25 +877,25 @@
 } auth_and_add;
 
 static void
-authorize_and_add_cb(auth_and_add *aa)
+free_auth_and_add(auth_and_add *aa)
 {
-	aa->auth_cb(aa->data);
-	purple_blist_request_add_buddy(aa->account, aa->username,
-	 	                    NULL, aa->alias);
-
 	g_free(aa->username);
 	g_free(aa->alias);
 	g_free(aa);
 }
 
 static void
+authorize_and_add_cb(auth_and_add *aa)
+{
+	aa->auth_cb(aa->data);
+	purple_blist_request_add_buddy(aa->account, aa->username,
+	 	                    NULL, aa->alias);
+}
+
+static void
 deny_no_add_cb(auth_and_add *aa)
 {
 	aa->deny_cb(aa->data);
-
-	g_free(aa->username);
-	g_free(aa->alias);
-	g_free(aa);
 }
 
 static void *
@@ -912,19 +924,47 @@
 		                (message != NULL ? ": " : "."),
 		                (message != NULL ? message  : ""));
 	if (!on_list) {
+		GntWidget *widget;
+		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->data = user_data;
 		aa->username = g_strdup(remote_user);
 		aa->alias = g_strdup(alias);
 		aa->account = account;
-		uihandle = purple_request_action(NULL, _("Authorize buddy?"), buffer, NULL,
+
+		uihandle = gnt_vwindow_new(FALSE);
+		gnt_box_set_title(GNT_BOX(uihandle), _("Authorize buddy?"));
+		gnt_box_set_pad(GNT_BOX(uihandle), 0);
+
+		widget = purple_request_action(NULL, _("Authorize buddy?"), buffer, NULL,
 			PURPLE_DEFAULT_ACTION_NONE,
 			account, remote_user, NULL,
 			aa, 2,
 			_("Authorize"), authorize_and_add_cb,
 			_("Deny"), deny_no_add_cb);
+		gnt_screen_release(widget);
+		gnt_box_set_toplevel(GNT_BOX(widget), FALSE);
+		gnt_box_add_widget(GNT_BOX(uihandle), widget);
+
+		gnt_box_add_widget(GNT_BOX(uihandle), gnt_hline_new());
+
+		widget = finch_retrieve_user_info(account->gc, remote_user);
+		for (iter = GNT_BOX(widget)->list; iter; iter = iter->next) {
+			if (GNT_IS_BUTTON(iter->data)) {
+				gnt_widget_destroy(iter->data);
+				gnt_box_remove(GNT_BOX(widget), iter->data);
+				break;
+			}
+		}
+		gnt_box_set_toplevel(GNT_BOX(widget), FALSE);
+		gnt_screen_release(widget);
+		gnt_box_add_widget(GNT_BOX(uihandle), widget);
+		gnt_widget_show(uihandle);
+
+		g_signal_connect_swapped(G_OBJECT(uihandle), "destroy", G_CALLBACK(free_auth_and_add), aa);
 	} else {
 		uihandle = purple_request_action(NULL, _("Authorize buddy?"), buffer, NULL,
 			PURPLE_DEFAULT_ACTION_NONE,
--- a/finch/gntblist.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/gntblist.c	Tue Jun 12 21:21:37 2007 +0000
@@ -104,6 +104,7 @@
 static void add_group(PurpleGroup *group, FinchBlist *ggblist);
 static void add_chat(PurpleChat *chat, FinchBlist *ggblist);
 static void add_node(PurpleBlistNode *node, FinchBlist *ggblist);
+static void node_update(PurpleBuddyList *list, PurpleBlistNode *node);
 static void draw_tooltip(FinchBlist *ggblist);
 static gboolean remove_typing_cb(gpointer null);
 static void remove_peripherals(FinchBlist *ggblist);
@@ -189,6 +190,8 @@
 		if ((!purple_prefs_get_bool(PREF_ROOT "/showoffline") && !is_contact_online(contact)) ||
 				contact->currentsize < 1)
 			node_remove(list, (PurpleBlistNode*)contact);
+		else
+			node_update(list, (PurpleBlistNode*)contact);
 	} else if (!PURPLE_BLIST_NODE_IS_GROUP(node)) {
 		PurpleGroup *group = (PurpleGroup*)node->parent;
 		if ((!purple_prefs_get_bool(PREF_ROOT "/showoffline") && !is_group_online(group)) ||
@@ -215,6 +218,9 @@
 	if (list->ui_data == NULL)
 		return;   /* XXX: this is probably the place to auto-join chats */
 
+	if (ggblist->window == NULL)
+		return;
+
 	if (node->ui_data != NULL) {
 		gnt_tree_change_text(GNT_TREE(ggblist->tree), node,
 				0, get_display_name(node));
@@ -634,9 +640,18 @@
 	if (PURPLE_BLIST_NODE_IS_BUDDY(node))
 	{
 		PurpleBuddy *buddy = (PurpleBuddy *)node;
-		PurpleConversation *conv =  purple_conversation_new(PURPLE_CONV_TYPE_IM,
-					purple_buddy_get_account(buddy),
-					purple_buddy_get_name(buddy));
+		PurpleConversation *conv;
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
+					purple_buddy_get_name(buddy),
+					purple_buddy_get_account(buddy));
+		if (!conv) {
+			conv =  purple_conversation_new(PURPLE_CONV_TYPE_IM,
+						purple_buddy_get_account(buddy),
+						purple_buddy_get_name(buddy));
+		} else {
+			FinchConv *ggconv = conv->ui_data;
+			gnt_window_present(ggconv->window);
+		}
 		finch_conversation_set_active(conv);
 	}
 	else if (PURPLE_BLIST_NODE_IS_CHAT(node))
@@ -824,17 +839,22 @@
 			PURPLE_CALLBACK(finch_add_group), group);
 }
 
+gpointer finch_retrieve_user_info(PurpleConnection *conn, const char *name)
+{
+	PurpleNotifyUserInfo *info = purple_notify_user_info_new();
+	gpointer uihandle;
+	purple_notify_user_info_add_pair(info, _("Information"), _("Retrieving..."));
+	uihandle = purple_notify_userinfo(conn, name, info, NULL, NULL);
+	purple_notify_user_info_destroy(info);
+
+	serv_get_info(conn, name);
+	return uihandle;
+}
+
 static void
 finch_blist_get_buddy_info_cb(PurpleBuddy *buddy, PurpleBlistNode *selected)
 {
-	/* Add a userinfo with a "Retrieving information", which will later be updated
-	 * when the server finally returns the information. */
-	PurpleNotifyUserInfo *info = purple_notify_user_info_new();
-	purple_notify_user_info_add_pair(info, _("Information"), _("Retrieving..."));
-	purple_notify_userinfo(buddy->account->gc, purple_buddy_get_name(buddy), info, NULL, NULL);
-	purple_notify_user_info_destroy(info);
-
-	serv_get_info(buddy->account->gc, purple_buddy_get_name(buddy));
+	finch_retrieve_user_info(buddy->account->gc, purple_buddy_get_name(buddy));
 }
 
 static void
@@ -2297,6 +2317,8 @@
 				ggblist, 0, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
 	g_signal_connect_data(G_OBJECT(ggblist->tree), "lost-focus", G_CALLBACK(remove_peripherals),
 				ggblist, 0, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
+	g_signal_connect_data(G_OBJECT(ggblist->window), "workspace-hidden", G_CALLBACK(remove_peripherals),
+				ggblist, 0, G_CONNECT_AFTER | G_CONNECT_SWAPPED);
 	g_signal_connect(G_OBJECT(ggblist->tree), "size_changed", G_CALLBACK(size_changed_cb), NULL);
 	g_signal_connect(G_OBJECT(ggblist->window), "position_set", G_CALLBACK(save_position_cb), NULL);
 	g_signal_connect(G_OBJECT(ggblist->window), "destroy", G_CALLBACK(reset_blist_window), NULL);
--- a/finch/gntblist.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/gntblist.h	Tue Jun 12 21:21:37 2007 +0000
@@ -90,6 +90,16 @@
  */
 void finch_blist_set_size(int width, int height);
 
+/**
+ * Get information about a user. Show immediate feedback.
+ *
+ * @param conn   The connection to get information fro
+ * @param name   The user to get information about.
+ *
+ * @return  Returns the ui-handle for the userinfo notification.
+ */
+gpointer finch_retrieve_user_info(PurpleConnection *conn, const char *name);
+
 /*@}*/
 
 #endif
--- a/finch/gntconv.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/gntconv.c	Tue Jun 12 21:21:37 2007 +0000
@@ -64,7 +64,7 @@
 send_typing_notification(GntWidget *w, FinchConv *ggconv)
 {
 	const char *text = gnt_entry_get_text(GNT_ENTRY(ggconv->entry));
-	gboolean empty = (!text || !*text);
+	gboolean empty = (!text || !*text || (*text == '/'));
 	if (purple_prefs_get_bool("/finch/conversations/notify_typing")) {
 		PurpleConversation *conv = ggconv->active_conv;
 		PurpleConvIm *im = PURPLE_CONV_IM(conv);
@@ -313,12 +313,7 @@
 get_info_cb(GntMenuItem *item, gpointer ggconv)
 {
 	FinchConv *ggc = ggconv;
-	PurpleNotifyUserInfo *info = purple_notify_user_info_new();
-	purple_notify_user_info_add_pair(info, _("Information"), _("Retrieving..."));
-	purple_notify_userinfo(ggc->active_conv->account->gc, purple_conversation_get_name(ggc->active_conv), info, NULL, NULL);
-	purple_notify_user_info_destroy(info);
-
-	serv_get_info(purple_conversation_get_gc(ggc->active_conv),
+	finch_retrieve_user_info(purple_conversation_get_gc(ggc->active_conv),
 			purple_conversation_get_name(ggc->active_conv));
 }
 
--- a/finch/gntnotify.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/gntnotify.c	Tue Jun 12 21:21:37 2007 +0000
@@ -69,6 +69,7 @@
 	gnt_box_set_title(GNT_BOX(window), title);
 	gnt_box_set_fill(GNT_BOX(window), FALSE);
 	gnt_box_set_alignment(GNT_BOX(window), GNT_ALIGN_MID);
+	gnt_box_set_pad(GNT_BOX(window), 0);
 
 	if (primary)
 		gnt_box_add_widget(GNT_BOX(window),
@@ -168,7 +169,7 @@
 			gnt_label_new_with_format(_("You have mail!"), GNT_TEXT_FLAG_BOLD));
 
 	emaildialog.tree = tree = gnt_tree_new_with_columns(3);
-	gnt_tree_set_column_titles(GNT_TREE(tree), _("Account"), _("From"), _("Subject"));
+	gnt_tree_set_column_titles(GNT_TREE(tree), _("Account"), _("Sender"), _("Subject"));
 	gnt_tree_set_show_title(GNT_TREE(tree), TRUE);
 	gnt_tree_set_col_width(GNT_TREE(tree), 0, 15);
 	gnt_tree_set_col_width(GNT_TREE(tree), 1, 25);
@@ -268,11 +269,11 @@
 		char *strip = purple_markup_strip_html(info);
 		int tvw, tvh, width, height, ntvw, ntvh;
 
+		while (GNT_WIDGET(ui_handle)->parent)
+			ui_handle = GNT_WIDGET(ui_handle)->parent;
 		gnt_widget_get_size(GNT_WIDGET(ui_handle), &width, &height);
 		gnt_widget_get_size(GNT_WIDGET(msg), &tvw, &tvh);
 
-		/* Ideally, I would replace the information in "info". But replacing tagged text is a
-		 * bit nasty right now. So clear the view and add the new stuff instead. */
 		gnt_text_view_clear(msg);
 		gnt_text_view_append_text_with_flags(msg, strip, GNT_TEXT_FLAG_NORMAL);
 		gnt_text_view_scroll(msg, 0);
@@ -280,7 +281,7 @@
 		ntvw += 3;
 		ntvh++;
 
-		gnt_screen_resize_widget(GNT_WIDGET(ui_handle), width + (ntvw - tvw), height + (ntvh - tvh));
+		gnt_screen_resize_widget(GNT_WIDGET(ui_handle), width + MAX(0, ntvw - tvw), height + MAX(0, ntvh - tvh));
 		g_free(strip);
 		g_free(key);
 	} else {
--- a/finch/gntpounce.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/gntpounce.c	Tue Jun 12 21:21:37 2007 +0000
@@ -288,7 +288,7 @@
 	GntWidget *hbox, *vbox;
 	GntWidget *button;
 	GntWidget *combo;
-	GList *list;
+	const GList *list;
 
 	g_return_if_fail((cur_pounce != NULL) ||
 	                 (account != NULL) ||
@@ -303,7 +303,7 @@
 		dialog->pounce  = NULL;
 		dialog->account = account;
 	} else {
-		GList *connections = purple_connections_get_all();
+		const GList *connections = purple_connections_get_all();
 		PurpleConnection *gc;
 
 		if (connections != NULL) {
--- a/finch/gntrequest.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/gntrequest.c	Tue Jun 12 21:21:37 2007 +0000
@@ -490,7 +490,7 @@
 			{
 				gboolean all;
 				PurpleAccount *def;
-				GList *list;
+				const GList *list;
 				GntWidget *combo = gnt_combo_box_new();
 				gnt_box_set_alignment(GNT_BOX(hbox), GNT_ALIGN_MID);
 				gnt_box_add_widget(GNT_BOX(hbox), combo);
--- a/finch/gntstatus.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/gntstatus.c	Tue Jun 12 21:21:37 2007 +0000
@@ -497,7 +497,7 @@
 	GntWidget *window, *box, *button, *entry, *combo, *label, *tree;
 	PurpleStatusPrimitive prims[] = {PURPLE_STATUS_AVAILABLE, PURPLE_STATUS_AWAY,
 		PURPLE_STATUS_INVISIBLE, PURPLE_STATUS_OFFLINE, PURPLE_STATUS_UNSET}, current;
-	GList *iter;
+	const GList *iter;
 	int i;
 
 	if (saved)
--- a/finch/libgnt/Makefile.am	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/libgnt/Makefile.am	Tue Jun 12 21:21:37 2007 +0000
@@ -1,6 +1,6 @@
 EXTRA_DIST=genmarshal
 
-SUBDIRS = . wms
+SUBDIRS = . 
 pkgconfigdir = $(libdir)/pkgconfig
 pkgconfig_DATA = gnt.pc
 
@@ -32,6 +32,7 @@
 	gntutils.c \
 	gntwindow.c \
 	gntwm.c \
+	gntws.c \
 	gntmain.c
 
 libgnt_la_headers = \
@@ -58,6 +59,7 @@
 	gntutils.h \
 	gntwindow.h \
 	gntwm.h \
+	gntws.h \
 	gnt.h
 
 CLEANFILES = \
--- a/finch/libgnt/gnt.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/libgnt/gnt.h	Tue Jun 12 21:21:37 2007 +0000
@@ -43,6 +43,7 @@
  */
 gboolean gnt_ascii_only(void);
 
+void gnt_window_present(GntWidget *window);
 /**
  * 
  * @param widget
@@ -137,4 +138,3 @@
  * @param string
  */
 void gnt_set_clipboard_string(gchar *string);
-
--- a/finch/libgnt/gntbindable.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/libgnt/gntbindable.h	Tue Jun 12 21:21:37 2007 +0000
@@ -28,7 +28,6 @@
 #include <glib-object.h>
 #include <ncurses.h>
 
-
 #define GNT_TYPE_BINDABLE				(gnt_bindable_get_gtype())
 #define GNT_BINDABLE(obj)				(G_TYPE_CHECK_INSTANCE_CAST((obj), GNT_TYPE_BINDABLE, GntBindable))
 #define GNT_BINDABLE_CLASS(klass)		(G_TYPE_CHECK_CLASS_CAST((klass), GNT_TYPE_BINDABLE, GntBindableClass))
--- a/finch/libgnt/gntkeys.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/libgnt/gntkeys.h	Tue Jun 12 21:21:37 2007 +0000
@@ -61,6 +61,7 @@
 #define GNT_KEY_BACKSPACE SAFE(key_backspace)
 #define GNT_KEY_DEL    SAFE(key_dc)
 #define GNT_KEY_INS    SAFE(key_ic)
+#define GNT_KEY_BACK_TAB SAFE(back_tab)
 
 #define GNT_KEY_CTRL_A     "\001"
 #define GNT_KEY_CTRL_B     "\002"
--- a/finch/libgnt/gntmain.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/libgnt/gntmain.c	Tue Jun 12 21:21:37 2007 +0000
@@ -21,7 +21,7 @@
  */
 
 #define _GNU_SOURCE
-#if defined(__APPLE__)
+#if defined(__APPLE__) || defined(__unix__)
 #define _XOPEN_SOURCE_EXTENDED
 #endif
 
@@ -116,7 +116,7 @@
 	GntWidget *widget = NULL;
 	PANEL *p = NULL;
 
-	if (!wm->ordered || buffer[0] != 27)
+	if (!wm->cws->ordered || buffer[0] != 27)
 		return FALSE;
 	
 	buffer++;
@@ -172,7 +172,7 @@
 	
 	if (event == GNT_LEFT_MOUSE_DOWN && widget && widget != wm->_list.window &&
 			!GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_TRANSIENT)) {
-		if (widget != wm->ordered->data) {
+		if (widget != wm->cws->ordered->data) {
 			gnt_wm_raise_window(wm, widget);
 		}
 		if (y == widget->priv.y) {
@@ -183,7 +183,7 @@
 	} else if (event == GNT_MOUSE_UP) {
 		if (button == MOUSE_NONE && y == getmaxy(stdscr) - 1) {
 			/* Clicked on the taskbar */
-			int n = g_list_length(wm->list);
+			int n = g_list_length(wm->cws->list);
 			if (n) {
 				int width = getmaxx(stdscr) / n;
 				gnt_bindable_perform_action_named(GNT_BINDABLE(wm), "switch-window-n", x/width, NULL);
@@ -387,8 +387,7 @@
 	switch (sig) {
 #ifdef SIGWINCH
 	case SIGWINCH:
-		werase(stdscr);
-		wrefresh(stdscr);
+		erase();
 		g_idle_add(refresh_screen, NULL);
 		org_winch_handler(sig);
 		signal(SIGWINCH, sighandler);
@@ -491,6 +490,10 @@
  * Stuff for 'window management' *
  *********************************/
 
+void gnt_window_present(GntWidget *window) {
+	gnt_wm_raise_window(wm, window);
+}
+
 void gnt_screen_occupy(GntWidget *widget)
 {
 	gnt_wm_new_window(wm, widget);
@@ -522,7 +525,7 @@
 
 	if (widget == wm->_list.window)
 		return TRUE;
-	if (wm->ordered && wm->ordered->data == widget) {
+	if (wm->cws->ordered && wm->cws->ordered->data == widget) {
 		if (GNT_IS_BOX(widget) &&
 				(GNT_BOX(widget)->active == w || widget == w))
 			return TRUE;
@@ -535,7 +538,7 @@
 	while (widget->parent)
 		widget = widget->parent;
 
-	if (wm->ordered && wm->ordered->data == widget)
+	if (wm->cws->ordered && wm->cws->ordered->data == widget)
 		return;
 
 	GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_URGENT);
--- a/finch/libgnt/gntstyle.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/libgnt/gntstyle.c	Tue Jun 12 21:21:37 2007 +0000
@@ -23,13 +23,21 @@
 #include "gntstyle.h"
 #include "gntcolors.h"
 
+#include "gntws.h"
+
+#include <glib.h>
 #include <ctype.h>
+#include <glib/gprintf.h>
+#include <stdlib.h>
 #include <string.h>
 
+#define MAX_WORKSPACES 99
+
 #if GLIB_CHECK_VERSION(2,6,0)
 static GKeyFile *gkfile;
 #endif
 
+static GHashTable *unknowns;
 static char * str_styles[GNT_STYLES];
 static int int_styles[GNT_STYLES];
 static int bool_styles[GNT_STYLES];
@@ -39,6 +47,11 @@
 	return str_styles[style];
 }
 
+const char *gnt_style_get_from_name(const char *name)
+{
+	return g_hash_table_lookup(unknowns, name);
+}
+
 gboolean gnt_style_get_bool(GntStyle style, gboolean def)
 {
 	int i;
@@ -109,6 +122,45 @@
 	return (char *)gnt_key_translate(key);
 }
 
+void gnt_style_read_workspaces(GntWM *wm)
+{
+#if GLIB_CHECK_VERSION(2,6,0)
+	int i;
+	gchar *name;
+	gsize c;
+
+	for (i = 1; i < MAX_WORKSPACES; ++i) {
+		int j;
+		GntWS *ws;
+		gchar **titles;
+		char *group = calloc(12, 1);
+		g_sprintf(group, "Workspace-%d", i);
+		name = g_key_file_get_value(gkfile, group, "name", NULL);
+		if (!name)
+			return;
+
+		ws = g_object_new(GNT_TYPE_WS, NULL);
+		gnt_ws_set_name(ws, name);
+		gnt_wm_add_workspace(wm, ws);
+		g_free(name);
+
+		titles = g_key_file_get_string_list(gkfile, group, "window-names", &c, NULL);
+		if (titles) {
+			for (j = 0; j < c; ++j)
+				g_hash_table_replace(wm->name_places, g_strdup(titles[j]), ws);
+			g_strfreev(titles);
+		}
+
+		titles = g_key_file_get_string_list(gkfile, group, "window-titles", &c, NULL);
+		if (titles) {
+			for (j = 0; j < c; ++j)
+				g_hash_table_replace(wm->title_places, g_strdup(titles[j]), ws);
+			g_strfreev(titles);
+		}
+		g_free(group);
+	}
+#endif
+}
 void gnt_style_read_actions(GType type, GntBindableClass *klass)
 {
 #if GLIB_CHECK_VERSION(2,6,0)
@@ -243,6 +295,9 @@
 			str_styles[styles[i].en] =
 					g_key_file_get_string(kfile, "general", styles[i].style, NULL);
 		}
+		for (i = 0; i < nkeys; i++)
+			g_hash_table_replace(unknowns, g_strdup(keys[i]),
+					g_strdup(g_key_file_get_string(kfile, "general", keys[i], NULL)));
 	}
 	g_strfreev(keys);
 }
@@ -253,6 +308,7 @@
 #if GLIB_CHECK_VERSION(2,6,0)
 	GError *error = NULL;
 	gkfile = g_key_file_new();
+	unknowns = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
 
 	if (!g_key_file_load_from_file(gkfile, filename, G_KEY_FILE_NONE, &error))
 	{
--- a/finch/libgnt/gntstyle.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/libgnt/gntstyle.h	Tue Jun 12 21:21:37 2007 +0000
@@ -21,6 +21,7 @@
  */
 
 #include "gnt.h"
+#include "gntwm.h"
 
 typedef enum
 {
@@ -40,6 +41,8 @@
 
 const char *gnt_style_get(GntStyle style);
 
+const char *gnt_style_get_from_name(const char *key);
+
 /**
  * 
  * @param style
@@ -64,6 +67,8 @@
  */
 void gnt_style_read_actions(GType type, GntBindableClass *klass);
 
+void gnt_style_read_workspaces(GntWM *wm);
+
 /**
  * 
  */
--- a/finch/libgnt/gntutils.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/libgnt/gntutils.c	Tue Jun 12 21:21:37 2007 +0000
@@ -30,7 +30,6 @@
 #include "gnttree.h"
 #include "gntutils.h"
 #include "gntwindow.h"
-#include "gntbindable.h"
 
 #include "config.h"
 
@@ -185,12 +184,53 @@
 	return continue_emission;
 }
 
+typedef struct {
+	GHashTable *hash;
+	GntTree *tree;
+} BindingView;
 
-GntWidget * gnt_widget_bindings_view(GntWidget *widget){
-	return GNT_WIDGET(gnt_bindable_bindings_view(GNT_BINDABLE(widget)));
+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);
+}
 
+GntWidget *gnt_widget_bindings_view(GntWidget *widget)
+{
+	GntBindable *bind = GNT_BINDABLE(widget);
+	GntWidget *tree = 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(tree);
+		tree = NULL;
+	} else
+		gnt_tree_adjust_columns(bv.tree);
+	g_hash_table_destroy(hash);
+
+	return tree;
+}
 
 #ifndef NO_LIBXML
 static GntWidget *
--- a/finch/libgnt/gntutils.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/libgnt/gntutils.h	Tue Jun 12 21:21:37 2007 +0000
@@ -93,8 +93,6 @@
 
 /**
  * Returns a GntTree populated with "key" -> "binding" for the widget.
- *
- * 
  */
 /**
  * 
--- a/finch/libgnt/gntwidget.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/libgnt/gntwidget.c	Tue Jun 12 21:21:37 2007 +0000
@@ -389,7 +389,7 @@
 
 	g_signal_emit(widget, signals[SIG_DRAW], 0);
 	gnt_widget_queue_update(widget);
-	GNT_WIDGET_UNSET_FLAGS(widget, GNT_WIDGET_DRAWING | GNT_WIDGET_INVISIBLE);
+	GNT_WIDGET_UNSET_FLAGS(widget, GNT_WIDGET_DRAWING);
 }
 
 gboolean
--- a/finch/libgnt/gntwindow.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/libgnt/gntwindow.c	Tue Jun 12 21:21:37 2007 +0000
@@ -27,9 +27,13 @@
 
 enum
 {
-	SIGS = 1,
+	SIG_WORKSPACE_HIDE,
+	SIG_WORKSPACE_SHOW,
+	SIGS,
 };
 
+static guint signals[SIGS] = { 0 };
+
 static GntBoxClass *parent_class = NULL;
 
 static void (*org_destroy)(GntWidget *widget);
@@ -64,6 +68,24 @@
 	org_destroy = wid_class->destroy;
 	wid_class->destroy = gnt_window_destroy;
 
+	signals[SIG_WORKSPACE_HIDE] =
+		g_signal_new("workspace-hidden",
+					 G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST,
+					 0,
+					 NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+
+	signals[SIG_WORKSPACE_SHOW] =
+		g_signal_new("workspace-shown",
+					 G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST,
+					 0,
+					 NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+
 	gnt_bindable_class_register_action(bindable, "show-menu", show_menu,
 				GNT_KEY_CTRL_O, NULL);
 	gnt_bindable_register_binding(bindable, "show-menu", GNT_KEY_F10, NULL);
@@ -131,6 +153,20 @@
 	return wid;
 }
 
+void
+gnt_window_workspace_hiding(GntWindow *window)
+{
+	if (window->menu)
+		gnt_widget_hide(GNT_WIDGET(window->menu));
+	g_signal_emit(window, signals[SIG_WORKSPACE_HIDE], 0);
+}
+
+void
+gnt_window_workspace_showing(GntWindow *window)
+{
+	g_signal_emit(window, signals[SIG_WORKSPACE_SHOW], 0);
+}
+
 void gnt_window_set_menu(GntWindow *window, GntMenu *menu)
 {
 	/* If a menu already existed, then destroy that first. */
--- a/finch/libgnt/gntwindow.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/libgnt/gntwindow.h	Tue Jun 12 21:21:37 2007 +0000
@@ -95,6 +95,9 @@
  */
 void gnt_window_set_menu(GntWindow *window, GntMenu *menu);
 
+void gnt_window_workspace_hiding(GntWindow *);
+void gnt_window_workspace_showing(GntWindow *);
+
 G_END_DECLS
 
 #endif /* GNT_WINDOW_H */
--- a/finch/libgnt/gntwm.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/libgnt/gntwm.c	Tue Jun 12 21:21:37 2007 +0000
@@ -21,13 +21,15 @@
  */
 
 #define _GNU_SOURCE
-#if defined(__APPLE__)
+#if defined(__APPLE__) || defined(__unix__)
 #define _XOPEN_SOURCE_EXTENDED
 #endif
 
 #include "config.h"
 
 #include <ctype.h>
+#include <glib/gprintf.h>
+#include <gmodule.h>
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
@@ -71,6 +73,8 @@
 static void gnt_wm_give_focus(GntWM *wm, GntWidget *widget);
 static void update_window_in_list(GntWM *wm, GntWidget *wid);
 static void shift_window(GntWM *wm, GntWidget *widget, int dir);
+static gboolean workspace_next(GntBindable *wm, GList *n);
+static gboolean workspace_prev(GntBindable *wm, GList *n);
 
 #ifndef NO_WIDECHAR
 static int widestringwidth(wchar_t *wide);
@@ -80,7 +84,7 @@
 static int write_timeout;
 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 *
@@ -100,61 +104,8 @@
 	g_free(node);
 }
 
-static void
-draw_taskbar(GntWM *wm, gboolean reposition)
-{
-	static WINDOW *taskbar = NULL;
-	GList *iter;
-	int n, width = 0;
-	int i;
-
-	if (taskbar == NULL) {
-		taskbar = newwin(1, getmaxx(stdscr), getmaxy(stdscr) - 1, 0);
-	} else if (reposition) {
-		int Y_MAX = getmaxy(stdscr) - 1;
-		mvwin(taskbar, Y_MAX, 0);
-	}
-
-	wbkgdset(taskbar, '\0' | COLOR_PAIR(GNT_COLOR_NORMAL));
-	werase(taskbar);
-
-	n = g_list_length(wm->list);
-	if (n)
-		width = getmaxx(stdscr) / n;
-
-	for (i = 0, iter = wm->list; iter; iter = iter->next, i++)
-	{
-		GntWidget *w = iter->data;
-		int color;
-		const char *title;
-
-		if (w == wm->ordered->data) {
-			/* This is the current window in focus */
-			color = GNT_COLOR_TITLE;
-		} else if (GNT_WIDGET_IS_FLAG_SET(w, GNT_WIDGET_URGENT)) {
-			/* This is a window with the URGENT hint set */
-			color = GNT_COLOR_URGENT;
-		} else {
-			color = GNT_COLOR_NORMAL;
-		}
-		wbkgdset(taskbar, '\0' | COLOR_PAIR(color));
-		if (iter->next)
-			mvwhline(taskbar, 0, width * i, ' ' | COLOR_PAIR(color), width);
-		else
-			mvwhline(taskbar, 0, width * i, ' ' | COLOR_PAIR(color), getmaxx(stdscr) - width * i);
-		title = GNT_BOX(w)->title;
-		mvwprintw(taskbar, 0, width * i, "%s", title ? title : "<gnt>");
-		if (i)
-			mvwaddch(taskbar, 0, width *i - 1, ACS_VLINE | A_STANDOUT | COLOR_PAIR(GNT_COLOR_NORMAL));
-
-		update_window_in_list(wm, w);
-	}
-
-	wrefresh(taskbar);
-}
-
-static void
-copy_win(GntWidget *widget, GntNode *node)
+void
+gnt_wm_copy_win(GntWidget *widget, GntNode *node)
 {
 	WINDOW *src, *dst;
 	int shadow;
@@ -225,6 +176,33 @@
 #endif
 }
 
+static void
+update_act_msg()
+{
+	GntWidget *label;
+	GList *iter;
+	static GntWidget *message = NULL;
+	GString *text = g_string_new("act: ");
+	if (message)
+		gnt_widget_destroy(message);
+	if (g_list_length(act) == 0)
+		return;
+	for (iter = act; iter; iter = iter->next) {
+		GntWS *ws = iter->data;
+		g_string_append_printf(text, "%s, ", gnt_ws_get_name(ws));
+	}
+	g_string_erase(text, text->len - 2, 2);
+	message = gnt_vbox_new(FALSE);
+	label = gnt_label_new_with_format(text->str, GNT_TEXT_FLAG_BOLD | GNT_TEXT_FLAG_HIGHLIGHT);
+	GNT_WIDGET_UNSET_FLAGS(GNT_BOX(message), GNT_WIDGET_CAN_TAKE_FOCUS);
+	GNT_WIDGET_SET_FLAGS(GNT_BOX(message), GNT_WIDGET_TRANSIENT);
+	gnt_box_add_widget(GNT_BOX(message), label);
+	gnt_widget_set_name(message, "wm-message");
+	gnt_widget_set_position(message, 0, 0);
+	gnt_widget_draw(message);
+	g_string_free(text, TRUE);
+}
+
 static gboolean
 update_screen(GntWM *wm)
 {
@@ -355,9 +333,19 @@
 gnt_wm_init(GTypeInstance *instance, gpointer class)
 {
 	GntWM *wm = GNT_WM(instance);
-	wm->list = NULL;
-	wm->ordered = NULL;
+	wm->workspaces = NULL;
+	wm->name_places = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+	wm->title_places = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+	gnt_style_read_workspaces(wm);
+	if (wm->workspaces == NULL) {
+		wm->cws = g_object_new(GNT_TYPE_WS, NULL);
+		gnt_ws_set_name(wm->cws, "default");
+		gnt_wm_add_workspace(wm, wm->cws);
+	} else {
+		wm->cws = wm->workspaces->data;
+	}
 	wm->event_stack = FALSE;
+	wm->tagged = NULL;
 	wm->windows = NULL;
 	wm->actions = NULL;
 	wm->nodes = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, free_node);
@@ -366,6 +354,7 @@
 		read_window_positions(wm);
 	g_timeout_add(IDLE_CHECK_INTERVAL * 1000, check_idle, NULL);
 	time(&last_active_time);
+	gnt_wm_switch_workspace(wm, 0);
 }
 
 static void
@@ -377,23 +366,23 @@
 	if (wm->_list.window || wm->menu)
 		return;
 
-	if (!wm->ordered || !wm->ordered->next)
+	if (!wm->cws->ordered || !wm->cws->ordered->next)
 		return;
 
-	w = wm->ordered->data;
-	pos = g_list_index(wm->list, w);
+	w = wm->cws->ordered->data;
+	pos = g_list_index(wm->cws->list, w);
 	pos += direction;
 
 	if (pos < 0)
-		wid = g_list_last(wm->list)->data;
-	else if (pos >= g_list_length(wm->list))
-		wid = wm->list->data;
+		wid = g_list_last(wm->cws->list)->data;
+	else if (pos >= g_list_length(wm->cws->list))
+		wid = wm->cws->list->data;
 	else if (pos >= 0)
-		wid = g_list_nth_data(wm->list, pos);
+		wid = g_list_nth_data(wm->cws->list, pos);
 
-	wm->ordered = g_list_bring_to_front(wm->ordered, wid);
+	wm->cws->ordered = g_list_bring_to_front(wm->cws->ordered, wid);
 
-	gnt_wm_raise_window(wm, wm->ordered->data);
+	gnt_wm_raise_window(wm, wm->cws->ordered->data);
 
 	if (w != wid) {
 		gnt_widget_set_focus(w, FALSE);
@@ -424,7 +413,7 @@
 	GList *l;
 	int n;
 
-	if (!wm->ordered)
+	if (!wm->cws->ordered)
 		return TRUE;
 
 	if (list)
@@ -432,9 +421,9 @@
 	else
 		n = 0;
 
-	w = wm->ordered->data;
+	w = wm->cws->ordered->data;
 
-	if ((l = g_list_nth(wm->list, n)) != NULL)
+	if ((l = g_list_nth(wm->cws->list, n)) != NULL)
 	{
 		gnt_wm_raise_window(wm, l->data);
 	}
@@ -453,17 +442,17 @@
 	GntWidget *window;
 	GntNode *node;
 
-	if (!wm->ordered)
+	if (!wm->cws->ordered)
 		return TRUE;
 
-	window = wm->ordered->data;
+	window = wm->cws->ordered->data;
 	node = g_hash_table_lookup(wm->nodes, window);
 	if (!node)
 		return TRUE;
 
 	if (node->scroll) {
 		node->scroll--;
-		copy_win(window, node);
+		gnt_wm_copy_win(window, node);
 		update_screen(wm);
 	}
 	return TRUE;
@@ -477,10 +466,10 @@
 	GntNode *node;
 	int w, h;
 
-	if (!wm->ordered)
+	if (!wm->cws->ordered)
 		return TRUE;
 
-	window = wm->ordered->data;
+	window = wm->cws->ordered->data;
 	node = g_hash_table_lookup(wm->nodes, window);
 	if (!node)
 		return TRUE;
@@ -488,7 +477,7 @@
 	gnt_widget_get_size(window, &w, &h);
 	if (h - node->scroll > getmaxy(node->window)) {
 		node->scroll++;
-		copy_win(window, node);
+		gnt_wm_copy_win(window, node);
 		update_screen(wm);
 	}
 	return TRUE;
@@ -502,14 +491,41 @@
 	if (wm->_list.window)
 		return TRUE;
 
-	if (wm->ordered) {
-		gnt_widget_destroy(wm->ordered->data);
+	if (wm->cws->ordered) {
+		gnt_widget_destroy(wm->cws->ordered->data);
 	}
 
 	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)
@@ -539,85 +555,124 @@
 static void
 window_list_activate(GntTree *tree, GntWM *wm)
 {
-	GntWidget *widget = gnt_tree_get_selection_data(GNT_TREE(tree));
+	GntBindable *sel = gnt_tree_get_selection_data(GNT_TREE(tree));
 
-	if (!wm->ordered || !widget)
+	gnt_widget_destroy(wm->_list.window);
+
+	if (!sel)
 		return;
 
-	gnt_widget_destroy(wm->_list.window);
-	gnt_wm_raise_window(wm, widget);
+	if (GNT_IS_WS(sel)) {
+		gnt_wm_switch_workspace(wm, g_list_index(wm->workspaces, sel));
+	} else {
+		gnt_wm_raise_window(wm, GNT_WIDGET(sel));
+	}
 }
 
 static void
-populate_window_list(GntWM *wm)
+populate_window_list(GntWM *wm, gboolean workspace)
 {
 	GList *iter;
 	GntTree *tree = GNT_TREE(wm->windows->tree);
-	for (iter = wm->list; iter; iter = iter->next) {
-		GntBox *box = GNT_BOX(iter->data);
+	if (!workspace) {
+		for (iter = wm->cws->list; iter; iter = iter->next) {
+			GntBox *box = GNT_BOX(iter->data);
 
-		gnt_tree_add_row_last(tree, box,
-				gnt_tree_create_row(tree, box->title), NULL);
-		update_window_in_list(wm, GNT_WIDGET(box));
+			gnt_tree_add_row_last(tree, box,
+					gnt_tree_create_row(tree, box->title), NULL);
+			update_window_in_list(wm, GNT_WIDGET(box));
+		}
+	} else {
+		GList *ws = wm->workspaces;
+		for (; ws; ws = ws->next) {
+			gnt_tree_add_row_last(tree, ws->data,
+					gnt_tree_create_row(tree, gnt_ws_get_name(GNT_WS(ws->data))), NULL);
+			for (iter = GNT_WS(ws->data)->list; iter; iter = iter->next) {
+				GntBox *box = GNT_BOX(iter->data);
+
+				gnt_tree_add_row_last(tree, box,
+						gnt_tree_create_row(tree, box->title), ws->data);
+				update_window_in_list(wm, GNT_WIDGET(box));
+			}
+		}
 	}
 }
 
 static gboolean
 window_list_key_pressed(GntWidget *widget, const char *text, GntWM *wm)
 {
-	if (text[1] == 0 && wm->ordered) {
-		GntWidget *sel = gnt_tree_get_selection_data(GNT_TREE(widget));
+	if (text[1] == 0 && wm->cws->ordered) {
+		GntBindable *sel = gnt_tree_get_selection_data(GNT_TREE(widget));
 		switch (text[0]) {
 			case '-':
 			case ',':
-				shift_window(wm, sel, -1);
+				if (GNT_IS_WS(sel)) {
+					/* reorder the workspace. */
+				} else
+					shift_window(wm, GNT_WIDGET(sel), -1);
 				break;
 			case '=':
 			case '.':
-				shift_window(wm, sel, 1);
+				if (GNT_IS_WS(sel)) {
+					/* reorder the workspace. */
+				} else
+					shift_window(wm, GNT_WIDGET(sel), 1);
 				break;
 			default:
 				return FALSE;
 		}
 		gnt_tree_remove_all(GNT_TREE(widget));
-		populate_window_list(wm);
+		populate_window_list(wm, GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "workspace")));
 		gnt_tree_set_selected(GNT_TREE(widget), sel);
 		return TRUE;
 	}
 	return FALSE;
 }
 
-static gboolean
-window_list(GntBindable *bindable, GList *null)
+static void
+list_of_windows(GntWM *wm, gboolean workspace)
 {
-	GntWM *wm = GNT_WM(bindable);
 	GntWidget *tree, *win;
 
-	if (wm->_list.window || wm->menu)
-		return TRUE;
-
-	if (!wm->ordered)
-		return TRUE;
-
 	setup__list(wm);
 	wm->windows = &wm->_list;
 
 	win = wm->windows->window;
 	tree = wm->windows->tree;
 
-	gnt_box_set_title(GNT_BOX(win), "Window List");
+	gnt_box_set_title(GNT_BOX(win), workspace ? "Workspace List" : "Window List");
 	
-	populate_window_list(wm);
+	populate_window_list(wm, workspace);
 
-	gnt_tree_set_selected(GNT_TREE(tree), wm->ordered->data);
+	if (wm->cws->ordered)
+		gnt_tree_set_selected(GNT_TREE(tree), wm->cws->ordered->data);
+	else if (workspace)
+		gnt_tree_set_selected(GNT_TREE(tree), wm->cws);
+
 	g_signal_connect(G_OBJECT(tree), "activate", G_CALLBACK(window_list_activate), wm);
 	g_signal_connect(G_OBJECT(tree), "key_pressed", G_CALLBACK(window_list_key_pressed), wm);
+	g_object_set_data(G_OBJECT(tree), "workspace", GINT_TO_POINTER(workspace));
 
 	gnt_tree_set_col_width(GNT_TREE(tree), 0, getmaxx(stdscr) / 3);
 	gnt_widget_set_size(tree, 0, getmaxy(stdscr) / 2);
 	gnt_widget_set_position(win, getmaxx(stdscr) / 3, getmaxy(stdscr) / 4);
 
 	gnt_widget_show(win);
+}
+
+static gboolean
+window_list(GntBindable *bindable, GList *null)
+{
+	GntWM *wm = GNT_WM(bindable);
+
+	if (wm->_list.window || wm->menu)
+		return TRUE;
+
+	if (!wm->cws->ordered)
+		return TRUE;
+
+	list_of_windows(wm, FALSE);
+
 	return TRUE;
 }
 
@@ -757,7 +812,7 @@
 static void
 shift_window(GntWM *wm, GntWidget *widget, int dir)
 {
-	GList *all = wm->list;
+	GList *all = wm->cws->list;
 	GList *list = g_list_find(all, widget);
 	int length, pos;
 	if (!list)
@@ -777,8 +832,8 @@
 
 	all = g_list_insert(all, widget, pos);
 	all = g_list_delete_link(all, list);
-	wm->list = all;
-	draw_taskbar(wm, FALSE);
+	wm->cws->list = all;
+	gnt_ws_draw_taskbar(wm->cws, FALSE);
 }
 
 static gboolean
@@ -788,7 +843,7 @@
 	if (wm->_list.window)
 		return TRUE;
 
-	shift_window(wm, wm->ordered->data, -1);
+	shift_window(wm, wm->cws->ordered->data, -1);
 	return TRUE;
 }
 
@@ -799,7 +854,7 @@
 	if (wm->_list.window)
 		return TRUE;
 
-	shift_window(wm, wm->ordered->data, 1);
+	shift_window(wm, wm->cws->ordered->data, 1);
 	return TRUE;
 }
 
@@ -923,7 +978,7 @@
 	for (i = 0; i < h; i += reverse_char(d, i, 0, set));
 	for (i = 0; i < h; i += reverse_char(d, i, w-1, set));
 
-	copy_win(win, g_hash_table_lookup(wm->nodes, win));
+	gnt_wm_copy_win(win, g_hash_table_lookup(wm->nodes, win));
 	update_screen(wm);
 }
 
@@ -933,11 +988,11 @@
 	GntWM *wm = GNT_WM(bindable);
 	if (wm->_list.window || wm->menu)
 		return TRUE;
-	if (!wm->ordered)
+	if (!wm->cws->ordered)
 		return TRUE;
 
 	wm->mode = GNT_KP_MODE_MOVE;
-	window_reverse(GNT_WIDGET(wm->ordered->data), TRUE, wm);
+	window_reverse(GNT_WIDGET(wm->cws->ordered->data), TRUE, wm);
 
 	return TRUE;
 }
@@ -948,11 +1003,11 @@
 	GntWM *wm = GNT_WM(bindable);
 	if (wm->_list.window || wm->menu)
 		return TRUE;
-	if (!wm->ordered)
+	if (!wm->cws->ordered)
 		return TRUE;
 
 	wm->mode = GNT_KP_MODE_RESIZE;
-	window_reverse(GNT_WIDGET(wm->ordered->data), TRUE, wm);
+	window_reverse(GNT_WIDGET(wm->cws->ordered->data), TRUE, wm);
 
 	return TRUE;
 }
@@ -984,7 +1039,7 @@
 
 	g_hash_table_foreach(wm->nodes, (GHFunc)refresh_node, NULL);
 	update_screen(wm);
-	draw_taskbar(wm, TRUE);
+	gnt_ws_draw_taskbar(wm->cws, TRUE);
 
 	return FALSE;
 }
@@ -1015,6 +1070,68 @@
 	return TRUE;
 }
 
+static void remove_tag(gpointer wid, gpointer wim)
+{
+	GntWM *wm = GNT_WM(wim);
+	GntWidget *w = GNT_WIDGET(wid);
+	wm->tagged = g_list_remove(wm->tagged, w);
+	mvwhline(w->window, 0, 1, ACS_HLINE | COLOR_PAIR(GNT_COLOR_NORMAL), 3);
+	gnt_widget_draw(w);
+}
+
+static gboolean
+tag_widget(GntBindable *b, GList *params)
+{
+	GntWM *wm = GNT_WM(b);
+	GntWidget *widget;
+
+	if (!wm->cws->ordered)
+		return FALSE;
+	widget = wm->cws->ordered->data;
+
+	if (g_list_find(wm->tagged, widget)) {
+		remove_tag(widget, wm);
+		return TRUE;
+	}
+
+	wm->tagged = g_list_prepend(wm->tagged, widget);
+	wbkgdset(widget->window, ' ' | COLOR_PAIR(GNT_COLOR_HIGHLIGHT));
+	mvwprintw(widget->window, 0, 1, "[T]");
+	gnt_widget_draw(widget);
+	return TRUE;
+}
+
+static void
+widget_move_ws(gpointer wid, gpointer w)
+{
+	GntWM *wm = GNT_WM(w);
+	gnt_wm_widget_move_workspace(wm, wm->cws, GNT_WIDGET(wid));
+}
+
+static gboolean
+place_tagged(GntBindable *b, GList *params)
+{
+	GntWM *wm = GNT_WM(b);
+	g_list_foreach(wm->tagged, widget_move_ws, wm);
+	g_list_foreach(wm->tagged, remove_tag, wm);
+	g_list_free(wm->tagged);
+	wm->tagged = NULL;
+	return TRUE;
+}
+
+static gboolean
+workspace_list(GntBindable *b, GList *params)
+{
+	GntWM *wm = GNT_WM(b);
+
+	if (wm->_list.window || wm->menu)
+		return TRUE;
+
+	list_of_windows(wm, TRUE);
+
+	return TRUE;
+}
+
 static gboolean
 ignore_keys_start(GntBindable *bindable, GList *n)
 {
@@ -1085,8 +1202,6 @@
 {
 	int i;
 
-	
-
 	klass->new_window = gnt_wm_new_window_real;
 	klass->decorate_window = NULL;
 	klass->close_window = NULL;
@@ -1165,6 +1280,7 @@
 					 NULL, NULL,
 					 g_cclosure_marshal_VOID__POINTER,
 					 G_TYPE_NONE, 1, G_TYPE_POINTER);
+
 	signals[SIG_GIVE_FOCUS] = 
 		g_signal_new("give_focus",
 					 G_TYPE_FROM_CLASS(klass),
@@ -1208,13 +1324,25 @@
 	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "refresh-screen", refresh_screen,
 				"\033" "l", NULL);
 	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "switch-window-n", switch_window_n,
-				NULL,       NULL);
+				NULL, NULL);
 	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "window-scroll-down", window_scroll_down,
 				"\033" GNT_KEY_CTRL_J, NULL);
 	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "window-scroll-up", window_scroll_up,
 				"\033" GNT_KEY_CTRL_K, NULL);
 	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "help-for-widget", help_for_widget,
 				"\033" "/", NULL);
+	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "workspace-next", workspace_next,
+				"\033" ">", NULL);
+	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "workspace-prev", workspace_prev,
+				"\033" "<", NULL);
+	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "window-tag", tag_widget,
+				"\033" "t", NULL);
+	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "place-tagged", place_tagged,
+				"\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), "help-for-wm", help_for_wm,
 				"\033" "\\", NULL);
 	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "help-for-window", help_for_window,
@@ -1267,6 +1395,118 @@
 
 	return type;
 }
+void
+gnt_wm_add_workspace(GntWM *wm, GntWS *ws)
+{
+	wm->workspaces = g_list_append(wm->workspaces, ws);
+}
+
+gboolean
+gnt_wm_switch_workspace(GntWM *wm, gint n)
+{
+	GntWS *s = g_list_nth_data(wm->workspaces, n);
+	if (!s)
+		return FALSE;
+
+	if (wm->_list.window) {
+		gnt_widget_destroy(wm->_list.window);
+	}
+	gnt_ws_hide(wm->cws, wm->nodes);
+	wm->cws = s;
+	gnt_ws_show(wm->cws, wm->nodes);
+
+	gnt_ws_draw_taskbar(wm->cws, TRUE);
+	update_screen(wm);
+	if (wm->cws->ordered) {
+		gnt_widget_set_focus(wm->cws->ordered->data, TRUE);
+		gnt_wm_raise_window(wm, wm->cws->ordered->data);
+	}
+
+	if (act && g_list_find(act, wm->cws)) {
+		act = g_list_remove(act, wm->cws);
+		update_act_msg();
+	}
+	return TRUE;
+}
+
+gboolean
+gnt_wm_switch_workspace_prev(GntWM *wm)
+{
+	int n = g_list_index(wm->workspaces, wm->cws);
+	return gnt_wm_switch_workspace(wm, --n);
+}
+
+gboolean
+gnt_wm_switch_workspace_next(GntWM *wm)
+{
+	int n = g_list_index(wm->workspaces, wm->cws);
+	return gnt_wm_switch_workspace(wm, ++n);
+}
+
+static gboolean
+workspace_next(GntBindable *wm, GList *n)
+{
+	return gnt_wm_switch_workspace_next(GNT_WM(wm));
+}
+
+static gboolean
+workspace_prev(GntBindable *wm, GList *n)
+{
+	return gnt_wm_switch_workspace_prev(GNT_WM(wm));
+}
+
+void
+gnt_wm_widget_move_workspace(GntWM *wm, GntWS *neww, GntWidget *widget)
+{
+	GntWS *oldw = gnt_wm_widget_find_workspace(wm, widget);
+	GntNode *node;
+	if (!oldw || oldw == neww)
+		return;
+	node = g_hash_table_lookup(wm->nodes, widget);
+	if (node && node->ws == neww)
+		return;
+
+	if (node)
+		node->ws = neww;
+
+	gnt_ws_remove_widget(oldw, widget);
+	gnt_ws_add_widget(neww, widget);
+	if (neww == wm->cws) {
+		gnt_ws_widget_show(widget, wm->nodes);
+	} else {
+		gnt_ws_widget_hide(widget, wm->nodes);
+	}
+}
+
+static gint widget_in_workspace(gconstpointer workspace, gconstpointer wid)
+{
+	GntWS *s = (GntWS *)workspace;
+	if (s->list && g_list_find(s->list, wid))
+		return 0;
+	return 1;
+}
+
+GntWS *gnt_wm_widget_find_workspace(GntWM *wm, GntWidget *widget)
+{
+	GList *l = g_list_find_custom(wm->workspaces, widget, widget_in_workspace);
+	if (l)
+		return l->data;
+	return NULL;
+}
+
+static void free_workspaces(gpointer data, gpointer n)
+{
+	GntWS *s = data;
+	g_free(s->name);
+}
+
+void gnt_wm_set_workspaces(GntWM *wm, GList *workspaces)
+{
+	g_list_foreach(wm->workspaces, free_workspaces, NULL);
+	wm->workspaces = workspaces;
+	gnt_wm_switch_workspace(wm, 0);
+}
+
 static void
 update_window_in_list(GntWM *wm, GntWidget *wid)
 {
@@ -1275,7 +1515,7 @@
 	if (wm->windows == NULL)
 		return;
 
-	if (wid == wm->ordered->data)
+	if (wm->cws->ordered && wid == wm->cws->ordered->data)
 		flag |= GNT_TEXT_FLAG_DIM;
 	else if (GNT_WIDGET_IS_FLAG_SET(wid, GNT_WIDGET_URGENT))
 		flag |= GNT_TEXT_FLAG_BOLD;
@@ -1283,6 +1523,29 @@
 	gnt_tree_set_row_flags(GNT_TREE(wm->windows->tree), wid, flag);
 }
 
+static gboolean
+match_title(gpointer title, gpointer n, gpointer wid_title)
+{
+	/* maybe check for regex.h? */
+	if (g_strrstr((gchar *)wid_title, (gchar *)title))
+		return TRUE;
+	return FALSE;
+}
+
+static GntWS *
+new_widget_find_workspace(GntWM *wm, GntWidget *widget, gchar *wid_title)
+{
+	GntWS *ret;
+	const gchar *name;
+	ret = g_hash_table_find(wm->title_places, match_title, wid_title);
+	if (ret)
+		return ret;
+	name = gnt_widget_get_name(widget);
+	if (name)
+		ret = g_hash_table_lookup(wm->name_places, name);
+	return ret ? ret : wm->cws;
+}
+
 static void
 gnt_wm_new_window_real(GntWM *wm, GntWidget *widget)
 {
@@ -1329,7 +1592,7 @@
 		w = MIN(w, maxx);
 		h = MIN(h, maxy);
 		node->window = newwin(h + shadow, w + shadow, y, x);
-		copy_win(widget, node);
+		gnt_wm_copy_win(widget, node);
 	}
 #endif
 
@@ -1337,18 +1600,25 @@
 	set_panel_userptr(node->panel, node);
 
 	if (!transient) {
+		GntWS *ws = wm->cws;
 		if (node->me != wm->_list.window) {
 			GntWidget *w = NULL;
 
-			if (wm->ordered)
-				w = wm->ordered->data;
+			if (GNT_IS_BOX(widget)) {
+				char *title = GNT_BOX(widget)->title;
+				ws = new_widget_find_workspace(wm, widget, title);
+			}
 
-			wm->list = g_list_append(wm->list, widget);
+			if (ws->ordered)
+				w = ws->ordered->data;
+
+			node->ws = ws;
+			ws->list = g_list_append(ws->list, widget);
 
 			if (wm->event_stack)
-				wm->ordered = g_list_prepend(wm->ordered, widget);
+				ws->ordered = g_list_prepend(ws->ordered, widget);
 			else
-				wm->ordered = g_list_append(wm->ordered, widget);
+				ws->ordered = g_list_append(ws->ordered, widget);
 
 			gnt_widget_set_focus(widget, TRUE);
 			if (w)
@@ -1360,6 +1630,8 @@
 		} else {
 			bottom_panel(node->panel);     /* New windows should not grab focus */
 			gnt_widget_set_urgent(node->me);
+			if (wm->cws != ws)
+				gnt_ws_widget_hide(widget, wm->nodes);
 		}
 	}
 }
@@ -1393,13 +1665,13 @@
 				&& GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_CAN_TAKE_FOCUS)) {
 			gnt_tree_add_row_last(GNT_TREE(wm->windows->tree), widget,
 					gnt_tree_create_row(GNT_TREE(wm->windows->tree), GNT_BOX(widget)->title),
-					NULL);
+					g_object_get_data(G_OBJECT(wm->windows->tree), "workspace") ? wm->cws : NULL);
 			update_window_in_list(wm, widget);
 		}
 	}
 
 	update_screen(wm);
-	draw_taskbar(wm, FALSE);
+	gnt_ws_draw_taskbar(wm->cws, FALSE);
 }
 
 void gnt_wm_window_decorate(GntWM *wm, GntWidget *widget)
@@ -1409,9 +1681,12 @@
 
 void gnt_wm_window_close(GntWM *wm, GntWidget *widget)
 {
+	GntWS *s;
 	GntNode *node;
 	int pos;
 
+	s = gnt_wm_widget_find_workspace(wm, widget);
+
 	if ((node = g_hash_table_lookup(wm->nodes, widget)) == NULL)
 		return;
 
@@ -1422,18 +1697,20 @@
 		gnt_tree_remove(GNT_TREE(wm->windows->tree), widget);
 	}
 
-	pos = g_list_index(wm->list, widget);
+	if (s) {
+		pos = g_list_index(s->list, widget);
 
-	if (pos != -1) {
-		wm->list = g_list_remove(wm->list, widget);
-		wm->ordered = g_list_remove(wm->ordered, widget);
+		if (pos != -1) {
+			s->list = g_list_remove(s->list, widget);
+			s->ordered = g_list_remove(s->ordered, widget);
 
-		if (wm->ordered)
-			gnt_wm_raise_window(wm, wm->ordered->data);
+			if (s->ordered && wm->cws == s)
+				gnt_wm_raise_window(wm, s->ordered->data);
+		}
 	}
 
 	update_screen(wm);
-	draw_taskbar(wm, FALSE);
+	gnt_ws_draw_taskbar(wm->cws, FALSE);
 }
 
 time_t gnt_wm_get_idle_time()
@@ -1455,7 +1732,7 @@
 				return TRUE;
 			}
 		}
-		return wm->ordered ? gnt_widget_key_pressed(GNT_WIDGET(wm->ordered->data), keys) : FALSE;
+		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)) {
@@ -1463,10 +1740,10 @@
 	}
 
 	/* Do some manual checking */
-	if (wm->ordered && wm->mode != GNT_KP_MODE_NORMAL) {
+	if (wm->cws->ordered && wm->mode != GNT_KP_MODE_NORMAL) {
 		int xmin = 0, ymin = 0, xmax = getmaxx(stdscr), ymax = getmaxy(stdscr) - 1;
 		int x, y, w, h;
-		GntWidget *widget = GNT_WIDGET(wm->ordered->data);
+		GntWidget *widget = GNT_WIDGET(wm->cws->ordered->data);
 		int ox, oy, ow, oh;
 
 		gnt_widget_get_position(widget, &x, &y);
@@ -1542,8 +1819,8 @@
 		ret = gnt_widget_key_pressed(GNT_WIDGET(wm->menu), keys);
 	else if (wm->_list.window)
 		ret = gnt_widget_key_pressed(wm->_list.window, keys);
-	else if (wm->ordered)
-		ret = gnt_widget_key_pressed(GNT_WIDGET(wm->ordered->data), keys);
+	else if (wm->cws->ordered)
+		ret = gnt_widget_key_pressed(GNT_WIDGET(wm->cws->ordered->data), keys);
 	return ret;
 }
 
@@ -1677,9 +1954,9 @@
 		return;
 	
 	if (widget != wm->_list.window && !GNT_IS_MENU(widget) &&
-				wm->ordered->data != widget) {
-		GntWidget *w = wm->ordered->data;
-		wm->ordered = g_list_bring_to_front(wm->ordered, widget);
+				wm->cws->ordered->data != widget) {
+		GntWidget *w = wm->cws->ordered->data;
+		wm->cws->ordered = g_list_bring_to_front(wm->cws->ordered, widget);
 		gnt_widget_set_focus(w, FALSE);
 	}
 
@@ -1693,27 +1970,35 @@
 		top_panel(nd->panel);
 	}
 	update_screen(wm);
-	draw_taskbar(wm, FALSE);
+	gnt_ws_draw_taskbar(wm->cws, FALSE);
 }
 
 void gnt_wm_update_window(GntWM *wm, GntWidget *widget)
 {
-	GntNode *node;
+	GntNode *node = NULL;
+	GntWS *ws;
 
 	while (widget->parent)
 		widget = widget->parent;
 	if (!GNT_IS_MENU(widget))
 		gnt_box_sync_children(GNT_BOX(widget));
 
+	ws = gnt_wm_widget_find_workspace(wm, widget);
 	node = g_hash_table_lookup(wm->nodes, widget);
 	if (node == NULL) {
 		gnt_wm_new_window(wm, widget);
 	} else
 		g_signal_emit(wm, signals[SIG_UPDATE_WIN], 0, node);
 
-	copy_win(widget, node);
-	update_screen(wm);
-	draw_taskbar(wm, FALSE);
+	if (ws == wm->cws || GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_TRANSIENT)) {
+		gnt_wm_copy_win(widget, node);
+		update_screen(wm);
+		gnt_ws_draw_taskbar(wm->cws, FALSE);
+	} else if (ws != wm->cws && GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_URGENT)) {
+		if (!act || (act && !g_list_find(act, ws)))
+			act = g_list_prepend(act, ws);
+		update_act_msg();
+	}
 }
 
 gboolean gnt_wm_process_click(GntWM *wm, GntMouseEvent event, int x, int y, GntWidget *widget)
@@ -1726,6 +2011,9 @@
 
 void gnt_wm_raise_window(GntWM *wm, GntWidget *widget)
 {
+	GntWS *ws = gnt_wm_widget_find_workspace(wm, widget);
+	if (wm->cws != ws)
+		gnt_wm_switch_workspace(wm, g_list_index(wm->workspaces, ws));
 	g_signal_emit(wm, signals[SIG_GIVE_FOCUS], 0, widget);
 }
 
--- a/finch/libgnt/gntwm.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/libgnt/gntwm.h	Tue Jun 12 21:21:37 2007 +0000
@@ -20,9 +20,12 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
+#ifndef GNTWM_H
+#define GNTWM_H
 
 #include "gntwidget.h"
 #include "gntmenu.h"
+#include "gntws.h"
 
 #include <panel.h>
 #include <time.h>
@@ -48,9 +51,10 @@
 	WINDOW *window;
 	int scroll;
 	PANEL *panel;
+	GntWS *ws;
 } GntNode;
 
-typedef struct _GnttWM GntWM;
+typedef struct _GntWM GntWM;
 
 typedef struct _GntPosition
 {
@@ -67,14 +71,15 @@
 	void (*callback)();
 } GntAction;
 
-struct _GnttWM
+struct _GntWM
 {
 	GntBindable inherit;
 
 	GMainLoop *loop;
 
-	GList *list;      /* List of windows ordered on their creation time */
-	GList *ordered;   /* List of windows ordered on their focus */
+	GList *workspaces;
+	GList *tagged; /* tagged windows */
+	GntWS *cws;
 
 	struct {
 		GntWidget *window;
@@ -84,6 +89,8 @@
 		*actions;         /* Action-list window */
 
 	GHashTable *nodes;    /* GntWidget -> GntNode */
+	GHashTable *name_places;    /* window name -> ws*/
+	GHashTable *title_places;    /* window title -> ws */
 
 	GList *acts;          /* List of actions */
 
@@ -173,6 +180,15 @@
  */
 GType gnt_wm_get_gtype(void);
 
+void gnt_wm_add_workspace(GntWM *wm, GntWS *ws);
+
+gboolean gnt_wm_switch_workspace(GntWM *wm, gint n);
+gboolean gnt_wm_switch_workspace_prev(GntWM *wm);
+gboolean gnt_wm_switch_workspace_next(GntWM *wm);
+void gnt_wm_widget_move_workspace(GntWM *wm, GntWS *neww, GntWidget *widget);
+void gnt_wm_set_workspaces(GntWM *wm, GList *workspaces);
+GntWS *gnt_wm_widget_find_workspace(GntWM *wm, GntWidget *widget);
+
 /**
  * 
  * @param wm
@@ -254,6 +270,8 @@
  */
 void gnt_wm_set_event_stack(GntWM *wm, gboolean set);
 
+void gnt_wm_copy_win(GntWidget *widget, GntNode *node);
+
 /**
  * 
  *
@@ -262,3 +280,4 @@
 time_t gnt_wm_get_idle_time(void);
 
 G_END_DECLS
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/libgnt/gntws.c	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,161 @@
+#include <gmodule.h>
+
+#include "gntbox.h"
+#include "gntwidget.h"
+#include "gntwindow.h"
+#include "gntwm.h"
+#include "gntws.h"
+
+static void
+widget_hide(gpointer data, gpointer nodes)
+{
+	GntWidget *widget = GNT_WIDGET(data);
+	GntNode *node = g_hash_table_lookup(nodes, widget);
+	if (GNT_IS_WINDOW(widget))
+		gnt_window_workspace_hiding(GNT_WINDOW(widget));
+	hide_panel(node->panel);
+}
+
+static void
+widget_show(gpointer data, gpointer nodes)
+{
+	GntNode *node = g_hash_table_lookup(nodes, data);
+	GNT_WIDGET_UNSET_FLAGS(GNT_WIDGET(data), GNT_WIDGET_INVISIBLE);
+	if (node) {
+		show_panel(node->panel);
+		gnt_wm_copy_win(GNT_WIDGET(data), node);
+	}
+}
+
+void
+gnt_ws_draw_taskbar(GntWS *ws, gboolean reposition)
+{
+	static WINDOW *taskbar = NULL;
+	GList *iter;
+	int n, width = 0;
+	int i;
+
+	if (taskbar == NULL) {
+		taskbar = newwin(1, getmaxx(stdscr), getmaxy(stdscr) - 1, 0);
+	} else if (reposition) {
+		int Y_MAX = getmaxy(stdscr) - 1;
+		mvwin(taskbar, Y_MAX, 0);
+	}
+
+	wbkgdset(taskbar, '\0' | COLOR_PAIR(GNT_COLOR_NORMAL));
+	werase(taskbar);
+
+	n = g_list_length(ws->list);
+	if (n)
+		width = getmaxx(stdscr) / n;
+
+	for (i = 0, iter = ws->list; iter; iter = iter->next, i++) {
+		GntWidget *w = iter->data;
+		int color;
+		const char *title;
+
+		if (w == ws->ordered->data) {
+			/* This is the current window in focus */
+			color = GNT_COLOR_TITLE;
+		} else if (GNT_WIDGET_IS_FLAG_SET(w, GNT_WIDGET_URGENT)) {
+			/* This is a window with the URGENT hint set */
+			color = GNT_COLOR_URGENT;
+		} else {
+			color = GNT_COLOR_NORMAL;
+		}
+		wbkgdset(taskbar, '\0' | COLOR_PAIR(color));
+		if (iter->next)
+			mvwhline(taskbar, 0, width * i, ' ' | COLOR_PAIR(color), width);
+		else
+			mvwhline(taskbar, 0, width * i, ' ' | COLOR_PAIR(color), getmaxx(stdscr) - width * i);
+		title = GNT_BOX(w)->title;
+		mvwprintw(taskbar, 0, width * i, "%s", title ? title : "<gnt>");
+		if (i)
+			mvwaddch(taskbar, 0, width *i - 1, ACS_VLINE | A_STANDOUT | COLOR_PAIR(GNT_COLOR_NORMAL));
+	}
+	wrefresh(taskbar);
+}
+
+static void
+gnt_ws_init(GTypeInstance *instance, gpointer class)
+{
+	GntWS *ws = GNT_WS(instance);
+	ws->list = NULL;
+	ws->ordered = NULL;
+	ws->name = NULL;
+}
+
+void gnt_ws_add_widget(GntWS *ws, GntWidget* wid)
+{
+	ws->list = g_list_append(ws->list, wid);
+	ws->ordered = g_list_prepend(ws->ordered, wid);
+}
+
+void gnt_ws_remove_widget(GntWS *ws, GntWidget* wid)
+{
+	ws->list = g_list_remove(ws->list, wid);
+	ws->ordered = g_list_remove(ws->ordered, wid);
+}
+
+void
+gnt_ws_set_name(GntWS *ws, const gchar *name)
+{
+	g_free(ws->name);
+	ws->name = g_strdup(name);
+}
+
+void
+gnt_ws_hide(GntWS *ws, GHashTable *nodes)
+{
+	g_list_foreach(ws->ordered, widget_hide, nodes);
+}
+
+void gnt_ws_widget_hide(GntWidget *widget, GHashTable *nodes) {
+	widget_hide(widget, nodes);
+}
+
+void gnt_ws_widget_show(GntWidget *widget, GHashTable *nodes) {
+	widget_show(widget, nodes);
+}
+
+void
+gnt_ws_show(GntWS *ws, GHashTable *nodes)
+{
+	GList *l;
+	for (l = g_list_last(ws->ordered); l; l = g_list_previous(l))
+		widget_show(l->data, nodes);
+}
+
+GType
+gnt_ws_get_gtype(void)
+{
+	static GType type = 0;
+
+	if(type == 0) {
+		static const GTypeInfo info = {
+			sizeof(GntWSClass),
+			NULL,					/* base_init		*/
+			NULL,					/* base_finalize	*/
+			NULL,
+			/*(GClassInitFunc)gnt_ws_class_init,*/
+			NULL,
+			NULL,					/* class_data		*/
+			sizeof(GntWS),
+			0,						/* n_preallocs		*/
+			gnt_ws_init,			/* instance_init	*/
+			NULL					/* value_table		*/
+		};
+
+		type = g_type_register_static(GNT_TYPE_BINDABLE,
+									  "GntWS",
+									  &info, 0);
+	}
+
+	return type;
+}
+
+const char * gnt_ws_get_name(GntWS *ws)
+{
+	return ws->name;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/libgnt/gntws.h	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,59 @@
+#ifndef GNTWS_H
+#define GNTWS_H
+
+#include "gntwidget.h"
+
+#include <panel.h>
+
+#define GNT_TYPE_WS				(gnt_ws_get_gtype())
+#define GNT_WS(obj)				(G_TYPE_CHECK_INSTANCE_CAST((obj), GNT_TYPE_WS, GntWS))
+#define GNT_IS_WS(obj)			(G_TYPE_CHECK_INSTANCE_TYPE((obj), GNT_TYPE_WS))
+#define GNT_IS_WS_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE((klass), GNT_TYPE_WS))
+#define GNT_WS_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS((obj), GNT_TYPE_WS, GntWSClass))
+
+typedef struct _GntWS GntWS;
+
+struct _GntWS
+{
+	GntBindable inherit;
+	gchar *name;
+	GList *list;
+	GList *ordered;
+	gpointer ui_data;
+	
+	void *res1;
+	void *res2;
+	void *res3;
+	void *res4;
+};
+
+typedef struct _GntWSClass GntWSClass;
+
+struct _GntWSClass
+{
+	GntBindableClass parent;
+
+	void (*draw_taskbar)(GntWS *ws, gboolean );
+
+	void (*res1)(void);
+	void (*res2)(void);
+	void (*res3)(void);
+	void (*res4)(void);
+};
+
+G_BEGIN_DECLS
+
+GType gnt_ws_get_gtype(void);
+
+void gnt_ws_set_name(GntWS *, const gchar *);
+void gnt_ws_add_widget(GntWS *, GntWidget *);
+void gnt_ws_remove_widget(GntWS *, GntWidget *);
+void gnt_ws_widget_hide(GntWidget *, GHashTable *nodes);
+void gnt_ws_widget_show(GntWidget *, GHashTable *nodes);
+void gnt_ws_draw_taskbar(GntWS *, gboolean reposition);
+void gnt_ws_hide(GntWS *, GHashTable *);
+void gnt_ws_show(GntWS *, GHashTable *);
+
+const char * gnt_ws_get_name(GntWS *ws);
+
+#endif
--- a/finch/libgnt/wms/Makefile.am	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/libgnt/wms/Makefile.am	Tue Jun 12 21:21:37 2007 +0000
@@ -1,9 +1,16 @@
 s_la_LDFLAGS             = -module -avoid-version
+irssi_la_LDFLAGS         = -module -avoid-version
 
 plugin_LTLIBRARIES = \
-	s.la
+	s.la \
+	irssi.la
+
+plugindir = $(libdir)/gnt
 
-plugindir = $(libdir)/finch
+irssi_la_SOURCES = irssi.c
+irssi_la_LIBADD =  \
+  $(GLIB_LIBS) \
+  $(top_builddir)/finch/libgnt/libgnt.la
 
 s_la_SOURCES = s.c
 s_la_LIBADD =  \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/finch/libgnt/wms/irssi.c	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,319 @@
+/**
+ * 1. Buddylist is aligned on the left.
+ * 2. The rest of the screen is split into MxN grid for conversation windows.
+ * 	- M = irssi-split-h in ~/.gntrc:[general]
+ * 	- N = irssi-split-v in ~/.gntrc:[general]
+ *	- Press alt-shift-k/j/l/h to move the selected window to the frame
+ *	  above/below/left/right of the current frame.
+ * 3. All the other windows are always centered.
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "gnt.h"
+#include "gntbox.h"
+#include "gntmenu.h"
+#include "gntstyle.h"
+#include "gntwm.h"
+#include "gntwindow.h"
+#include "gntlabel.h"
+
+#define TYPE_IRSSI				(irssi_get_gtype())
+
+typedef struct _Irssi
+{
+	GntWM inherit;
+	int vert;
+	int horiz;
+
+	/* This is changed whenever the buddylist is opened/closed or resized. */
+	int buddylistwidth;
+} Irssi;
+
+typedef struct _IrssiClass
+{
+	GntWMClass inherit;
+} IrssiClass;
+
+GType irssi_get_gtype(void);
+void gntwm_init(GntWM **wm);
+
+static void (*org_new_window)(GntWM *wm, GntWidget *win);
+
+static void
+get_xywh_for_frame(Irssi *irssi, int hor, int vert, int *x, int *y, int *w, int *h)
+{
+	int width, height, rx, ry;
+
+	width = (getmaxx(stdscr) - irssi->buddylistwidth) / irssi->horiz;
+	height = (getmaxy(stdscr) - 1) / irssi->vert;
+
+	rx = irssi->buddylistwidth;
+	if (hor)
+		rx += hor * width;
+	rx++;
+
+	ry = 0;
+	if (vert)
+		ry += vert * height + 1;
+
+	if (x) *x = rx;
+	if (y) *y = ry;
+	if (w) {
+		*w = (hor == irssi->horiz - 1) ? (getmaxx(stdscr) - rx) : (width - 1);
+	}
+	if (h) {
+		*h = (vert == irssi->vert - 1) ? (getmaxy(stdscr) - 1 - ry) : (height - !!vert);
+	}
+}
+
+static void
+draw_line_separators(Irssi *irssi)
+{
+	int x, y;
+	int width, height;
+
+	wclear(stdscr);
+	/* Draw the separator for the buddylist */
+	if (irssi->buddylistwidth)
+		mvwvline(stdscr, 0, irssi->buddylistwidth,
+				ACS_VLINE | COLOR_PAIR(GNT_COLOR_NORMAL), getmaxy(stdscr) - 1);
+
+	/* Now the separators for the conversation windows */
+	width = (getmaxx(stdscr) - irssi->buddylistwidth) / irssi->horiz;
+	height = (getmaxy(stdscr) - 1) / irssi->vert;
+	for (x = 1; x < irssi->horiz; x++) {
+		mvwvline(stdscr, 0, irssi->buddylistwidth + x * width,
+				ACS_VLINE | COLOR_PAIR(GNT_COLOR_NORMAL), getmaxy(stdscr) - 1);
+	}
+
+	for (y = 1; y < irssi->vert; y++) {
+		mvwhline(stdscr, y * height, irssi->buddylistwidth + 1, ACS_HLINE | COLOR_PAIR(GNT_COLOR_NORMAL),
+				getmaxx(stdscr) - irssi->buddylistwidth);
+		for (x = 1; x < irssi->horiz; x++) {
+			mvwaddch(stdscr, y * height, x * width + irssi->buddylistwidth, ACS_PLUS | COLOR_PAIR(GNT_COLOR_NORMAL));
+		}
+		if (irssi->buddylistwidth)
+			mvwaddch(stdscr, y * height, irssi->buddylistwidth, ACS_LTEE | COLOR_PAIR(GNT_COLOR_NORMAL));
+	}
+}
+
+static gboolean
+is_budddylist(GntWidget *win)
+{
+	const char *name = gnt_widget_get_name(win);
+	if (name && strcmp(name, "buddylist") == 0)
+		return TRUE;
+	return FALSE;
+}
+
+static void
+remove_border_set_position_size(GntWM *wm, GntWidget *win, int x, int y, int w, int h)
+{
+	gnt_box_set_toplevel(GNT_BOX(win), FALSE);
+	GNT_WIDGET_SET_FLAGS(win, GNT_WIDGET_CAN_TAKE_FOCUS);
+
+	gnt_widget_set_position(win, x, y);
+	mvwin(win->window, y, x);
+	gnt_widget_set_size(win, (w < 0) ? -1 : w + 2, h + 2);
+}
+
+static void
+irssi_new_window(GntWM *wm, GntWidget *win)
+{
+	const char *name;
+	int x, y, w, h;
+
+	name = gnt_widget_get_name(win);
+	if (!name || strcmp(name, "conversation-window")) {
+		if (!GNT_IS_MENU(win) && !GNT_WIDGET_IS_FLAG_SET(win, GNT_WIDGET_TRANSIENT)) {
+			if ((!name || strcmp(name, "buddylist"))) {
+				gnt_widget_get_size(win, &w, &h);
+				x = (getmaxx(stdscr) - w) / 2;
+				y = (getmaxy(stdscr) - h) / 2;
+				gnt_widget_set_position(win, x, y);
+				mvwin(win->window, y, x);
+			} else {
+				remove_border_set_position_size(wm, win, 0, 0, -1, getmaxy(stdscr) - 1);
+				gnt_widget_get_size(win, &((Irssi*)wm)->buddylistwidth, NULL);
+				draw_line_separators((Irssi*)wm);
+			}
+		}
+		org_new_window(wm, win);
+		return;
+	}
+
+	/* The window we have here is a conversation window. */
+
+	/* XXX: There should be some way to remember which frame a conversation window
+	 * was in the last time. Perhaps save them in some ~/.gntpositionirssi or some
+	 * such. */
+	get_xywh_for_frame((Irssi*)wm, 0, 0, &x, &y, &w, &h);
+	remove_border_set_position_size(wm, win, x, y, w, h);
+	org_new_window(wm, win);
+}
+
+static void
+irssi_window_resized(GntWM *wm, GntNode *node)
+{
+	if (!is_budddylist(node->me))
+		return;
+
+	gnt_widget_get_size(node->me, &((Irssi*)wm)->buddylistwidth, NULL);
+	draw_line_separators((Irssi*)wm);
+}
+
+static gboolean
+irssi_close_window(GntWM *wm, GntWidget *win)
+{
+	if (is_budddylist(win))
+		((Irssi*)wm)->buddylistwidth = 0;
+	return FALSE;
+}
+
+static gboolean
+update_conv_window_title(GntNode *node)
+{
+	char title[256];
+	snprintf(title, sizeof(title), "%d: %s",
+			(int)g_object_get_data(G_OBJECT(node->me), "irssi-index") + 1, GNT_BOX(node->me)->title);
+	wbkgdset(node->window, '\0' | COLOR_PAIR(gnt_widget_has_focus(node->me) ? GNT_COLOR_TITLE : GNT_COLOR_TITLE_D));
+	mvwaddstr(node->window, 0, 0, title);
+	update_panels();
+	doupdate();
+	return FALSE;
+}
+
+static void
+irssi_update_window(GntWM *wm, GntNode *node)
+{
+	GntWidget *win = node->me;
+	const char *name = gnt_widget_get_name(win);
+	if (!name || !GNT_IS_BOX(win) || strcmp(name, "conversation-window"))
+		return;
+	g_object_set_data(G_OBJECT(win), "irssi-index", GINT_TO_POINTER(g_list_index(wm->list, win)));
+	g_timeout_add(0, (GSourceFunc)update_conv_window_title, node);
+}
+
+static void
+find_window_position(Irssi *irssi, GntWidget *win, int *h, int *v)
+{
+	int x, y;
+	int width, height;
+
+	gnt_widget_get_position(win, &x, &y);
+	width = (getmaxx(stdscr) - irssi->buddylistwidth) / irssi->horiz;
+	height = (getmaxy(stdscr) - 1) / irssi->vert;
+
+	if (h)
+		*h = (x - irssi->buddylistwidth) / width;
+	if (v)
+		*v = y / height;
+}
+
+static gboolean
+move_direction(GntBindable *bindable, GList *list)
+{
+	GntWM *wm = GNT_WM(bindable);
+	Irssi *irssi = (Irssi*)wm;
+	int vert, hor;
+	int x, y, w, h;
+	GntWidget *win;
+
+	if (wm->ordered == NULL || is_budddylist(win = GNT_WIDGET(wm->ordered->data)))
+		return FALSE;
+
+	find_window_position(irssi, win, &hor, &vert);
+
+	switch ((int)list->data) {
+		case 'k':
+			vert = MAX(0, vert - 1);
+			break;
+		case 'j':
+			vert = MIN(vert + 1, irssi->vert - 1);
+			break;
+		case 'l':
+			hor = MIN(hor + 1, irssi->horiz - 1);
+			break;
+		case 'h':
+			hor = MAX(0, hor - 1);
+			break;
+	}
+	get_xywh_for_frame(irssi, hor, vert, &x, &y, &w, &h);
+	gnt_wm_move_window(wm, win, x, y);
+	gnt_wm_resize_window(wm, win, w, h);
+	return TRUE;
+}
+
+static void
+irssi_class_init(IrssiClass *klass)
+{
+	GntWMClass *pclass = GNT_WM_CLASS(klass);
+
+	org_new_window = pclass->new_window;
+
+	pclass->new_window = irssi_new_window;
+	pclass->window_resized = irssi_window_resized;
+	pclass->close_window = irssi_close_window;
+	pclass->window_update = irssi_update_window;
+
+	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "move-up", move_direction,
+			"\033" "K", GINT_TO_POINTER('k'), NULL);
+	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "move-down", move_direction,
+			"\033" "J", GINT_TO_POINTER('j'), NULL);
+	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "move-right", move_direction,
+			"\033" "L", GINT_TO_POINTER('l'), NULL);
+	gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "move-left", move_direction,
+			"\033" "H", GINT_TO_POINTER('h'), NULL);
+
+	gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), GNT_BINDABLE_CLASS(klass));
+	GNTDEBUG;
+}
+
+void gntwm_init(GntWM **wm)
+{
+	const char *style = NULL;
+	Irssi *irssi;
+
+	irssi = g_object_new(TYPE_IRSSI, NULL);
+	*wm = GNT_WM(irssi);
+
+	style = gnt_style_get_from_name("irssi-split-v");
+	irssi->vert = style ? atoi(style) : 1;
+
+	style = gnt_style_get_from_name("irssi-split-h");
+	irssi->horiz = style ? atoi(style) : 1;
+
+	irssi->vert = MAX(irssi->vert, 1);
+	irssi->horiz = MAX(irssi->horiz, 1);
+
+	irssi->buddylistwidth = 0;
+}
+
+GType irssi_get_gtype(void)
+{
+	static GType type = 0;
+
+	if(type == 0) {
+		static const GTypeInfo info = {
+			sizeof(IrssiClass),
+			NULL,           /* base_init		*/
+			NULL,           /* base_finalize	*/
+			(GClassInitFunc)irssi_class_init,
+			NULL,
+			NULL,           /* class_data		*/
+			sizeof(Irssi),
+			0,              /* n_preallocs		*/
+			NULL,	        /* instance_init	*/
+			NULL
+		};
+
+		type = g_type_register_static(GNT_TYPE_WM,
+		                              "GntIrssi",
+		                              &info, 0);
+	}
+
+	return type;
+}
+
--- a/finch/libgnt/wms/s.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/finch/libgnt/wms/s.c	Tue Jun 12 21:21:37 2007 +0000
@@ -121,7 +121,7 @@
 static GntWidget *
 find_widget(GntWM *wm, const char *wname)
 {
-	const GList *iter = wm->list;
+	const GList *iter = wm->cws->list;
 	for (; iter; iter = iter->next) {
 		GntWidget *widget = iter->data;
 		const char *name = gnt_widget_get_name(widget);
--- a/libpurple/Makefile.am	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/Makefile.am	Tue Jun 12 21:21:37 2007 +0000
@@ -151,7 +151,7 @@
 dbus_headers  = dbus-bindings.h dbus-purple.h dbus-server.h dbus-useful.h dbus-define-api.h
 
 dbus_exported = dbus-useful.h dbus-define-api.h account.h blist.h buddyicon.h \
-                connection.h conversation.h core.h log.h notify.h prefs.h roomlist.h \
+                connection.h conversation.h core.h ft.h log.h notify.h prefs.h roomlist.h \
                 savedstatuses.h status.h server.h util.h xmlnode.h
 
 purple_build_coreheaders = $(addprefix $(srcdir)/, $(purple_coreheaders))
--- a/libpurple/account.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/account.c	Tue Jun 12 21:21:37 2007 +0000
@@ -371,7 +371,7 @@
 accounts_to_xmlnode(void)
 {
 	xmlnode *node, *child;
-	GList *cur;
+	const GList *cur;
 
 	node = xmlnode_new("account");
 	xmlnode_set_attrib(node, "version", "1.0");
@@ -417,7 +417,7 @@
 schedule_accounts_save()
 {
 	if (save_timer == 0)
-		save_timer = purple_timeout_add(5000, save_cb, NULL);
+		save_timer = purple_timeout_add_seconds(5, save_cb, NULL);
 }
 
 
@@ -877,7 +877,7 @@
 void
 purple_account_destroy(PurpleAccount *account)
 {
-	GList *l;
+	const GList *l;
 
 	g_return_if_fail(account != NULL);
 
@@ -2272,7 +2272,7 @@
 	schedule_accounts_save();
 }
 
-GList *
+const GList *
 purple_accounts_get_all(void)
 {
 	return accounts;
@@ -2282,7 +2282,7 @@
 purple_accounts_get_all_active(void)
 {
 	GList *list = NULL;
-	GList *all = purple_accounts_get_all();
+	const GList *all = purple_accounts_get_all();
 
 	while (all != NULL) {
 		PurpleAccount *account = all->data;
@@ -2300,7 +2300,7 @@
 purple_accounts_find(const char *name, const char *protocol_id)
 {
 	PurpleAccount *account = NULL;
-	GList *l;
+	const GList *l;
 	char *who;
 
 	g_return_val_if_fail(name != NULL, NULL);
@@ -2327,7 +2327,7 @@
 void
 purple_accounts_restore_current_statuses()
 {
-	GList *l;
+	const GList *l;
 	PurpleAccount *account;
 
 	/* If we're not connected to the Internet right now, we bail on this */
--- a/libpurple/account.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/account.h	Tue Jun 12 21:21:37 2007 +0000
@@ -886,7 +886,7 @@
  *
  * @return A list of all accounts.
  */
-GList *purple_accounts_get_all(void);
+const GList *purple_accounts_get_all(void);
 
 /**
  * Returns a list of all enabled accounts
--- a/libpurple/accountopt.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/accountopt.c	Tue Jun 12 21:21:37 2007 +0000
@@ -308,6 +308,7 @@
 	split->text = g_strdup(text);
 	split->field_sep = sep;
 	split->default_value = g_strdup(default_value);
+	split->reverse = TRUE;
 
 	return split;
 }
@@ -345,3 +346,19 @@
 
 	return split->field_sep;
 }
+
+gboolean
+purple_account_user_split_get_reverse(const PurpleAccountUserSplit *split)
+{
+	g_return_val_if_fail(split != NULL, FALSE);
+
+	return split->reverse;
+}
+
+void
+purple_account_user_split_set_reverse(PurpleAccountUserSplit *split, gboolean reverse)
+{
+	g_return_if_fail(split != NULL);
+
+	split->reverse = reverse;
+}
--- a/libpurple/accountopt.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/accountopt.h	Tue Jun 12 21:21:37 2007 +0000
@@ -64,6 +64,9 @@
 	char *text;             /**< The text that will appear to the user. */
 	char *default_value;    /**< The default value.                     */
 	char  field_sep;        /**< The field separator.                   */
+	gboolean reverse;       /**< TRUE if the separator should be found
+							  starting a the end of the string, FALSE
+							  otherwise                                 */
 
 } PurpleAccountUserSplit;
 
@@ -353,6 +356,23 @@
  */
 char purple_account_user_split_get_separator(const PurpleAccountUserSplit *split);
 
+/**
+ * Returns the 'reverse' value for an account split.
+ *
+ * @param split The account username split.
+ *
+ * @return The 'reverse' value.
+ */
+gboolean purple_account_user_split_get_reverse(const PurpleAccountUserSplit *split);
+
+/**
+ * Sets the 'reverse' value for an account split.
+ *
+ * @param split   The account username split.
+ * @param reverse The 'reverse' value
+ */
+void purple_account_user_split_set_reverse(PurpleAccountUserSplit *split, gboolean reverse);
+
 /*@}*/
 
 #ifdef __cplusplus
--- a/libpurple/blist.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/blist.c	Tue Jun 12 21:21:37 2007 +0000
@@ -304,7 +304,7 @@
 {
 	xmlnode *node, *child, *grandchild;
 	PurpleBlistNode *gnode;
-	GList *cur;
+	const GList *cur;
 
 	node = xmlnode_new("purple");
 	xmlnode_set_attrib(node, "version", "1.0");
@@ -365,7 +365,7 @@
 purple_blist_schedule_save()
 {
 	if (save_timer == 0)
-		save_timer = purple_timeout_add(5000, save_cb, NULL);
+		save_timer = purple_timeout_add_seconds(5, save_cb, NULL);
 }
 
 
@@ -1893,7 +1893,7 @@
 {
 	PurpleBlistUiOps *ops = purple_blist_get_ui_ops();
 	PurpleBlistNode *node;
-	GList *l;
+	const GList *l;
 
 	g_return_if_fail(group != NULL);
 
@@ -2498,6 +2498,13 @@
 	return node->flags;
 }
 
+PurpleBlistNodeType
+purple_blist_node_get_type(PurpleBlistNode *node)
+{
+	g_return_val_if_fail(node != NULL, PURPLE_BLIST_OTHER_NODE);
+	return node->type;
+}
+
 void
 purple_blist_node_set_bool(PurpleBlistNode* node, const char *key, gboolean data)
 {
--- a/libpurple/blist.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/blist.h	Tue Jun 12 21:21:37 2007 +0000
@@ -864,6 +864,15 @@
  */
 PurpleBlistNodeFlags purple_blist_node_get_flags(PurpleBlistNode *node);
 
+/**
+ * Get the type of a given node.
+ *
+ * @param node The node.
+ *
+ * @return The type of the node.
+ */
+PurpleBlistNodeType purple_blist_node_get_type(PurpleBlistNode *node);
+
 /*@}*/
 
 /**
--- a/libpurple/buddyicon.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/buddyicon.c	Tue Jun 12 21:21:37 2007 +0000
@@ -24,7 +24,6 @@
  */
 #include "internal.h"
 #include "buddyicon.h"
-#include "cipher.h"
 #include "conversation.h"
 #include "dbus-maybe.h"
 #include "debug.h"
@@ -93,33 +92,6 @@
 	}
 }
 
-static char *
-purple_buddy_icon_data_calculate_filename(guchar *icon_data, size_t icon_len)
-{
-	PurpleCipherContext *context;
-	gchar digest[41];
-
-	context = purple_cipher_context_new_by_name("sha1", NULL);
-	if (context == NULL)
-	{
-		purple_debug_error("buddyicon", "Could not find sha1 cipher\n");
-		g_return_val_if_reached(NULL);
-	}
-
-	/* Hash the icon data */
-	purple_cipher_context_append(context, icon_data, icon_len);
-	if (!purple_cipher_context_digest_to_str(context, sizeof(digest), digest, NULL))
-	{
-		purple_debug_error("buddyicon", "Failed to get SHA-1 digest.\n");
-		g_return_val_if_reached(NULL);
-	}
-	purple_cipher_context_destroy(context);
-
-	/* Return the filename */
-	return g_strdup_printf("%s.%s", digest,
-	                       purple_util_get_image_extension(icon_data, icon_len));
-}
-
 static void
 purple_buddy_icon_data_cache(PurpleStoredImage *img)
 {
@@ -238,7 +210,7 @@
 
 	if (filename == NULL)
 	{
-		file = purple_buddy_icon_data_calculate_filename(icon_data, icon_len);
+		file = purple_util_get_image_filename(icon_data, icon_len);
 		if (file == NULL)
 		{
 			g_free(icon_data);
@@ -966,7 +938,7 @@
 
 		g_free(path);
 
-		new_filename = purple_buddy_icon_data_calculate_filename(icon_data, icon_len);
+		new_filename = purple_util_get_image_filename(icon_data, icon_len);
 		if (new_filename == NULL)
 		{
 			purple_debug_error("buddyicon",
@@ -1049,7 +1021,7 @@
 _purple_buddy_icons_account_loaded_cb()
 {
 	const char *dirname = purple_buddy_icons_get_cache_dir();
-	GList *cur;
+	const GList *cur;
 
 	for (cur = purple_accounts_get_all(); cur != NULL; cur = cur->next)
 	{
--- a/libpurple/buddyicon.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/buddyicon.h	Tue Jun 12 21:21:37 2007 +0000
@@ -31,6 +31,7 @@
 #include "blist.h"
 #include "imgstore.h"
 #include "prpl.h"
+#include "util.h"
 
 #ifdef __cplusplus
 extern "C" {
--- a/libpurple/connection.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/connection.c	Tue Jun 12 21:21:37 2007 +0000
@@ -72,7 +72,7 @@
 	if (on && !gc->keepalive)
 	{
 		purple_debug_info("connection", "Activating keepalive.\n");
-		gc->keepalive = purple_timeout_add(30000, send_keepalive, gc);
+		gc->keepalive = purple_timeout_add_seconds(30, send_keepalive, gc);
 	}
 	else if (!on && gc->keepalive > 0)
 	{
@@ -436,7 +436,7 @@
 	g_return_if_fail(gc   != NULL);
 
 	if (text == NULL) {
-		g_critical("purple_connection_error: check `text != NULL' failed");
+		purple_debug_error("connection", "purple_connection_error: check `text != NULL' failed");
 		text = _("Unknown error");
 	}
 
@@ -456,7 +456,7 @@
 void
 purple_connections_disconnect_all(void)
 {
-	GList *l;
+	const GList *l;
 	PurpleConnection *gc;
 
 	while ((l = purple_connections_get_all()) != NULL) {
@@ -466,13 +466,13 @@
 	}
 }
 
-GList *
+const GList *
 purple_connections_get_all(void)
 {
 	return connections;
 }
 
-GList *
+const GList *
 purple_connections_get_connecting(void)
 {
 	return connections_connecting;
--- a/libpurple/connection.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/connection.h	Tue Jun 12 21:21:37 2007 +0000
@@ -261,14 +261,14 @@
  *
  * @return A list of all active connections.
  */
-GList *purple_connections_get_all(void);
+const GList *purple_connections_get_all(void);
 
 /**
  * Returns a list of all connections in the process of connecting.
  *
  * @return A list of connecting connections.
  */
-GList *purple_connections_get_connecting(void);
+const GList *purple_connections_get_connecting(void);
 
 /**
  * Checks if gc is still a valid pointer to a gc.
@@ -279,7 +279,7 @@
  * TODO: Eventually this bad boy will be removed, because it is
  *       a gross fix for a crashy problem.
  */
-#define PURPLE_CONNECTION_IS_VALID(gc) (g_list_find(purple_connections_get_all(), (gc)) != NULL)
+#define PURPLE_CONNECTION_IS_VALID(gc) (g_list_find((GList *)purple_connections_get_all(), (gc)) != NULL)
 
 /*@}*/
 
--- a/libpurple/conversation.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/conversation.c	Tue Jun 12 21:21:37 2007 +0000
@@ -21,6 +21,7 @@
  */
 #include "internal.h"
 #include "blist.h"
+#include "cmds.h"
 #include "conversation.h"
 #include "dbus-maybe.h"
 #include "debug.h"
@@ -108,8 +109,12 @@
 
 	type = purple_conversation_get_type(conv);
 
-	/* Always linkfy the text for display */
-	displayed = purple_markup_linkify(message);
+	/* Always linkfy the text for display, unless we're
+	 * explicitly asked to do otheriwse*/
+	if(msgflags & PURPLE_MESSAGE_NO_LINKIFY)
+		displayed = g_strdup(message);
+	else
+		displayed = purple_markup_linkify(message);
 
 	if ((conv->features & PURPLE_CONNECTION_HTML) &&
 		!(msgflags & PURPLE_MESSAGE_RAW))
@@ -626,7 +631,7 @@
 purple_conversation_foreach(void (*func)(PurpleConversation *conv))
 {
 	PurpleConversation *conv;
-	GList *l;
+	const GList *l;
 
 	g_return_if_fail(func != NULL);
 
@@ -727,19 +732,19 @@
 	return g_hash_table_lookup(conv->data, key);
 }
 
-GList *
+const GList *
 purple_get_conversations(void)
 {
 	return conversations;
 }
 
-GList *
+const GList *
 purple_get_ims(void)
 {
 	return ims;
 }
 
-GList *
+const GList *
 purple_get_chats(void)
 {
 	return chats;
@@ -754,7 +759,7 @@
 	PurpleConversation *c = NULL;
 	gchar *name1;
 	const gchar *name2;
-	GList *cnv;
+	const GList *cnv;
 
 	g_return_val_if_fail(name != NULL, NULL);
 
@@ -814,7 +819,7 @@
 		return;
 
 	if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM &&
-		!g_list_find(purple_get_conversations(), conv))
+		!g_list_find((GList *)purple_get_conversations(), conv))
 		return;
 
 	displayed = g_strdup(message);
@@ -1010,7 +1015,7 @@
 	conv = purple_conv_im_get_conversation(im);
 	name = purple_conversation_get_name(conv);
 
-	im->typing_timeout = purple_timeout_add(timeout * 1000, reset_typing_cb, conv);
+	im->typing_timeout = purple_timeout_add_seconds(timeout, reset_typing_cb, conv);
 }
 
 void
@@ -1245,7 +1250,7 @@
 	return users;
 }
 
-GList *
+const GList *
 purple_conv_chat_get_users(const PurpleConvChat *chat)
 {
 	g_return_val_if_fail(chat != NULL, NULL);
@@ -1264,7 +1269,7 @@
 		return;
 
 	purple_conv_chat_set_ignored(chat,
-		g_list_append(purple_conv_chat_get_ignored(chat), g_strdup(name)));
+		g_list_append(chat->ignored, g_strdup(name)));
 }
 
 void
@@ -1279,11 +1284,11 @@
 	if (!purple_conv_chat_is_user_ignored(chat, name))
 		return;
 
-	item = g_list_find(purple_conv_chat_get_ignored(chat),
+	item = g_list_find((GList *)purple_conv_chat_get_ignored(chat),
 					   purple_conv_chat_get_ignored_user(chat, name));
 
 	purple_conv_chat_set_ignored(chat,
-		g_list_remove_link(purple_conv_chat_get_ignored(chat), item));
+		g_list_remove_link(chat->ignored, item));
 
 	g_free(item->data);
 	g_list_free_1(item);
@@ -1299,7 +1304,7 @@
 	return ignored;
 }
 
-GList *
+const GList *
 purple_conv_chat_get_ignored(const PurpleConvChat *chat)
 {
 	g_return_val_if_fail(chat != NULL, NULL);
@@ -1310,7 +1315,7 @@
 const char *
 purple_conv_chat_get_ignored_user(const PurpleConvChat *chat, const char *user)
 {
-	GList *ignored;
+	const GList *ignored;
 
 	g_return_val_if_fail(chat != NULL, NULL);
 	g_return_val_if_fail(user != NULL, NULL);
@@ -1560,7 +1565,7 @@
 		cbuddy = purple_conv_chat_cb_new(user, alias, flag);
 		/* This seems dumb. Why should we set users thousands of times? */
 		purple_conv_chat_set_users(chat,
-				g_list_prepend(purple_conv_chat_get_users(chat), cbuddy));
+				g_list_prepend(chat->in_room, cbuddy));
 
 		cbuddies = g_list_prepend(cbuddies, cbuddy);
 
@@ -1578,7 +1583,9 @@
 			}
 			g_free(escaped);
 
-			purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
+			purple_conversation_write(conv, NULL, tmp,
+					PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LINKIFY,
+					time(NULL));
 			g_free(tmp);
 		}
 
@@ -1627,7 +1634,7 @@
 	flags = purple_conv_chat_user_get_flags(chat, old_user);
 	cb = purple_conv_chat_cb_new(new_user, NULL, flags);
 	purple_conv_chat_set_users(chat,
-		g_list_prepend(purple_conv_chat_get_users(chat), cb));
+		g_list_prepend(chat->in_room, cb));
 
 	if (!strcmp(chat->nick, purple_normalize(conv->account, old_user))) {
 		const char *alias;
@@ -1659,7 +1666,7 @@
 
 	if (cb) {
 		purple_conv_chat_set_users(chat,
-				g_list_remove(purple_conv_chat_get_users(chat), cb));
+				g_list_remove(chat->in_room, cb));
 		purple_conv_chat_cb_destroy(cb);
 	}
 
@@ -1704,7 +1711,9 @@
 			g_free(escaped2);
 		}
 
-		purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
+		purple_conversation_write(conv, NULL, tmp,
+				PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LINKIFY,
+				time(NULL));
 	}
 }
 
@@ -1751,7 +1760,7 @@
 
 		if (cb) {
 			purple_conv_chat_set_users(chat,
-					g_list_remove(purple_conv_chat_get_users(chat), cb));
+					g_list_remove(chat->in_room, cb));
 			purple_conv_chat_cb_destroy(cb);
 		}
 
@@ -1781,7 +1790,9 @@
 			}
 			g_free(escaped);
 
-			purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
+			purple_conversation_write(conv, NULL, tmp,
+					PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LINKIFY,
+					time(NULL));
 			g_free(tmp);
 		}
 
@@ -1798,19 +1809,20 @@
 {
 	PurpleConversation *conv;
 	PurpleConversationUiOps *ops;
-	GList *users, *names = NULL;
-	GList *l;
+	GList *users;
+	const GList *l;
+	GList *names = NULL;
 
 	g_return_if_fail(chat != NULL);
 
 	conv  = purple_conv_chat_get_conversation(chat);
 	ops   = purple_conversation_get_ui_ops(conv);
-	users = purple_conv_chat_get_users(chat);
+	users = chat->in_room;
 
 	if (ops != NULL && ops->chat_remove_users != NULL) {
 		for (l = users; l; l = l->next) {
 			PurpleConvChatBuddy *cb = l->data;
-			names = g_list_append(names, cb->name);
+			names = g_list_prepend(names, cb->name);
 		}
 		ops->chat_remove_users(conv, names);
 		g_list_free(names);
@@ -1907,7 +1919,7 @@
 PurpleConversation *
 purple_find_chat(const PurpleConnection *gc, int id)
 {
-	GList *l;
+	const GList *l;
 	PurpleConversation *conv;
 
 	for (l = purple_get_chats(); l != NULL; l = l->next) {
@@ -1956,7 +1968,7 @@
 PurpleConvChatBuddy *
 purple_conv_chat_cb_find(PurpleConvChat *chat, const char *name)
 {
-	GList *l;
+	const GList *l;
 	PurpleConvChatBuddy *cb = NULL;
 
 	g_return_val_if_fail(chat != NULL, NULL);
@@ -1993,6 +2005,29 @@
 	return cb->name;
 }
 
+GList *
+purple_conversation_get_extended_menu(PurpleConversation *conv)
+{
+	GList *menu = NULL;
+
+	g_return_val_if_fail(conv != NULL, NULL);
+
+	purple_signal_emit(purple_conversations_get_handle(),
+			"conversation-extended-menu", conv, &menu);
+	return menu;
+}
+
+gboolean
+purple_conversation_do_command(PurpleConversation *conv, const gchar *cmdline,
+				const gchar *markup, gchar **error)
+{
+	char *mark = (markup && *markup) ? NULL : g_markup_escape_text(cmdline, -1), *err = NULL;
+	PurpleCmdStatus status = purple_cmd_do_command(conv, cmdline, mark ? mark : markup, error ? error : &err);
+	g_free(mark);
+	g_free(err);
+	return (status == PURPLE_CMD_STATUS_OK);
+}
+
 void *
 purple_conversations_get_handle(void)
 {
@@ -2256,6 +2291,12 @@
 										PURPLE_SUBTYPE_CONVERSATION),
 						 purple_value_new(PURPLE_TYPE_STRING),
 						 purple_value_new(PURPLE_TYPE_STRING));
+
+	purple_signal_register(handle, "conversation-extended-menu",
+			     purple_marshal_VOID__POINTER_POINTER, NULL, 2,
+			     purple_value_new(PURPLE_TYPE_SUBTYPE,
+					    PURPLE_SUBTYPE_CONVERSATION),
+			     purple_value_new(PURPLE_TYPE_BOXED, "GList **"));
 }
 
 void
--- a/libpurple/conversation.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/conversation.h	Tue Jun 12 21:21:37 2007 +0000
@@ -116,7 +116,9 @@
 	PURPLE_MESSAGE_RAW         = 0x0800, /**< "Raw" message - don't
 	                                        apply formatting         */
 	PURPLE_MESSAGE_IMAGES      = 0x1000, /**< Message contains images  */
-	PURPLE_MESSAGE_NOTIFY      = 0x2000  /**< Message is a notification */
+	PURPLE_MESSAGE_NOTIFY      = 0x2000, /**< Message is a notification */
+	PURPLE_MESSAGE_NO_LINKIFY  = 0x4000  /**< Message should not be auto-
+										   linkified */
 
 } PurpleMessageFlags;
 
@@ -502,21 +504,21 @@
  *
  * @return A GList of all conversations.
  */
-GList *purple_get_conversations(void);
+const GList *purple_get_conversations(void);
 
 /**
  * Returns a list of all IMs.
  *
  * @return A GList of all IMs.
  */
-GList *purple_get_ims(void);
+const GList *purple_get_ims(void);
 
 /**
  * Returns a list of all chats.
  *
  * @return A GList of all chats.
  */
-GList *purple_get_chats(void);
+const GList *purple_get_chats(void);
 
 /**
  * Finds a conversation with the specified type, name, and Purple account.
@@ -875,7 +877,7 @@
  *
  * @return The list of users.
  */
-GList *purple_conv_chat_get_users(const PurpleConvChat *chat);
+const GList *purple_conv_chat_get_users(const PurpleConvChat *chat);
 
 /**
  * Ignores a user in a chat room.
@@ -910,7 +912,7 @@
  *
  * @return The list of ignored users.
  */
-GList *purple_conv_chat_get_ignored(const PurpleConvChat *chat);
+const GList *purple_conv_chat_get_ignored(const PurpleConvChat *chat);
 
 /**
  * Returns the actual name of the specified ignored user, if it exists in
@@ -1190,6 +1192,30 @@
  */
 void purple_conv_chat_cb_destroy(PurpleConvChatBuddy *cb);
 
+/**
+ * Retrieves the extended menu items for the conversation.
+ *
+ * @param conv The conversation.
+ * 
+ * @return  A list of PurpleMenuAction items, harvested by the
+ *          chat-extended-menu signal. The list and the menuaction
+ *          items should be freed by the caller.
+ */
+GList * purple_conversation_get_extended_menu(PurpleConversation *conv);
+
+/**
+ * Perform a command in a conversation. Similar to @see purple_cmd_do_command
+ *
+ * @param conv    The conversation.
+ * @param cmdline The entire command including the arguments.
+ * @param markup  @c NULL, or the formatted command line.
+ * @param error   If the command failed errormsg is filled in with the appropriate error
+ *                message, if not @c NULL. It must be freed by the caller with g_free().
+ *
+ * @return  @c TRUE if the command was executed successfully, @c FALSE otherwise.
+ */
+gboolean purple_conversation_do_command(PurpleConversation *conv, const gchar *cmdline, const gchar *markup, gchar **error);
+
 /*@}*/
 
 /**************************************************************************/
--- a/libpurple/core.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/core.c	Tue Jun 12 21:21:37 2007 +0000
@@ -48,7 +48,11 @@
 #include "util.h"
 
 #ifdef HAVE_DBUS
+#  define DBUS_API_SUBJECT_TO_CHANGE
+#  include <dbus/dbus.h>
+#  include "dbus-purple.h"
 #  include "dbus-server.h"
+#  include "dbus-bindings.h"
 #endif
 
 struct PurpleCore
@@ -276,6 +280,91 @@
 	return _ops;
 }
 
+#ifdef HAVE_DBUS
+static char *purple_dbus_owner_user_dir(void)
+{
+	DBusMessage *msg = NULL, *reply = NULL;
+	DBusConnection *dbus_connection = NULL;
+	DBusError dbus_error;
+	char *remote_user_dir = NULL;
+
+	if ((dbus_connection = purple_dbus_get_connection()) == NULL)
+		return NULL;
+
+	if ((msg = dbus_message_new_method_call(DBUS_SERVICE_PURPLE, DBUS_PATH_PURPLE, DBUS_INTERFACE_PURPLE, "PurpleUserDir")) == NULL)
+		return NULL;
+
+	dbus_error_init(&dbus_error);
+	reply = dbus_connection_send_with_reply_and_block(dbus_connection, msg, 5000, &dbus_error);
+	dbus_message_unref(msg);
+	dbus_error_free(&dbus_error);
+
+	if (reply)
+	{
+		dbus_error_init(&dbus_error);
+		dbus_message_get_args(reply, &dbus_error, DBUS_TYPE_STRING, &remote_user_dir, DBUS_TYPE_INVALID);
+		remote_user_dir = g_strdup(remote_user_dir);
+		dbus_error_free(&dbus_error);
+		dbus_message_unref(reply);
+	}
+
+	return remote_user_dir;
+}
+
+static void purple_dbus_owner_show_buddy_list(void)
+{
+	DBusError dbus_error;
+	DBusMessage *msg = NULL, *reply = NULL;
+	DBusConnection *dbus_connection = NULL;
+
+	if ((dbus_connection = purple_dbus_get_connection()) == NULL)
+		return;
+
+	if ((msg = dbus_message_new_method_call(DBUS_SERVICE_PURPLE, DBUS_PATH_PURPLE, DBUS_INTERFACE_PURPLE, "PurpleBlistShow")) == NULL)
+		return;
+
+	dbus_error_init(&dbus_error);
+	if ((reply = dbus_connection_send_with_reply_and_block(dbus_connection, msg, 5000, &dbus_error)) != NULL)
+	{
+		dbus_message_unref(msg);
+	}
+	dbus_error_free(&dbus_error);
+}
+#endif /* HAVE_DBUS */
+
+gboolean
+purple_core_ensure_single_instance()
+{
+	gboolean is_single_instance = TRUE;
+#ifdef HAVE_DBUS
+	/* in the future, other mechanisms might have already set this to FALSE */
+	if (is_single_instance)
+	{
+		if (!purple_dbus_is_owner())
+		{
+			const char *user_dir = purple_user_dir();
+			char *dbus_owner_user_dir = purple_dbus_owner_user_dir();
+
+			if (NULL == user_dir && NULL != dbus_owner_user_dir)
+				is_single_instance = TRUE;
+			else if (NULL != user_dir && NULL == dbus_owner_user_dir)
+				is_single_instance = TRUE;
+			else if (NULL == user_dir && NULL == dbus_owner_user_dir)
+				is_single_instance = FALSE;
+			else
+				is_single_instance = strcmp(dbus_owner_user_dir, user_dir);
+
+			if (!is_single_instance)
+				purple_dbus_owner_show_buddy_list();
+
+			g_free(dbus_owner_user_dir);
+		}
+	}
+#endif /* HAVE_DBUS */
+
+	return is_single_instance;
+}
+
 static gboolean
 move_and_symlink_dir(const char *path, const char *basename, const char *old_base, const char *new_base, const char *relative)
 {
--- a/libpurple/core.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/core.h	Tue Jun 12 21:21:37 2007 +0000
@@ -121,6 +121,16 @@
  */
 gboolean purple_core_migrate(void);
 
+/**
+ * Ensures that only one instance is running.
+ *
+ * @return A boolean such that @c TRUE indicates that this is the first instance,
+ *         whereas @c FALSE indicates that there is another instance running.
+ *
+ * @since 2.1.0
+ */
+gboolean purple_core_ensure_single_instance(void);
+
 #ifdef __cplusplus
 }
 #endif
--- a/libpurple/dbus-analyze-functions.py	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/dbus-analyze-functions.py	Tue Jun 12 21:21:37 2007 +0000
@@ -33,9 +33,13 @@
     ]
 
 # This is a list of functions that return a GList* whose elements are
-# string, not pointers to objects.  Don't put any functions here, it
-# won't work.
-stringlists = []
+# string, not pointers to objects.
+stringlists = [
+    "purple_prefs_get_path_list",
+    "purple_prefs_get_string_list",
+    "purple_uri_list_extract_filenames",
+    "purple_uri_list_extract_uris",
+]
 
 pointer = "#pointer#"
 myexception = "My Exception"
@@ -148,7 +152,7 @@
                 return self.outputpurplestructure(type, name)
 
             if type[0] in ["GList", "GSList"]:
-                return self.outputlist(type, name)
+                return self.outputlist(type, name, const)
 
         raise myexception
     
@@ -165,9 +169,9 @@
         self.returncode = []
 
     def flush(self):
-	paramslist = ", ".join(self.paramshdr)
-	if (paramslist == "") :
-	    paramslist = "void"
+        paramslist = ", ".join(self.paramshdr)
+        if (paramslist == "") :
+            paramslist = "void"
         print "%s %s(%s)" % (self.functiontype, self.function.name,
                              paramslist),
 
@@ -250,7 +254,7 @@
         self.returncode.append("return (%s*) GINT_TO_POINTER(%s);" % (type[0], name));
         self.definepurplestructure(type)
 
-    def outputlist(self, type, name):
+    def outputlist(self, type, name, const):
         self.functiontype = "%s*" % type[0]
         self.decls.append("GArray *%s;" % name)
         self.outputparams.append(('dbus_g_type_get_collection("GArray", G_TYPE_INT)', name))
@@ -279,7 +283,7 @@
         for decl in self.cdecls:
             print decl
 
-        print "\t%s(message_DBUS, error_DBUS, " % self.argfunc,
+        print "\t%s(message_DBUS, error_DBUS," % self.argfunc,
         for param in self.cparams:
             print "DBUS_TYPE_%s, &%s," % param,
         print "DBUS_TYPE_INVALID);"
@@ -289,14 +293,14 @@
         for code in self.ccode:
             print code
 
-        print "\treply_DBUS =  dbus_message_new_method_return (message_DBUS);"
+        print "\treply_DBUS = dbus_message_new_method_return (message_DBUS);"
 
-        print "\tdbus_message_append_args(reply_DBUS, ",
+        print "\tdbus_message_append_args(reply_DBUS,",
         for param in self.cparamsout:
             if type(param) is str:
-                print "%s, " % param
+                print "%s," % param
             else:
-                print "DBUS_TYPE_%s, &%s, " % param,
+                print "DBUS_TYPE_%s, &%s," % param,
         print "DBUS_TYPE_INVALID);"
 
         for code in self.ccodeout:
@@ -385,28 +389,40 @@
         self.addouttype("i", name)
 
     # GList*, GSList*, assume that list is a list of objects
-
-    # fixme: at the moment, we do NOT free the memory occupied by
-    # the list, we should free it if the list has NOT been declared const
-
-    # fixme: we assume that this is a list of objects, not a list
-    # of strings
-
-    def outputlist(self, type, name):
+    # unless the function is in stringlists
+    def outputlist(self, type, name, const):
         self.cdecls.append("\tdbus_int32_t %s_LEN;" % name)
         self.ccodeout.append("\tg_free(%s);" % name)
 
+        if const:
+            const_prefix = "const_"
+        else:
+            const_prefix = ""
+
+        if (const):
+            self.cdecls.append("\tconst %s *list;" % type[0]);
+        else:
+            self.cdecls.append("\t%s *list;" % type[0]);
+
         if self.function.name in stringlists:
             self.cdecls.append("\tchar **%s;" % name)
-            self.ccode.append("\t%s = purple_%s_to_array(%s, FALSE, &%s_LEN);" % \
-                         (name, type[0], self.call, name))
+            self.ccode.append("\tlist = %s;" % self.call)
+            self.ccode.append("\t%s = (char **)purple_const_%s_to_array(list, &%s_LEN);" % \
+                         (name, type[0], name))
             self.cparamsout.append("\tDBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &%s, %s_LEN" \
                           % (name, name))
+            if (not const):
+                type_name = type[0].lower()[1:]
+                self.ccodeout.append("\tg_%s_foreach(list, (GFunc)g_free, NULL);" % type_name)
+                self.ccodeout.append("\tg_%s_free(list);" % type_name)
             self.addouttype("as", name)
         else:
             self.cdecls.append("\tdbus_int32_t *%s;" % name)
-            self.ccode.append("\t%s = purple_dbusify_%s(%s, FALSE, &%s_LEN);" % \
-                         (name, type[0], self.call, name))
+            self.ccode.append("\tlist = %s;" % self.call)
+            self.ccode.append("\t%s = purple_dbusify_const_%s(list, &%s_LEN);" % \
+                         (name, type[0], name))
+            if (not const):
+                self.ccode.append("\tg_%s_free(list);" % type[0].lower()[1:])
             self.cparamsout.append("\tDBUS_TYPE_ARRAY, DBUS_TYPE_INT32, &%s, %s_LEN" \
                               % (name, name))
             self.addouttype("ai", name)
--- a/libpurple/dbus-bindings.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/dbus-bindings.h	Tue Jun 12 21:21:37 2007 +0000
@@ -83,14 +83,52 @@
 					int              first_arg_type,
 					va_list          var_args);
 
+/**
+ * @deprecated In 3.0.0, this method will have a signature and behavior
+ *             like that of purple_dbusify_const_GList().
+ */
 dbus_int32_t* purple_dbusify_GList(GList *list, gboolean free_memory, 
 				 dbus_int32_t *len);
+/**
+ * @deprecated In 3.0.0, this method will have a signature and behavior
+ *             like that of purple_dbusify_const_GSList().
+ */
 dbus_int32_t* purple_dbusify_GSList(GSList *list, gboolean free_memory,
 				  dbus_int32_t *len);
+
+/**
+ * @since 2.1.0
+ */
+dbus_int32_t* purple_dbusify_const_GList(const GList *list, dbus_int32_t *len);
+
+/**
+ * @since 2.1.0
+ */
+dbus_int32_t* purple_dbusify_const_GSList(const GSList *list, dbus_int32_t *len);
+
+/**
+ * @deprecated In 3.0.0, this method will have a signature and behavior
+ *             like that of purple_const_GList_to_array().
+ */
 gpointer* purple_GList_to_array(GList *list, gboolean free_memory,
 			      dbus_int32_t *len);
+/**
+ * @deprecated In 3.0.0, this method will have a signature and behavior
+ *             like that of purple_const_GSList_to_array().
+ */
 gpointer* purple_GSList_to_array(GSList *list, gboolean free_memory,
 			      dbus_int32_t *len);
+
+/**
+ * @since 2.1.0
+ */
+gpointer* purple_const_GList_to_array(const GList *list, dbus_int32_t *len);
+
+/**
+ * @since 2.1.0
+ */
+gpointer* purple_const_GSList_to_array(const GSList *list, dbus_int32_t *len);
+
 GHashTable *purple_dbus_iter_hash_table(DBusMessageIter *iter, DBusError *error);
 
 const char* empty_to_null(const char *str);
--- a/libpurple/dbus-server.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/dbus-server.c	Tue Jun 12 21:21:37 2007 +0000
@@ -65,6 +65,12 @@
 static GHashTable *map_id_type;
 
 static gchar *init_error;
+static int dbus_request_name_reply = DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER;
+
+gboolean purple_dbus_is_owner(void)
+{
+	return(DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER == dbus_request_name_reply);
+}
 
 /**
  * This function initializes the pointer-id traslation system.  It
@@ -115,7 +121,7 @@
 	{
 		purple_debug_warning("dbus",
 				"Need to register an object with the dbus subsystem.\n");
-		g_return_val_if_reached(0);
+		return 0;
 	}
 	return id;
 }
@@ -284,19 +290,45 @@
 }
 
 dbus_int32_t *
-purple_dbusify_GList(GList *list, gboolean free_memory, dbus_int32_t *len)
+purple_dbusify_const_GList(const GList *list, dbus_int32_t *len)
 {
 	dbus_int32_t *array;
 	int i;
-	GList *elem;
+	const GList *elem;
 
-	*len = g_list_length(list);
-	array = g_new0(dbus_int32_t, g_list_length(list));
+	/* g_list_length() should really take a const GList */
+	*len = g_list_length((GList *)list);
+	array = g_new0(dbus_int32_t, *len);
 	for (i = 0, elem = list; elem != NULL; elem = elem->next, i++)
 		array[i] = purple_dbus_pointer_to_id(elem->data);
 
-	if (free_memory)
-		g_list_free(list);
+	return array;
+}
+
+dbus_int32_t *
+purple_dbusify_GList(GList *list, gboolean free_memory, dbus_int32_t *len)
+{
+	dbus_int32_t *array = purple_dbusify_const_GList(list, len);
+
+	if (!free_memory)
+		return array;
+
+	g_list_free(list);
+	return array;
+}
+
+dbus_int32_t *
+purple_dbusify_const_GSList(const GSList *list, dbus_int32_t *len)
+{
+	dbus_int32_t *array;
+	int i;
+	const GSList *elem;
+
+	/* g_slist_length should really take a const GSList */
+	*len = g_slist_length((GSList *)list);
+	array = g_new0(dbus_int32_t, *len);
+	for (i = 0, elem = list; elem != NULL; elem = elem->next, i++)
+		array[i] = purple_dbus_pointer_to_id(elem->data);
 
 	return array;
 }
@@ -304,17 +336,26 @@
 dbus_int32_t *
 purple_dbusify_GSList(GSList *list, gboolean free_memory, dbus_int32_t *len)
 {
-	dbus_int32_t *array;
-	int i;
-	GSList *elem;
+	dbus_int32_t *array = purple_dbusify_const_GSList(list, len);
+
+	if (!free_memory)
+		return array;
+
+	g_slist_free(list);
+	return array;
+}
 
-	*len = g_slist_length(list);
-	array = g_new0(dbus_int32_t, g_slist_length(list));
+gpointer *
+purple_const_GList_to_array(const GList *list, dbus_int32_t *len)
+{
+	gpointer *array;
+	int i;
+	const GList *elem;
+
+	*len = g_list_length((GList *)list);
+	array = g_new0(gpointer, *len);
 	for (i = 0, elem = list; elem != NULL; elem = elem->next, i++)
-		array[i] = purple_dbus_pointer_to_id(elem->data);
-
-	if (free_memory)
-		g_slist_free(list);
+		array[i] = elem->data;
 
 	return array;
 }
@@ -322,36 +363,39 @@
 gpointer *
 purple_GList_to_array(GList *list, gboolean free_memory, dbus_int32_t *len)
 {
+	gpointer *array = purple_const_GList_to_array(list, len);
+
+	if (!free_memory)
+		return array;
+
+	g_list_free(list);
+	return array;
+}
+
+gpointer *
+purple_const_GSList_to_array(const GSList *list, dbus_int32_t *len)
+{
 	gpointer *array;
 	int i;
-	GList *elem;
+	const GSList *elem;
 
-	*len = g_list_length(list);
-	array = g_new0(gpointer, g_list_length(list));
+	*len = g_slist_length((GSList *)list);
+	array = g_new0(gpointer, *len);
 	for (i = 0, elem = list; elem != NULL; elem = elem->next, i++)
 		array[i] = elem->data;
 
-	if (free_memory)
-		g_list_free(list);
-
 	return array;
 }
 
 gpointer *
 purple_GSList_to_array(GSList *list, gboolean free_memory, dbus_int32_t *len)
 {
-	gpointer *array;
-	int i;
-	GSList *elem;
+	gpointer *array = purple_const_GSList_to_array(list, len);
 
-	*len = g_slist_length(list);
-	array = g_new0(gpointer, g_slist_length(list));
-	for (i = 0, elem = list; elem != NULL; elem = elem->next, i++)
-		array[i] = elem->data;
+	if (!free_memory)
+		return array;
 
-	if (free_memory)
-		g_slist_free(list);
-
+	g_slist_free(list);
 	return array;
 }
 
@@ -592,6 +636,7 @@
 		return;
 	}
 
+	dbus_request_name_reply =
 	result = dbus_bus_request_name(purple_dbus_connection,
 			DBUS_SERVICE_PURPLE, 0, &error);
 
--- a/libpurple/dbus-server.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/dbus-server.h	Tue Jun 12 21:21:37 2007 +0000
@@ -169,6 +169,13 @@
 void *purple_dbus_get_handle(void);
 
 /**
+ * Determines whether this instance owns the DBus service name
+ * 
+ * @since 2.1.0
+ */
+gboolean purple_dbus_is_owner(void);
+
+/**
  * Starts Purple's D-BUS server.  It is responsible for handling DBUS
  * requests from other applications.
  */
--- a/libpurple/dbus-useful.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/dbus-useful.c	Tue Jun 12 21:21:37 2007 +0000
@@ -11,7 +11,7 @@
 		       gboolean (*account_test)(const PurpleAccount *account))
 {
 	PurpleAccount *result = NULL;
-	GList *l;
+	const GList *l;
 	char *who;
 
 	if (name)
--- a/libpurple/eventloop.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/eventloop.c	Tue Jun 12 21:21:37 2007 +0000
@@ -35,6 +35,17 @@
 	return ops->timeout_add(interval, function, data);
 }
 
+guint
+purple_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data)
+{
+	PurpleEventLoopUiOps *ops = purple_eventloop_get_ui_ops();
+
+	if (ops->timeout_add_seconds)
+		return ops->timeout_add_seconds(interval, function, data);
+	else
+		return ops->timeout_add(1000 * interval, function, data);
+}
+
 gboolean
 purple_timeout_remove(guint tag)
 {
--- a/libpurple/eventloop.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/eventloop.h	Tue Jun 12 21:21:37 2007 +0000
@@ -48,7 +48,7 @@
 struct _PurpleEventLoopUiOps
 {
 	/**
-	 * Creates a callback timer.
+	 * Creates a callback timer with an interval measured in milliseconds.
 	 * @see g_timeout_add, purple_timeout_add
 	 **/
 	guint (*timeout_add)(guint interval, GSourceFunc function, gpointer data);
@@ -81,7 +81,20 @@
 	 */
 	int (*input_get_error)(int fd, int *error);
 
-	void (*_purple_reserved1)(void);
+	/**
+	 * Creates a callback timer with an interval measured in seconds.
+	 *
+	 * This allows UIs to group timers for better power efficiency.  For
+	 * this reason, @a interval may be rounded by up to a second.
+	 *
+	 * Implementation of this UI op is optional.  If it's not implemented,
+	 * calls to purple_timeout_add_seconds() will be serviced by the
+	 * timeout_add UI op.
+	 *
+	 * @see g_timeout_add_seconds, purple_timeout_add_seconds()
+	 **/
+	guint (*timeout_add_seconds)(guint interval, GSourceFunc function, gpointer data);
+
 	void (*_purple_reserved2)(void);
 	void (*_purple_reserved3)(void);
 	void (*_purple_reserved4)(void);
@@ -93,10 +106,15 @@
 /*@{*/
 /**
  * Creates a callback timer.
+ * 
  * The timer will repeat until the function returns @c FALSE. The
  * first call will be at the end of the first interval.
+ *
+ * If the timer is in a multiple of seconds, use purple_timeout_add_seconds()
+ * instead as it allows UIs to group timers for power efficiency.
+ *
  * @param interval	The time between calls of the function, in
- *					milliseconds.
+ *                      milliseconds.
  * @param function	The function to call.
  * @param data		data to pass to @a function.
  * @return A handle to the timer which can be passed to 
@@ -105,6 +123,24 @@
 guint purple_timeout_add(guint interval, GSourceFunc function, gpointer data);
 
 /**
+ * Creates a callback timer.
+ *
+ * The timer will repeat until the function returns @c FALSE. The
+ * first call will be at the end of the first interval.
+ *
+ * This function allows UIs to group timers for better power efficiency.  For
+ * this reason, @a interval may be rounded by up to a second.
+ * 
+ * @param interval	The time between calls of the function, in
+ *                      seconds.
+ * @param function	The function to call.
+ * @param data		data to pass to @a function.
+ * @return A handle to the timer which can be passed to 
+ *         purple_timeout_remove to remove the timer.
+ */
+guint purple_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data);
+
+/**
  * Removes a timeout handler.
  *
  * @param handle The handle, as returned by purple_timeout_add.
--- a/libpurple/example/nullclient.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/example/nullclient.c	Tue Jun 12 21:21:37 2007 +0000
@@ -108,11 +108,15 @@
 	glib_input_add,
 	g_source_remove,
 	NULL,
+#if GLIB_CHECK_VERSION(2,14,0)
+	g_timeout_add_seconds,
+#else
+	NULL,
+#endif
 
 	/* padding */
 	NULL,
 	NULL,
-	NULL,
 	NULL
 };
 /*** End of the eventloop functions. ***/
--- a/libpurple/ft.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/ft.c	Tue Jun 12 21:21:37 2007 +0000
@@ -23,6 +23,7 @@
  *
  */
 #include "internal.h"
+#include "dbus-maybe.h"
 #include "ft.h"
 #include "network.h"
 #include "notify.h"
@@ -56,6 +57,7 @@
 	g_return_val_if_fail(who     != NULL,              NULL);
 
 	xfer = g_new0(PurpleXfer, 1);
+	PURPLE_DBUS_REGISTER_POINTER(xfer, PurpleXfer);
 
 	xfer->ref = 1;
 	xfer->type    = type;
@@ -97,6 +99,7 @@
 	g_free(xfer->remote_ip);
 	g_free(xfer->local_filename);
 
+	PURPLE_DBUS_UNREGISTER_POINTER(xfer);
 	g_free(xfer);
 	xfers = g_list_remove(xfers, xfer);
 }
@@ -551,6 +554,13 @@
 	return xfer->account;
 }
 
+const char *
+purple_xfer_get_remote_user(const PurpleXfer *xfer)
+{
+	g_return_val_if_fail(xfer != NULL, NULL);
+	return xfer->who;
+}
+
 PurpleXferStatusType
 purple_xfer_get_status(const PurpleXfer *xfer)
 {
--- a/libpurple/ft.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/ft.h	Tue Jun 12 21:21:37 2007 +0000
@@ -237,6 +237,15 @@
 PurpleAccount *purple_xfer_get_account(const PurpleXfer *xfer);
 
 /**
+ * Returns the name of the remote user.
+ *
+ * @param xfer The file transfer.
+ *
+ * @return The name of the remote user.
+ */
+const char *purple_xfer_get_remote_user(const PurpleXfer *xfer);
+
+/**
  * Returns the status of the xfer.
  *
  * @param xfer The file transfer.
--- a/libpurple/idle.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/idle.c	Tue Jun 12 21:21:37 2007 +0000
@@ -199,7 +199,7 @@
 	/* Idle reporting stuff */
 	if (report_idle && (time_idle >= IDLEMARK))
 	{
-		GList *l;
+		const GList *l;
 		for (l = purple_connections_get_all(); l != NULL; l = l->next)
 		{
 			PurpleConnection *gc = l->data;
@@ -224,7 +224,11 @@
 	if (time_until_next_idle_event == 0)
 		idle_timer = 0;
 	else
-		idle_timer = purple_timeout_add(1000 * (time_until_next_idle_event + 1), check_idleness_timer, NULL);
+	{
+		/* +1 for the boundary,
+		 * +1 more for g_timeout_add_seconds rounding. */
+		idle_timer = purple_timeout_add_seconds(time_until_next_idle_event + 2, check_idleness_timer, NULL);
+	}
 	return FALSE;
 }
 
@@ -303,8 +307,10 @@
 void
 purple_idle_init()
 {
-	/* Add the timer to check if we're idle */
-	idle_timer = purple_timeout_add(1000 * (IDLEMARK + 1), check_idleness_timer, NULL);
+	/* Add the timer to check if we're idle.
+	 * IDLEMARK + 1 as the boundary,
+	 * +1 more for g_timeout_add_seconds rounding. */
+	idle_timer = purple_timeout_add_seconds((IDLEMARK + 2), check_idleness_timer, NULL);
 
 	purple_signal_connect(purple_conversations_get_handle(), "sent-im-msg",
 						purple_idle_get_handle(),
--- a/libpurple/imgstore.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/imgstore.c	Tue Jun 12 21:21:37 2007 +0000
@@ -25,6 +25,7 @@
 */
 
 #include <glib.h>
+#include "dbus-maybe.h"
 #include "debug.h"
 #include "imgstore.h"
 #include "util.h"
@@ -56,6 +57,7 @@
 	g_return_val_if_fail(size > 0, 0);
 
 	img = g_new(PurpleStoredImage, 1);
+	PURPLE_DBUS_REGISTER_POINTER(img, PurpleStoredImage);
 	img->data = data;
 	img->size = size;
 	img->filename = g_strdup(filename);
@@ -159,6 +161,7 @@
 
 		g_free(img->data);
 		g_free(img->filename);
+		PURPLE_DBUS_UNREGISTER_POINTER(img);
 		g_free(img);
 		img = NULL;
 	}
--- a/libpurple/log.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/log.c	Tue Jun 12 21:21:37 2007 +0000
@@ -32,6 +32,7 @@
 #include "prefs.h"
 #include "util.h"
 #include "stringref.h"
+#include "imgstore.h"
 
 static GSList *loggers = NULL;
 
@@ -690,6 +691,107 @@
 		return g_strdup(purple_time_format(&tm));
 }
 
+/* NOTE: This can return msg (which you may or may not want to g_free())
+ * NOTE: or a newly allocated string which you MUST g_free(). */
+static char *
+convert_image_tags(const PurpleLog *log, const char *msg)
+{
+	const char *tmp;
+	const char *start;
+	const char *end;
+	GData *attributes;
+	GString *newmsg = NULL;
+
+	tmp = msg;
+
+	while (purple_markup_find_tag("img", tmp, &start, &end, &attributes)) {
+		int imgid = 0;
+		char *idstr = NULL;
+
+		if (newmsg == NULL)
+			newmsg = g_string_new("");
+
+		/* copy any text before the img tag */
+		if (tmp < start)
+			g_string_append_len(newmsg, tmp, start - tmp);
+
+		idstr = g_datalist_get_data(&attributes, "id");
+
+		imgid = atoi(idstr);
+		if (imgid != 0)
+		{
+			FILE *image_file;
+			char *dir;
+			PurpleStoredImage *image;
+			gconstpointer image_data;
+			char *new_filename = NULL;
+			char *path = NULL;
+			size_t image_byte_count;
+
+			image = purple_imgstore_find_by_id(imgid);
+			if (image == NULL)
+			{
+				/* This should never happen. */
+				g_string_free(newmsg, TRUE);
+				g_return_val_if_reached((char *)msg);
+			}
+
+			image_data       = purple_imgstore_get_data(image);
+			image_byte_count = purple_imgstore_get_size(image);
+			dir              = purple_log_get_log_dir(log->type, log->name, log->account);
+			new_filename     = purple_util_get_image_filename(image_data, image_byte_count);
+
+			path = g_build_filename(dir, new_filename, NULL);
+
+			/* Only save unique files. */
+			if (!g_file_test(path, G_FILE_TEST_EXISTS))
+			{
+				if ((image_file = g_fopen(path, "wb")) != NULL)
+				{
+					if (!fwrite(image_data, image_byte_count, 1, image_file))
+					{
+						purple_debug_error("log", "Error writing %s: %s\n",
+						                   path, strerror(errno));
+						fclose(image_file);
+
+						/* Attempt to not leave half-written files around. */
+						unlink(path);
+					}
+					else
+					{
+						purple_debug_info("log", "Wrote image file: %s\n", path);
+						fclose(image_file);
+					}
+				}
+				else
+				{
+					purple_debug_error("log", "Unable to create file %s: %s\n",
+					                   path, strerror(errno));
+				}
+			}
+
+			/* Write the new image tag */
+			g_string_append_printf(newmsg, "<IMG SRC=\"%s\">", new_filename);
+			g_free(new_filename);
+			g_free(path);
+		}
+
+		/* Continue from the end of the tag */
+		tmp = end + 1;
+	}
+
+	if (newmsg == NULL)
+	{
+		/* No images were found to change. */
+		return (char *)msg;
+	}
+
+	/* Append any remaining message data */
+	g_string_append(newmsg, tmp);
+
+	return g_string_free(newmsg, FALSE);
+}
+
 void purple_log_common_writer(PurpleLog *log, const char *ext)
 {
 	PurpleLogCommonLoggerData *data = log->logger_data;
@@ -880,7 +982,7 @@
 		GDir *protocol_dir;
 		const gchar *username;
 		gchar *protocol_unescaped;
-		GList *account_iter;
+		const GList *account_iter;
 		GList *accounts = NULL;
 
 		if ((protocol_dir = g_dir_open(protocol_path, 0, NULL)) == NULL) {
@@ -1191,6 +1293,7 @@
 							  const char *from, time_t time, const char *message)
 {
 	char *msg_fixed;
+	char *image_corrected_msg;
 	char *date;
 	char *header;
 	PurplePlugin *plugin = purple_find_prpl(purple_account_get_protocol_id(log->account));
@@ -1231,7 +1334,14 @@
 	if(!data->file)
 		return 0;
 
-	purple_markup_html_to_xhtml(message, &msg_fixed, NULL);
+	image_corrected_msg = convert_image_tags(log, message);
+	purple_markup_html_to_xhtml(image_corrected_msg, &msg_fixed, NULL);
+
+	/* Yes, this breaks encapsulation.  But it's a static function and
+	 * this saves a needless strdup(). */
+	if (image_corrected_msg != message)
+		g_free(image_corrected_msg);
+
 	date = log_get_timestamp(log, time);
 
 	if(log->type == PURPLE_LOG_SYSTEM){
--- a/libpurple/network.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/network.c	Tue Jun 12 21:21:37 2007 +0000
@@ -598,8 +598,6 @@
 void
 nm_callback_func(libnm_glib_ctx* ctx, gpointer user_data)
 {
-	GList *l;
-	PurpleAccount *account;
 	static libnm_glib_state prev = LIBNM_NO_DBUS;
 	libnm_glib_state current;
 	PurpleConnectionUiOps *ui_ops = purple_connections_get_ui_ops();
--- a/libpurple/notify.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/notify.c	Tue Jun 12 21:21:37 2007 +0000
@@ -530,7 +530,7 @@
 	g_free(user_info);
 }
 
-GList *
+const GList *
 purple_notify_user_info_get_entries(PurpleNotifyUserInfo *user_info)
 {
 	g_return_val_if_fail(user_info != NULL, NULL);
--- a/libpurple/notify.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/notify.h	Tue Jun 12 21:21:37 2007 +0000
@@ -458,7 +458,7 @@
  *
  * @result                   A GList of PurpleNotifyUserInfoEntry objects
  */
-GList *purple_notify_user_info_get_entries(PurpleNotifyUserInfo *user_info);
+const GList *purple_notify_user_info_get_entries(PurpleNotifyUserInfo *user_info);
 
 /**
  * Create a textual representation of a PurpleNotifyUserInfo, separating entries with newline
--- a/libpurple/plugins/joinpart.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/plugins/joinpart.c	Tue Jun 12 21:21:37 2007 +0000
@@ -86,7 +86,7 @@
 	/* If the room is small, don't bother. */
 	chat = PURPLE_CONV_CHAT(conv);
 	threshold = purple_prefs_get_int(THRESHOLD_PREF);
-	if (g_list_length(purple_conv_chat_get_users(chat)) < threshold)
+	if (g_list_length((GList *)purple_conv_chat_get_users(chat)) < threshold)
 		return FALSE;
 
 	/* We always care about our buddies! */
--- a/libpurple/plugins/log_reader.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/plugins/log_reader.c	Tue Jun 12 21:21:37 2007 +0000
@@ -1425,7 +1425,7 @@
 	char *escaped;
 	GString *formatted;
 	char *c;
-	char *line;
+	const char *line;
 
 	g_return_val_if_fail(log != NULL, g_strdup(""));
 
@@ -1460,240 +1460,234 @@
 	read = escaped;
 
 	/* Apply formatting... */
-	formatted = g_string_new("");
+	formatted = g_string_sized_new(strlen(read));
 	c = read;
 	line = read;
-	while (*c)
+	while (c)
 	{
-		if (*c == '\n')
-		{
-			char *link_temp_line;
-			char *link;
-			char *timestamp;
-			char *footer = NULL;
-			*c = '\0';
+		const char *link;
+		const char *footer = NULL;
+		GString *temp = NULL;
 
-			/* Convert links.
-			 *
-			 * The format is (Link: URL)URL
-			 * So, I want to find each occurance of "(Link: " and replace that chunk with:
-			 * <a href="
-			 * Then, replace the next ")" with:
-			 * ">
-			 * Then, replace the next " " (or add this if the end-of-line is reached) with:
-			 * </a>
-			 * 
-			 * As implemented, this isn't perfect, but it should cover common cases.
-			 */
-			link_temp_line = NULL;
-			while ((link = strstr(line, "(Link: ")))
-			{
-				char *tmp = link;
-
-				link += 7;
-				if (*link)
-				{
-					char *end_paren;
-					char *space;
-					GString *temp;
-
-					if (!(end_paren = strstr(link, ")")))
-					{
-						/* Something is not as we expect.  Bail out. */
-						break;
-					}
-
-					*tmp = '\0';
-					temp = g_string_new(line);
-
-					/* Start an <a> tag. */
-					g_string_append(temp, "<a href=\"");
-
-					/* Append up to the ) */
-					g_string_append_len(temp, link, end_paren - link);
+		if ((c = strstr(c, "\n")))
+		{
+			*c = '\0';
+			c++;
+		}
 
-					/* Finish the <a> tag. */
-					g_string_append(temp, "\">");
-
-					/* The \r is a bit of a hack to keep there from being a \r in
-					 * the link text, which may not matter. */
-					if ((space = strstr(end_paren, " ")) || (space = strstr(end_paren, "\r")))
-					{
-						g_string_append_len(temp, end_paren + 1, space - end_paren - 1);
-
-						/* Close the <a> tag. */
-						g_string_append(temp, "</a>");
+		/* Convert links.
+		 *
+		 * The format is (Link: URL)URL
+		 * So, I want to find each occurance of "(Link: " and replace that chunk with:
+		 * <a href="
+		 * Then, replace the next ")" with:
+		 * ">
+		 * Then, replace the next " " (or add this if the end-of-line is reached) with:
+		 * </a>
+		 * 
+		 * As implemented, this isn't perfect, but it should cover common cases.
+		 */
+		while (line && (link = strstr(line, "(Link: ")))
+		{
+			const char *tmp = link;
 
-						space++;
-						if (*space)
-						{
-							g_string_append_c(temp, ' ');
-							/* Keep the rest of the line. */
-							g_string_append(temp, space);
-						}
-					}
-					else
-					{
-						/* There is no space before the end of the line. */
-						g_string_append(temp, end_paren + 1);
-						/* Close the <a> tag. */
-						g_string_append(temp, "</a>");
-					}
-
-					g_free(link_temp_line);
-					line = g_string_free(temp, FALSE);
+			link += 7;
+			if (*link)
+			{
+				char *end_paren;
+				char *space;
 
-					/* Save this memory location so we can free it later. */
-					link_temp_line = line;
-				}
-			}
-
-			timestamp = "";
-			if (*line == '[') {
-				timestamp = line;
-				while (*timestamp && *timestamp != ']')
-					timestamp++;
-				if (*timestamp == ']') {
-					*timestamp = '\0';
-					line++;
-					/* TODO: Parse the timestamp and convert it to Purple's format. */
-					g_string_append_printf(formatted,
-						"<font size=\"2\">(%s)</font> ", line);
-					line = timestamp;
-					if (line[1] && line[2])
-						line += 2;
+				if (!(end_paren = strstr(link, ")")))
+				{
+					/* Something is not as we expect.  Bail out. */
+					break;
 				}
 
-				if (purple_str_has_prefix(line, "*** ")) {
-					line += (sizeof("*** ") - 1);
-					g_string_append(formatted, "<b>");
-					footer = "</b>";
-					if (purple_str_has_prefix(line, "NOTE: This user is offline.")) {
-						line = _("User is offline.");
-					} else if (purple_str_has_prefix(line,
-							"NOTE: Your status is currently set to ")) {
+				if (!temp)
+					temp = g_string_sized_new(c ? (c - 1 - line) : strlen(line));
+
+				g_string_append_len(temp, line, (tmp - line));
+
+				/* Start an <a> tag. */
+				g_string_append(temp, "<a href=\"");
+
+				/* Append up to the ) */
+				g_string_append_len(temp, link, end_paren - link);
+
+				/* Finish the <a> tag. */
+				g_string_append(temp, "\">");
+
+				/* The \r is a bit of a hack to keep there from being a \r in
+				 * the link text, which may not matter. */
+				if ((space = strstr(end_paren, " ")) || (space = strstr(end_paren, "\r")))
+				{
+					g_string_append_len(temp, end_paren + 1, space - end_paren - 1);
+
+					/* Close the <a> tag. */
+					g_string_append(temp, "</a>");
+
+					space++;
+				}
+				else
+				{
+					/* There is no space before the end of the line. */
+					g_string_append(temp, end_paren + 1);
+					/* Close the <a> tag. */
+					g_string_append(temp, "</a>");
+				}
+				line = space;
+			}
+			else
+			{
+				/* Something is not as we expect.  Bail out. */
+				break;
+			}
+		}
+
+		if (temp)
+		{
+			if (line)
+				g_string_append(temp, line);
+			line = temp->str;
+		}
+
+		if (*line == '[') {
+			const char *timestamp;
+
+			if ((timestamp = strstr(line, "]"))) {
+				line++;
+				/* TODO: Parse the timestamp and convert it to Purple's format. */
+				g_string_append(formatted, "<font size=\"2\">(");
+				g_string_append_len(formatted, line, (timestamp - line));
+				g_string_append(formatted,")</font> ");
+				line = timestamp + 1;
+				if (line[0] && line[1])
+					line++;
+			}
 
-						line += (sizeof("NOTE: ") - 1);
-					} else if (purple_str_has_prefix(line, "Auto-response sent to ")) {
-						g_string_append(formatted, _("Auto-response sent:"));
-						while (*line && *line != ':')
-							line++;
-						if (*line)
-							line++;
-						g_string_append(formatted, "</b>");
-						footer = NULL;
-					} else if (strstr(line, " signed off ")) {
-						if (buddy != NULL && buddy->alias)
-							g_string_append_printf(formatted,
-								_("%s has signed off."), buddy->alias);
-						else
-							g_string_append_printf(formatted,
-								_("%s has signed off."), log->name);
-						line = "";
-					} else if (strstr(line, " signed on ")) {
-						if (buddy != NULL && buddy->alias)
-							g_string_append(formatted, buddy->alias);
-						else
-							g_string_append(formatted, log->name);
-						line = " logged in.";
-					} else if (purple_str_has_prefix(line,
-						"One or more messages may have been undeliverable.")) {
+			if (purple_str_has_prefix(line, "*** ")) {
+				line += (sizeof("*** ") - 1);
+				g_string_append(formatted, "<b>");
+				footer = "</b>";
+				if (purple_str_has_prefix(line, "NOTE: This user is offline.")) {
+					line = _("User is offline.");
+				} else if (purple_str_has_prefix(line,
+						"NOTE: Your status is currently set to ")) {
+
+					line += (sizeof("NOTE: ") - 1);
+				} else if (purple_str_has_prefix(line, "Auto-response sent to ")) {
+					g_string_append(formatted, _("Auto-response sent:"));
+					while (*line && *line != ':')
+						line++;
+					if (*line)
+						line++;
+					g_string_append(formatted, "</b>");
+					footer = NULL;
+				} else if (strstr(line, " signed off ")) {
+					if (buddy != NULL && buddy->alias)
+						g_string_append_printf(formatted,
+							_("%s has signed off."), buddy->alias);
+					else
+						g_string_append_printf(formatted,
+							_("%s has signed off."), log->name);
+					line = "";
+				} else if (strstr(line, " signed on ")) {
+					if (buddy != NULL && buddy->alias)
+						g_string_append(formatted, buddy->alias);
+					else
+						g_string_append(formatted, log->name);
+					line = " logged in.";
+				} else if (purple_str_has_prefix(line,
+					"One or more messages may have been undeliverable.")) {
 
-						g_string_append(formatted,
-							"<span style=\"color: #ff0000;\">");
-						g_string_append(formatted,
-							_("One or more messages may have been "
-							  "undeliverable."));
-						line = "";
-						footer = "</span></b>";
-					} else if (purple_str_has_prefix(line,
-							"You have been disconnected.")) {
+					g_string_append(formatted,
+						"<span style=\"color: #ff0000;\">");
+					g_string_append(formatted,
+						_("One or more messages may have been "
+						  "undeliverable."));
+					line = "";
+					footer = "</span></b>";
+				} else if (purple_str_has_prefix(line,
+						"You have been disconnected.")) {
 
-						g_string_append(formatted,
-							"<span style=\"color: #ff0000;\">");
-						g_string_append(formatted,
-							_("You were disconnected from the server."));
-						line = "";
-						footer = "</span></b>";
-					} else if (purple_str_has_prefix(line,
-							"You are currently disconnected.")) {
+					g_string_append(formatted,
+						"<span style=\"color: #ff0000;\">");
+					g_string_append(formatted,
+						_("You were disconnected from the server."));
+					line = "";
+					footer = "</span></b>";
+				} else if (purple_str_has_prefix(line,
+						"You are currently disconnected.")) {
+
+					g_string_append(formatted,
+						"<span style=\"color: #ff0000;\">");
+					line = _("You are currently disconnected. Messages "
+					         "will not be received unless you are "
+					         "logged in.");
+					footer = "</span></b>";
+				} else if (purple_str_has_prefix(line,
+						"Your previous message has not been sent.")) {
+
+					g_string_append(formatted,
+						"<span style=\"color: #ff0000;\">");
+
+					if (purple_str_has_prefix(line,
+						"Your previous message has not been sent.  "
+						"Reason: Maximum length exceeded.")) {
 
 						g_string_append(formatted,
-							"<span style=\"color: #ff0000;\">");
-						line = _("You are currently disconnected. Messages "
-						         "will not be received unless you are "
-						         "logged in.");
-						footer = "</span></b>";
-					} else if (purple_str_has_prefix(line,
-							"Your previous message has not been sent.")) {
-
+							_("Message could not be sent because "
+							  "the maximum length was exceeded."));
+						line = "";
+					} else {
 						g_string_append(formatted,
-							"<span style=\"color: #ff0000;\">");
-
-						if (purple_str_has_prefix(line,
-							"Your previous message has not been sent.  "
-							"Reason: Maximum length exceeded.")) {
+							_("Message could not be sent."));
+						line += (sizeof(
+							"Your previous message "
+							"has not been sent. ") - 1);
+					}
 
-							g_string_append(formatted,
-								_("Message could not be sent because "
-								  "the maximum length was exceeded."));
-							line = "";
-						} else {
-							g_string_append(formatted,
-								_("Message could not be sent."));
-							line += (sizeof(
-								"Your previous message "
-								"has not been sent. ") - 1);
-						}
+					footer = "</span></b>";
+				}
+			} else if (purple_str_has_prefix(line, data->their_nickname)) {
+				if (buddy != NULL && buddy->alias) {
+					line += strlen(data->their_nickname) + 2;
+					g_string_append_printf(formatted,
+						"<span style=\"color: #A82F2F;\">"
+						"<b>%s</b></span>: ", buddy->alias);
+				}
+			} else {
+				const char *line2 = strstr(line, ":");
+				if (line2) {
+					const char *acct_name;
+					line2++;
+					line = line2;
+					acct_name = purple_account_get_alias(log->account);
+					if (!acct_name)
+						acct_name = purple_account_get_username(log->account);
 
-						footer = "</span></b>";
-					}
-				} else if (purple_str_has_prefix(line, data->their_nickname)) {
-					if (buddy != NULL && buddy->alias) {
-						line += strlen(data->their_nickname) + 2;
-						g_string_append_printf(formatted,
-							"<span style=\"color: #A82F2F;\">"
-							"<b>%s</b></span>: ", buddy->alias);
-					}
-				} else {
-					char *line2 = line;
-					while (*line2 && *line2 != ':')
-						line2++;
-					if (*line2 == ':') {
-						const char *acct_name;
-						line2++;
-						line = line2;
-						acct_name = purple_account_get_alias(log->account);
-						if (!acct_name)
-							acct_name = purple_account_get_username(log->account);
-
-						g_string_append_printf(formatted,
-							"<span style=\"color: #16569E;\">"
-							"<b>%s</b></span>:", acct_name);
-					}
+					g_string_append_printf(formatted,
+						"<span style=\"color: #16569E;\">"
+						"<b>%s</b></span>:", acct_name);
 				}
 			}
-
-			g_string_append(formatted, line);
+		}
 
-			if (footer)
-				g_string_append(formatted, footer);
+		g_string_append(formatted, line);
 
-			g_string_append_c(formatted, '\n');
-
-			g_free(link_temp_line);
+		line = c;
+		if (temp)
+			g_string_free(temp, TRUE);
 
-			c++;
-			line = c;
-		} else
-			c++;
+		if (footer)
+			g_string_append(formatted, footer);
+
+		g_string_append_c(formatted, '\n');
 	}
 
 	g_free(read);
-	read = formatted->str;
-	g_string_free(formatted, FALSE);
-
-	return read;
+	/* XXX: TODO: Avoid this g_strchomp() */
+	return g_strchomp(g_string_free(formatted, FALSE));
 }
 
 static int trillian_logger_size (PurpleLog *log)
--- a/libpurple/plugins/perl/common/Account.xs	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/plugins/perl/common/Account.xs	Tue Jun 12 21:21:37 2007 +0000
@@ -215,6 +215,7 @@
         t_GL = g_list_append(t_GL, SvPV(*av_fetch((AV *)SvRV(list), i, 0), t_sl));
     }
     purple_account_add_buddies(account, t_GL);
+    g_list_free(t_GL);
 
 void
 purple_account_add_buddy(account, buddy)
@@ -252,6 +253,8 @@
         t_GL2 = g_list_append(t_GL2, SvPV(*av_fetch((AV *)SvRV(B), i, 0), t_sl));
     }
     purple_account_remove_buddies(account, t_GL1, t_GL2);
+    g_list_free(t_GL1);
+    g_list_free(t_GL2);
 
 void
 purple_account_remove_buddy(account, buddy, group)
@@ -287,7 +290,7 @@
 void
 purple_accounts_get_all()
 PREINIT:
-    GList *l;
+    const GList *l;
 PPCODE:
     for (l = purple_accounts_get_all(); l != NULL; l = l->next) {
         XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::Account")));
--- a/libpurple/plugins/perl/common/BuddyList.xs	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/plugins/perl/common/BuddyList.xs	Tue Jun 12 21:21:37 2007 +0000
@@ -112,6 +112,10 @@
 	Purple::BuddyList::Group  group
 	Purple::Account account
 
+const char *
+purple_group_get_name(group)
+	Purple::BuddyList::Group group
+
 MODULE = Purple::BuddyList  PACKAGE = Purple::BuddyList  PREFIX = purple_blist_
 PROTOTYPES: ENABLE
 
@@ -248,6 +252,9 @@
 Purple::Handle
 purple_blist_get_handle()
 
+Purple::BuddyList::Node
+purple_blist_get_root()
+
 void
 purple_blist_init()
 
@@ -263,7 +270,7 @@
 PREINIT:
 	GList *l;
 PPCODE:
-	for (l = purple_blist_node_get_extended_menu(node); l != NULL; l = l->next) {
+	for (l = purple_blist_node_get_extended_menu(node); l != NULL; l = g_list_delete_link(l, l)) {
 		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::Menu::Action")));
 	}
 
@@ -308,6 +315,15 @@
 purple_blist_node_get_flags(node)
 	Purple::BuddyList::Node node
 
+Purple::BuddyList::NodeType
+purple_blist_node_get_type(node)
+	Purple::BuddyList::Node node
+
+Purple::BuddyList::Node
+purple_blist_node_next(node, offline)
+	Purple::BuddyList::Node node
+	gboolean offline
+
 MODULE = Purple::BuddyList  PACKAGE = Purple::BuddyList::Chat  PREFIX = purple_chat_
 PROTOTYPES: ENABLE
 
--- a/libpurple/plugins/perl/common/Connection.xs	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/plugins/perl/common/Connection.xs	Tue Jun 12 21:21:37 2007 +0000
@@ -72,7 +72,7 @@
 void
 purple_connections_get_all()
 PREINIT:
-	GList *l;
+	const GList *l;
 PPCODE:
 	for (l = purple_connections_get_all(); l != NULL; l = l->next) {
 		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::Connection")));
@@ -81,7 +81,7 @@
 void
 purple_connections_get_connecting()
 PREINIT:
-	GList *l;
+	const GList *l;
 PPCODE:
 	for (l = purple_connections_get_connecting(); l != NULL; l = l->next) {
 		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::Connection")));
--- a/libpurple/plugins/perl/common/Conversation.xs	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/plugins/perl/common/Conversation.xs	Tue Jun 12 21:21:37 2007 +0000
@@ -93,7 +93,7 @@
 void
 purple_get_ims()
 PREINIT:
-	GList *l;
+	const GList *l;
 PPCODE:
 	for (l = purple_get_ims(); l != NULL; l = l->next) {
 		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::Conversation")));
@@ -102,7 +102,7 @@
 void
 purple_get_conversations()
 PREINIT:
-	GList *l;
+	const GList *l;
 PPCODE:
 	for (l = purple_get_conversations(); l != NULL; l = l->next) {
 		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::Conversation")));
@@ -111,7 +111,7 @@
 void
 purple_get_chats()
 PREINIT:
-	GList *l;
+	const GList *l;
 PPCODE:
 	for (l = purple_get_chats(); l != NULL; l = l->next) {
 		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::Conversation")));
@@ -218,6 +218,21 @@
 	Purple::Conversation conv
 	Purple::Account account
 
+void
+purple_conversation_write(conv, who, message, flags, mtime)
+	Purple::Conversation conv
+	const char *who
+	const char *message
+	Purple::MessageFlags flags
+	time_t mtime
+
+gboolean
+purple_conversation_do_command(conv, cmdline, markup, error)
+	Purple::Conversation conv
+	const char *cmdline
+	const char *markup
+	char **error
+
 MODULE = Purple::Conversation  PACKAGE = Purple::Conversation::IM  PREFIX = purple_conv_im_
 PROTOTYPES: ENABLE
 
@@ -339,7 +354,7 @@
 purple_conv_chat_get_users(chat)
 	Purple::Conversation::Chat chat
 PREINIT:
-	GList *l;
+	const GList *l;
 PPCODE:
 	for (l = purple_conv_chat_get_users(chat); l != NULL; l = l->next) {
 		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::ListEntry")));
@@ -379,7 +394,7 @@
 purple_conv_chat_get_ignored(chat)
 	Purple::Conversation::Chat chat
 PREINIT:
-	GList *l;
+	const GList *l;
 PPCODE:
 	for (l = purple_conv_chat_get_ignored(chat); l != NULL; l = l->next) {
 		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::ListEntry")));
--- a/libpurple/plugins/perl/common/Prefs.xs	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/plugins/perl/common/Prefs.xs	Tue Jun 12 21:21:37 2007 +0000
@@ -94,8 +94,9 @@
 PREINIT:
 	GList *l;
 PPCODE:
-	for (l = purple_prefs_get_string_list(name); l != NULL; l = l->next) {
-		XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::PrefValue")));
+	for (l = purple_prefs_get_string_list(name); l != NULL; l = g_list_delete_link(l, l)) {
+		XPUSHs(sv_2mortal(newSVpv(l->data, 0)));
+		g_free(l->data);
 	}
 
 Purple::PrefType
--- a/libpurple/plugins/perl/common/module.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/plugins/perl/common/module.h	Tue Jun 12 21:21:37 2007 +0000
@@ -69,6 +69,7 @@
 /* blist.h */
 typedef PurpleBlistNode *			Purple__BuddyList__Node;
 typedef PurpleBlistNodeFlags		Purple__BuddyList__NodeFlags;
+typedef PurpleBlistNodeType		Purple__BuddyList__NodeType;
 typedef PurpleBlistUiOps *		Purple__BuddyList__UiOps;
 typedef PurpleBuddyList *			Purple__BuddyList;
 typedef PurpleBuddy *			Purple__BuddyList__Buddy;
--- a/libpurple/plugins/perl/common/typemap	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/plugins/perl/common/typemap	Tue Jun 12 21:21:37 2007 +0000
@@ -52,6 +52,7 @@
 Purple::BuddyList::Group			T_PurpleObj
 Purple::BuddyList::Node			T_PurpleObj
 Purple::BuddyList::NodeFlags		T_IV
+Purple::BuddyList::NodeType		T_IV
 Purple::BuddyList::UiOps			T_PurpleObj
 
 Purple::Cipher				T_PurpleObj
--- a/libpurple/plugins/ssl/ssl-nss.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/plugins/ssl/ssl-nss.c	Tue Jun 12 21:21:37 2007 +0000
@@ -32,7 +32,6 @@
 #undef HAVE_LONG_LONG /* Make Mozilla less angry. If angry, Mozilla SMASH! */
 
 #include <nspr.h>
-#include <private/pprio.h>
 #include <nss.h>
 #include <pk11func.h>
 #include <prio.h>
@@ -42,6 +41,10 @@
 #include <sslerr.h>
 #include <sslproto.h>
 
+/* This is defined in NSPR's <private/pprio.h>, but to avoid including a
+ * private header we duplicate the prototype here */
+NSPR_API(PRFileDesc*)  PR_ImportTCPSocket(PRInt32 osfd);
+
 typedef struct
 {
 	PRFileDesc *fd;
@@ -311,8 +314,13 @@
 	if(!nss_data)
 		return;
 
-	if (nss_data->in) PR_Close(nss_data->in);
-	/* if (nss_data->fd) PR_Close(nss_data->fd); */
+	if (nss_data->in) {
+		PR_Close(nss_data->in);
+		gsc->fd = -1;
+	} else if (nss_data->fd) {
+		PR_Close(nss_data->fd);
+		gsc->fd = -1;
+	}
 
 	if (nss_data->handshake_handler)
 		purple_input_remove(nss_data->handshake_handler);
--- a/libpurple/plugins/tcl/tcl.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/plugins/tcl/tcl.c	Tue Jun 12 21:21:37 2007 +0000
@@ -457,11 +457,23 @@
 
 	if ((version = wpurple_read_reg_string(HKEY_LOCAL_MACHINE, regkey, "CurrentVersion"))
 			|| (version = wpurple_read_reg_string(HKEY_CURRENT_USER, regkey, "CurrentVersion"))) {
-		char *path;
+		char *path = NULL;
 		char *regkey2;
+		char **tokens;
+		int major = 0, minor = 0, micro = 0;
+
+		tokens = g_strsplit(version, ".", 0);
+		if (tokens[0] && tokens[1] && tokens[2]) {
+			major = atoi(tokens[0]);
+			minor = atoi(tokens[1]);
+			micro = atoi(tokens[2]);
+		}
+		g_strfreev(tokens);
 
 		regkey2 = g_strdup_printf("%s%s\\", regkey, version);
-		if ((path = wpurple_read_reg_string(HKEY_LOCAL_MACHINE, regkey2, NULL)) || (path = wpurple_read_reg_string(HKEY_CURRENT_USER, regkey2, NULL))) {
+		if (!(major == 8 && minor == 4 && micro >= 5))
+			purple_debug(PURPLE_DEBUG_INFO, "tcl", "Unsupported ActiveTCL version %s found.\n", version);
+		else if ((path = wpurple_read_reg_string(HKEY_LOCAL_MACHINE, regkey2, NULL)) || (path = wpurple_read_reg_string(HKEY_CURRENT_USER, regkey2, NULL))) {
 			char *tclpath;
 			char *tkpath;
 
--- a/libpurple/plugins/tcl/tcl_cmds.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/plugins/tcl/tcl_cmds.c	Tue Jun 12 21:21:37 2007 +0000
@@ -43,7 +43,7 @@
 static PurpleAccount *tcl_validate_account(Tcl_Obj *obj, Tcl_Interp *interp)
 {
 	PurpleAccount *account;
-	GList *cur;
+	const GList *cur;
 
 	account = purple_tcl_ref_get(interp, obj, PurpleTclRefAccount);
 
@@ -62,7 +62,7 @@
 static PurpleConversation *tcl_validate_conversation(Tcl_Obj *obj, Tcl_Interp *interp)
 {
 	PurpleConversation *convo;
-	GList *cur;
+	const GList *cur;
 
 	convo = purple_tcl_ref_get(interp, obj, PurpleTclRefConversation);
 
@@ -81,7 +81,7 @@
 static PurpleConnection *tcl_validate_gc(Tcl_Obj *obj, Tcl_Interp *interp)
 {
 	PurpleConnection *gc;
-	GList *cur;
+	const GList *cur;
 
 	gc = purple_tcl_ref_get(interp, obj, PurpleTclRefConnection);
 
@@ -612,7 +612,7 @@
 	const char *cmds[] = { "account", "displayname", "handle", "list", NULL };
 	enum { CMD_CONN_ACCOUNT, CMD_CONN_DISPLAYNAME, CMD_CONN_HANDLE, CMD_CONN_LIST } cmd;
 	int error;
-	GList *cur;
+	const GList *cur;
 	PurpleConnection *gc;
 
 	if (objc < 2) {
@@ -680,7 +680,7 @@
 	PurpleConversation *convo;
 	PurpleAccount *account;
 	PurpleConversationType type;
-	GList *cur;
+	const GList *cur;
 	char *opt, *from, *what;
 	int error, argsused, flags = 0;
 
--- a/libpurple/pounce.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/pounce.c	Tue Jun 12 21:21:37 2007 +0000
@@ -273,7 +273,7 @@
 schedule_pounces_save(void)
 {
 	if (save_timer == 0)
-		save_timer = purple_timeout_add(5000, save_cb, NULL);
+		save_timer = purple_timeout_add_seconds(5, save_cb, NULL);
 }
 
 
--- a/libpurple/prefs.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/prefs.c	Tue Jun 12 21:21:37 2007 +0000
@@ -226,7 +226,7 @@
 schedule_prefs_save(void)
 {
 	if (save_timer == 0)
-		save_timer = purple_timeout_add(5000, save_cb, NULL);
+		save_timer = purple_timeout_add_seconds(5, save_cb, NULL);
 }
 
 
--- a/libpurple/protocols/Makefile.am	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/Makefile.am	Tue Jun 12 21:21:37 2007 +0000
@@ -1,5 +1,5 @@
 EXTRA_DIST = Makefile.mingw
 
-DIST_SUBDIRS = bonjour gg irc jabber msn novell null oscar qq sametime silc toc simple yahoo zephyr
+DIST_SUBDIRS = bonjour gg irc jabber msn novell null oscar qq sametime silc silc10 toc simple yahoo zephyr
 
 SUBDIRS = $(DYNAMIC_PRPLS) $(STATIC_PRPLS)
--- a/libpurple/protocols/Makefile.mingw	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/Makefile.mingw	Tue Jun 12 21:21:37 2007 +0000
@@ -8,7 +8,7 @@
 PIDGIN_TREE_TOP := ../..
 include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
 
-SUBDIRS = gg irc jabber msn novell null oscar qq sametime silc simple yahoo
+SUBDIRS = gg irc jabber msn novell null oscar qq sametime silc simple yahoo bonjour
 
 .PHONY: all install clean
 
--- a/libpurple/protocols/bonjour/Makefile.am	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/bonjour/Makefile.am	Tue Jun 12 21:21:37 2007 +0000
@@ -1,4 +1,6 @@
 EXTRA_DIST = \
+		mdns_win32.c \
+		mdns_win32.h \
 		Makefile.mingw
 
 pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
@@ -8,12 +10,16 @@
 	bonjour.h \
 	buddy.c \
 	buddy.h \
-	dns_sd.c \
-	dns_sd.h \
+	dns_sd_proxy.h \
 	jabber.c \
-	jabber.h
+	jabber.h \
+	mdns_common.c \
+	mdns_common.h \
+	mdns_howl.c \
+	mdns_howl.h \
+	mdns_types.h
 
-AM_CFLAGS = $(st)
+AM_CFLAGS = $(st) -DUSE_BONJOUR_HOWL
 
 libbonjour_la_LDFLAGS = -module -avoid-version
 
--- a/libpurple/protocols/bonjour/Makefile.mingw	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/bonjour/Makefile.mingw	Tue Jun 12 21:21:37 2007 +0000
@@ -8,7 +8,6 @@
 include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
 
 TARGET = libbonjour
-NEEDED_DLLS = $(HOWL_TOP)/bin/libhowl-1.dll
 TYPE = PLUGIN
 
 # Static or Plugin...
@@ -21,20 +20,22 @@
 endif
 endif
 
+CFLAGS += -DUSE_BONJOUR_APPLE
+
 ##
 ## INCLUDE PATHS
 ##
-INCLUDE_PATHS +=	-I$(BONJOUR_ROOT) \
+INCLUDE_PATHS +=	-I. \
 			-I$(GTK_TOP)/include \
 			-I$(GTK_TOP)/include/glib-2.0 \
 			-I$(GTK_TOP)/lib/glib-2.0/include \
-			-I$(HOWL_TOP)/include \
+			-I$(BONJOUR_TOP)/include \
 			-I$(PURPLE_TOP) \
 			-I$(PURPLE_TOP)/win32 \
 			-I$(PIDGIN_TREE_TOP)
 
 LIB_PATHS +=		-L$(GTK_TOP)/lib \
-			-L$(HOWL_TOP)/lib \
+			-L$(BONJOUR_TOP)/lib \
 			-L$(PURPLE_TOP)
 
 ##
@@ -42,7 +43,8 @@
 ##
 C_SRC =			bonjour.c \
 			buddy.c \
-			dns_sd.c \
+			mdns_common.c \
+			mdns_win32.c \
 			jabber.c
 
 OBJECTS = $(C_SRC:%.c=%.o)
@@ -54,7 +56,7 @@
 			-lglib-2.0 \
 			-lws2_32 \
 			-lintl \
-			-lhowl \
+			-ldnssd \
 			-lpurple
 
 include $(PIDGIN_COMMON_RULES)
@@ -68,7 +70,6 @@
 
 install: all $(DLL_INSTALL_DIR)
 	cp $(TARGET).dll $(DLL_INSTALL_DIR)
-	cp $(NEEDED_DLLS) $(PURPLE_INSTALL_DIR)
 
 $(OBJECTS): $(PURPLE_CONFIG_H)
 
--- a/libpurple/protocols/bonjour/bonjour.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Tue Jun 12 21:21:37 2007 +0000
@@ -37,7 +37,7 @@
 #include "version.h"
 
 #include "bonjour.h"
-#include "dns_sd.h"
+#include "mdns_common.h"
 #include "jabber.h"
 #include "buddy.h"
 
@@ -94,14 +94,13 @@
 bonjour_login(PurpleAccount *account)
 {
 	PurpleConnection *gc = purple_account_get_connection(account);
-	PurpleGroup *bonjour_group = NULL;
-	BonjourData *bd = NULL;
+	PurpleGroup *bonjour_group;
+	BonjourData *bd;
 	PurpleStatus *status;
 	PurplePresence *presence;
 
 	gc->flags |= PURPLE_CONNECTION_HTML;
-	gc->proto_data = g_new0(BonjourData, 1);
-	bd = gc->proto_data;
+	gc->proto_data = bd = g_new0(BonjourData, 1);
 
 	/* Start waiting for jabber connections (iChat style) */
 	bd->jabber_data = g_new(BonjourJabber, 1);
@@ -111,26 +110,16 @@
 	if (bonjour_jabber_start(bd->jabber_data) == -1) {
 		/* Send a message about the connection error */
 		purple_connection_error(gc, _("Unable to listen for incoming IM connections\n"));
-
-		/* Free the data */
-		g_free(bd->jabber_data);
-		bd->jabber_data = NULL;
 		return;
 	}
 
 	/* Connect to the mDNS daemon looking for buddies in the LAN */
 	bd->dns_sd_data = bonjour_dns_sd_new();
-	bd->dns_sd_data->name = (sw_string)purple_account_get_username(account);
-	bd->dns_sd_data->txtvers = g_strdup("1");
-	bd->dns_sd_data->version = g_strdup("1");
 	bd->dns_sd_data->first = g_strdup(purple_account_get_string(account, "first", default_firstname));
 	bd->dns_sd_data->last = g_strdup(purple_account_get_string(account, "last", default_lastname));
 	bd->dns_sd_data->port_p2pj = bd->jabber_data->port;
-	bd->dns_sd_data->phsh = g_strdup("");
-	bd->dns_sd_data->email = g_strdup(purple_account_get_string(account, "email", ""));
-	bd->dns_sd_data->vc = g_strdup("");
-	bd->dns_sd_data->jid = g_strdup(purple_account_get_string(account, "jid", ""));
-	bd->dns_sd_data->AIM = g_strdup(purple_account_get_string(account, "AIM", ""));
+	/* Not engaged in AV conference */
+	bd->dns_sd_data->vc = g_strdup("!");
 
 	status = purple_account_get_active_status(account);
 	presence = purple_account_get_presence(account);
@@ -161,7 +150,7 @@
 bonjour_close(PurpleConnection *connection)
 {
 	PurpleGroup *bonjour_group;
-	BonjourData *bd = (BonjourData*)connection->proto_data;
+	BonjourData *bd = connection->proto_data;
 
 	/* Stop looking for buddies in the LAN */
 	if (bd->dns_sd_data != NULL)
@@ -278,6 +267,7 @@
 bonjour_convo_closed(PurpleConnection *connection, const char *who)
 {
 	PurpleBuddy *buddy = purple_find_buddy(connection->account, who);
+	BonjourBuddy *bb;
 
 	if (buddy == NULL)
 	{
@@ -288,7 +278,9 @@
 		return;
 	}
 
-	bonjour_jabber_close_conversation(((BonjourData*)(connection->proto_data))->jabber_data, buddy);
+	bb = buddy->proto_data;
+	bonjour_jabber_close_conversation(bb->conversation);
+	bb->conversation = NULL;
 }
 
 static char *
@@ -331,6 +323,8 @@
 static gboolean
 plugin_unload(PurplePlugin *plugin)
 {
+	/* These shouldn't happen here because they are allocated in _init() */
+
 	g_free(default_firstname);
 	g_free(default_lastname);
 	g_free(default_hostname);
@@ -491,8 +485,8 @@
 		LPUSER_INFO_10 user_info = NULL;
 		LPSERVER_INFO_100 server_info = NULL;
 		wchar_t *servername = NULL;
-		wchar_t username[UNLEN + 1] = {'\0'};
-		DWORD dwLenUsername = sizeof(username);
+		wchar_t username[UNLEN + 1];
+		DWORD dwLenUsername = UNLEN + 1;
 		FARPROC myNetServerEnum = wpurple_find_and_loadproc(
 			"Netapi32.dll", "NetServerEnum");
 		FARPROC myNetApiBufferFree = wpurple_find_and_loadproc(
@@ -517,7 +511,7 @@
 			}
 		}
 
-		if (!GetUserNameW(&username, &dwLenUsername)) {
+		if (!GetUserNameW((LPWSTR) &username, &dwLenUsername)) {
 			purple_debug_warning("bonjour",
 				"Unable to look up username\n");
 		}
@@ -553,7 +547,7 @@
 		 */
 		splitpoint = strchr(tmp, ',');
 		if (splitpoint != NULL)
-			default_lastname = g_strndup(tmp, splitpoint - tmp);			
+			default_lastname = g_strndup(tmp, splitpoint - tmp);
 		else
 			default_lastname = g_strdup(tmp);
 	}
--- a/libpurple/protocols/bonjour/bonjour.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/bonjour/bonjour.h	Tue Jun 12 21:21:37 2007 +0000
@@ -26,9 +26,7 @@
 #ifndef _BONJOUR_H_
 #define _BONJOUR_H_
 
-#include <howl.h>
-
-#include "dns_sd.h"
+#include "mdns_common.h"
 #include "internal.h"
 #include "jabber.h"
 
--- a/libpurple/protocols/bonjour/buddy.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.c	Tue Jun 12 21:21:37 2007 +0000
@@ -27,51 +27,66 @@
  * Creates a new buddy.
  */
 BonjourBuddy *
-bonjour_buddy_new(const gchar *name, const gchar *first, gint port_p2pj,
-	const gchar *phsh, const gchar *status, const gchar *email,
-	const gchar *last, const gchar *jid, const gchar *AIM,
-	const gchar *vc, const gchar *ip, const gchar *msg)
+bonjour_buddy_new(const gchar *name, PurpleAccount* account)
 {
-	BonjourBuddy *buddy = malloc(sizeof(BonjourBuddy));
+	BonjourBuddy *buddy = g_new0(BonjourBuddy, 1);
 
+	buddy->account = account;
 	buddy->name = g_strdup(name);
-	buddy->first = g_strdup(first);
-	buddy->port_p2pj = port_p2pj;
-	buddy->phsh = g_strdup(phsh);
-	buddy->status = g_strdup(status);
-	buddy->email = g_strdup(email);
-	buddy->last = g_strdup(last);
-	buddy->jid = g_strdup(jid);
-	buddy->AIM = g_strdup(AIM);
-	buddy->vc = g_strdup(vc);
-	buddy->ip = g_strdup(ip);
-	buddy->msg = g_strdup(msg);
-	buddy->conversation = NULL;
 
 	return buddy;
 }
 
+void
+set_bonjour_buddy_value(BonjourBuddy* buddy, const char *record_key, const char *value, uint32_t len){
+	gchar **fld = NULL;
+
+	if (!strcmp(record_key, "1st"))
+		fld = &buddy->first;
+	else if(!strcmp(record_key, "email"))
+		fld = &buddy->email;
+	else if(!strcmp(record_key, "ext"))
+		fld = &buddy->ext;
+	else if(!strcmp(record_key, "jid"))
+		fld = &buddy->jid;
+	else if(!strcmp(record_key, "last"))
+		fld = &buddy->last;
+	else if(!strcmp(record_key, "msg"))
+		fld = &buddy->msg;
+	else if(!strcmp(record_key, "nick"))
+		fld = &buddy->nick;
+	else if(!strcmp(record_key, "node"))
+		fld = &buddy->node;
+	else if(!strcmp(record_key, "phsh"))
+		fld = &buddy->phsh;
+	else if(!strcmp(record_key, "status"))
+		fld = &buddy->status;
+	else if(!strcmp(record_key, "vc"))
+		fld = &buddy->vc;
+	else if(!strcmp(record_key, "ver"))
+		fld = &buddy->ver;
+	else if(!strcmp(record_key, "AIM"))
+		fld = &buddy->AIM;
+
+	if(fld == NULL)
+		return;
+
+	g_free(*fld);
+	*fld = NULL;
+	*fld = g_strndup(value, len);
+}
+
 /**
  * Check if all the compulsory buddy data is present.
  */
 gboolean
 bonjour_buddy_check(BonjourBuddy *buddy)
 {
-	if (buddy->name == NULL) {
+	if (buddy->account == NULL)
 		return FALSE;
-	}
-
-	if (buddy->first == NULL) {
-		return FALSE;
-	}
 
-	if (buddy->last == NULL) {
+	if (buddy->name == NULL)
 		return FALSE;
-	}
-
-	if (buddy->status == NULL) {
-		return FALSE;
-	}
 
 	return TRUE;
 }
@@ -82,12 +97,12 @@
  * the buddy.
  */
 void
-bonjour_buddy_add_to_purple(PurpleAccount *account, BonjourBuddy *bonjour_buddy)
+bonjour_buddy_add_to_purple(BonjourBuddy *bonjour_buddy)
 {
 	PurpleBuddy *buddy;
 	PurpleGroup *group;
 	const char *status_id, *first, *last;
-	char *alias;
+	gchar *alias;
 
 	/* Translate between the Bonjour status and the Purple status */
 	if (g_ascii_strcasecmp("dnd", bonjour_buddy->status) == 0)
@@ -117,10 +132,11 @@
 	}
 
 	/* Make sure the buddy exists in our buddy list */
-	buddy = purple_find_buddy(account, bonjour_buddy->name);
+	buddy = purple_find_buddy(bonjour_buddy->account, bonjour_buddy->name);
+
 	if (buddy == NULL)
 	{
-		buddy = purple_buddy_new(account, bonjour_buddy->name, alias);
+		buddy = purple_buddy_new(bonjour_buddy->account, bonjour_buddy->name, alias);
 		buddy->proto_data = bonjour_buddy;
 		purple_blist_node_set_flags((PurpleBlistNode *)buddy, PURPLE_BLIST_NODE_FLAG_NO_SAVE);
 		purple_blist_add_buddy(buddy, NULL, group, NULL);
@@ -128,13 +144,13 @@
 
 	/* Set the user's status */
 	if (bonjour_buddy->msg != NULL)
-		purple_prpl_got_user_status(account, buddy->name, status_id,
+		purple_prpl_got_user_status(bonjour_buddy->account, buddy->name, status_id,
 								  "message", bonjour_buddy->msg,
 								  NULL);
 	else
-		purple_prpl_got_user_status(account, buddy->name, status_id,
+		purple_prpl_got_user_status(bonjour_buddy->account, buddy->name, status_id,
 								  NULL);
-	purple_prpl_got_user_idle(account, buddy->name, FALSE, 0);
+	purple_prpl_got_user_idle(bonjour_buddy->account, buddy->name, FALSE, 0);
 
 	g_free(alias);
 }
@@ -146,6 +162,8 @@
 bonjour_buddy_delete(BonjourBuddy *buddy)
 {
 	g_free(buddy->name);
+	g_free(buddy->ip);
+
 	g_free(buddy->first);
 	g_free(buddy->phsh);
 	g_free(buddy->status);
@@ -154,14 +172,22 @@
 	g_free(buddy->jid);
 	g_free(buddy->AIM);
 	g_free(buddy->vc);
-	g_free(buddy->ip);
 	g_free(buddy->msg);
+	g_free(buddy->ext);
+	g_free(buddy->nick);
+	g_free(buddy->node);
+	g_free(buddy->ver);
 
-	if (buddy->conversation != NULL)
+	bonjour_jabber_close_conversation(buddy->conversation);
+	buddy->conversation = NULL;
+
+#ifdef USE_BONJOUR_APPLE
+	if (buddy->txt_query != NULL)
 	{
-		g_free(buddy->conversation->buddy_name);
-		g_free(buddy->conversation);
+		purple_input_remove(buddy->txt_query_fd);
+		DNSServiceRefDeallocate(buddy->txt_query);
 	}
+#endif
 
-	free(buddy);
+	g_free(buddy);
 }
--- a/libpurple/protocols/bonjour/buddy.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.h	Tue Jun 12 21:21:37 2007 +0000
@@ -17,17 +17,27 @@
 #ifndef _BONJOUR_BUDDY
 #define _BONJOUR_BUDDY
 
-#include <howl.h>
 #include <glib.h>
 
+#include "config.h"
 #include "account.h"
 #include "jabber.h"
 
+#ifdef USE_BONJOUR_APPLE 
+#include "dns_sd_proxy.h"
+#else /* USE_BONJOUR_HOWL */
+#include <howl.h>
+#endif
+
 typedef struct _BonjourBuddy
 {
+	PurpleAccount *account;
+
 	gchar *name;
+	gchar *ip;
+	gint port_p2pj;
+
 	gchar *first;
-	gint port_p2pj;
 	gchar *phsh;
 	gchar *status;
 	gchar *email;
@@ -35,18 +45,49 @@
 	gchar *jid;
 	gchar *AIM;
 	gchar *vc;
-	gchar *ip;
 	gchar *msg;
+	gchar *ext;
+	gchar *nick;
+	gchar *node;
+	gchar *ver;
+
 	BonjourJabberConversation *conversation;
+
+#ifdef USE_BONJOUR_APPLE
+	DNSServiceRef txt_query;
+	int txt_query_fd;
+#endif
+
 } BonjourBuddy;
 
+static const char *const buddy_TXT_records[] = {
+	"1st",
+	"email",
+	"ext",
+	"jid",
+	"last",
+	"msg",
+	"nick",
+	"node",
+	"phsh",
+/*	"port.p2pj", Deprecated - MUST ignore */
+	"status",
+/*	"txtvers", Deprecated - hardcoded to 1 */
+	"vc",
+	"ver",
+	"AIM", /* non standard */
+	NULL
+};
+
 /**
  * Creates a new buddy.
  */
-BonjourBuddy *bonjour_buddy_new(const gchar *name, const gchar *first,
-	gint port_p2pj, const gchar *phsh, const gchar *status,
-	const gchar *email, const gchar *last, const gchar *jid,
-	const gchar *AIM, const gchar *vc, const gchar *ip, const gchar *msg);
+BonjourBuddy *bonjour_buddy_new(const gchar *name, PurpleAccount *account);
+
+/**
+ * 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);
 
 /**
  * Check if all the compulsory buddy data is present.
@@ -56,7 +97,7 @@
 /**
  * If the buddy doesn't previoulsy exists, it is created. Else, its data is changed (???)
  */
-void bonjour_buddy_add_to_purple(PurpleAccount *account, BonjourBuddy *buddy);
+void bonjour_buddy_add_to_purple(BonjourBuddy *buddy);
 
 /**
  * Deletes a buddy from memory.
--- a/libpurple/protocols/bonjour/dns_sd.c	Tue Jun 12 13:54:04 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,387 +0,0 @@
-/*
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU Library General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- */
-
-#include <string.h>
-
-#include "dns_sd.h"
-#include "bonjour.h"
-#include "buddy.h"
-#include "debug.h"
-
-/* Private functions */
-
-static sw_result HOWL_API
-_publish_reply(sw_discovery discovery, sw_discovery_oid oid,
-			   sw_discovery_publish_status status, sw_opaque extra)
-{
-	purple_debug_warning("bonjour", "_publish_reply --> Start\n");
-
-	/* Check the answer from the mDNS daemon */
-	switch (status)
-	{
-		case SW_DISCOVERY_PUBLISH_STARTED :
-			purple_debug_info("bonjour", "_publish_reply --> Service started\n");
-			break;
-		case SW_DISCOVERY_PUBLISH_STOPPED :
-			purple_debug_info("bonjour", "_publish_reply --> Service stopped\n");
-			break;
-		case SW_DISCOVERY_PUBLISH_NAME_COLLISION :
-			purple_debug_info("bonjour", "_publish_reply --> Name collision\n");
-			break;
-		case SW_DISCOVERY_PUBLISH_INVALID :
-			purple_debug_info("bonjour", "_publish_reply --> Service invalid\n");
-			break;
-	}
-
-	return SW_OKAY;
-}
-
-static sw_result HOWL_API
-_resolve_reply(sw_discovery discovery, sw_discovery_oid oid,
-			   sw_uint32 interface_index, sw_const_string name,
-			   sw_const_string type, sw_const_string domain,
-			   sw_ipv4_address address, sw_port port,
-			   sw_octets text_record, sw_ulong text_record_len,
-			   sw_opaque extra)
-{
-	BonjourBuddy *buddy;
-	PurpleAccount *account = (PurpleAccount*)extra;
-	gchar *txtvers = NULL;
-	gchar *version = NULL;
-	gchar *first = NULL;
-	gchar *phsh = NULL;
-	gchar *status = NULL;
-	gchar *email = NULL;
-	gchar *last = NULL;
-	gchar *jid = NULL;
-	gchar *AIM = NULL;
-	gchar *vc = NULL;
-	gchar *msg = NULL;
-	gint address_length = 16;
-	gchar *ip = NULL;
-	sw_text_record_iterator iterator;
-	char key[SW_TEXT_RECORD_MAX_LEN];
-	char value[SW_TEXT_RECORD_MAX_LEN];
-	sw_uint32 value_length;
-
-	sw_discovery_cancel(discovery, oid);
-
-	/* Get the ip as a string */
-	ip = malloc(address_length);
-	sw_ipv4_address_name(address, ip, address_length);
-
-	/* Obtain the parameters from the text_record */
-	if ((text_record_len > 0) && (text_record) && (*text_record != '\0'))
-	{
-		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)
-		{
-			/* Compare the keys with the possible ones and save them on */
-			/* the appropiate place of the buddy_list */
-			if (strcmp(key, "txtvers") == 0) {
-				txtvers = g_strdup(value);
-			} else if (strcmp(key, "version") == 0) {
-				version = g_strdup(value);
-			} else if (strcmp(key, "1st") == 0) {
-				first = g_strdup(value);
-			} else if (strcmp(key, "status") == 0) {
-				status = g_strdup(value);
-			} else if (strcmp(key, "email") == 0) {
-				email = g_strdup(value);
-			} else if (strcmp(key, "last") == 0) {
-				last = g_strdup(value);
-			} else if (strcmp(key, "jid") == 0) {
-				jid = g_strdup(value);
-			} else if (strcmp(key, "AIM") == 0) {
-				AIM = g_strdup(value);
-			} else if (strcmp(key, "vc") == 0) {
-				vc = g_strdup(value);
-			} else if (strcmp(key, "phsh") == 0) {
-				phsh = g_strdup(value);
-			} else if (strcmp(key, "msg") == 0) {
-				msg = g_strdup(value);
-			}
-		}
-	}
-
-	/* Put the parameters of the text_record in a buddy and add the buddy to */
-	/* the buddy list */
-	buddy = bonjour_buddy_new(name, first, port, phsh,
-							  status, email, last, jid, AIM, vc, ip, msg);
-
-	if (bonjour_buddy_check(buddy) == FALSE)
-	{
-		bonjour_buddy_delete(buddy);
-		return SW_DISCOVERY_E_UNKNOWN;
-	}
-
-	/* Add or update the buddy in our buddy list */
-	bonjour_buddy_add_to_purple(account, buddy);
-
-	/* Free all the temporal strings */
-	g_free(txtvers);
-	g_free(version);
-	g_free(first);
-	g_free(last);
-	g_free(status);
-	g_free(email);
-	g_free(jid);
-	g_free(AIM);
-	g_free(vc);
-	g_free(phsh);
-	g_free(msg);
-
-	return SW_OKAY;
-}
-
-static sw_result HOWL_API
-_browser_reply(sw_discovery discovery, sw_discovery_oid oid,
-			   sw_discovery_browse_status status,
-			   sw_uint32 interface_index, sw_const_string name,
-			   sw_const_string type, sw_const_string domain,
-			   sw_opaque_t extra)
-{
-	sw_discovery_resolve_id rid;
-	PurpleAccount *account = (PurpleAccount*)extra;
-	PurpleBuddy *gb = NULL;
-
-	switch (status)
-	{
-		case SW_DISCOVERY_BROWSE_INVALID:
-			purple_debug_warning("bonjour", "_browser_reply --> Invalid\n");
-			break;
-		case SW_DISCOVERY_BROWSE_RELEASE:
-			purple_debug_warning("bonjour", "_browser_reply --> Release\n");
-			break;
-		case SW_DISCOVERY_BROWSE_ADD_DOMAIN:
-			purple_debug_warning("bonjour", "_browser_reply --> Add domain\n");
-			break;
-		case SW_DISCOVERY_BROWSE_ADD_DEFAULT_DOMAIN:
-			purple_debug_warning("bonjour", "_browser_reply --> Add default domain\n");
-			break;
-		case SW_DISCOVERY_BROWSE_REMOVE_DOMAIN:
-			purple_debug_warning("bonjour", "_browser_reply --> Remove domain\n");
-			break;
-		case SW_DISCOVERY_BROWSE_ADD_SERVICE:
-			/* A new peer has joined the network and uses iChat bonjour */
-			purple_debug_info("bonjour", "_browser_reply --> Add service\n");
-			if (g_ascii_strcasecmp(name, account->username) != 0)
-			{
-				if (sw_discovery_resolve(discovery, interface_index, name, type,
-						domain, _resolve_reply, extra, &rid) != SW_OKAY)
-				{
-					purple_debug_warning("bonjour", "_browser_reply --> Cannot send resolve\n");
-				}
-			}
-			break;
-		case SW_DISCOVERY_BROWSE_REMOVE_SERVICE:
-			purple_debug_info("bonjour", "_browser_reply --> Remove service\n");
-			gb = purple_find_buddy((PurpleAccount*)extra, name);
-			if (gb != NULL)
-			{
-				bonjour_buddy_delete(gb->proto_data);
-				purple_blist_remove_buddy(gb);
-			}
-			break;
-		case SW_DISCOVERY_BROWSE_RESOLVED:
-			purple_debug_info("bonjour", "_browse_reply --> Resolved\n");
-			break;
-		default:
-			break;
-	}
-
-	return SW_OKAY;
-}
-
-static int
-_dns_sd_publish(BonjourDnsSd *data, PublishType type)
-{
-	sw_text_record dns_data;
-	sw_result publish_result = SW_OKAY;
-	char portstring[6];
-
-	/* Fill the data for the service */
-	if (sw_text_record_init(&dns_data) != SW_OKAY)
-	{
-		purple_debug_error("bonjour", "Unable to initialize the data for the mDNS.\n");
-		return -1;
-	}
-
-	/* Convert the port to a string */
-	snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj);
-
-	/* Publish standard records */
-	sw_text_record_add_key_and_string_value(dns_data, "txtvers", data->txtvers);
-	sw_text_record_add_key_and_string_value(dns_data, "version", data->version);
-	sw_text_record_add_key_and_string_value(dns_data, "1st", data->first);
-	sw_text_record_add_key_and_string_value(dns_data, "last", data->last);
-	sw_text_record_add_key_and_string_value(dns_data, "port.p2pj", portstring);
-	sw_text_record_add_key_and_string_value(dns_data, "phsh", data->phsh);
-	sw_text_record_add_key_and_string_value(dns_data, "status", data->status);
-	sw_text_record_add_key_and_string_value(dns_data, "vc", data->vc);
-
-	/* Publish extra records */
-	if ((data->email != NULL) && (*data->email != '\0'))
-		sw_text_record_add_key_and_string_value(dns_data, "email", data->email);
-
-	if ((data->jid != NULL) && (*data->jid != '\0'))
-		sw_text_record_add_key_and_string_value(dns_data, "jid", data->jid);
-
-	if ((data->AIM != NULL) && (*data->AIM != '\0'))
-		sw_text_record_add_key_and_string_value(dns_data, "AIM", data->AIM);
-
-	if ((data->msg != NULL) && (*data->msg != '\0'))
-		sw_text_record_add_key_and_string_value(dns_data, "msg", data->msg);
-
-	/* Publish the service */
-	switch (type)
-	{
-		case PUBLISH_START:
-			publish_result = sw_discovery_publish(data->session, 0, data->name, ICHAT_SERVICE, NULL,
-								NULL, data->port_p2pj, sw_text_record_bytes(dns_data), sw_text_record_len(dns_data),
-								_publish_reply, NULL, &data->session_id);
-			break;
-		case PUBLISH_UPDATE:
-			publish_result = sw_discovery_publish_update(data->session, data->session_id,
-								sw_text_record_bytes(dns_data), sw_text_record_len(dns_data));
-			break;
-	}
-	if (publish_result != SW_OKAY)
-	{
-		purple_debug_error("bonjour", "Unable to publish or change the status of the _presence._tcp service.\n");
-		return -1;
-	}
-
-	/* Free the memory used by temp data */
-	sw_text_record_fina(dns_data);
-
-	return 0;
-}
-
-static void
-_dns_sd_handle_packets(gpointer data, gint source, PurpleInputCondition condition)
-{
-	sw_discovery_read_socket((sw_discovery)data);
-}
-
-/* End private functions */
-
-/**
- * Allocate space for the dns-sd data.
- */
-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)
-{
-	g_free(data->first);
-	g_free(data->last);
-	g_free(data->email);
-	g_free(data);
-}
-
-/**
- * Send a new dns-sd packet updating our status.
- */
-void
-bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message)
-{
-	g_free(data->status);
-	g_free(data->msg);
-
-	data->status = g_strdup(status);
-	data->msg = g_strdup(status_message);
-
-	/* Update our text record with the new status */
-	_dns_sd_publish(data, PUBLISH_UPDATE); /* <--We must control the errors */
-}
-
-/**
- * Advertise our presence within the dns-sd daemon and start browsing
- * for other bonjour peers.
- */
-gboolean
-bonjour_dns_sd_start(BonjourDnsSd *data)
-{
-	PurpleAccount *account;
-	PurpleConnection *gc;
-	gint dns_sd_socket;
-	sw_discovery_oid session_id;
-
-	account = data->account;
-	gc = purple_account_get_connection(account);
-
-	/* Initialize the dns-sd data and session */
-	if (sw_discovery_init(&data->session) != SW_OKAY)
-	{
-		purple_debug_error("bonjour", "Unable to initialize an mDNS session.\n");
-
-		/* In Avahi, sw_discovery_init frees data->session but doesn't clear it */
-		data->session = NULL;
-
-		return FALSE;
-	}
-
-	/* Publish our bonjour IM client at the mDNS daemon */
-	_dns_sd_publish(data, PUBLISH_START); /* <--We must control the errors */
-
-	/* Advise the daemon that we are waiting for connections */
-	if (sw_discovery_browse(data->session, 0, ICHAT_SERVICE, NULL, _browser_reply,
-			data->account, &session_id) != SW_OKAY)
-	{
-		purple_debug_error("bonjour", "Unable to get service.");
-		return FALSE;
-	}
-
-	/* Get the socket that communicates with the mDNS daemon and bind it to a */
-	/* callback that will handle the dns_sd packets */
-	dns_sd_socket = sw_discovery_socket(data->session);
-	gc->inpa = purple_input_add(dns_sd_socket, PURPLE_INPUT_READ,
-									_dns_sd_handle_packets, data->session);
-
-	return TRUE;
-}
-
-/**
- * Unregister the "_presence._tcp" service at the mDNS daemon.
- */
-void
-bonjour_dns_sd_stop(BonjourDnsSd *data)
-{
-	PurpleAccount *account;
-	PurpleConnection *gc;
-
-	if (data->session == NULL)
-		return;
-
-	sw_discovery_cancel(data->session, data->session_id);
-
-	account = data->account;
-	gc = purple_account_get_connection(account);
-	purple_input_remove(gc->inpa);
-
-	g_free(data->session);
-	data->session = NULL;
-}
--- a/libpurple/protocols/bonjour/dns_sd.h	Tue Jun 12 13:54:04 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +0,0 @@
-/*
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU Library General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- */
-
-#ifndef _BONJOUR_DNS_SD
-#define _BONJOUR_DNS_SD
-
-#include <howl.h>
-#include <glib.h>
-#include "account.h"
-
-#define ICHAT_SERVICE "_presence._tcp."
-
-/**
- * Data to be used by the dns-sd connection.
- */
-typedef struct _BonjourDnsSd
-{
-	sw_discovery session;
-	sw_discovery_oid session_id;
-	PurpleAccount *account;
-	gchar *name;
-	gchar *txtvers;
-	gchar *version;
-	gchar *first;
-	gchar *last;
-	gint port_p2pj;
-	gchar *phsh;
-	gchar *status;
-	gchar *email;
-	gchar *vc;
-	gchar *jid;
-	gchar *AIM;
-	gchar *msg;
-	GHashTable *buddies;
-} BonjourDnsSd;
-
-typedef enum _PublishType {
-	PUBLISH_START,
-	PUBLISH_UPDATE
-} PublishType;
-
-/**
- * Allocate space for the dns-sd data.
- */
-BonjourDnsSd *bonjour_dns_sd_new(void);
-
-/**
- * Deallocate the space of the dns-sd data.
- */
-void bonjour_dns_sd_free(BonjourDnsSd *data);
-
-/**
- * Send a new dns-sd packet updating our status.
- */
-void bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message);
-
-/**
- * Advertise our presence within the dns-sd daemon and start
- * browsing for other bonjour peers.
- */
-gboolean bonjour_dns_sd_start(BonjourDnsSd *data);
-
-/**
- * Unregister the "_presence._tcp" service at the mDNS daemon.
- */
-void bonjour_dns_sd_stop(BonjourDnsSd *data);
-
-#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/bonjour/dns_sd_proxy.h	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,19 @@
+#ifndef _DNS_SD_PROXY
+#define _DNS_SD_PROXY
+
+#include <stdint.h>
+
+/* fixup to make pidgin compile against win32 bonjour */
+#ifdef _WIN32
+#define _MSL_STDINT_H
+#undef bzero
+#endif
+
+#include <dns_sd.h>
+
+/* dns_sd.h defines bzero and we also do in libc_internal.h */
+#ifdef _WIN32
+#undef bzero
+#endif
+
+#endif
--- a/libpurple/protocols/bonjour/issues.txt	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/bonjour/issues.txt	Tue Jun 12 21:21:37 2007 +0000
@@ -6,4 +6,3 @@
 * Avatars
 * File transfers
 * Typing notifications
-* Check if it works on win32
--- a/libpurple/protocols/bonjour/jabber.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/bonjour/jabber.c	Tue Jun 12 21:21:37 2007 +0000
@@ -28,7 +28,6 @@
 #endif
 #include <sys/types.h>
 #include <glib.h>
-#include <glib/gprintf.h>
 #include <unistd.h>
 #include <fcntl.h>
 
@@ -45,29 +44,10 @@
 #include "bonjour.h"
 #include "buddy.h"
 
-static gint
-_connect_to_buddy(PurpleBuddy *gb)
-{
-	gint socket_fd;
-	gint retorno = 0;
-	struct sockaddr_in buddy_address;
-
-	/* Create a socket and make it non-blocking */
-	socket_fd = socket(PF_INET, SOCK_STREAM, 0);
-
-	buddy_address.sin_family = PF_INET;
-	buddy_address.sin_port = htons(((BonjourBuddy*)(gb->proto_data))->port_p2pj);
-	inet_aton(((BonjourBuddy*)(gb->proto_data))->ip, &(buddy_address.sin_addr));
-	memset(&(buddy_address.sin_zero), '\0', 8);
-
-	retorno = connect(socket_fd, (struct sockaddr*)&buddy_address, sizeof(struct sockaddr));
-	if (retorno == -1) {
-		purple_debug_warning("bonjour", "connect error: %s\n", strerror(errno));
-	}
-	fcntl(socket_fd, F_SETFL, O_NONBLOCK);
-
-	return socket_fd;
-}
+#define STREAM_END "</stream:stream>"
+/* TODO: specify version='1.0' and send stream features */
+#define DOCTYPE "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" \
+		"<stream:stream xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\" from=\"%s\" to=\"%s\">"
 
 #if 0 /* this isn't used anywhere... */
 static const char *
@@ -94,6 +74,19 @@
 }
 #endif
 
+static BonjourJabberConversation *
+bonjour_jabber_conv_new() {
+
+	BonjourJabberConversation *bconv = g_new0(BonjourJabberConversation, 1);
+	bconv->socket = -1;
+	bconv->tx_buf = purple_circ_buffer_new(512);
+	bconv->tx_handler = -1;
+	bconv->rx_handler = -1;
+
+	return bconv;
+}
+
+
 static const char *
 _font_size_ichat_to_purple(int size)
 {
@@ -113,46 +106,34 @@
 
 	return "1";
 }
+
 static void
-_jabber_parse_and_write_message_to_ui(char *message, PurpleConnection *connection, PurpleBuddy *gb)
+_jabber_parse_and_write_message_to_ui(xmlnode *message_node, PurpleConnection *connection, PurpleBuddy *pb)
 {
-	xmlnode *body_node = NULL;
-	char *body = NULL;
-	xmlnode *html_node = NULL;
-	gboolean isHTML = FALSE;
-	xmlnode *html_body_node = NULL;
+	xmlnode *body_node, *html_node, *events_node;
+	char *body, *html_body = NULL;
 	const char *ichat_balloon_color = NULL;
 	const char *ichat_text_color = NULL;
-	xmlnode *html_body_font_node = NULL;
 	const char *font_face = NULL;
 	const char *font_size = NULL;
 	const char *font_color = NULL;
-	char *html_body = NULL;
-	xmlnode *events_node = NULL;
 	gboolean composing_event = FALSE;
-	gint garbage = -1;
-	xmlnode *message_node = NULL;
-
-	/* Parsing of the message */
-	message_node = xmlnode_from_str(message, strlen(message));
-	if (message_node == NULL) {
-		return;
-	}
 
 	body_node = xmlnode_get_child(message_node, "body");
-	if (body_node != NULL) {
-		body = xmlnode_get_data(body_node);
-	} else {
+	if (body_node == NULL)
 		return;
-	}
+	body = xmlnode_get_data(body_node);
 
 	html_node = xmlnode_get_child(message_node, "html");
 	if (html_node != NULL)
 	{
-		isHTML = TRUE;
+		xmlnode *html_body_node;
+
 		html_body_node = xmlnode_get_child(html_node, "body");
 		if (html_body_node != NULL)
 		{
+			xmlnode *html_body_font_node;
+
 			ichat_balloon_color = xmlnode_get_attrib(html_body_node, "ichatballooncolor");
 			ichat_text_color = xmlnode_get_attrib(html_body_node, "ichattextcolor");
 			html_body_font_node = xmlnode_get_child(html_body_node, "font");
@@ -162,36 +143,25 @@
 				/* The absolute iChat font sizes should be converted to 1..7 range */
 				font_size = xmlnode_get_attrib(html_body_font_node, "ABSZ");
 				if (font_size != NULL)
-				{
 					font_size = _font_size_ichat_to_purple(atoi(font_size));
-				}
 				font_color = xmlnode_get_attrib(html_body_font_node, "color");
 				html_body = xmlnode_get_data(html_body_font_node);
 				if (html_body == NULL)
-				{
 					/* This is the kind of formated messages that Purple creates */
-					html_body = xmlnode_to_str(html_body_font_node, &garbage);
-				}
-			} else {
-				isHTML = FALSE;
+					html_body = xmlnode_to_str(html_body_font_node, NULL);
 			}
-		} else {
-			isHTML = FALSE;
 		}
-
 	}
 
 	events_node = xmlnode_get_child_with_namespace(message_node, "x", "jabber:x:event");
 	if (events_node != NULL)
 	{
 		if (xmlnode_get_child(events_node, "composing") != NULL)
-		{
 			composing_event = TRUE;
-		}
 		if (xmlnode_get_child(events_node, "id") != NULL)
 		{
 			/* The user is just typing */
-			xmlnode_free(message_node);
+			/* TODO: Deal with typing notification */
 			g_free(body);
 			g_free(html_body);
 			return;
@@ -199,51 +169,51 @@
 	}
 
 	/* Compose the message */
-	if (isHTML)
+	if (html_body != NULL)
 	{
+		g_free(body);
+
 		if (font_face == NULL) font_face = "Helvetica";
 		if (font_size == NULL) font_size = "3";
 		if (ichat_text_color == NULL) ichat_text_color = "#000000";
 		if (ichat_balloon_color == NULL) ichat_balloon_color = "#FFFFFF";
-		body = g_strconcat("<font face='", font_face, "' size='", font_size, "' color='", ichat_text_color,
-							"' back='", ichat_balloon_color, "'>", html_body, "</font>", NULL);
+		body = g_strdup_printf("<font face='%s' size='%s' color='%s' back='%s'>%s</font>",
+				       font_face, font_size, ichat_text_color, ichat_balloon_color,
+				       html_body);
 	}
 
+	/* TODO: Should we do something with "composing_event" here? */
+
 	/* Send the message to the UI */
-	serv_got_im(connection, gb->name, body, 0, time(NULL));
+	serv_got_im(connection, pb->name, body, 0, time(NULL));
 
-	/* Free all the strings and nodes (the attributes are freed with their nodes) */
-	xmlnode_free(message_node);
 	g_free(body);
 	g_free(html_body);
 }
 
 struct _check_buddy_by_address_t {
-	char *address;
-	PurpleBuddy **gb;
+	const char *address;
+	PurpleBuddy **pb;
 	BonjourJabber *bj;
 };
 
 static void
 _check_buddy_by_address(gpointer key, gpointer value, gpointer data)
 {
-	PurpleBuddy *gb = (PurpleBuddy*)value;
+	PurpleBuddy *pb = value;
 	BonjourBuddy *bb;
-	struct _check_buddy_by_address_t *cbba;
-
-	gb = value;
-	cbba = data;
+	struct _check_buddy_by_address_t *cbba = data;
 
 	/*
 	 * If the current PurpleBuddy's data is not null and the PurpleBuddy's account
 	 * is the same as the account requesting the check then continue to determine
 	 * whether the buddies IP matches the target IP.
 	 */
-	if (cbba->bj->account == gb->account)
+	if (cbba->bj->account == pb->account)
 	{
-		bb = gb->proto_data;
+		bb = pb->proto_data;
 		if ((bb != NULL) && (g_ascii_strcasecmp(bb->ip, cbba->address) == 0))
-			*(cbba->gb) = gb;
+			*(cbba->pb) = pb;
 	}
 }
 
@@ -258,44 +228,116 @@
 	/* Read chunks of 512 bytes till the end of the data */
 	while ((partial_message_length = recv(socket, partial_data, 512, 0)) > 0)
 	{
-			g_string_append_len(data, partial_data, partial_message_length);
-			total_message_length += partial_message_length;
+		g_string_append_len(data, partial_data, partial_message_length);
+		total_message_length += partial_message_length;
 	}
 
 	if (partial_message_length == -1)
 	{
-		purple_debug_warning("bonjour", "receive error: %s\n", strerror(errno));
+		if (errno != EAGAIN)
+			purple_debug_warning("bonjour", "receive error: %s\n", strerror(errno));
 		if (total_message_length == 0) {
 			return -1;
 		}
 	}
 
-	*message = data->str;
-	g_string_free(data, FALSE);
+	*message = g_string_free(data, FALSE);
 	if (total_message_length != 0)
 		purple_debug_info("bonjour", "Receive: -%s- %d bytes\n", *message, total_message_length);
 
 	return total_message_length;
 }
 
-static gint
-_send_data(gint socket, char *message)
+static void
+_send_data_write_cb(gpointer data, gint source, PurpleInputCondition cond)
 {
-	gint message_len = strlen(message);
-	gint partial_sent = 0;
-	gchar *partial_message = message;
+	PurpleBuddy *pb = data;
+	BonjourBuddy *bb = pb->proto_data;
+	BonjourJabberConversation *bconv = bb->conversation;
+	int ret, writelen;
+
+	/* TODO: Make sure that the stream has been established before sending */
+
+	writelen = purple_circ_buffer_get_max_read(bconv->tx_buf);
+
+	if (writelen == 0) {
+		purple_input_remove(bconv->tx_handler);
+		bconv->tx_handler = -1;
+		return;
+	}
 
-	while ((partial_sent = send(socket, partial_message, message_len, 0)) < message_len)
-	{
-		if (partial_sent != -1) {
-			partial_message += partial_sent;
-			message_len -= partial_sent;
-		} else {
-			return -1;
-		}
+	ret = send(bconv->socket, bconv->tx_buf->outptr, writelen, 0);
+
+	if (ret < 0 && errno == EAGAIN)
+		return;
+	else if (ret <= 0) {
+		PurpleConversation *conv;
+		const char *error = strerror(errno);
+
+		purple_debug_error("bonjour", "Error sending message to buddy %s error: %s\n",
+				   purple_buddy_get_name(pb), error ? error : "(null)");
+
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
+		if (conv != NULL)
+			purple_conversation_write(conv, NULL,
+				  _("Unable to send message."),
+				  PURPLE_MESSAGE_SYSTEM, time(NULL));
+
+		bonjour_jabber_close_conversation(bb->conversation);
+		bb->conversation = NULL;
+		return;
 	}
 
-	return strlen(message);
+	purple_circ_buffer_mark_read(bconv->tx_buf, ret);
+}
+
+static gint
+_send_data(PurpleBuddy *pb, char *message)
+{
+	gint ret;
+	int len = strlen(message);
+	BonjourBuddy *bb = pb->proto_data;
+	BonjourJabberConversation *bconv = bb->conversation;
+
+	/* If we're not ready to actually send, append it to the buffer */
+	if (bconv->tx_handler != -1
+			|| bconv->connect_data != NULL
+			|| !bconv->stream_started
+			|| purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) {
+		ret = -1;
+		errno = EAGAIN;
+	} else {
+		ret = send(bconv->socket, message, len, 0);
+	}
+
+	if (ret == -1 && errno == EAGAIN)
+		ret = 0;
+	else if (ret <= 0) {
+		PurpleConversation *conv;
+		const char *error = strerror(errno);
+
+		purple_debug_error("bonjour", "Error sending message to buddy %s error: %s\n",
+				   purple_buddy_get_name(pb), error ? error : "(null)");
+
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
+		if (conv != NULL)
+			purple_conversation_write(conv, NULL,
+				  _("Unable to send message."),
+				  PURPLE_MESSAGE_SYSTEM, time(NULL));
+
+		bonjour_jabber_close_conversation(bb->conversation);
+		bb->conversation = NULL;
+		return -1;
+	}
+
+	if (ret < len) {
+		if (bconv->tx_handler == -1)
+			bconv->tx_handler = purple_input_add(bconv->socket, PURPLE_INPUT_WRITE,
+				_send_data_write_cb, pb);
+		purple_circ_buffer_append(bconv->tx_buf, message + ret, len - ret);
+	}
+
+	return ret;
 }
 
 static void
@@ -303,17 +345,22 @@
 {
 	char *message = NULL;
 	gint message_length;
-	PurpleBuddy *gb = (PurpleBuddy*)data;
-	PurpleAccount *account = gb->account;
-	PurpleConversation *conversation;
-	char *closed_conv_message;
-	BonjourBuddy *bb = (BonjourBuddy*)gb->proto_data;
+	PurpleBuddy *pb = data;
+	PurpleAccount *account = pb->account;
+	BonjourBuddy *bb = pb->proto_data;
 	gboolean closed_conversation = FALSE;
-	xmlnode *message_node = NULL;
+	xmlnode *message_node;
 
 	/* Read the data from the socket */
 	if ((message_length = _read_data(socket, &message)) == -1) {
 		/* There have been an error reading from the socket */
+		if (errno != EAGAIN) {
+			bonjour_jabber_close_conversation(bb->conversation);
+			bb->conversation = NULL;
+
+			/* I guess we really don't need to notify the user.
+			 * If they try to send another message it'll reconnect */
+		}
 		return;
 	} else if (message_length == 0) { /* The other end has closed the socket */
 		closed_conversation = TRUE;
@@ -329,118 +376,184 @@
 	/* Parse the message into an XMLnode for analysis */
 	message_node = xmlnode_from_str(message, strlen(message));
 
-	/* Check if the start of the stream has been received, if not check that the current */
-	/* data is the start of the stream */
-	if (!(bb->conversation->stream_started))
-	{
-		/* Check if this is the start of the stream */
-		if ((message_node != NULL) &&
-		   g_ascii_strcasecmp(xmlnode_get_attrib(message_node, "xmlns"), "jabber:client") &&
-		   (xmlnode_get_attrib(message_node,"xmlns:stream") != NULL))
-		{
-			bb->conversation->stream_started = TRUE;
-		}
-		else
-		{
-			/* TODO: This needs to be nonblocking! */
-			if (send(bb->conversation->socket, DOCTYPE, strlen(DOCTYPE), 0) == -1)
-			{
-				purple_debug_error("bonjour", "Unable to start a conversation with %s\n", bb->name);
-			}
-			else
-			{
-				bb->conversation->stream_started = TRUE;
-			}
-		}
-	}
-
 	/*
 	 * Check that this is not the end of the conversation.  This is
 	 * using a magic string, but xmlnode won't play nice when just
 	 * parsing an end tag
 	 */
-	if (purple_str_has_prefix(message, STREAM_END) || (closed_conversation == TRUE)) {
+	if (closed_conversation || purple_str_has_prefix(message, STREAM_END)) {
+		PurpleConversation *conv;
+
 		/* Close the socket, clear the watcher and free memory */
-		if (bb->conversation != NULL) {
-			close(bb->conversation->socket);
-			purple_input_remove(bb->conversation->watcher_id);
-			g_free(bb->conversation->buddy_name);
-			g_free(bb->conversation);
-			bb->conversation = NULL;
-		}
+		bonjour_jabber_close_conversation(bb->conversation);
+		bb->conversation = NULL;
 
 		/* Inform the user that the conversation has been closed */
-		conversation = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, gb->name, account);
-		closed_conv_message = g_strdup_printf(_("%s has closed the conversation."), gb->name);
-		purple_conversation_write(conversation, NULL, closed_conv_message, PURPLE_MESSAGE_SYSTEM, time(NULL));
-		g_free(closed_conv_message);
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, account);
+		if (conv != NULL) {
+			char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name);
+			purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
+			g_free(tmp);
+		}
+	} else if (message_node != NULL) {
+		/* Parse the message to get the data and send to the ui */
+		_jabber_parse_and_write_message_to_ui(message_node, account->gc, pb);
 	} else {
-		/* Parse the message to get the data and send to the ui */
-		_jabber_parse_and_write_message_to_ui(message, account->gc, gb);
+		/* TODO: Deal with receiving only a partial message */
 	}
 
+	g_free(message);
 	if (message_node != NULL)
 		xmlnode_free(message_node);
 }
 
+struct _stream_start_data {
+	char *msg;
+	PurpleInputFunction tx_handler_cb;
+};
+
+static void
+_start_stream(gpointer data, gint source, PurpleInputCondition condition)
+{
+	PurpleBuddy *pb = data;
+	BonjourBuddy *bb = pb->proto_data;
+	struct _stream_start_data *ss = bb->conversation->stream_data;
+	int len, ret;
+
+	len = strlen(ss->msg);
+
+	/* Start Stream */
+	ret = send(source, ss->msg, len, 0);
+
+	if (ret == -1 && errno == EAGAIN)
+		return;
+	else if (ret <= 0) {
+		const char *err = strerror(errno);
+		PurpleConversation *conv;
+
+		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
+				   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
+
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
+		if (conv != NULL)
+			purple_conversation_write(conv, NULL,
+				  _("Unable to send the message, the conversation couldn't be started."),
+				  PURPLE_MESSAGE_SYSTEM, time(NULL));
+
+		bonjour_jabber_close_conversation(bb->conversation);
+		bb->conversation = NULL;
+
+		return;
+	}
+
+	/* This is EXTREMELY unlikely to happen */
+	if (ret < len) {
+		char *tmp = g_strdup(ss->msg + ret);
+		g_free(ss->msg);
+		ss->msg = tmp;
+		return;
+	}
+
+	/* Stream started; process the send buffer if there is one*/
+	purple_input_remove(bb->conversation->tx_handler);
+	bb->conversation->tx_handler= -1;
+
+	bb->conversation->stream_started = TRUE;
+
+	g_free(ss->msg);
+	g_free(ss);
+	bb->conversation->stream_data = NULL;
+
+	if (ss->tx_handler_cb) {
+		bb->conversation->tx_handler = purple_input_add(source, PURPLE_INPUT_WRITE,
+			ss->tx_handler_cb, pb);
+		/* We can probably write the data now. */
+		(ss->tx_handler_cb)(pb, source, PURPLE_INPUT_WRITE);
+	}
+}
+
 static void
 _server_socket_handler(gpointer data, int server_socket, PurpleInputCondition condition)
 {
-	PurpleBuddy *gb = NULL;
+	PurpleBuddy *pb = NULL;
 	struct sockaddr_in their_addr; /* connector's address information */
 	socklen_t sin_size = sizeof(struct sockaddr);
 	int client_socket;
-	BonjourBuddy *bb = NULL;
-	BonjourJabber *bj = data;
+	BonjourBuddy *bb;
 	char *address_text = NULL;
 	PurpleBuddyList *bl = purple_get_blist();
 	struct _check_buddy_by_address_t *cbba;
 
 	/* Check that it is a read condition */
-	if (condition != PURPLE_INPUT_READ) {
+	if (condition != PURPLE_INPUT_READ)
 		return;
-	}
 
 	if ((client_socket = accept(server_socket, (struct sockaddr *)&their_addr, &sin_size)) == -1)
-	{
 		return;
-	}
+
 	fcntl(client_socket, F_SETFL, O_NONBLOCK);
 
 	/* Look for the buddy that has opened the conversation and fill information */
 	address_text = inet_ntoa(their_addr.sin_addr);
 	cbba = g_new0(struct _check_buddy_by_address_t, 1);
 	cbba->address = address_text;
-	cbba->gb = &gb;
-	cbba->bj = bj;
+	cbba->pb = &pb;
+	cbba->bj = data;
 	g_hash_table_foreach(bl->buddies, _check_buddy_by_address, cbba);
 	g_free(cbba);
-	if (gb == NULL)
+	if (pb == NULL)
 	{
 		purple_debug_info("bonjour", "We don't like invisible buddies, this is not a superheros comic\n");
 		close(client_socket);
 		return;
 	}
-	bb = (BonjourBuddy*)gb->proto_data;
+	bb = pb->proto_data;
 
 	/* Check if the conversation has been previously started */
 	if (bb->conversation == NULL)
 	{
-		bb->conversation = g_new(BonjourJabberConversation, 1);
+		int ret, len;
+		char *stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account),
+			purple_buddy_get_name(pb));
+
+		len = strlen(stream_start);
+
+		/* Start the stream */
+		ret = send(client_socket, stream_start, len, 0);
+
+		if (ret == -1 && errno == EAGAIN)
+			ret = 0;
+		else if (ret <= 0) {
+			const char *err = strerror(errno);
+
+			purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
+					   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
+
+			close(client_socket);
+			g_free(stream_start);
+
+			return;
+		}
+
+		bb->conversation = bonjour_jabber_conv_new();
 		bb->conversation->socket = client_socket;
-		bb->conversation->stream_started = FALSE;
-		bb->conversation->buddy_name = g_strdup(gb->name);
-		bb->conversation->message_id = 1;
+		bb->conversation->rx_handler = purple_input_add(client_socket,
+			PURPLE_INPUT_READ, _client_socket_handler, pb);
 
-		if (bb->conversation->stream_started == FALSE) {
-			/* Start the stream */
-			send(bb->conversation->socket, DOCTYPE, strlen(DOCTYPE), 0);
+		/* This is unlikely to happen */
+		if (ret < len) {
+			struct _stream_start_data *ss = g_new(struct _stream_start_data, 1);
+			ss->msg = g_strdup(stream_start + ret);
+			ss->tx_handler_cb = NULL; /* We have nothing to write yet */
+			bb->conversation->stream_data = ss;
+			/* Finish sending the stream start */
+			bb->conversation->tx_handler = purple_input_add(client_socket,
+				PURPLE_INPUT_WRITE, _start_stream, pb);
+		} else {
 			bb->conversation->stream_started = TRUE;
 		}
 
-		/* Open a watcher for the client socket */
-		bb->conversation->watcher_id = purple_input_add(client_socket, PURPLE_INPUT_READ,
-													_client_socket_handler, gb);
+		g_free(stream_start);
 	} else {
 		close(client_socket);
 	}
@@ -518,158 +631,223 @@
 	return data->port;
 }
 
+static void
+_connected_to_buddy(gpointer data, gint source, const gchar *error)
+{
+	PurpleBuddy *pb = data;
+	BonjourBuddy *bb = pb->proto_data;
+	int len, ret;
+	char *stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account), purple_buddy_get_name(pb));
+
+	bb->conversation->connect_data = NULL;
+
+	if (source < 0) {
+		PurpleConversation *conv;
+
+		purple_debug_error("bonjour", "Error connecting to buddy %s at %s:%d error: %s\n",
+				   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, error ? error : "(null)");
+
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
+		if (conv != NULL)
+			purple_conversation_write(conv, NULL,
+				  _("Unable to send the message, the conversation couldn't be started."),
+				  PURPLE_MESSAGE_SYSTEM, time(NULL));
+
+		bonjour_jabber_close_conversation(bb->conversation);
+		bb->conversation = NULL;
+		return;
+	}
+
+	len = strlen(stream_start);
+
+	/* Start the stream and send queued messages */
+	ret = send(source, stream_start, len, 0);
+
+	if (ret == -1 && errno == EAGAIN)
+		ret = 0;
+	else if (ret <= 0) {
+		const char *err = strerror(errno);
+		PurpleConversation *conv;
+
+		purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
+				   purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
+
+		conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
+		if (conv != NULL)
+			purple_conversation_write(conv, NULL,
+				  _("Unable to send the message, the conversation couldn't be started."),
+				  PURPLE_MESSAGE_SYSTEM, time(NULL));
+
+		close(source);
+		bonjour_jabber_close_conversation(bb->conversation);
+		bb->conversation = NULL;
+
+		g_free(stream_start);
+
+		return;
+	}
+
+	bb->conversation->socket = source;
+	bb->conversation->rx_handler = purple_input_add(source,
+		PURPLE_INPUT_READ, _client_socket_handler, pb);
+
+	/* This is unlikely to happen */
+	if (ret < len) {
+		struct _stream_start_data *ss = g_new(struct _stream_start_data, 1);
+		ss->msg = g_strdup(stream_start + ret);
+		ss->tx_handler_cb = _send_data_write_cb;
+		bb->conversation->stream_data = ss;
+		/* Finish sending the stream start */
+		bb->conversation->tx_handler = purple_input_add(source,
+			PURPLE_INPUT_WRITE, _start_stream, pb);
+	}
+	/* Process the send buffer */
+	else {
+		bb->conversation->stream_started = TRUE;
+		/* Watch for when we can write the buffered messages */
+		bb->conversation->tx_handler = purple_input_add(source, PURPLE_INPUT_WRITE,
+			_send_data_write_cb, pb);
+		/* We can probably write the data now. */
+		_send_data_write_cb(pb, source, PURPLE_INPUT_WRITE);
+	}
+
+	g_free(stream_start);
+}
+
 int
 bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gchar *body)
 {
-	xmlnode *message_node = NULL;
-	gchar *message = NULL;
-	gint message_length = -1;
-	xmlnode *message_body_node = NULL;
-	xmlnode *message_html_node = NULL;
-	xmlnode *message_html_body_node = NULL;
-	xmlnode *message_html_body_font_node = NULL;
-	xmlnode *message_x_node = NULL;
-	PurpleBuddy *gb = NULL;
-	BonjourBuddy *bb = NULL;
-	PurpleConversation *conversation = NULL;
-	char *message_from_ui = NULL;
-	char *stripped_message = NULL;
-	gint ret;
+	xmlnode *message_node, *node, *node2;
+	gchar *message;
+	PurpleBuddy *pb;
+	BonjourBuddy *bb;
+	int ret;
 
-	gb = purple_find_buddy(data->account, to);
-	if (gb == NULL)
+	pb = purple_find_buddy(data->account, to);
+	if (pb == NULL) {
+		purple_debug_info("bonjour", "Can't send a message to an offline buddy (%s).\n", to);
 		/* You can not send a message to an offline buddy */
 		return -10000;
-
-	bb = (BonjourBuddy *)gb->proto_data;
-
-	/* Enclose the message from the UI within a "font" node */
-	message_body_node = xmlnode_new("body");
-	stripped_message = purple_markup_strip_html(body);
-	xmlnode_insert_data(message_body_node, stripped_message, strlen(stripped_message));
-	g_free(stripped_message);
-
-	message_from_ui = g_strconcat("<font>", body, "</font>", NULL);
-	message_html_body_font_node = xmlnode_from_str(message_from_ui, strlen(message_from_ui));
-	g_free(message_from_ui);
-
-	message_html_body_node = xmlnode_new("body");
-	xmlnode_insert_child(message_html_body_node, message_html_body_font_node);
+	}
 
-	message_html_node = xmlnode_new("html");
-	xmlnode_set_attrib(message_html_node, "xmlns", "http://www.w3.org/1999/xhtml");
-	xmlnode_insert_child(message_html_node, message_html_body_node);
-
-	message_x_node = xmlnode_new("x");
-	xmlnode_set_attrib(message_x_node, "xmlns", "jabber:x:event");
-	xmlnode_insert_child(message_x_node, xmlnode_new("composing"));
-
-	message_node = xmlnode_new("message");
-	xmlnode_set_attrib(message_node, "to", ((BonjourBuddy*)(gb->proto_data))->name);
-	xmlnode_set_attrib(message_node, "from", data->account->username);
-	xmlnode_set_attrib(message_node, "type", "chat");
-	xmlnode_insert_child(message_node, message_body_node);
-	xmlnode_insert_child(message_node, message_html_node);
-	xmlnode_insert_child(message_node, message_x_node);
-
-	message = xmlnode_to_str(message_node, &message_length);
-	xmlnode_free(message_node);
+	bb = pb->proto_data;
 
 	/* Check if there is a previously open conversation */
 	if (bb->conversation == NULL)
 	{
-		bb->conversation = g_new(BonjourJabberConversation, 1);
-		bb->conversation->socket = _connect_to_buddy(gb);
-		bb->conversation->stream_started = FALSE;
-		bb->conversation->buddy_name = g_strdup(gb->name);
-		bb->conversation->watcher_id = purple_input_add(bb->conversation->socket,
-				PURPLE_INPUT_READ, _client_socket_handler, gb);
+		PurpleProxyConnectData *connect_data;
+
+		/* Make sure that the account always has a proxy of "none".
+		 * This is kind of dirty, but proxy_connect_none() isn't exposed. */
+		static PurpleProxyInfo *tmp_none_proxy_info = NULL;
+		if (!tmp_none_proxy_info) {
+			tmp_none_proxy_info = purple_proxy_info_new();
+			purple_proxy_info_set_type(tmp_none_proxy_info, PURPLE_PROXY_NONE);
+		}
+		purple_account_set_proxy_info(data->account, tmp_none_proxy_info);
+
+		connect_data =
+			purple_proxy_connect(data->account->gc, data->account, bb->ip,
+					     bb->port_p2pj, _connected_to_buddy, pb);
+
+		if (connect_data == NULL) {
+			purple_debug_error("bonjour", "Unable to connect to buddy (%s).\n", to);
+			return -10001;
+		}
+
+		bb->conversation = bonjour_jabber_conv_new();
+		bb->conversation->connect_data = connect_data;
+		/* We don't want _send_data() to register the tx_handler;
+		 * that neeeds to wait until we're actually connected. */
+		bb->conversation->tx_handler = 0;
 	}
 
-	/* Check if the stream for the conversation has been started */
-	if (bb->conversation->stream_started == FALSE)
-	{
-		/* Start the stream */
-		if (send(bb->conversation->socket, DOCTYPE, strlen(DOCTYPE), 0) == -1)
-		{
-				purple_debug_error("bonjour", "Unable to start a conversation\n");
-				purple_debug_warning("bonjour", "send error: %s\n", strerror(errno));
-				conversation = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, data->account);
-				purple_conversation_write(conversation, NULL, 
-										_("Unable to send the message, the conversation couldn't be started."),
-										PURPLE_MESSAGE_SYSTEM, time(NULL));
-				close(bb->conversation->socket);
-				purple_input_remove(bb->conversation->watcher_id);
+	message_node = xmlnode_new("message");
+	xmlnode_set_attrib(message_node, "to", bb->name);
+	xmlnode_set_attrib(message_node, "from", purple_account_get_username(data->account));
+	xmlnode_set_attrib(message_node, "type", "chat");
 
-				/* Free all the data related to the conversation */
-				g_free(bb->conversation->buddy_name);
-				g_free(bb->conversation);
-				bb->conversation = NULL;
-				g_free(message);
-				return 0;
-		}
-
-		bb->conversation->stream_started = TRUE;
-	}
-
-	/* Send the message */
-	ret = _send_data(bb->conversation->socket, message) == -1;
+	/* Enclose the message from the UI within a "font" node */
+	node = xmlnode_new_child(message_node, "body");
+	message = purple_markup_strip_html(body);
+	xmlnode_insert_data(node, message, strlen(message));
 	g_free(message);
 
-	if (ret == -1)
-		return -10000;
+	node = xmlnode_new_child(message_node, "html");
+	xmlnode_set_namespace(node, "http://www.w3.org/1999/xhtml");
+
+	node = xmlnode_new_child(node, "body");
+	message = g_strdup_printf("<font>%s</font>", body);
+	node2 = xmlnode_from_str(message, strlen(message));
+	g_free(message);
+	xmlnode_insert_child(node, node2);
 
-	return 1;
+	node = xmlnode_new_child(message_node, "x");
+	xmlnode_set_namespace(node, "jabber:x:event");
+	xmlnode_insert_child(node, xmlnode_new("composing"));
+
+	message = xmlnode_to_str(message_node, NULL);
+	xmlnode_free(message_node);
+
+	ret = _send_data(pb, message) >= 0;
+
+	g_free(message);
+
+	return ret;
 }
 
 void
-bonjour_jabber_close_conversation(BonjourJabber *data, PurpleBuddy *gb)
+bonjour_jabber_close_conversation(BonjourJabberConversation *bconv)
 {
-	BonjourBuddy *bb = (BonjourBuddy*)gb->proto_data;
-
-	if (bb->conversation != NULL)
+	if (bconv != NULL)
 	{
-		/* Send the end of the stream to the other end of the conversation */
-		send(bb->conversation->socket, STREAM_END, strlen(STREAM_END), 0);
-
 		/* Close the socket and remove the watcher */
-		close(bb->conversation->socket);
-		purple_input_remove(bb->conversation->watcher_id);
+		if (bconv->socket >= 0) {
+			/* Send the end of the stream to the other end of the conversation */
+			if (bconv->stream_started)
+				send(bconv->socket, STREAM_END, strlen(STREAM_END), 0);
+			/* TODO: We're really supposed to wait for "</stream:stream>" before closing the socket */
+			close(bconv->socket);
+		}
+		if (bconv->rx_handler != -1)
+			purple_input_remove(bconv->rx_handler);
+		if (bconv->tx_handler > 0)
+			purple_input_remove(bconv->tx_handler);
 
 		/* Free all the data related to the conversation */
-		g_free(bb->conversation->buddy_name);
-		g_free(bb->conversation);
-		bb->conversation = NULL;
+		purple_circ_buffer_destroy(bconv->tx_buf);
+		if (bconv->connect_data != NULL)
+			purple_proxy_connect_cancel(bconv->connect_data);
+		if (bconv->stream_data != NULL) {
+			struct _stream_start_data *ss = bconv->stream_data;
+			g_free(ss->msg);
+			g_free(ss);
+		}
+		g_free(bconv);
 	}
 }
 
 void
 bonjour_jabber_stop(BonjourJabber *data)
 {
-	PurpleBuddy *gb = NULL;
-	BonjourBuddy *bb = NULL;
-	GSList *buddies;
-	GSList *l;
+	/* Close the server socket and remove the watcher */
+	if (data->socket >= 0)
+		close(data->socket);
+	if (data->watcher_id > 0)
+		purple_input_remove(data->watcher_id);
 
-	/* Close the server socket and remove all the watcher */
-	close(data->socket);
-	purple_input_remove(data->watcher_id);
-
-	/* Close all the sockets and remove all the watchers after sending end streams */
+	/* Close all the conversation sockets and remove all the watchers after sending end streams */
 	if (data->account->gc != NULL)
 	{
-		buddies = purple_find_buddies(data->account, data->account->username);
-		for (l = buddies; l; l = l->next)
-		{
-			gb = (PurpleBuddy*)l->data;
-			bb = (BonjourBuddy*)gb->proto_data;
-			if (bb->conversation != NULL)
-			{
-				send(bb->conversation->socket, STREAM_END, strlen(STREAM_END), 0);
-				close(bb->conversation->socket);
-				purple_input_remove(bb->conversation->watcher_id);
-			}
+		GSList *buddies, *l;
+
+		buddies = purple_find_buddies(data->account, purple_account_get_username(data->account));
+		for (l = buddies; l; l = l->next) {
+			BonjourBuddy *bb = ((PurpleBuddy*) l->data)->proto_data;
+			bonjour_jabber_close_conversation(bb->conversation);
+			bb->conversation = NULL;
 		}
+
 		g_slist_free(buddies);
 	}
 }
--- a/libpurple/protocols/bonjour/jabber.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/bonjour/jabber.h	Tue Jun 12 21:21:37 2007 +0000
@@ -27,9 +27,7 @@
 #define _BONJOUR_JABBER_H_
 
 #include "account.h"
-
-#define STREAM_END "</stream:stream>"
-#define DOCTYPE "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<stream:stream xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\">"
+#include "circbuffer.h"
 
 typedef struct _BonjourJabber
 {
@@ -42,10 +40,12 @@
 typedef struct _BonjourJabberConversation
 {
 	gint socket;
-	gint watcher_id;
-	gchar* buddy_name;
+	guint rx_handler;
+	guint tx_handler;
+	PurpleCircBuffer *tx_buf;
 	gboolean stream_started;
-	gint message_id;
+	PurpleProxyConnectData *connect_data;
+	gpointer stream_data;
 } BonjourJabberConversation;
 
 /**
@@ -58,7 +58,7 @@
 
 int bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gchar *body);
 
-void bonjour_jabber_close_conversation(BonjourJabber *data, PurpleBuddy *gb);
+void bonjour_jabber_close_conversation(BonjourJabberConversation *bconv);
 
 void bonjour_jabber_stop(BonjourJabber *data);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/bonjour/mdns_common.c	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,175 @@
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <string.h>
+
+#include "config.h"
+#include "mdns_common.h"
+#include "bonjour.h"
+#include "buddy.h"
+#include "debug.h"
+
+
+/**
+ * Allocate space for the dns-sd data.
+ */
+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)
+{
+	g_free(data->first);
+	g_free(data->last);
+	g_free(data->phsh);
+	g_free(data->status);
+	g_free(data->vc);
+	g_free(data->msg);
+	g_free(data);
+}
+
+/**
+ * Send a new dns-sd packet updating our status.
+ */
+void
+bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message)
+{
+	g_free(data->status);
+	g_free(data->msg);
+
+	data->status = g_strdup(status);
+	data->msg = g_strdup(status_message);
+
+	/* Update our text record with the new status */
+	_mdns_publish(data, PUBLISH_UPDATE); /* <--We must control the errors */
+}
+
+/**
+ * Advertise our presence within the dns-sd daemon and start browsing
+ * for other bonjour peers.
+ */
+gboolean
+bonjour_dns_sd_start(BonjourDnsSd *data)
+{
+	PurpleAccount *account;
+	PurpleConnection *gc;
+	gint dns_sd_socket;
+	gpointer opaque_data;
+
+#ifdef USE_BONJOUR_HOWL
+	sw_discovery_oid session_id;
+#endif
+
+	account = data->account;
+	gc = purple_account_get_connection(account);
+
+	/* Initialize the dns-sd data and session */
+#ifndef USE_BONJOUR_APPLE
+	if (sw_discovery_init(&data->session) != SW_OKAY)
+	{
+		purple_debug_error("bonjour", "Unable to initialize an mDNS session.\n");
+
+		/* In Avahi, sw_discovery_init frees data->session but doesn't clear it */
+		data->session = NULL;
+
+		return FALSE;
+	}
+#endif
+
+	/* Publish our bonjour IM client at the mDNS daemon */
+
+	if (0 != _mdns_publish(data, PUBLISH_START))
+	{
+		return FALSE;
+	}
+
+	/* Advise the daemon that we are waiting for connections */
+	
+#ifdef USE_BONJOUR_APPLE
+	if (DNSServiceBrowse(&data->browser, 0, 0, ICHAT_SERVICE, NULL, _mdns_service_browse_callback, account) 
+			!= kDNSServiceErr_NoError)
+#else /* USE_BONJOUR_HOWL */
+	if (sw_discovery_browse(data->session, 0, ICHAT_SERVICE, NULL, _browser_reply,
+			account, &session_id) != SW_OKAY)
+#endif
+	{
+		purple_debug_error("bonjour", "Unable to get service.");
+		return FALSE;
+	}
+
+	/* Get the socket that communicates with the mDNS daemon and bind it to a */
+	/* callback that will handle the dns_sd packets */
+
+#ifdef USE_BONJOUR_APPLE
+	dns_sd_socket = DNSServiceRefSockFD(data->browser);
+	opaque_data = data->browser;
+#else /* USE_BONJOUR_HOWL */
+	dns_sd_socket = sw_discovery_socket(data->session);
+	opaque_data = data->session;
+#endif
+
+	gc->inpa = purple_input_add(dns_sd_socket, PURPLE_INPUT_READ,
+				    _mdns_handle_event, opaque_data);
+
+	return TRUE;
+}
+
+/**
+ * Unregister the "_presence._tcp" service at the mDNS daemon.
+ */
+
+void
+bonjour_dns_sd_stop(BonjourDnsSd *data)
+{
+	PurpleAccount *account;
+	PurpleConnection *gc;
+
+#ifdef USE_BONJOUR_APPLE
+	if (data->advertisement == NULL || data->browser == NULL)
+#else /* USE_BONJOUR_HOWL */
+	if (data->session == NULL)
+#endif
+		return;
+
+#ifdef USE_BONJOUR_HOWL
+	sw_discovery_cancel(data->session, data->session_id);
+#endif
+
+	account = data->account;
+	gc = purple_account_get_connection(account);
+	purple_input_remove(gc->inpa);
+
+#ifdef USE_BONJOUR_APPLE
+	/* hack: for win32, we need to stop listening to the advertisement pipe too */
+	purple_input_remove(data->advertisement_handler);
+
+	DNSServiceRefDeallocate(data->advertisement);
+	DNSServiceRefDeallocate(data->browser);
+	data->advertisement = NULL;
+	data->browser = NULL;
+#else /* USE_BONJOUR_HOWL */
+	g_free(data->session);
+	data->session = NULL;
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/bonjour/mdns_common.h	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,54 @@
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _BONJOUR_MDNS_COMMON
+#define _BONJOUR_MDNS_COMMON
+
+#include "mdns_types.h"
+
+#ifdef USE_BONJOUR_APPLE
+#include "mdns_win32.h"
+#elif defined USE_BONJOUR_HOWL
+#include "mdns_howl.h"
+#endif
+
+/**
+ * Allocate space for the dns-sd data.
+ */
+BonjourDnsSd *bonjour_dns_sd_new(void);
+
+/**
+ * Deallocate the space of the dns-sd data.
+ */
+void bonjour_dns_sd_free(BonjourDnsSd *data);
+
+/**
+ * Send a new dns-sd packet updating our status.
+ */
+void bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message);
+
+/**
+ * Advertise our presence within the dns-sd daemon and start
+ * browsing for other bonjour peers.
+ */
+gboolean bonjour_dns_sd_start(BonjourDnsSd *data);
+
+/**
+ * Unregister the "_presence._tcp" service at the mDNS daemon.
+ */
+void bonjour_dns_sd_stop(BonjourDnsSd *data);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/bonjour/mdns_howl.c	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,238 @@
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "mdns_howl.h"
+
+#include "debug.h"
+#include "buddy.h"
+
+sw_result HOWL_API
+_publish_reply(sw_discovery discovery, sw_discovery_oid oid,
+			   sw_discovery_publish_status status, sw_opaque extra)
+{
+	purple_debug_warning("bonjour", "_publish_reply --> Start\n");
+
+	/* Check the answer from the mDNS daemon */
+	switch (status)
+	{
+		case SW_DISCOVERY_PUBLISH_STARTED :
+			purple_debug_info("bonjour", "_publish_reply --> Service started\n");
+			break;
+		case SW_DISCOVERY_PUBLISH_STOPPED :
+			purple_debug_info("bonjour", "_publish_reply --> Service stopped\n");
+			break;
+		case SW_DISCOVERY_PUBLISH_NAME_COLLISION :
+			purple_debug_info("bonjour", "_publish_reply --> Name collision\n");
+			break;
+		case SW_DISCOVERY_PUBLISH_INVALID :
+			purple_debug_info("bonjour", "_publish_reply --> Service invalid\n");
+			break;
+	}
+
+	return SW_OKAY;
+}
+
+sw_result HOWL_API
+_resolve_reply(sw_discovery discovery, sw_discovery_oid oid,
+			   sw_uint32 interface_index, sw_const_string name,
+			   sw_const_string type, sw_const_string domain,
+			   sw_ipv4_address address, sw_port port,
+			   sw_octets text_record, sw_ulong text_record_len,
+			   sw_opaque extra)
+{
+	BonjourBuddy *buddy;
+	PurpleAccount *account = (PurpleAccount*)extra;
+	gint address_length = 16;
+	sw_text_record_iterator iterator;
+	char key[SW_TEXT_RECORD_MAX_LEN];
+	char value[SW_TEXT_RECORD_MAX_LEN];
+	sw_uint32 value_length;
+
+	sw_discovery_cancel(discovery, oid);
+
+	/* create a buddy record */
+	buddy = bonjour_buddy_new(name, account);
+
+	/* Get the ip as a string */
+	buddy->ip = g_malloc(address_length);
+	sw_ipv4_address_name(address, buddy->ip, address_length);
+
+	buddy->port_p2pj = port;
+
+	/* Obtain the parameters from the text_record */
+	if ((text_record_len > 0) && (text_record) && (*text_record != '\0'))
+	{
+		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);
+
+		sw_text_record_iterator_fina(iterator);
+	}
+
+	if (!bonjour_buddy_check(buddy))
+	{
+		bonjour_buddy_delete(buddy);
+		return SW_DISCOVERY_E_UNKNOWN;
+	}
+
+	/* Add or update the buddy in our buddy list */
+	bonjour_buddy_add_to_purple(buddy);
+
+	return SW_OKAY;
+}
+
+sw_result HOWL_API
+_browser_reply(sw_discovery discovery, sw_discovery_oid oid,
+			   sw_discovery_browse_status status,
+			   sw_uint32 interface_index, sw_const_string name,
+			   sw_const_string type, sw_const_string domain,
+			   sw_opaque_t extra)
+{
+	sw_discovery_resolve_id rid;
+	PurpleAccount *account = (PurpleAccount*)extra;
+	PurpleBuddy *gb = NULL;
+
+	switch (status)
+	{
+		case SW_DISCOVERY_BROWSE_INVALID:
+			purple_debug_warning("bonjour", "_browser_reply --> Invalid\n");
+			break;
+		case SW_DISCOVERY_BROWSE_RELEASE:
+			purple_debug_warning("bonjour", "_browser_reply --> Release\n");
+			break;
+		case SW_DISCOVERY_BROWSE_ADD_DOMAIN:
+			purple_debug_warning("bonjour", "_browser_reply --> Add domain\n");
+			break;
+		case SW_DISCOVERY_BROWSE_ADD_DEFAULT_DOMAIN:
+			purple_debug_warning("bonjour", "_browser_reply --> Add default domain\n");
+			break;
+		case SW_DISCOVERY_BROWSE_REMOVE_DOMAIN:
+			purple_debug_warning("bonjour", "_browser_reply --> Remove domain\n");
+			break;
+		case SW_DISCOVERY_BROWSE_ADD_SERVICE:
+			/* A new peer has joined the network and uses iChat bonjour */
+			purple_debug_info("bonjour", "_browser_reply --> Add service\n");
+			if (g_ascii_strcasecmp(name, account->username) != 0)
+			{
+				if (sw_discovery_resolve(discovery, interface_index, name, type,
+						domain, _resolve_reply, extra, &rid) != SW_OKAY)
+				{
+					purple_debug_warning("bonjour", "_browser_reply --> Cannot send resolve\n");
+				}
+			}
+			break;
+		case SW_DISCOVERY_BROWSE_REMOVE_SERVICE:
+			purple_debug_info("bonjour", "_browser_reply --> Remove service\n");
+			gb = purple_find_buddy((PurpleAccount*)extra, name);
+			if (gb != NULL)
+			{
+				bonjour_buddy_delete(gb->proto_data);
+				purple_blist_remove_buddy(gb);
+			}
+			break;
+		case SW_DISCOVERY_BROWSE_RESOLVED:
+			purple_debug_info("bonjour", "_browse_reply --> Resolved\n");
+			break;
+		default:
+			break;
+	}
+
+	return SW_OKAY;
+}
+
+int
+_mdns_publish(BonjourDnsSd *data, PublishType type)
+{
+	sw_text_record dns_data;
+	sw_result publish_result = SW_OKAY;
+	char portstring[6];
+	const char *jid, *aim, *email;
+
+	/* Fill the data for the service */
+	if (sw_text_record_init(&dns_data) != SW_OKAY)
+	{
+		purple_debug_error("bonjour", "Unable to initialize the data for the mDNS.\n");
+		return -1;
+	}
+
+	/* 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 */
+
+	/* Publish the service */
+	switch (type)
+	{
+		case PUBLISH_START:
+			publish_result = sw_discovery_publish(data->session, 0, purple_account_get_username(data->account), ICHAT_SERVICE, NULL,
+								NULL, data->port_p2pj, sw_text_record_bytes(dns_data), sw_text_record_len(dns_data),
+								_publish_reply, NULL, &data->session_id);
+			break;
+		case PUBLISH_UPDATE:
+			publish_result = sw_discovery_publish_update(data->session, data->session_id,
+								sw_text_record_bytes(dns_data), sw_text_record_len(dns_data));
+			break;
+	}
+	if (publish_result != SW_OKAY)
+	{
+		purple_debug_error("bonjour", "Unable to publish or change the status of the _presence._tcp service.\n");
+		return -1;
+	}
+
+	/* Free the memory used by temp data */
+	sw_text_record_fina(dns_data);
+
+	return 0;
+}
+
+void
+_mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition)
+{
+	sw_discovery_read_socket((sw_discovery)data);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/bonjour/mdns_howl.h	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,47 @@
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _BONJOUR_MDNS_HOWL
+#define _BONJOUR_MDNS_HOWL
+
+#include "config.h"
+
+#ifdef USE_BONJOUR_HOWL
+
+#include <howl.h>
+#include <glib.h>
+#include "mdns_types.h"
+
+/* callback functions */
+
+sw_result HOWL_API _publish_reply(sw_discovery discovery, sw_discovery_oid oid, sw_discovery_publish_status status, sw_opaque extra);
+
+sw_result HOWL_API _resolve_reply(sw_discovery discovery, sw_discovery_oid oid, sw_uint32 interface_index, sw_const_string name,
+	sw_const_string type, sw_const_string domain, sw_ipv4_address address, sw_port port, sw_octets text_record, 
+	sw_ulong text_record_len, sw_opaque extra);
+
+sw_result HOWL_API _browser_reply(sw_discovery discovery, sw_discovery_oid oid, sw_discovery_browse_status status,
+	sw_uint32 interface_index, sw_const_string name, sw_const_string type, sw_const_string domain, sw_opaque_t extra);
+
+
+/* interface functions */
+
+int _mdns_publish(BonjourDnsSd *data, PublishType type);
+void _mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition);
+
+#endif
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/bonjour/mdns_types.h	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,63 @@
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _BONJOUR_MDNS_TYPES
+#define _BONJOUR_MDNS_TYPES
+
+#include <glib.h>
+#include "account.h"
+#include "config.h"
+
+#ifdef USE_BONJOUR_APPLE
+#include "dns_sd_proxy.h"
+#else /* USE_BONJOUR_HOWL */
+#include <howl.h>
+#endif
+
+#define ICHAT_SERVICE "_presence._tcp."
+
+/**
+ * Data to be used by the dns-sd connection.
+ */
+typedef struct _BonjourDnsSd
+{
+#ifdef USE_BONJOUR_APPLE
+	DNSServiceRef advertisement;
+	DNSServiceRef browser;
+
+	int advertisement_handler; /* hack... windows bonjour is broken, so we have to have this */
+#else /* USE_BONJOUR_HOWL */
+	sw_discovery session;
+	sw_discovery_oid session_id;
+#endif
+
+	PurpleAccount *account;
+	gchar *first;
+	gchar *last;
+	gint port_p2pj;
+	gchar *phsh;
+	gchar *status;
+	gchar *vc;
+	gchar *msg;
+} BonjourDnsSd;
+
+typedef enum _PublishType {
+	PUBLISH_START,
+	PUBLISH_UPDATE
+} PublishType;
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/bonjour/mdns_win32.c	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,296 @@
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "mdns_win32.h"
+
+#include "debug.h"
+
+/* data structure for the resolve callback */
+typedef struct _ResolveCallbackArgs
+{
+	DNSServiceRef resolver;
+	int resolver_fd;
+
+	PurpleDnsQueryData *query;
+	gchar *fqn;
+
+	BonjourBuddy* buddy;
+} ResolveCallbackArgs;
+
+static void
+_mdns_parse_text_record(BonjourBuddy* buddy, const char* record, uint16_t record_len)
+{
+	const char *txt_entry;
+	uint8_t txt_len;
+	int i;
+
+	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)
+			set_bonjour_buddy_value(buddy, buddy_TXT_records[i], txt_entry, txt_len);
+	}
+}
+
+static void DNSSD_API
+_mdns_text_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)
+{
+	if (kDNSServiceErr_NoError != errorCode)
+		purple_debug_error("bonjour", "text record query - callback error.\n");
+	else if (flags & kDNSServiceFlagsAdd)
+	{
+		BonjourBuddy *buddy = (BonjourBuddy*)context;
+		_mdns_parse_text_record(buddy, rdata, rdlen);
+		bonjour_buddy_add_to_purple(buddy);
+	}
+}
+
+static void
+_mdns_resolve_host_callback(GSList *hosts, gpointer data, const char *error_message)
+{
+	ResolveCallbackArgs* args = (ResolveCallbackArgs*)data;
+
+	if (!hosts || !hosts->data)
+		purple_debug_error("bonjour", "host resolution - callback error.\n");
+	else
+	{
+		struct sockaddr_in *addr = (struct sockaddr_in*)g_slist_nth_data(hosts, 1);
+		BonjourBuddy* buddy = args->buddy;
+
+		buddy->ip = g_strdup(inet_ntoa(addr->sin_addr));
+
+		/* finally, set up the continuous txt record watcher, and add the buddy to purple */
+
+		if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&buddy->txt_query, 0, 0, args->fqn,
+				kDNSServiceType_TXT, kDNSServiceClass_IN, _mdns_text_record_query_callback, buddy))
+		{
+			gint fd = DNSServiceRefSockFD(buddy->txt_query);
+			buddy->txt_query_fd = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, buddy->txt_query);
+
+			bonjour_buddy_add_to_purple(buddy);
+		}
+		else
+			bonjour_buddy_delete(buddy);
+
+	}
+
+	/* free the hosts list*/
+	g_slist_free(hosts);
+
+	/* free the remaining args memory */
+	purple_dnsquery_destroy(args->query);
+	g_free(args->fqn);
+	g_free(args);
+}
+
+static void DNSSD_API
+_mdns_service_resolve_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode,
+    const char *fullname, const char *hosttarget, uint16_t port, uint16_t txtLen, const char *txtRecord, void *context)
+{
+	ResolveCallbackArgs *args = (ResolveCallbackArgs*)context;
+
+	/* remove the input fd and destroy the service ref */
+	purple_input_remove(args->resolver_fd);
+	DNSServiceRefDeallocate(args->resolver);
+
+	if (kDNSServiceErr_NoError != errorCode)
+	{
+		purple_debug_error("bonjour", "service resolver - callback error.\n");
+		bonjour_buddy_delete(args->buddy);
+		g_free(args);
+	}
+	else
+	{
+		args->buddy->port_p2pj = ntohs(port);
+
+		/* parse the text record */
+		_mdns_parse_text_record(args->buddy, txtRecord, txtLen);
+
+		/* set more arguments, and start the host resolver */
+		args->fqn = 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->fqn);
+			g_free(args);
+		}
+	}
+
+}
+
+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 */
+	if (kDNSServiceErr_NoError != errorCode)
+		purple_debug_error("bonjour", "service advertisement - callback error.\n");
+	else
+		purple_debug_info("bonjour", "service advertisement - callback.\n");
+}
+
+void DNSSD_API
+_mdns_service_browse_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
+    DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context)
+{
+	PurpleAccount *account = (PurpleAccount*)context;
+	PurpleBuddy *gb = NULL;
+
+	if (kDNSServiceErr_NoError != errorCode)
+		purple_debug_error("bonjour", "service browser - callback error");
+	else if (flags & kDNSServiceFlagsAdd)
+	{
+		/* A presence service instance has been discovered... check it isn't us! */
+		if (g_ascii_strcasecmp(serviceName, account->username) != 0)
+		{
+			/* OK, lets go ahead and resolve it to add to the buddy list */
+			ResolveCallbackArgs *args = g_new0(ResolveCallbackArgs, 1);
+			args->buddy = bonjour_buddy_new(serviceName, account);
+
+			if (kDNSServiceErr_NoError != DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype, replyDomain, _mdns_service_resolve_callback, args))
+			{
+				bonjour_buddy_delete(args->buddy);
+				g_free(args);
+				purple_debug_error("bonjour", "service browser - failed to resolve service.\n");
+			}
+			else
+			{
+				/* get a file descriptor for this service ref, and add it to the input list */
+				gint resolver_fd = DNSServiceRefSockFD(args->resolver);
+				args->resolver_fd = purple_input_add(resolver_fd, PURPLE_INPUT_READ, _mdns_handle_event, args->resolver);
+			}
+		}
+	}
+	else
+	{
+		/* A peer has sent a goodbye packet, remove them from the buddy list */
+		purple_debug_info("bonjour", "service browser - remove notification\n");
+		gb = purple_find_buddy(account, serviceName);
+		if (gb != NULL)
+		{
+			bonjour_buddy_delete(gb->proto_data);
+			purple_blist_remove_buddy(gb);
+		}
+	}
+}
+
+int
+_mdns_publish(BonjourDnsSd *data, PublishType type)
+{
+	TXTRecordRef dns_data;
+	char portstring[6];
+	int ret = 0;
+	const char *jid, *aim, *email;
+	DNSServiceErrorType set_ret;
+
+	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 */
+
+	if (set_ret != kDNSServiceErr_NoError)
+	{
+		purple_debug_error("bonjour", "Unable to allocate memory for text record.\n");
+		ret = -1;
+	}
+	else
+	{
+		DNSServiceErrorType err = kDNSServiceErr_NoError;
+
+		/* OK, we're done constructing the text record, (re)publish the service */
+
+		switch (type)
+		{
+			case PUBLISH_START:
+				err = DNSServiceRegister(&data->advertisement, 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(data->advertisement, NULL, 0, TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data), 0);
+				break;
+		}
+
+		if (kDNSServiceErr_NoError != err)
+		{
+			purple_debug_error("bonjour", "Failed to publish presence service.\n");
+			ret = -1;
+		}
+		else if (PUBLISH_START == type)
+		{
+			/* hack: Bonjour on windows is broken. We don't care about the callback but we have to listen anyway */
+			gint advertisement_fd = DNSServiceRefSockFD(data->advertisement);
+			data->advertisement_handler = purple_input_add(advertisement_fd, PURPLE_INPUT_READ, _mdns_handle_event, data->advertisement);
+		}
+	}
+
+	/* Free the memory used by temp data */
+	TXTRecordDeallocate(&dns_data);
+	return ret;
+}
+
+void
+_mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition)
+{
+	DNSServiceProcessResult((DNSServiceRef)data);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/bonjour/mdns_win32.h	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,40 @@
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _BONJOUR_MDNS_WIN32
+#define _BONJOUR_MDNS_WIN32
+
+#ifdef USE_BONJOUR_APPLE
+
+#include <glib.h>
+#include "mdns_types.h"
+#include "buddy.h"
+#include "dnsquery.h"
+#include "dns_sd_proxy.h"
+
+/* Bonjour async callbacks */
+
+void DNSSD_API _mdns_service_browse_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
+    DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context);
+
+/* interface functions */
+
+int _mdns_publish(BonjourDnsSd *data, PublishType type);
+void _mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition);
+
+#endif
+
+#endif
--- a/libpurple/protocols/irc/irc.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/irc/irc.c	Tue Jun 12 21:21:37 2007 +0000
@@ -592,7 +592,7 @@
 	struct irc_conn *irc = gc->proto_data;
 	int len;
 
-	if(!g_list_find(purple_connections_get_all(), gc)) {
+	if(!g_list_find((GList *)purple_connections_get_all(), gc)) {
 		purple_ssl_close(gsc);
 		return;
 	}
@@ -814,7 +814,8 @@
 
 static PurplePluginProtocolInfo prpl_info =
 {
-	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_PASSWORD_OPTIONAL,
+	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_PASSWORD_OPTIONAL |
+	OPT_PROTO_SLASH_COMMANDS_NATIVE,
 	NULL,					/* user_splits */
 	NULL,					/* protocol_options */
 	NO_BUDDY_ICONS,		/* icon_spec */
--- a/libpurple/protocols/jabber/.todo	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/jabber/.todo	Tue Jun 12 21:21:37 2007 +0000
@@ -38,7 +38,7 @@
             formatted. enhancement-request so that the birthday field in the setinfo form would split up into relevant fields allowing for a strict syntax (like year--month--day or so, perhaps even dropdown menus)
         </note>
         <note priority="low" time="1037890968">
-            have set info pre-fill values from the server when no local vcard exists. this will help people migrating to gaim
+            have set info pre-fill values from the server when no local vcard exists. this will help people migrating to libpurple-based clients
         </note>
     </note>
     <note priority="verylow" time="1036044192">
--- a/libpurple/protocols/jabber/Makefile.am	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/jabber/Makefile.am	Tue Jun 12 21:21:37 2007 +0000
@@ -43,15 +43,12 @@
 if STATIC_JABBER
 
 st = -DPURPLE_STATIC_PRPL
-noinst_LIBRARIES = libjabber.a libxmpp.a
+noinst_LIBRARIES = libjabber.a
 pkg_LTLIBRARIES =
 
-libjabber_a_SOURCES = $(JABBERSOURCES)
+libjabber_a_SOURCES = $(JABBERSOURCES) libxmpp.c
 libjabber_a_CFLAGS  = $(AM_CFLAGS)
 
-libxmpp_a_SOURCES = libxmpp.c
-libxmpp_a_CFLAGS = $(AM_CFLAGS)
-
 else
 
 st =
--- a/libpurple/protocols/jabber/jabber.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Tue Jun 12 21:21:37 2007 +0000
@@ -463,7 +463,11 @@
 	JabberStream *js = gc->proto_data;
 
 	if (source < 0) {
-		purple_connection_error(gc, _("Couldn't connect to host"));
+		gchar *tmp;
+		tmp = g_strdup_printf(_("Could not establish a connection with the server:\n%s"),
+				error);
+		purple_connection_error(gc, tmp);
+		g_free(tmp);
 		return;
 	}
 
@@ -1070,8 +1074,10 @@
 			/* lets make sure our buddy icon is up to date
 			 * before we go letting people know we're here */
 			img = purple_buddy_icons_find_account_icon(js->gc->account);
-			jabber_set_buddy_icon(js->gc, img);
-			purple_imgstore_unref(img);
+			if(NULL != img) {
+				jabber_set_buddy_icon(js->gc, img);
+				purple_imgstore_unref(img);
+			}
 
 			/* now we can alert the core that we're ready to send status */
 			purple_connection_set_state(js->gc, PURPLE_CONNECTED);
--- a/libpurple/protocols/jabber/jutil.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/jabber/jutil.c	Tue Jun 12 21:21:37 2007 +0000
@@ -221,7 +221,7 @@
 jabber_find_unnormalized_conv(const char *name, PurpleAccount *account)
 {
 	PurpleConversation *c = NULL;
-	GList *cnv;
+	const GList *cnv;
 
 	g_return_val_if_fail(name != NULL, NULL);
 
--- a/libpurple/protocols/jabber/libxmpp.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Tue Jun 12 21:21:37 2007 +0000
@@ -43,9 +43,11 @@
 {
 #ifdef HAVE_CYRUS_SASL
 	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME |
-	OPT_PROTO_MAIL_CHECK | OPT_PROTO_PASSWORD_OPTIONAL,
+	OPT_PROTO_MAIL_CHECK | OPT_PROTO_PASSWORD_OPTIONAL |
+	OPT_PROTO_SLASH_COMMANDS_NATIVE,
 #else
-	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_MAIL_CHECK,
+	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME | OPT_PROTO_MAIL_CHECK |
+	OPT_PROTO_SLASH_COMMANDS_NATIVE,
 #endif
 	NULL,							/* user_splits */
 	NULL,							/* protocol_options */
@@ -193,9 +195,11 @@
 
 	/* Translators: 'domain' is used here in the context of Internet domains, e.g. pidgin.im */
         split = purple_account_user_split_new(_("Domain"), NULL, '@');
+		purple_account_user_split_set_reverse(split, FALSE);
         prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
 
         split = purple_account_user_split_new(_("Resource"), "Home", '/');
+		purple_account_user_split_set_reverse(split, FALSE);
         prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
 
         option = purple_account_option_bool_new(_("Force old (port 5223) SSL"), "old_ssl", FALSE);
--- a/libpurple/protocols/jabber/si.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/jabber/si.c	Tue Jun 12 21:21:37 2007 +0000
@@ -105,6 +105,9 @@
 	jsx->connect_data = NULL;
 
 	if(source < 0) {
+		purple_debug_warning("jabber",
+				"si connection failed, jid was %s, host was %s, error was %s\n",
+				streamhost->jid, streamhost->host, error_message);
 		jsx->streamhosts = g_list_remove(jsx->streamhosts, streamhost);
 		g_free(streamhost->jid);
 		g_free(streamhost->host);
--- a/libpurple/protocols/msn/msn.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/msn/msn.c	Tue Jun 12 21:21:37 2007 +0000
@@ -1463,7 +1463,7 @@
 	purple_debug_info("msn", "In msn_got_info\n");
 
 	/* Make sure the connection is still valid */
-	if (g_list_find(purple_connections_get_all(), info_data->gc) == NULL)
+	if (g_list_find((GList *)purple_connections_get_all(), info_data->gc) == NULL)
 	{
 		purple_debug_warning("msn", "invalid connection. ignoring buddy info.\n");
 		g_free(info_data->name);
@@ -1883,7 +1883,7 @@
 
 	/* Make sure the connection is still valid if we got here by fetching a photo url */
 	if (url_text && (error_message != NULL ||
-					 g_list_find(purple_connections_get_all(), info_data->gc) == NULL))
+					 g_list_find((GList *)purple_connections_get_all(), info_data->gc) == NULL))
 	{
 		purple_debug_warning("msn", "invalid connection. ignoring buddy photo info.\n");
 		g_free(stripped);
@@ -1982,7 +1982,7 @@
 		if (acct && !purple_account_is_connected(acct))
 			acct = NULL;
 	} else { /* Otherwise find an active account for the protocol */
-		GList *l = purple_accounts_get_all();
+		const GList *l = purple_accounts_get_all();
 		while (l) {
 			if (!strcmp(prpl, purple_account_get_protocol_id(l->data))
 					&& purple_account_is_connected(l->data)) {
--- a/libpurple/protocols/msn/servconn.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/msn/servconn.c	Tue Jun 12 21:21:37 2007 +0000
@@ -195,6 +195,7 @@
 	}
 	else
 	{
+		purple_debug_error("msn", "Connection error: %s\n", error_message);
 		msn_servconn_got_error(servconn, MSN_SERVCONN_ERROR_CONNECT);
 	}
 }
--- a/libpurple/protocols/oscar/.todo	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/oscar/.todo	Tue Jun 12 21:21:37 2007 +0000
@@ -15,16 +15,10 @@
             <note priority="veryhigh" time="1036040919">
                 some way to close direct connect w/out closing convo.
             </note>
-            <note priority="low" time="1036040970">
-                canceled direct im should still allow new attempt
-            </note>
             <note priority="low" time="1036041084">
                 failed direct im attempt should allow new attempt some way to cancel an attempt that isn't happening
             </note>
         </note>
-        <note priority="low" time="1036041105">
-            Colors in Chat room are wrong (using Gold too much)
-        </note>
         <note priority="verylow" time="1036041121">
             Voice Chat
         </note>
@@ -44,7 +38,7 @@
             color support
         </note>
         <note priority="high" time="1036041251">
-            set status message and of course when gaim can set them, it needs to be able to get the ones it sets. (yes this is redundant. its a reflection of my current mood)
+            set status message and of course when libpurple can set them, it needs to be able to get the ones it sets. (yes this is redundant. its a reflection of my current mood)
         </note>
         <note priority="medium" time="1036041165">
             Chat (this is different from aim chat)
@@ -66,6 +60,6 @@
         </note>
     </note>
     <note priority="medium" time="1036040870">
-        The order of groups and buddies in the server list is not updated when groups and buddies are re-arranged locally in Gaim.
+        The order of groups and buddies in the server list is not updated when groups and buddies are re-arranged locally in libpurple.
     </note>
 </todo>
--- a/libpurple/protocols/oscar/AUTHORS	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/oscar/AUTHORS	Tue Jun 12 21:21:37 2007 +0000
@@ -35,7 +35,7 @@
 N: Eric Warmenhoven
 T: 1998-2001
 E: warmenhoven a.t linux d.o.t com
-D: Some OFT info, author of the faim interface for gaim
+D: Some OFT info, initial author of the libpurple-side of the oscar protocol plugin
 
 N: Brock Wilcox
 T: 1998-2001
--- a/libpurple/protocols/oscar/Makefile.am	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/oscar/Makefile.am	Tue Jun 12 21:21:37 2007 +0000
@@ -52,15 +52,10 @@
 if STATIC_OSCAR
 
 st = -DPURPLE_STATIC_PRPL
-noinst_LIBRARIES   = liboscar.a libaim.a libicq.a
-liboscar_a_SOURCES = $(OSCARSOURCES)
-liboscar_a_CFLAGS  = $(AM_CFLAGS)
+noinst_LIBRARIES   = liboscar.a
 
-libaim_a_CFLAGS = $(AM_CFLAGS)
-libaim_a_SOURCES = libaim.c
-
-libicq_a_CFLAGS = $(AM_CFLAGS)
-libicq_a_SOURCES = libicq.c
+liboscar_a_SOURCES = $(OSCARSOURCES) libaim.c libicq.c
+liboscar_a_CFLAGS  = $(AM_CFLAGS)
 
 else
 
--- a/libpurple/protocols/oscar/oscar.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Tue Jun 12 21:21:37 2007 +0000
@@ -4386,7 +4386,7 @@
 	if (purple_account_get_bool(account, "web_aware", OSCAR_DEFAULT_WEB_AWARE))
 		data |= AIM_ICQ_STATE_WEBAWARE;
 
-	if (!strcmp(status_id, OSCAR_STATUS_ID_AVAILABLE) || !strcmp(status_id, OSCAR_STATUS_ID_AVAILABLE))
+	if (!strcmp(status_id, OSCAR_STATUS_ID_AVAILABLE))
 		data |= AIM_ICQ_STATE_NORMAL;
 	else if (!strcmp(status_id, OSCAR_STATUS_ID_AWAY))
 		data |= AIM_ICQ_STATE_AWAY;
@@ -6493,7 +6493,7 @@
 		if (acct && !purple_account_is_connected(acct))
 			acct = NULL;
 	} else { /* Otherwise find an active account for the protocol */
-		GList *l = purple_accounts_get_all();
+		const GList *l = purple_accounts_get_all();
 		while (l) {
 			if (!strcmp(prpl, purple_account_get_protocol_id(l->data))
 					&& purple_account_is_connected(l->data)) {
--- a/libpurple/protocols/qq/AUTHORS	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/qq/AUTHORS	Tue Jun 12 21:21:37 2007 +0000
@@ -1,7 +1,7 @@
 Code Contributors
 =====
 puzzlebird  : original author
-gfhuang     : patches for gaim 2.0.0beta2, maintainer
+gfhuang     : patches for libpurple 2.0.0beta2, maintainer
 henryouly   : file transfer, udp sock5 proxy and qq_show, maintainer
 hzhr        : maintainer
 joymarquis  : maintainer
@@ -10,7 +10,7 @@
 yyw         : improved performance on PPC linux
 lvxiang     : provided ip to location original code
 csyfek      : faces
-markhuetsch : OpenQ merge into gaim, maintainer 2006-2007
+markhuetsch : OpenQ merge into libpurple, maintainer 2006-2007
 
 Acknowledgement
 =====
--- a/libpurple/protocols/silc/README	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/silc/README	Tue Jun 12 21:21:37 2007 +0000
@@ -2,19 +2,19 @@
 ==================
 
 This is the Purple protocol plugin of the protocol called Secure Internet
-Live Conferencing (SILC).  The implementation will use the SILC Toolkit, 
-freely available from the http://silcnet.org/ site, for the actual SILC 
+Live Conferencing (SILC).  The implementation will use the SILC Toolkit,
+freely available from the http://silcnet.org/ site, for the actual SILC
 protocol implementation.
 
-To include SILC into Purple, one needs to first compile and install 
+To include SILC into Purple, one needs to first compile and install
 the SILC Toolkit.  It is done as follows:
 
-	./configure --enable-shared
+	./configure
 	make
 	make install
 
-This will compile shared libraries of the SILC Toolkit.  If the --prefix 
-is not given to ./configure, the binaries are installed into the 
+This will compile shared libraries of the SILC Toolkit.  If the --prefix
+is not given to ./configure, the binaries are installed into the
 /usr/local/silc directory.
 
 Once the Toolkit is installed one needs to tell Purple's ./configure
--- a/libpurple/protocols/silc/TODO	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/silc/TODO	Tue Jun 12 21:21:37 2007 +0000
@@ -1,14 +1,6 @@
 Features TODO (maybe)
 =====================
 
-Sending images
-	- Sending images to channel too, if libpurple allows it.
-
 Preferences
 	- Add joined channels to buddy list automatically (during
 	  session)
-	- Add joined channels to buddy list automatically permanently
-
-Buddy icon
-	- After SILC Toolkit 1.0.2 buddy icon support can be added
-	  (SILC_ATTERIBUTE_USER_ICON).
--- a/libpurple/protocols/silc/buddy.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/silc/buddy.c	Tue Jun 12 21:21:37 2007 +0000
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 2004 Pekka Riikonen
+  Copyright (C) 2004 - 2007 Pekka Riikonen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
@@ -17,7 +17,7 @@
 
 */
 
-#include "silcincludes.h"
+#include "silc.h"
 #include "silcclient.h"
 #include "silcpurple.h"
 #include "wb.h"
@@ -29,7 +29,7 @@
 
 static void
 silcpurple_buddy_keyagr_do(PurpleConnection *gc, const char *name,
-			 			 gboolean force_local);
+			   gboolean force_local);
 
 typedef struct {
 	char *nick;
@@ -38,10 +38,10 @@
 
 static void
 silcpurple_buddy_keyagr_resolved(SilcClient client,
-			       SilcClientConnection conn,
-			       SilcClientEntry *clients,
-			       SilcUInt32 clients_count,
-			       void *context)
+				 SilcClientConnection conn,
+				 SilcStatus status,
+				 SilcDList clients,
+				 void *context)
 {
 	PurpleConnection *gc = client->application;
 	SilcPurpleResolve r = context;
@@ -62,21 +62,16 @@
 	silc_free(r);
 }
 
-typedef struct {
-	gboolean responder;
-} *SilcPurpleKeyAgr;
-
 static void
 silcpurple_buddy_keyagr_cb(SilcClient client,
-			 SilcClientConnection conn,
-			 SilcClientEntry client_entry,
-			 SilcKeyAgreementStatus status,
-			 SilcSKEKeyMaterial *key,
-			 void *context)
+			   SilcClientConnection conn,
+			   SilcClientEntry client_entry,
+			   SilcKeyAgreementStatus status,
+			   SilcSKEKeyMaterial key,
+			   void *context)
 {
 	PurpleConnection *gc = client->application;
 	SilcPurple sg = gc->proto_data;
-	SilcPurpleKeyAgr a = context;
 
 	if (!sg->conn)
 		return;
@@ -90,13 +85,13 @@
 			/* Set the private key for this client */
 			silc_client_del_private_message_key(client, conn, client_entry);
 			silc_client_add_private_message_key_ske(client, conn, client_entry,
-								NULL, NULL, key, a->responder);
+								NULL, NULL, key);
 			silc_ske_free_key_material(key);
 
-			
+
 			/* Open IM window */
 			convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
-									client_entry->nickname, sg->account);
+								      client_entry->nickname, sg->account);
 			if (convo) {
 				/* we don't have windows in the core anymore...but we may want to
 				 * provide some method for asking the UI to show the window
@@ -104,7 +99,7 @@
 				 */
 			} else {
 				convo = purple_conversation_new(PURPLE_CONV_TYPE_IM, sg->account,
-							      client_entry->nickname);
+								client_entry->nickname);
 			}
 			g_snprintf(tmp, sizeof(tmp), "%s [private key]", client_entry->nickname);
 			purple_conversation_set_title(convo, tmp);
@@ -113,7 +108,7 @@
 
 	case SILC_KEY_AGREEMENT_ERROR:
 		purple_notify_error(gc, _("Key Agreement"),
-				  _("Error occurred during key agreement"), NULL);
+				    _("Error occurred during key agreement"), NULL);
 		break;
 
 	case SILC_KEY_AGREEMENT_FAILURE:
@@ -122,53 +117,48 @@
 
 	case SILC_KEY_AGREEMENT_TIMEOUT:
 		purple_notify_error(gc, _("Key Agreement"),
-				  _("Timeout during key agreement"), NULL);
+				    _("Timeout during key agreement"), NULL);
 		break;
 
 	case SILC_KEY_AGREEMENT_ABORTED:
 		purple_notify_error(gc, _("Key Agreement"),
-				  _("Key agreement was aborted"), NULL);
+				    _("Key agreement was aborted"), NULL);
 		break;
 
 	case SILC_KEY_AGREEMENT_ALREADY_STARTED:
 		purple_notify_error(gc, _("Key Agreement"),
-				  _("Key agreement is already started"), NULL);
+				    _("Key agreement is already started"), NULL);
 		break;
 
 	case SILC_KEY_AGREEMENT_SELF_DENIED:
 		purple_notify_error(gc, _("Key Agreement"),
-				  _("Key agreement cannot be started with yourself"),
-				  NULL);
+				    _("Key agreement cannot be started with yourself"),
+				    NULL);
 		break;
 
 	default:
 		break;
 	}
-
-	silc_free(a);
 }
 
 static void
 silcpurple_buddy_keyagr_do(PurpleConnection *gc, const char *name,
-			 gboolean force_local)
+			   gboolean force_local)
 {
 	SilcPurple sg = gc->proto_data;
-	SilcClientEntry *clients;
-	SilcUInt32 clients_count;
+	SilcDList clients;
+	SilcClientEntry client_entry;
+	SilcClientConnectionParams params;
 	char *local_ip = NULL, *remote_ip = NULL;
 	gboolean local = TRUE;
-	char *nickname;
-	SilcPurpleKeyAgr a;
+	SilcSocket sock;
 
 	if (!sg->conn || !name)
 		return;
 
-	if (!silc_parse_userfqdn(name, &nickname, NULL))
-		return;
-
 	/* Find client entry */
-	clients = silc_client_get_clients_local(sg->client, sg->conn, nickname, name,
-						&clients_count);
+	clients = silc_client_get_clients_local(sg->client, sg->conn, name,
+						FALSE);
 	if (!clients) {
 		/* Resolve unknown user */
 		SilcPurpleResolve r = silc_calloc(1, sizeof(*r));
@@ -176,12 +166,14 @@
 			return;
 		r->nick = g_strdup(name);
 		r->gc = gc;
-		silc_client_get_clients(sg->client, sg->conn, nickname, NULL,
+		silc_client_get_clients(sg->client, sg->conn, name, NULL,
 					silcpurple_buddy_keyagr_resolved, r);
-		silc_free(nickname);
 		return;
 	}
 
+	silc_socket_stream_get_info(silc_packet_stream_get_stream(sg->conn->stream),
+				    &sock, NULL, NULL, NULL);
+
 	/* Resolve the local IP from the outgoing socket connection.  We resolve
 	   it to check whether we have a private range IP address or public IP
 	   address.  If we have public then we will assume that we are not behind
@@ -196,14 +188,14 @@
 
 	   Naturally this algorithm does not always get things right. */
 
-	if (silc_net_check_local_by_sock(sg->conn->sock->sock, NULL, &local_ip)) {
+	if (silc_net_check_local_by_sock(sock, NULL, &local_ip)) {
 		/* Check if the IP is private */
 		if (!force_local && silcpurple_ip_is_private(local_ip)) {
 			local = FALSE;
 
 			/* Local IP is private, resolve the remote server IP to see whether
 			   we are talking to Internet or just on LAN. */
-			if (silc_net_check_host_by_sock(sg->conn->sock->sock, NULL,
+			if (silc_net_check_host_by_sock(sock, NULL,
 							&remote_ip))
 				if (silcpurple_ip_is_private(remote_ip))
 					/* We assume we are in LAN.  Let's provide
@@ -218,19 +210,24 @@
 	if (local && !local_ip)
 		local_ip = silc_net_localip();
 
-	a = silc_calloc(1, sizeof(*a));
-	if (!a)
-		return;
-	a->responder = local;
+	silc_dlist_start(clients);
+	client_entry = silc_dlist_get(clients);
+
+	memset(&params, 0, sizeof(params));
+	params.timeout_secs = 60;
+	if (local)
+	  /* Provide connection point */
+	  params.local_ip = local_ip;
 
 	/* Send the key agreement request */
-	silc_client_send_key_agreement(sg->client, sg->conn, clients[0],
-				       local ? local_ip : NULL, NULL, 0, 60,
-				       silcpurple_buddy_keyagr_cb, a);
+	silc_client_send_key_agreement(sg->client, sg->conn, client_entry,
+				       &params, sg->public_key,
+				       sg->private_key,
+				       silcpurple_buddy_keyagr_cb, NULL);
 
 	silc_free(local_ip);
 	silc_free(remote_ip);
-	silc_free(clients);
+	silc_client_list_free(sg->client, sg->conn, clients);
 }
 
 typedef struct {
@@ -244,8 +241,8 @@
 static void
 silcpurple_buddy_keyagr_request_cb(SilcPurpleKeyAgrAsk a, gint id)
 {
-	SilcPurpleKeyAgr ai;
 	SilcClientEntry client_entry;
+	SilcClientConnectionParams params;
 
 	if (id != 1)
 		goto out;
@@ -255,26 +252,27 @@
 						    &a->client_id);
 	if (!client_entry) {
 		purple_notify_error(a->client->application, _("Key Agreement"),
-				  _("The remote user is not present in the network any more"),
-				  NULL);
+				    _("The remote user is not present in the network any more"),
+				    NULL);
 		goto out;
 	}
 
 	/* If the hostname was provided by the requestor perform the key agreement
 	   now.  Otherwise, we will send him a request to connect to us. */
 	if (a->hostname) {
-		ai = silc_calloc(1, sizeof(*ai));
-		if (!ai)
-			goto out;
-		ai->responder = FALSE;
-		silc_client_perform_key_agreement(a->client, a->conn, client_entry,
+		memset(&params, 0, sizeof(params));
+		params.timeout_secs = 60;
+		silc_client_perform_key_agreement(a->client, a->conn,
+						  client_entry, &params,
+						  a->conn->public_key,
+						  a->conn->private_key,
 						  a->hostname, a->port,
-						  silcpurple_buddy_keyagr_cb, ai);
+						  silcpurple_buddy_keyagr_cb, NULL);
 	} else {
 		/* Send request.  Force us as the point of connection since requestor
 		   did not provide the point of connection. */
 		silcpurple_buddy_keyagr_do(a->client->application,
-					 client_entry->nickname, TRUE);
+					   client_entry->nickname, TRUE);
 	}
 
  out:
@@ -283,14 +281,19 @@
 }
 
 void silcpurple_buddy_keyagr_request(SilcClient client,
-				   SilcClientConnection conn,
-				   SilcClientEntry client_entry,
-				   const char *hostname, SilcUInt16 port)
+				     SilcClientConnection conn,
+				     SilcClientEntry client_entry,
+				     const char *hostname, SilcUInt16 port,
+				     SilcUInt16 protocol)
 {
 	char tmp[128], tmp2[128];
 	SilcPurpleKeyAgrAsk a;
 	PurpleConnection *gc = client->application;
 
+	/* For now Pidgin don't support UDP key agreement */
+	if (protocol == 1)
+	  return;
+
 	g_snprintf(tmp, sizeof(tmp),
 		   _("Key agreement request received from %s. Would you like to "
 		     "perform the key agreement?"), client_entry->nickname);
@@ -304,15 +307,15 @@
 		return;
 	a->client = client;
 	a->conn = conn;
-	a->client_id = *client_entry->id;
+	a->client_id = client_entry->id;
 	if (hostname)
 		a->hostname = strdup(hostname);
 	a->port = port;
 
 	purple_request_action(client->application, _("Key Agreement Request"), tmp,
-			    hostname ? tmp2 : NULL, 1, gc->account, client_entry->nickname,
-				NULL, a, 2, _("Yes"), G_CALLBACK(silcpurple_buddy_keyagr_request_cb),
-			    _("No"), G_CALLBACK(silcpurple_buddy_keyagr_request_cb));
+			      hostname ? tmp2 : NULL, 1, gc->account, client_entry->nickname,
+			      NULL, a, 2, _("Yes"), G_CALLBACK(silcpurple_buddy_keyagr_request_cb),
+			      _("No"), G_CALLBACK(silcpurple_buddy_keyagr_request_cb));
 }
 
 static void
@@ -333,9 +336,7 @@
 	PurpleBuddy *b;
 	PurpleConnection *gc;
         SilcPurple sg;
-	char *nickname;
-	SilcClientEntry *clients;
-	SilcUInt32 clients_count;
+	SilcDList clients;
 
 	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
 
@@ -343,23 +344,16 @@
 	gc = purple_account_get_connection(b->account);
 	sg = gc->proto_data;
 
-	if (!silc_parse_userfqdn(b->name, &nickname, NULL))
-		return;
-
 	/* Find client entry */
 	clients = silc_client_get_clients_local(sg->client, sg->conn,
-						nickname, b->name,
-						&clients_count);
-	if (!clients) {
-		silc_free(nickname);
+						b->name, FALSE);
+	if (!clients)
 		return;
-	}
 
-	clients[0]->prv_resp = FALSE;
+	silc_dlist_start(clients);
 	silc_client_del_private_message_key(sg->client, sg->conn,
-					    clients[0]);
-	silc_free(clients);
-	silc_free(nickname);
+					    silc_dlist_get(clients));
+	silc_client_list_free(sg->client, sg->conn, clients);
 }
 
 typedef struct {
@@ -386,8 +380,8 @@
 						    &p->client_id);
 	if (!client_entry) {
 		purple_notify_error(p->client->application, _("IM With Password"),
-				  _("The remote user is not present in the network any more"),
-				  NULL);
+				    _("The remote user is not present in the network any more"),
+				    NULL);
 		silc_free(p);
 		return;
 	}
@@ -398,21 +392,16 @@
 	silc_client_add_private_message_key(p->client, p->conn,
 					    client_entry, NULL, NULL,
 					    (unsigned char *)passphrase,
-					    strlen(passphrase), FALSE,
-					    client_entry->prv_resp);
-	if (!client_entry->prv_resp)
-		silc_client_send_private_message_key_request(p->client,
-							     p->conn,
-							     client_entry);
+					    strlen(passphrase));
         silc_free(p);
 }
 
 static void
 silcpurple_buddy_privkey_resolved(SilcClient client,
-				SilcClientConnection conn,
-				SilcClientEntry *clients,
-				SilcUInt32 clients_count,
-				void *context)
+				  SilcClientConnection conn,
+				  SilcStatus status,
+				  SilcDList clients,
+				  void *context)
 {
 	char tmp[256];
 
@@ -434,42 +423,39 @@
 silcpurple_buddy_privkey(PurpleConnection *gc, const char *name)
 {
 	SilcPurple sg = gc->proto_data;
-	char *nickname;
 	SilcPurplePrivkey p;
-	SilcClientEntry *clients;
-	SilcUInt32 clients_count;
+	SilcDList clients;
+	SilcClientEntry client_entry;
 
 	if (!name)
 		return;
-	if (!silc_parse_userfqdn(name, &nickname, NULL))
-		return;
 
 	/* Find client entry */
 	clients = silc_client_get_clients_local(sg->client, sg->conn,
-						nickname, name,
-						&clients_count);
+						name, FALSE);
 	if (!clients) {
-		silc_client_get_clients(sg->client, sg->conn, nickname, NULL,
+		silc_client_get_clients(sg->client, sg->conn, name, NULL,
 					silcpurple_buddy_privkey_resolved,
 					g_strdup(name));
-		silc_free(nickname);
 		return;
 	}
 
+	silc_dlist_start(clients);
+	client_entry = silc_dlist_get(clients);
+
 	p = silc_calloc(1, sizeof(*p));
 	if (!p)
 		return;
 	p->client = sg->client;
 	p->conn = sg->conn;
-	p->client_id = *clients[0]->id;
+	p->client_id = client_entry->id;
 	purple_request_input(gc, _("IM With Password"), NULL,
 	                     _("Set IM Password"), NULL, FALSE, TRUE, NULL,
 	                     _("OK"), G_CALLBACK(silcpurple_buddy_privkey_cb),
 	                     _("Cancel"), G_CALLBACK(silcpurple_buddy_privkey_cb),
 	                     gc->account, NULL, NULL, p);
 
-	silc_free(clients);
-	silc_free(nickname);
+	silc_client_list_free(sg->client, sg->conn, clients);
 }
 
 static void
@@ -498,13 +484,21 @@
 static void
 silcpurple_buddy_getkey(PurpleConnection *gc, const char *name);
 
-static void
-silcpurple_buddy_getkey_cb(SilcPurpleBuddyGetkey g,
-			 SilcClientCommandReplyContext cmd)
+static SilcBool
+silcpurple_buddy_getkey_cb(SilcClient client, SilcClientConnection conn,
+			   SilcCommand command, SilcStatus status,
+			   SilcStatus error, void *context, va_list ap)
 {
 	SilcClientEntry client_entry;
-	unsigned char *pk;
-	SilcUInt32 pk_len;
+	SilcPurpleBuddyGetkey g = context;
+
+	if (status != SILC_STATUS_OK) {
+		purple_notify_error(g->client->application, _("Get Public Key"),
+				  _("The remote user is not present in the network any more"),
+				  NULL);
+		silc_free(g);
+		return FALSE;
+	}
 
 	/* Get the client entry. */
 	client_entry = silc_client_get_client_by_id(g->client, g->conn,
@@ -514,30 +508,28 @@
 				  _("The remote user is not present in the network any more"),
 				  NULL);
 		silc_free(g);
-		return;
+		return FALSE;
 	}
 
 	if (!client_entry->public_key) {
 		silc_free(g);
-		return;
+		return FALSE;
 	}
 
 	/* Now verify the public key */
-	pk = silc_pkcs_public_key_encode(client_entry->public_key, &pk_len);
 	silcpurple_verify_public_key(g->client, g->conn, client_entry->nickname,
-				   SILC_SOCKET_TYPE_CLIENT,
-				   pk, pk_len, SILC_SKE_PK_TYPE_SILC,
-				   NULL, NULL);
-	silc_free(pk);
+				     SILC_CONN_CLIENT, client_entry->public_key,
+				     NULL, NULL);
 	silc_free(g);
+	return TRUE;
 }
 
 static void
 silcpurple_buddy_getkey_resolved(SilcClient client,
-			       SilcClientConnection conn,
-			       SilcClientEntry *clients,
-			       SilcUInt32 clients_count,
-			       void *context)
+				 SilcClientConnection conn,
+				 SilcStatus status,
+				 SilcDList clients,
+				 void *context)
 {
 	char tmp[256];
 
@@ -546,7 +538,7 @@
 			   _("User %s is not present in the network"),
 			   (const char *)context);
 		purple_notify_error(client->application, _("Get Public Key"),
-				  _("Cannot fetch the public key"), tmp);
+				    _("Cannot fetch the public key"), tmp);
 		g_free(context);
 		return;
 	}
@@ -561,42 +553,38 @@
 	SilcPurple sg = gc->proto_data;
 	SilcClient client = sg->client;
 	SilcClientConnection conn = sg->conn;
-	SilcClientEntry *clients;
-	SilcUInt32 clients_count;
+	SilcClientEntry client_entry;
+	SilcDList clients;
 	SilcPurpleBuddyGetkey g;
-	char *nickname;
+	SilcUInt16 cmd_ident;
 
 	if (!name)
 		return;
 
-	if (!silc_parse_userfqdn(name, &nickname, NULL))
-		return;
-
 	/* Find client entry */
-	clients = silc_client_get_clients_local(client, conn, nickname, name,
-						&clients_count);
+	clients = silc_client_get_clients_local(client, conn, name, FALSE);
 	if (!clients) {
-		silc_client_get_clients(client, conn, nickname, NULL,
+		silc_client_get_clients(client, conn, name, NULL,
 					silcpurple_buddy_getkey_resolved,
 					g_strdup(name));
-		silc_free(nickname);
 		return;
 	}
 
+	silc_dlist_start(clients);
+	client_entry = silc_dlist_get(clients);
+
 	/* Call GETKEY */
 	g = silc_calloc(1, sizeof(*g));
 	if (!g)
 		return;
 	g->client = client;
 	g->conn = conn;
-	g->client_id = *clients[0]->id;
-	silc_client_command_call(client, conn, NULL, "GETKEY",
-				 clients[0]->nickname, NULL);
-	silc_client_command_pending(conn, SILC_COMMAND_GETKEY,
-				    conn->cmd_ident,
-				    (SilcCommandCb)silcpurple_buddy_getkey_cb, g);
-	silc_free(clients);
-	silc_free(nickname);
+	g->client_id = client_entry->id;
+	cmd_ident = silc_client_command_call(client, conn, NULL, "GETKEY",
+					     client_entry->nickname, NULL);
+	silc_client_command_pending(conn, SILC_COMMAND_GETKEY, cmd_ident,
+				    silcpurple_buddy_getkey_cb, g);
+	silc_client_list_free(client, conn, clients);
 }
 
 static void
@@ -629,8 +617,7 @@
 	sg = gc->proto_data;
 
 	pkfile = purple_blist_node_get_string(node, "public-key");
-	if (!silc_pkcs_load_public_key(pkfile, &public_key, SILC_PKCS_FILE_PEM) &&
-	    !silc_pkcs_load_public_key(pkfile, &public_key, SILC_PKCS_FILE_BIN)) {
+	if (!silc_pkcs_load_public_key(pkfile, &public_key)) {
 		purple_notify_error(gc,
 				  _("Show Public Key"),
 				  _("Could not load public key"), NULL);
@@ -661,6 +648,7 @@
 	PurpleBuddy *b;
 	unsigned char *offline_pk;
 	SilcUInt32 offline_pk_len;
+	SilcPublicKey public_key;
 	unsigned int offline        : 1;
 	unsigned int pubkey_search  : 1;
 	unsigned int init           : 1;
@@ -670,10 +658,10 @@
 silcpurple_add_buddy_ask_pk_cb(SilcPurpleBuddyRes r, gint id);
 static void
 silcpurple_add_buddy_resolved(SilcClient client,
-			    SilcClientConnection conn,
-			    SilcClientEntry *clients,
-			    SilcUInt32 clients_count,
-			    void *context);
+			      SilcClientConnection conn,
+			      SilcStatus status,
+			      SilcDList clients,
+			      void *context);
 
 void silcpurple_get_info(PurpleConnection *gc, const char *who)
 {
@@ -735,35 +723,36 @@
 	g_snprintf(tmp, sizeof(tmp), _("The %s buddy is not trusted"),
 		   r->b->name);
 	purple_notify_error(r->client->application, _("Add Buddy"), tmp,
-			  _("You cannot receive buddy notifications until you "
-			    "import his/her public key.  You can use the Get Public Key "
-			    "command to get the public key."));
+			    _("You cannot receive buddy notifications until you "
+			      "import his/her public key.  You can use the Get Public Key "
+			      "command to get the public key."));
 	purple_prpl_got_user_status(purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), SILCPURPLE_STATUS_ID_OFFLINE, NULL);
 }
 
 static void
-silcpurple_add_buddy_save(bool success, void *context)
+silcpurple_add_buddy_save(SilcBool success, void *context)
 {
 	SilcPurpleBuddyRes r = context;
 	PurpleBuddy *b = r->b;
-	SilcClient client = r->client;
 	SilcClientEntry client_entry;
 	SilcAttributePayload attr;
 	SilcAttribute attribute;
 	SilcVCardStruct vcard;
-	SilcAttributeObjMime message, extension;
-#ifdef SILC_ATTRIBUTE_USER_ICON
-	SilcAttributeObjMime usericon;
-#endif
+	SilcMime message = NULL, extension = NULL;
+	SilcMime usericon = NULL;
 	SilcAttributeObjPk serverpk, usersign, serversign;
 	gboolean usign_success = TRUE, ssign_success = TRUE;
 	char filename[512], filename2[512], *fingerprint = NULL, *tmp;
 	SilcUInt32 len;
+	SilcHash hash;
 	int i;
 
 	if (!success) {
 		/* The user did not trust the public key. */
 		silcpurple_add_buddy_pk_no(r);
+		silc_free(r->offline_pk);
+		if (r->public_key)
+		  silc_pkcs_public_key_free(r->public_key);
 		silc_free(r);
 		return;
 	}
@@ -783,6 +772,8 @@
 		purple_prpl_got_user_status(purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), SILCPURPLE_STATUS_ID_OFFLINE, NULL);
 		silc_free(fingerprint);
 		silc_free(r->offline_pk);
+		if (r->public_key)
+		  silc_pkcs_public_key_free(r->public_key);
 		silc_free(r);
 		return;
 	}
@@ -791,16 +782,15 @@
 	client_entry = silc_client_get_client_by_id(r->client, r->conn,
 						    &r->client_id);
 	if (!client_entry) {
+		silc_free(r->offline_pk);
+		silc_pkcs_public_key_free(r->public_key);
+		if (r->public_key)
+		  silc_pkcs_public_key_free(r->public_key);
 		silc_free(r);
 		return;
 	}
 
 	memset(&vcard, 0, sizeof(vcard));
-	memset(&message, 0, sizeof(message));
-	memset(&extension, 0, sizeof(extension));
-#ifdef SILC_ATTRIBUTE_USER_ICON
-	memset(&usericon, 0, sizeof(usericon));
-#endif
 	memset(&serverpk, 0, sizeof(serverpk));
 	memset(&usersign, 0, sizeof(usersign));
 	memset(&serversign, 0, sizeof(serversign));
@@ -822,24 +812,25 @@
 				break;
 
 			case SILC_ATTRIBUTE_STATUS_MESSAGE:
-				if (!silc_attribute_get_object(attr, (void *)&message,
-							       sizeof(message)))
+				message = silc_mime_alloc();
+				if (!silc_attribute_get_object(attr, (void *)message,
+							       sizeof(*message)))
 					continue;
 				break;
 
 			case SILC_ATTRIBUTE_EXTENSION:
-				if (!silc_attribute_get_object(attr, (void *)&extension,
-							       sizeof(extension)))
+				extension = silc_mime_alloc();
+				if (!silc_attribute_get_object(attr, (void *)extension,
+							       sizeof(*extension)))
 					continue;
 				break;
 
-#ifdef SILC_ATTRIBUTE_USER_ICON
 			case SILC_ATTRIBUTE_USER_ICON:
-				if (!silc_attribute_get_object(attr, (void *)&usericon,
-							       sizeof(usericon)))
+				usericon = silc_mime_alloc();
+				if (!silc_attribute_get_object(attr, (void *)usericon,
+							       sizeof(*usericon)))
 					continue;
 				break;
-#endif
 
 			case SILC_ATTRIBUTE_SERVER_PUBLIC_KEY:
 				if (serverpk.type)
@@ -872,50 +863,54 @@
 	}
 
 	/* Verify the attribute signatures */
+	silc_hash_alloc((const unsigned char *)"sha1", &hash);
 
 	if (usersign.data) {
-		SilcPKCS pkcs;
 		unsigned char *verifyd;
 		SilcUInt32 verify_len;
 
-		silc_pkcs_alloc((unsigned char*)"rsa", &pkcs);
 		verifyd = silc_attribute_get_verify_data(client_entry->attrs,
 							 FALSE, &verify_len);
-		if (verifyd && silc_pkcs_public_key_set(pkcs, client_entry->public_key)){
-			if (!silc_pkcs_verify_with_hash(pkcs, client->sha1hash,
-							usersign.data,
-							usersign.data_len,
-							verifyd, verify_len))
-				usign_success = FALSE;
-		}
+		if (verifyd && !silc_pkcs_verify(client_entry->public_key,
+						 usersign.data,
+						 usersign.data_len,
+						 verifyd, verify_len, hash))
+			usign_success = FALSE;
 		silc_free(verifyd);
 	}
 
-	if (serversign.data && !strcmp(serverpk.type, "silc-rsa")) {
+	if (serversign.data) {
 		SilcPublicKey public_key;
-		SilcPKCS pkcs;
+		SilcPKCSType type = 0;
 		unsigned char *verifyd;
 		SilcUInt32 verify_len;
 
-		if (silc_pkcs_public_key_decode(serverpk.data, serverpk.data_len,
-						&public_key)) {
-			silc_pkcs_alloc((unsigned char *)"rsa", &pkcs);
+		if (!strcmp(serverpk.type, "silc-rsa"))
+		  type = SILC_PKCS_SILC;
+		else if (!strcmp(serverpk.type, "ssh-rsa"))
+		  type = SILC_PKCS_SSH2;
+		else if (!strcmp(serverpk.type, "x509v3-sign-rsa"))
+		  type = SILC_PKCS_X509V3;
+		else if (!strcmp(serverpk.type, "pgp-sign-rsa"))
+		  type = SILC_PKCS_OPENPGP;
+
+		if (silc_pkcs_public_key_alloc(type, serverpk.data,
+					       serverpk.data_len,
+					       &public_key)) {
 			verifyd = silc_attribute_get_verify_data(client_entry->attrs,
 								 TRUE, &verify_len);
-			if (verifyd && silc_pkcs_public_key_set(pkcs, public_key)) {
-				if (!silc_pkcs_verify_with_hash(pkcs, client->sha1hash,
-							       serversign.data,
-							       serversign.data_len,
-							       verifyd, verify_len))
-					ssign_success = FALSE;
-			}
+			if (verifyd && !silc_pkcs_verify(public_key,
+							 serversign.data,
+							 serversign.data_len,
+							 verifyd, verify_len,
+							 hash))
+				ssign_success = FALSE;
 			silc_pkcs_public_key_free(public_key);
 			silc_free(verifyd);
 		}
 	}
 
-	fingerprint = silc_fingerprint(client_entry->fingerprint,
-				       client_entry->fingerprint_len);
+	fingerprint = silc_fingerprint(client_entry->fingerprint, 20);
 	for (i = 0; i < strlen(fingerprint); i++)
 		if (fingerprint[i] == ' ')
 			fingerprint[i] = '_';
@@ -954,48 +949,45 @@
 		}
 
 		/* Save status message */
-		if (message.mime) {
+		if (message) {
 			memset(filename2, 0, sizeof(filename2));
 			g_snprintf(filename2, sizeof(filename2) - 1,
 				   "%s" G_DIR_SEPARATOR_S "status_message.mime",
 				   filename);
-			silc_file_writefile(filename2, (char *)message.mime,
-					    message.mime_len);
+			tmp = (char *)silc_mime_get_data(message, &len);
+			silc_file_writefile(filename2, tmp, len);
+			silc_mime_free(message);
 		}
 
 		/* Save extension data */
-		if (extension.mime) {
+		if (extension) {
 			memset(filename2, 0, sizeof(filename2));
 			g_snprintf(filename2, sizeof(filename2) - 1,
 				   "%s" G_DIR_SEPARATOR_S "extension.mime",
 				   filename);
-			silc_file_writefile(filename2, (char *)extension.mime,
-					    extension.mime_len);
+			tmp = (char *)silc_mime_get_data(extension, &len);
+			silc_file_writefile(filename2, tmp, len);
+			silc_mime_free(extension);
 		}
 
-#ifdef SILC_ATTRIBUTE_USER_ICON
 		/* Save user icon */
-		if (usericon.mime) {
-			SilcMime m = silc_mime_decode(usericon.mime,
-						      usericon.mime_len);
-			if (m) {
-				const char *type = silc_mime_get_field(m, "Content-Type");
-				if (!strcmp(type, "image/jpeg") ||
-				    !strcmp(type, "image/gif") ||
-				    !strcmp(type, "image/bmp") ||
-				    !strcmp(type, "image/png")) {
-					const unsigned char *data;
-					SilcUInt32 data_len;
-					data = silc_mime_get_data(m, &data_len);
-					if (data) {
-						/* TODO: Check if SILC gives us something to use as the checksum instead */
-						purple_buddy_icons_set_for_user(purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), g_memdup(data, data_len), data_len, NULL);
-					}
+		if (usericon) {
+			const char *type = silc_mime_get_field(usericon, "Content-Type");
+			if (type &&
+			    (!strcmp(type, "image/jpeg") ||
+			     !strcmp(type, "image/gif") ||
+			     !strcmp(type, "image/bmp") ||
+			     !strcmp(type, "image/png"))) {
+				const unsigned char *data;
+				SilcUInt32 data_len;
+				data = silc_mime_get_data(usericon, &data_len);
+				if (data) {
+					/* TODO: Check if SILC gives us something to use as the checksum instead */
+					purple_buddy_icons_set_for_user(purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), g_memdup(data, data_len), data_len, NULL);
 				}
-				silc_mime_free(m);
 			}
+			silc_mime_free(usericon);
 		}
-#endif
 	}
 
 	/* Save the public key path to buddy properties, as it is used
@@ -1015,7 +1007,11 @@
 	silc_client_command_call(r->client, r->conn, NULL, "WATCH", "-pubkey",
 				 filename2, NULL);
 
+	silc_hash_free(hash);
 	silc_free(fingerprint);
+	silc_free(r->offline_pk);
+	if (r->public_key)
+	  silc_pkcs_public_key_free(r->public_key);
 	silc_free(r);
 }
 
@@ -1023,11 +1019,9 @@
 silcpurple_add_buddy_ask_import(void *user_data, const char *name)
 {
 	SilcPurpleBuddyRes r = (SilcPurpleBuddyRes)user_data;
-	SilcPublicKey public_key;
 
 	/* Load the public key */
-	if (!silc_pkcs_load_public_key(name, &public_key, SILC_PKCS_FILE_PEM) &&
-	    !silc_pkcs_load_public_key(name, &public_key, SILC_PKCS_FILE_BIN)) {
+	if (!silc_pkcs_load_public_key(name, &r->public_key)) {
 		silcpurple_add_buddy_ask_pk_cb(r, 0);
 		purple_notify_error(r->client->application,
 				  _("Add Buddy"), _("Could not load public key"), NULL);
@@ -1035,12 +1029,10 @@
 	}
 
 	/* Now verify the public key */
-	r->offline_pk = silc_pkcs_public_key_encode(public_key, &r->offline_pk_len);
+	r->offline_pk = silc_pkcs_public_key_encode(r->public_key, &r->offline_pk_len);
 	silcpurple_verify_public_key(r->client, r->conn, r->b->name,
-				   SILC_SOCKET_TYPE_CLIENT,
-				   r->offline_pk, r->offline_pk_len,
-				   SILC_SKE_PK_TYPE_SILC,
-				   silcpurple_add_buddy_save, r);
+				     SILC_CONN_CLIENT, r->public_key,
+				     silcpurple_add_buddy_save, r);
 }
 
 static void
@@ -1065,9 +1057,9 @@
 
 	/* Open file selector to select the public key. */
 	purple_request_file(r->client->application, _("Open..."), NULL, FALSE,
-			  G_CALLBACK(silcpurple_add_buddy_ask_import),
-			  G_CALLBACK(silcpurple_add_buddy_ask_pk_cancel),
-			  purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), NULL, r);
+			    G_CALLBACK(silcpurple_add_buddy_ask_import),
+			    G_CALLBACK(silcpurple_add_buddy_ask_pk_cancel),
+			    purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), NULL, r);
 
 }
 
@@ -1078,20 +1070,29 @@
 	g_snprintf(tmp, sizeof(tmp), _("The %s buddy is not present in the network"),
 		   r->b->name);
 	purple_request_action(r->client->application, _("Add Buddy"), tmp,
-			    _("To add the buddy you must import his/her public key. "
-			      "Press Import to import a public key."), 0,
-				  purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), NULL, r, 2,
-			    _("Cancel"), G_CALLBACK(silcpurple_add_buddy_ask_pk_cb),
-			    _("_Import..."), G_CALLBACK(silcpurple_add_buddy_ask_pk_cb));
+			      _("To add the buddy you must import his/her public key. "
+				"Press Import to import a public key."), 0,
+			      purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), NULL, r, 2,
+			      _("Cancel"), G_CALLBACK(silcpurple_add_buddy_ask_pk_cb),
+			      _("_Import..."), G_CALLBACK(silcpurple_add_buddy_ask_pk_cb));
 }
 
-static void
-silcpurple_add_buddy_getkey_cb(SilcPurpleBuddyRes r,
-			     SilcClientCommandReplyContext cmd)
+static SilcBool
+silcpurple_add_buddy_getkey_cb(SilcClient client, SilcClientConnection conn,
+			       SilcCommand command, SilcStatus status,
+			       SilcStatus error, void *context, va_list ap)
 {
+	SilcPurpleBuddyRes r = context;
 	SilcClientEntry client_entry;
-	unsigned char *pk;
-	SilcUInt32 pk_len;
+
+	if (status != SILC_STATUS_OK) {
+		/* The buddy is offline/nonexistent. We will require user
+		   to associate a public key with the buddy or the buddy
+		   cannot be added. */
+		r->offline = TRUE;
+		silcpurple_add_buddy_ask_pk(r);
+		return FALSE;
+	}
 
 	/* Get the client entry. */
 	client_entry = silc_client_get_client_by_id(r->client, r->conn,
@@ -1102,16 +1103,14 @@
 		   cannot be added. */
 		r->offline = TRUE;
 		silcpurple_add_buddy_ask_pk(r);
-		return;
+		return FALSE;
 	}
 
 	/* Now verify the public key */
-	pk = silc_pkcs_public_key_encode(client_entry->public_key, &pk_len);
 	silcpurple_verify_public_key(r->client, r->conn, client_entry->nickname,
-				   SILC_SOCKET_TYPE_CLIENT,
-				   pk, pk_len, SILC_SKE_PK_TYPE_SILC,
-				   silcpurple_add_buddy_save, r);
-	silc_free(pk);
+				     SILC_CONN_CLIENT, client_entry->public_key,
+				     silcpurple_add_buddy_save, r);
+	return TRUE;
 }
 
 static void
@@ -1120,6 +1119,7 @@
 	PurpleRequestField *f;
 	const GList *list;
 	SilcClientEntry client_entry;
+	SilcDList clients;
 
 	f = purple_request_fields_get_field(fields, "list");
 	list = purple_request_field_list_get_selected(f);
@@ -1131,7 +1131,11 @@
 	}
 
 	client_entry = purple_request_field_list_get_data(f, list->data);
-	silcpurple_add_buddy_resolved(r->client, r->conn, &client_entry, 1, r);
+	clients = silc_dlist_init();
+	silc_dlist_add(clients, client_entry);
+	silcpurple_add_buddy_resolved(r->client, r->conn, SILC_STATUS_OK,
+				      clients, r);
+	silc_dlist_uninit(clients);
 }
 
 static void
@@ -1143,16 +1147,14 @@
 }
 
 static void
-silcpurple_add_buddy_select(SilcPurpleBuddyRes r,
-			  SilcClientEntry *clients,
-			  SilcUInt32 clients_count)
+silcpurple_add_buddy_select(SilcPurpleBuddyRes r, SilcDList clients)
 {
 	PurpleRequestFields *fields;
 	PurpleRequestFieldGroup *g;
 	PurpleRequestField *f;
 	char tmp[512], tmp2[128];
-	int i;
 	char *fingerprint;
+	SilcClientEntry client_entry;
 
 	fields = purple_request_fields_new();
 	g = purple_request_field_group_new(NULL);
@@ -1161,56 +1163,56 @@
 	purple_request_field_list_set_multi_select(f, FALSE);
 	purple_request_fields_add_group(fields, g);
 
-	for (i = 0; i < clients_count; i++) {
+	silc_dlist_start(clients);
+	while ((client_entry = silc_dlist_get(clients))) {
 		fingerprint = NULL;
-		if (clients[i]->fingerprint) {
-			fingerprint = silc_fingerprint(clients[i]->fingerprint,
-						       clients[i]->fingerprint_len);
+		if (*client_entry->fingerprint) {
+			fingerprint = silc_fingerprint(client_entry->fingerprint, 20);
 			g_snprintf(tmp2, sizeof(tmp2), "\n%s", fingerprint);
 		}
 		g_snprintf(tmp, sizeof(tmp), "%s - %s (%s@%s)%s",
-			   clients[i]->realname, clients[i]->nickname,
-			   clients[i]->username, clients[i]->hostname ?
-			   clients[i]->hostname : "",
+			   client_entry->realname, client_entry->nickname,
+			   client_entry->username, *client_entry->hostname ?
+			   client_entry->hostname : "",
 			   fingerprint ? tmp2 : "");
-		purple_request_field_list_add(f, tmp, clients[i]);
+		purple_request_field_list_add(f, tmp, client_entry);
 		silc_free(fingerprint);
 	}
 
 	purple_request_fields(r->client->application, _("Add Buddy"),
-				_("Select correct user"),
-				r->pubkey_search
-					? _("More than one user was found with the same public key. Select "
-						"the correct user from the list to add to the buddy list.")
-					: _("More than one user was found with the same name. Select "
-						"the correct user from the list to add to the buddy list."),
-				fields,
-				_("OK"), G_CALLBACK(silcpurple_add_buddy_select_cb),
-				_("Cancel"), G_CALLBACK(silcpurple_add_buddy_select_cancel),
-				purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), NULL, r);
+			      _("Select correct user"),
+			      r->pubkey_search
+			      ? _("More than one user was found with the same public key. Select "
+				  "the correct user from the list to add to the buddy list.")
+			      : _("More than one user was found with the same name. Select "
+				  "the correct user from the list to add to the buddy list."),
+			      fields,
+			      _("OK"), G_CALLBACK(silcpurple_add_buddy_select_cb),
+			      _("Cancel"), G_CALLBACK(silcpurple_add_buddy_select_cancel),
+			      purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), NULL, r);
 }
 
 static void
 silcpurple_add_buddy_resolved(SilcClient client,
-			    SilcClientConnection conn,
-			    SilcClientEntry *clients,
-			    SilcUInt32 clients_count,
-			    void *context)
+			      SilcClientConnection conn,
+			      SilcStatus status,
+			      SilcDList clients,
+			      void *context)
 {
 	SilcPurpleBuddyRes r = context;
 	PurpleBuddy *b = r->b;
 	SilcAttributePayload pub;
 	SilcAttributeObjPk userpk;
-	unsigned char *pk;
-	SilcUInt32 pk_len;
 	const char *filename;
+	SilcClientEntry client_entry = NULL;
+	SilcUInt16 cmd_ident;
 
 	filename = purple_blist_node_get_string((PurpleBlistNode *)b, "public-key");
 
 	/* If the buddy is offline/nonexistent, we will require user
 	   to associate a public key with the buddy or the buddy
 	   cannot be added. */
-	if (!clients_count) {
+	if (!clients) {
 		if (r->init) {
 			silc_free(r);
 			return;
@@ -1228,33 +1230,37 @@
 
 	/* If more than one client was found with nickname, we need to verify
 	   from user which one is the correct. */
-	if (clients_count > 1 && !r->pubkey_search) {
+	if (silc_dlist_count(clients) > 1 && !r->pubkey_search) {
 		if (r->init) {
 			silc_free(r);
 			return;
 		}
 
-		silcpurple_add_buddy_select(r, clients, clients_count);
+		silcpurple_add_buddy_select(r, clients);
 		return;
 	}
 
+	silc_dlist_start(clients);
+	client_entry = silc_dlist_get(clients);
+
 	/* If we searched using public keys and more than one entry was found
 	   the same person is logged on multiple times. */
-	if (clients_count > 1 && r->pubkey_search && b->name) {
+	if (silc_dlist_count(clients) > 1 && r->pubkey_search && b->name) {
 		if (r->init) {
 			/* Find the entry that closest matches to the
 			   buddy nickname. */
-			int i;
-			for (i = 0; i < clients_count; i++) {
-				if (!strncasecmp(b->name, clients[i]->nickname,
+			SilcClientEntry entry;
+			silc_dlist_start(clients);
+			while ((entry = silc_dlist_get(clients))) {
+				if (!strncasecmp(b->name, entry->nickname,
 						 strlen(b->name))) {
-					clients[0] = clients[i];
+					client_entry = entry;
 					break;
 				}
 			}
 		} else {
 			/* Verify from user which one is correct */
-			silcpurple_add_buddy_select(r, clients, clients_count);
+			silcpurple_add_buddy_select(r, clients);
 			return;
 		}
 	}
@@ -1262,61 +1268,60 @@
 	/* The client was found.  Now get its public key and verify
 	   that before adding the buddy. */
 	memset(&userpk, 0, sizeof(userpk));
-	b->proto_data = silc_memdup(clients[0]->id, sizeof(*clients[0]->id));
-	r->client_id = *clients[0]->id;
+	b->proto_data = silc_memdup(&client_entry->id, sizeof(client_entry->id));
+	r->client_id = client_entry->id;
 
 	/* Get the public key from attributes, if not present then
 	   resolve it with GETKEY unless we have it cached already. */
-	if (clients[0]->attrs && !clients[0]->public_key) {
-		pub = silcpurple_get_attr(clients[0]->attrs,
-					SILC_ATTRIBUTE_USER_PUBLIC_KEY);
+	if (client_entry->attrs && !client_entry->public_key) {
+		pub = silcpurple_get_attr(client_entry->attrs,
+					  SILC_ATTRIBUTE_USER_PUBLIC_KEY);
 		if (!pub || !silc_attribute_get_object(pub, (void *)&userpk,
 						       sizeof(userpk))) {
 			/* Get public key with GETKEY */
-			silc_client_command_call(client, conn, NULL,
-						 "GETKEY", clients[0]->nickname, NULL);
+			cmd_ident =
+			  silc_client_command_call(client, conn, NULL,
+						   "GETKEY", client_entry->nickname, NULL);
 			silc_client_command_pending(conn, SILC_COMMAND_GETKEY,
-						    conn->cmd_ident,
-						    (SilcCommandCb)silcpurple_add_buddy_getkey_cb,
+						    cmd_ident,
+						    silcpurple_add_buddy_getkey_cb,
 						    r);
 			return;
 		}
-		if (!silc_pkcs_public_key_decode(userpk.data, userpk.data_len,
-						 &clients[0]->public_key))
+		if (!silc_pkcs_public_key_alloc(SILC_PKCS_SILC,
+						userpk.data, userpk.data_len,
+						&client_entry->public_key))
 			return;
 		silc_free(userpk.data);
-	} else if (filename && !clients[0]->public_key) {
-		if (!silc_pkcs_load_public_key(filename, &clients[0]->public_key,
-					       SILC_PKCS_FILE_PEM) &&
-		    !silc_pkcs_load_public_key(filename, &clients[0]->public_key,
-					       SILC_PKCS_FILE_BIN)) {
+	} else if (filename && !client_entry->public_key) {
+		if (!silc_pkcs_load_public_key(filename, &client_entry->public_key)) {
 			/* Get public key with GETKEY */
-			silc_client_command_call(client, conn, NULL,
-						 "GETKEY", clients[0]->nickname, NULL);
+			cmd_ident =
+			  silc_client_command_call(client, conn, NULL,
+						   "GETKEY", client_entry->nickname, NULL);
 			silc_client_command_pending(conn, SILC_COMMAND_GETKEY,
-						    conn->cmd_ident,
-						    (SilcCommandCb)silcpurple_add_buddy_getkey_cb,
+						    cmd_ident,
+						    silcpurple_add_buddy_getkey_cb,
 						    r);
 			return;
 		}
-	} else if (!clients[0]->public_key) {
+	} else if (!client_entry->public_key) {
 		/* Get public key with GETKEY */
-		silc_client_command_call(client, conn, NULL,
-					 "GETKEY", clients[0]->nickname, NULL);
+		cmd_ident =
+		  silc_client_command_call(client, conn, NULL,
+					   "GETKEY", client_entry->nickname, NULL);
 		silc_client_command_pending(conn, SILC_COMMAND_GETKEY,
-					    conn->cmd_ident,
-					    (SilcCommandCb)silcpurple_add_buddy_getkey_cb,
+					    cmd_ident,
+					    silcpurple_add_buddy_getkey_cb,
 					    r);
 		return;
 	}
 
 	/* We have the public key, verify it. */
-	pk = silc_pkcs_public_key_encode(clients[0]->public_key, &pk_len);
-	silcpurple_verify_public_key(client, conn, clients[0]->nickname,
-				   SILC_SOCKET_TYPE_CLIENT,
-				   pk, pk_len, SILC_SKE_PK_TYPE_SILC,
-				   silcpurple_add_buddy_save, r);
-	silc_free(pk);
+	silcpurple_verify_public_key(client, conn, client_entry->nickname,
+				     SILC_CONN_CLIENT,
+				     client_entry->public_key,
+				     silcpurple_add_buddy_save, r);
 }
 
 static void
@@ -1344,10 +1349,7 @@
 		SilcPublicKey public_key;
 		SilcAttributeObjPk userpk;
 
-		if (!silc_pkcs_load_public_key(filename, &public_key,
-					       SILC_PKCS_FILE_PEM) &&
-		    !silc_pkcs_load_public_key(filename, &public_key,
-					       SILC_PKCS_FILE_BIN))
+		if (!silc_pkcs_load_public_key(filename, &public_key))
 			return;
 
 		/* Get all attributes, and use the public key to search user */
@@ -1361,9 +1363,7 @@
 						       SILC_ATTRIBUTE_PREFERRED_CONTACT,
 						       SILC_ATTRIBUTE_TIMEZONE,
 						       SILC_ATTRIBUTE_GEOLOCATION,
-#ifdef SILC_ATTRIBUTE_USER_ICON
 						       SILC_ATTRIBUTE_USER_ICON,
-#endif
 						       SILC_ATTRIBUTE_DEVICE_INFO, 0);
 		userpk.type = "silc-rsa";
 		userpk.data = silc_pkcs_public_key_encode(public_key, &userpk.data_len);
@@ -1632,12 +1632,13 @@
 						    sg->conn,
 						    buddy->proto_data);
 
-	if (client_entry && client_entry->send_key) {
+	if (client_entry &&
+	    silc_client_private_message_key_is_set(sg->client,
+						   sg->conn, client_entry)) {
 		act = purple_menu_action_new(_("Reset IM Key"),
 		                           PURPLE_CALLBACK(silcpurple_buddy_resetkey),
 		                           NULL, NULL);
 		m = g_list_append(m, act);
-
 	} else {
 		act = purple_menu_action_new(_("IM with Key Exchange"),
 		                           PURPLE_CALLBACK(silcpurple_buddy_keyagr),
@@ -1682,7 +1683,6 @@
 	return m;
 }
 
-#ifdef SILC_ATTRIBUTE_USER_ICON
 void silcpurple_buddy_set_icon(PurpleConnection *gc, PurpleStoredImage *img)
 {
 	SilcPurple sg = gc->proto_data;
@@ -1690,9 +1690,7 @@
 	SilcClientConnection conn = sg->conn;
 	SilcMime mime;
 	char type[32];
-	unsigned char *icon;
 	const char *t;
-	SilcAttributeObjMime obj;
 
 	/* Remove */
 	if (!img) {
@@ -1717,12 +1715,8 @@
 	silc_mime_add_field(mime, "Content-Type", type);
 	silc_mime_add_data(mime, purple_imgstore_get_data(img), purple_imgstore_get_size(img));
 
-	obj.mime = icon = silc_mime_encode(mime, &obj.mime_len);
-	if (obj.mime)
-		silc_client_attribute_add(client, conn, 
-					  SILC_ATTRIBUTE_USER_ICON, &obj, sizeof(obj));
+	silc_client_attribute_add(client, conn,
+				  SILC_ATTRIBUTE_USER_ICON, mime, sizeof(*mime));
 
-	silc_free(icon);
 	silc_mime_free(mime);
 }
-#endif
--- a/libpurple/protocols/silc/chat.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/silc/chat.c	Tue Jun 12 21:21:37 2007 +0000
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 2004 Pekka Riikonen
+  Copyright (C) 2004 - 2007 Pekka Riikonen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
@@ -17,7 +17,7 @@
 
 */
 
-#include "silcincludes.h"
+#include "silc.h"
 #include "silcclient.h"
 #include "silcpurple.h"
 #include "wb.h"
@@ -61,10 +61,10 @@
 
 static void
 silcpurple_chat_getinfo_res(SilcClient client,
-			  SilcClientConnection conn,
-			  SilcChannelEntry *channels,
-			  SilcUInt32 channels_count,
-			  void *context)
+			    SilcClientConnection conn,
+			    SilcStatus status,
+			    SilcDList channels,
+			    void *context)
 {
 	GHashTable *components = context;
 	PurpleConnection *gc = client->application;
@@ -134,13 +134,14 @@
 	}
 	silc_hash_table_list_reset(&htl);
 
-	if (channel->channel_key)
+	if (channel->cipher)
 		g_string_append_printf(s, _("<br><b>Channel Cipher:</b> %s"),
-				       silc_cipher_get_name(channel->channel_key));
+				       channel->cipher);
+
 	if (channel->hmac)
 		/* Definition of HMAC: http://en.wikipedia.org/wiki/HMAC */
 		g_string_append_printf(s, _("<br><b>Channel HMAC:</b> %s"),
-				       silc_hmac_get_name(channel->hmac));
+				       channel->hmac);
 
 	if (channel->topic) {
 		tmp2 = g_markup_escape_text(channel->topic, -1);
@@ -211,7 +212,7 @@
 	SilcPurple sg;
 	SilcChannelEntry channel;
 	PurpleChat *c;
-	SilcBuffer pubkeys;
+	SilcDList pubkeys;
 } *SilcPurpleChauth;
 
 static void
@@ -227,22 +228,21 @@
 	SilcUInt32 m;
 
 	/* Load the public key */
-	if (!silc_pkcs_load_public_key(name, &public_key, SILC_PKCS_FILE_PEM) &&
-	    !silc_pkcs_load_public_key(name, &public_key, SILC_PKCS_FILE_BIN)) {
+	if (!silc_pkcs_load_public_key(name, &public_key)) {
 		silcpurple_chat_chauth_show(sgc->sg, sgc->channel, sgc->pubkeys);
-		silc_buffer_free(sgc->pubkeys);
+		silc_dlist_uninit(sgc->pubkeys);
 		silc_free(sgc);
 		purple_notify_error(client->application,
-				  _("Add Channel Public Key"),
-				  _("Could not load public key"), NULL);
+				    _("Add Channel Public Key"),
+				    _("Could not load public key"), NULL);
 		return;
 	}
 
-	pk = silc_pkcs_public_key_payload_encode(public_key);
+	pk = silc_public_key_payload_encode(public_key);
 	chpks = silc_buffer_alloc_size(2);
 	SILC_PUT16_MSB(1, chpks->head);
 	chpks = silc_argument_payload_encode_one(chpks, pk->data,
-						 pk->len, 0x00);
+						 silc_buffer_len(pk), 0x00);
 	silc_buffer_free(pk);
 
 	m = sgc->channel->mode;
@@ -250,15 +250,20 @@
 
 	/* Send CMODE */
 	SILC_PUT32_MSB(m, mode);
-	chidp = silc_id_payload_encode(sgc->channel->id, SILC_ID_CHANNEL);
+	chidp = silc_id_payload_encode(&sgc->channel->id, SILC_ID_CHANNEL);
 	silc_client_command_send(client, conn, SILC_COMMAND_CMODE,
-				 ++conn->cmd_ident, 3,
-				 1, chidp->data, chidp->len,
+				 silcpurple_command_reply, NULL, 3,
+				 1, chidp->data, silc_buffer_len(chidp),
 				 2, mode, sizeof(mode),
-				 9, chpks->data, chpks->len);
+				 9, chpks->data, silc_buffer_len(chpks));
 	silc_buffer_free(chpks);
 	silc_buffer_free(chidp);
-	silc_buffer_free(sgc->pubkeys);
+	if (sgc->pubkeys) {
+	  silc_dlist_start(sgc->pubkeys);
+	  while ((public_key = silc_dlist_get(sgc->pubkeys)))
+		silc_pkcs_public_key_free(public_key);
+	  silc_dlist_uninit(sgc->pubkeys);
+	}
 	silc_free(sgc);
 }
 
@@ -266,8 +271,16 @@
 silcpurple_chat_chpk_cancel(void *user_data, const char *name)
 {
 	SilcPurpleChauth sgc = (SilcPurpleChauth)user_data;
+	SilcPublicKey public_key;
+
 	silcpurple_chat_chauth_show(sgc->sg, sgc->channel, sgc->pubkeys);
-	silc_buffer_free(sgc->pubkeys);
+
+	if (sgc->pubkeys) {
+	  silc_dlist_start(sgc->pubkeys);
+	  while ((public_key = silc_dlist_get(sgc->pubkeys)))
+		silc_pkcs_public_key_free(public_key);
+	  silc_dlist_uninit(sgc->pubkeys);
+	}
 	silc_free(sgc);
 }
 
@@ -289,9 +302,9 @@
 	if (!purple_request_field_list_get_selected(f)) {
 		/* Add new public key */
 		purple_request_file(sg->gc, _("Open Public Key..."), NULL, FALSE,
-				  G_CALLBACK(silcpurple_chat_chpk_add),
-				  G_CALLBACK(silcpurple_chat_chpk_cancel),
-				  purple_connection_get_account(sg->gc), NULL, NULL, sgc);
+				    G_CALLBACK(silcpurple_chat_chpk_add),
+				    G_CALLBACK(silcpurple_chat_chpk_cancel),
+				    purple_connection_get_account(sg->gc), NULL, NULL, sgc);
 		return;
 	}
 
@@ -302,13 +315,12 @@
 		public_key = purple_request_field_list_get_data(f, list->data);
 		if (purple_request_field_list_is_selected(f, list->data)) {
 			/* Delete this public key */
-			pk = silc_pkcs_public_key_payload_encode(public_key);
+			pk = silc_public_key_payload_encode(public_key);
 			chpks = silc_argument_payload_encode_one(chpks, pk->data,
-								 pk->len, 0x01);
+								 silc_buffer_len(pk), 0x01);
 			silc_buffer_free(pk);
 			c++;
 		}
-		silc_pkcs_public_key_free(public_key);
 	}
 	if (!c) {
 		silc_buffer_free(chpks);
@@ -322,15 +334,20 @@
 
 	/* Send CMODE */
 	SILC_PUT32_MSB(m, mode);
-	chidp = silc_id_payload_encode(sgc->channel->id, SILC_ID_CHANNEL);
+	chidp = silc_id_payload_encode(&sgc->channel->id, SILC_ID_CHANNEL);
 	silc_client_command_send(client, conn, SILC_COMMAND_CMODE,
-				 ++conn->cmd_ident, 3,
-				 1, chidp->data, chidp->len,
+				 silcpurple_command_reply, NULL, 3,
+				 1, chidp->data, silc_buffer_len(chidp),
 				 2, mode, sizeof(mode),
-				 9, chpks->data, chpks->len);
+				 9, chpks->data, silc_buffer_len(chpks));
 	silc_buffer_free(chpks);
 	silc_buffer_free(chidp);
-	silc_buffer_free(sgc->pubkeys);
+	if (sgc->pubkeys) {
+	  silc_dlist_start(sgc->pubkeys);
+	  while ((public_key = silc_dlist_get(sgc->pubkeys)))
+		silc_pkcs_public_key_free(public_key);
+	  silc_dlist_uninit(sgc->pubkeys);
+	}
 	silc_free(sgc);
 }
 
@@ -339,6 +356,7 @@
 {
 	SilcPurple sg = sgc->sg;
 	PurpleRequestField *f;
+	SilcPublicKey public_key;
 	const char *curpass, *val;
 	int set;
 
@@ -365,19 +383,23 @@
 		purple_blist_node_remove_setting((PurpleBlistNode *)sgc->c, "passphrase");
 	}
 
-	silc_buffer_free(sgc->pubkeys);
+	if (sgc->pubkeys) {
+	  silc_dlist_start(sgc->pubkeys);
+	  while ((public_key = silc_dlist_get(sgc->pubkeys)))
+		silc_pkcs_public_key_free(public_key);
+	  silc_dlist_uninit(sgc->pubkeys);
+	}
 	silc_free(sgc);
 }
 
 void silcpurple_chat_chauth_show(SilcPurple sg, SilcChannelEntry channel,
-			       SilcBuffer channel_pubkeys)
+				 SilcDList channel_pubkeys)
 {
-	SilcUInt16 argc;
-	SilcArgumentPayload chpks;
+	SilcPublicKey public_key;
+	SilcSILCPublicKey silc_pubkey;
 	unsigned char *pk;
-	SilcUInt32 pk_len, type;
+	SilcUInt32 pk_len;
 	char *fingerprint, *babbleprint;
-	SilcPublicKey pubkey;
 	SilcPublicKeyIdentifier ident;
 	char tmp2[1024], t[512];
 	PurpleRequestFields *fields;
@@ -399,7 +421,7 @@
 
 	g = purple_request_field_group_new(NULL);
 	f = purple_request_field_string_new("passphrase", _("Channel Passphrase"),
-					  curpass, FALSE);
+					    curpass, FALSE);
 	purple_request_field_string_set_masked(f, TRUE);
 	purple_request_field_group_add_field(g, f);
 	purple_request_fields_add_group(fields, g);
@@ -416,55 +438,49 @@
 		     "is required to be able to join. If channel public keys are set "
 		     "then only users whose public keys are listed are able to join."));
 
-	if (!channel_pubkeys) {
+	if (!channel_pubkeys || !silc_dlist_count(channel_pubkeys)) {
 		f = purple_request_field_list_new("list", NULL);
 		purple_request_field_group_add_field(g, f);
 		purple_request_fields(sg->gc, _("Channel Authentication"),
-				    _("Channel Authentication"), t, fields,
-				    _("Add / Remove"), G_CALLBACK(silcpurple_chat_chpk_cb),
-				    _("OK"), G_CALLBACK(silcpurple_chat_chauth_ok),
-					purple_connection_get_account(sg->gc), NULL, NULL, sgc);
+				      _("Channel Authentication"), t, fields,
+				      _("Add / Remove"), G_CALLBACK(silcpurple_chat_chpk_cb),
+				      _("OK"), G_CALLBACK(silcpurple_chat_chauth_ok),
+				      purple_connection_get_account(sg->gc), NULL, NULL, sgc);
+		if (channel_pubkeys)
+		  silc_dlist_uninit(channel_pubkeys);
 		return;
 	}
-	sgc->pubkeys = silc_buffer_copy(channel_pubkeys);
+	sgc->pubkeys = channel_pubkeys;
 
 	g = purple_request_field_group_new(NULL);
 	f = purple_request_field_list_new("list", NULL);
 	purple_request_field_group_add_field(g, f);
 	purple_request_fields_add_group(fields, g);
 
-	SILC_GET16_MSB(argc, channel_pubkeys->data);
-	chpks = silc_argument_payload_parse(channel_pubkeys->data + 2,
-					    channel_pubkeys->len - 2, argc);
-	if (!chpks)
-		return;
-
-	pk = silc_argument_get_first_arg(chpks, &type, &pk_len);
-	while (pk) {
+	silc_dlist_start(channel_pubkeys);
+	while ((public_key = silc_dlist_get(channel_pubkeys))) {
+		pk = silc_pkcs_public_key_encode(public_key, &pk_len);
 		fingerprint = silc_hash_fingerprint(NULL, pk + 4, pk_len - 4);
 		babbleprint = silc_hash_babbleprint(NULL, pk + 4, pk_len - 4);
-		silc_pkcs_public_key_payload_decode(pk, pk_len, &pubkey);
-		ident = silc_pkcs_decode_identifier(pubkey->identifier);
+
+		silc_pubkey = silc_pkcs_get_context(SILC_PKCS_SILC, public_key);
+		ident = &silc_pubkey->identifier;
 
 		g_snprintf(tmp2, sizeof(tmp2), "%s\n  %s\n  %s",
 			   ident->realname ? ident->realname : ident->username ?
 			   ident->username : "", fingerprint, babbleprint);
-		purple_request_field_list_add(f, tmp2, pubkey);
+		purple_request_field_list_add(f, tmp2, public_key);
 
 		silc_free(fingerprint);
 		silc_free(babbleprint);
-		silc_pkcs_free_identifier(ident);
-		pk = silc_argument_get_next_arg(chpks, &type, &pk_len);
 	}
 
 	purple_request_field_list_set_multi_select(f, FALSE);
 	purple_request_fields(sg->gc, _("Channel Authentication"),
-			    _("Channel Authentication"), t, fields,
-			    _("Add / Remove"), G_CALLBACK(silcpurple_chat_chpk_cb),
-			    _("OK"), G_CALLBACK(silcpurple_chat_chauth_ok),
-				purple_connection_get_account(sg->gc), NULL, NULL, sgc);
-
-	silc_argument_payload_free(chpks);
+			      _("Channel Authentication"), t, fields,
+			      _("Add / Remove"), G_CALLBACK(silcpurple_chat_chpk_cb),
+			      _("OK"), G_CALLBACK(silcpurple_chat_chauth_ok),
+			      purple_connection_get_account(sg->gc), NULL, NULL, sgc);
 }
 
 static void
@@ -525,9 +541,9 @@
 
 	/* Add private group to buddy list */
 	g_snprintf(tmp, sizeof(tmp), "%s [Private Group]", name);
-	comp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
-	g_hash_table_replace(comp, g_strdup("channel"), g_strdup(tmp));
-	g_hash_table_replace(comp, g_strdup("passphrase"), g_strdup(passphrase));
+	comp = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
+	g_hash_table_replace(comp, "channel", g_strdup(tmp));
+	g_hash_table_replace(comp, "passphrase", g_strdup(passphrase));
 
 	cn = purple_chat_new(sg->account, alias, comp);
 	g = (PurpleGroup *)p->c->node.parent;
@@ -596,9 +612,9 @@
 		   _("Please enter the %s channel private group name and passphrase."),
 		   p->channel);
 	purple_request_fields(gc, _("Add Channel Private Group"), NULL, tmp, fields,
-			    _("Add"), G_CALLBACK(silcpurple_chat_prv_add),
-			    _("Cancel"), G_CALLBACK(silcpurple_chat_prv_cancel),
-				purple_connection_get_account(gc), NULL, NULL, p);
+			      _("Add"), G_CALLBACK(silcpurple_chat_prv_add),
+			      _("Cancel"), G_CALLBACK(silcpurple_chat_prv_cancel),
+			      purple_connection_get_account(gc), NULL, NULL, p);
 }
 
 
@@ -907,7 +923,7 @@
 		m = g_list_append(m, act);
 	}
 
-	if (mode & SILC_CHANNEL_UMODE_CHANFO) {
+	if (chu && mode & SILC_CHANNEL_UMODE_CHANFO) {
 		act = purple_menu_action_new(_("Channel Authentication"),
 		                           PURPLE_CALLBACK(silcpurple_chat_chauth),
 		                           NULL, NULL);
@@ -926,7 +942,7 @@
 		}
 	}
 
-	if (mode & SILC_CHANNEL_UMODE_CHANOP) {
+	if (chu && mode & SILC_CHANNEL_UMODE_CHANOP) {
 		act = purple_menu_action_new(_("Set User Limit"),
 		                           PURPLE_CALLBACK(silcpurple_chat_ulimit),
 		                           NULL, NULL);
@@ -969,7 +985,7 @@
 		}
 	}
 
-	if (channel) {
+	if (chu && channel) {
 		SilcPurpleChatWb wb;
 		wb = silc_calloc(1, sizeof(*wb));
 		wb->sg = sg;
@@ -986,86 +1002,10 @@
 
 /******************************* Joining Etc. ********************************/
 
-void silcpurple_chat_join_done(SilcClient client,
-			     SilcClientConnection conn,
-			     SilcClientEntry *clients,
-			     SilcUInt32 clients_count,
-			     void *context)
-{
-	PurpleConnection *gc = client->application;
-	SilcPurple sg = gc->proto_data;
-	SilcChannelEntry channel = context;
-	PurpleConversation *convo;
-	SilcUInt32 retry = SILC_PTR_TO_32(channel->context);
-	SilcHashTableList htl;
-	SilcChannelUser chu;
-	GList *users = NULL, *flags = NULL;
-	char tmp[256];
-
-	if (!clients && retry < 1) {
-		/* Resolving users failed, try again. */
-		channel->context = SILC_32_TO_PTR(retry + 1);
-		silc_client_get_clients_by_channel(client, conn, channel,
-						   silcpurple_chat_join_done, channel);
-		return;
-	}
-
-	/* Add channel to Purple */
-	channel->context = SILC_32_TO_PTR(++sg->channel_ids);
-	serv_got_joined_chat(gc, sg->channel_ids, channel->channel_name);
-	convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
-							channel->channel_name, sg->account);
-	if (!convo)
-		return;
-
-	/* Add all users to channel */
-	silc_hash_table_list(channel->user_list, &htl);
-	while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
-		PurpleConvChatBuddyFlags f = PURPLE_CBFLAGS_NONE;
-		if (!chu->client->nickname)
-			continue;
-		chu->context = SILC_32_TO_PTR(sg->channel_ids);
-
-		if (chu->mode & SILC_CHANNEL_UMODE_CHANFO)
-			f |= PURPLE_CBFLAGS_FOUNDER;
-		if (chu->mode & SILC_CHANNEL_UMODE_CHANOP)
-			f |= PURPLE_CBFLAGS_OP;
-		users = g_list_append(users, g_strdup(chu->client->nickname));
-		flags = g_list_append(flags, GINT_TO_POINTER(f));
-
-		if (chu->mode & SILC_CHANNEL_UMODE_CHANFO) {
-			if (chu->client == conn->local_entry)
-				g_snprintf(tmp, sizeof(tmp),
-					   _("You are channel founder on <I>%s</I>"),
-					   channel->channel_name);
-			else
-				g_snprintf(tmp, sizeof(tmp),
-					   _("Channel founder on <I>%s</I> is <I>%s</I>"),
-					   channel->channel_name, chu->client->nickname);
-
-			purple_conversation_write(convo, NULL, tmp,
-						PURPLE_MESSAGE_SYSTEM, time(NULL));
-
-		}
-	}
-	silc_hash_table_list_reset(&htl);
-
-	purple_conv_chat_add_users(PURPLE_CONV_CHAT(convo), users, NULL, flags, FALSE);
-	g_list_free(users);
-	g_list_free(flags);
-
-	/* Set topic */
-	if (channel->topic)
-		purple_conv_chat_set_topic(PURPLE_CONV_CHAT(convo), NULL, channel->topic);
-
-	/* Set nick */
-	purple_conv_chat_set_nick(PURPLE_CONV_CHAT(convo), conn->local_entry->nickname);
-}
-
 char *silcpurple_get_chat_name(GHashTable *data)
 {
 	return g_strdup(g_hash_table_lookup(data, "channel"));
-}	
+}
 
 void silcpurple_chat_join(PurpleConnection *gc, GHashTable *data)
 {
@@ -1073,6 +1013,9 @@
 	SilcClient client = sg->client;
 	SilcClientConnection conn = sg->conn;
 	const char *channel, *passphrase, *parentch;
+#if 0
+	PurpleChat *chat;
+#endif
 
 	if (!conn)
 		return;
@@ -1128,6 +1071,22 @@
 		return;
 	}
 
+#if 0
+	/* If the channel is not on buddy list, automatically add it there. */
+	chat = purple_blist_find_chat(sg->account, channel);
+	if (!chat) {
+		data = g_hash_table_new_full(g_str_hash, g_str_equal,
+					     g_free, g_free);
+		g_hash_table_replace(data, g_strdup("channel"),
+				     g_strdup(channel));
+		if (passphrase)
+		  g_hash_table_replace(data, g_strdup("passphrase"),
+				       g_strdup(passphrase));
+		chat = purple_chat_new(sg->account, NULL, data);
+		purple_blist_add_chat(chat, NULL, NULL);
+	}
+#endif
+
 	/* XXX We should have other properties here as well:
 	   1. whether to try to authenticate to the channel
 	     1a. with default key,
@@ -1150,7 +1109,7 @@
 }
 
 void silcpurple_chat_invite(PurpleConnection *gc, int id, const char *msg,
-			  const char *name)
+			    const char *name)
 {
 	SilcPurple sg = gc->proto_data;
 	SilcClient client = sg->client;
@@ -1264,7 +1223,8 @@
 		}
 }
 
-int silcpurple_chat_send(PurpleConnection *gc, int id, const char *msg, PurpleMessageFlags msgflags)
+int silcpurple_chat_send(PurpleConnection *gc, int id, const char *msg,
+			 PurpleMessageFlags msgflags)
 {
 	SilcPurple sg = gc->proto_data;
 	SilcClient client = sg->client;
@@ -1274,10 +1234,11 @@
 	SilcChannelEntry channel = NULL;
 	SilcChannelPrivateKey key = NULL;
 	SilcUInt32 flags;
-	int ret;
+	int ret = 0;
 	char *msg2, *tmp;
 	gboolean found = FALSE;
 	gboolean sign = purple_account_get_bool(sg->account, "sign-verify", FALSE);
+	SilcDList list;
 
 	if (!msg || !conn)
 		return 0;
@@ -1297,7 +1258,7 @@
 	} else if (strlen(msg) > 1 && msg[0] == '/') {
 		if (!silc_client_command_call(client, conn, msg + 1))
 			purple_notify_error(gc, _("Call Command"), _("Cannot call command"),
-							  _("Unknown command"));
+					    _("Unknown command"));
 		g_free(tmp);
 		return 0;
 	}
@@ -1346,10 +1307,35 @@
 		channel = chu->channel;
 	}
 
+	/* Check for images */
+	if (msgflags & PURPLE_MESSAGE_IMAGES) {
+		list = silcpurple_image_message(msg, &flags);
+		if (list) {
+			/* Send one or more MIME message.  If more than one, they
+			   are MIME fragments due to over large message */
+			SilcBuffer buf;
+
+			silc_dlist_start(list);
+			while ((buf = silc_dlist_get(list)) != SILC_LIST_END)
+				ret =
+			 	silc_client_send_channel_message(client, conn,
+								 channel, key,
+								 flags, NULL,
+								 buf->data,
+								 silc_buffer_len(buf));
+			silc_mime_partial_free(list);
+			g_free(tmp);
+
+			if (ret)
+				  serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), 0, msg, time(NULL));
+			return ret;
+		}
+	}
+
 	/* Send channel message */
 	ret = silc_client_send_channel_message(client, conn, channel, key,
-					       flags, (unsigned char *)msg2,
-					       strlen(msg2), TRUE);
+					       flags, NULL, (unsigned char *)msg2,
+					       strlen(msg2));
 	if (ret) {
 		serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), 0, msg,
 				 time(NULL));
--- a/libpurple/protocols/silc/ft.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/silc/ft.c	Tue Jun 12 21:21:37 2007 +0000
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 2004 Pekka Riikonen
+  Copyright (C) 2004 - 2007 Pekka Riikonen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
@@ -17,7 +17,7 @@
 
 */
 
-#include "silcincludes.h"
+#include "silc.h"
 #include "silcclient.h"
 #include "silcpurple.h"
 
@@ -74,11 +74,23 @@
 	char tmp[256];
 
 	if (status == SILC_CLIENT_FILE_MONITOR_CLOSED) {
+		/* All started sessions terminate here */
+		xfer->xfer->data = NULL;
 		purple_xfer_unref(xfer->xfer);
 		silc_free(xfer);
 		return;
 	}
 
+	if (status == SILC_CLIENT_FILE_MONITOR_DISCONNECT) {
+		purple_notify_error(gc, _("Secure File Transfer"),
+				    _("Error during file transfer"),
+				    _("Remote disconnected"));
+		xfer->xfer->status = PURPLE_XFER_STATUS_CANCEL_REMOTE;
+		purple_xfer_update_progress(xfer->xfer);
+		silc_client_file_close(client, conn, session_id);
+		return;
+	}
+
 	if (status == SILC_CLIENT_FILE_MONITOR_KEY_AGREEMENT)
 		return;
 
@@ -96,17 +108,22 @@
 			purple_notify_error(gc, _("Secure File Transfer"),
 					  _("Error during file transfer"),
 					  _("Key agreement failed"));
+		} else if (error == SILC_CLIENT_FILE_TIMEOUT) {
+			purple_notify_error(gc, _("Secure File Transfer"),
+					  _("Error during file transfer"),
+					  _("Connection timedout"));
+		} else if (error == SILC_CLIENT_FILE_CONNECT_FAILED) {
+			purple_notify_error(gc, _("Secure File Transfer"),
+					  _("Error during file transfer"),
+					  _("Creating connection failed"));
 		} else if (error == SILC_CLIENT_FILE_UNKNOWN_SESSION) {
 			purple_notify_error(gc, _("Secure File Transfer"),
 					  _("Error during file transfer"),
 					  _("File transfer session does not exist"));
-		} else {
-			purple_notify_error(gc, _("Secure File Transfer"),
-					  _("Error during file transfer"), NULL);
 		}
+		xfer->xfer->status = PURPLE_XFER_STATUS_CANCEL_REMOTE;
+		purple_xfer_update_progress(xfer->xfer);
 		silc_client_file_close(client, conn, session_id);
-		purple_xfer_unref(xfer->xfer);
-		silc_free(xfer);
 		return;
 	}
 
@@ -133,6 +150,10 @@
 silcpurple_ftp_cancel(PurpleXfer *x)
 {
 	SilcPurpleXfer xfer = x->data;
+
+	if (!xfer)
+		return;
+
 	xfer->xfer->status = PURPLE_XFER_STATUS_CANCEL_LOCAL;
 	purple_xfer_update_progress(xfer->xfer);
 	silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id);
@@ -143,6 +164,9 @@
 {
 	SilcPurpleXfer xfer = x->data;
 
+	if (!xfer)
+		return;
+
 	/* Cancel the transmission */
 	xfer->completion(NULL, xfer->completion_context);
 	silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id);
@@ -154,6 +178,9 @@
 	SilcPurpleXfer xfer = x->data;
 	const char *name;
 
+	if (!xfer)
+		return;
+
 	name = purple_xfer_get_local_filename(x);
 	g_unlink(name);
 	xfer->completion(name, xfer->completion_context);
@@ -187,17 +214,57 @@
 	SilcPurpleXfer xfer = x->data;
 	SilcClientFileError status;
 	PurpleConnection *gc = xfer->sg->gc;
+	SilcClientConnectionParams params;
+	gboolean local = xfer->hostname ? FALSE : TRUE;
+	char *local_ip = NULL, *remote_ip = NULL;
+	SilcSocket sock;
 
 	if (purple_xfer_get_status(x) != PURPLE_XFER_STATUS_ACCEPTED)
 		return;
+	if (!xfer)
+		return;
+
+	silc_socket_stream_get_info(silc_packet_stream_get_stream(xfer->sg->conn->stream),
+				    &sock, NULL, NULL, NULL);
+
+	if (local) {
+		/* Do the same magic what we do with key agreement (see silcpurple_buddy.c)
+		   to see if we are behind NAT. */
+		if (silc_net_check_local_by_sock(sock, NULL, &local_ip)) {
+			/* Check if the IP is private */
+			if (silcpurple_ip_is_private(local_ip)) {
+				local = TRUE;
+				/* Local IP is private, resolve the remote server IP to see whether
+				   we are talking to Internet or just on LAN. */
+				if (silc_net_check_host_by_sock(sock, NULL,
+								&remote_ip))
+				  if (silcpurple_ip_is_private(remote_ip))
+				    /* We assume we are in LAN.  Let's provide the connection point. */
+				    local = TRUE;
+			}
+		}
+
+		if (local && !local_ip)
+		  local_ip = silc_net_localip();
+	}
+
+	memset(&params, 0, sizeof(params));
+	params.timeout_secs = 60;
+	if (local)
+	  /* Provide connection point */
+	  params.local_ip = local_ip;
 
 	/* Start the file transfer */
 	status = silc_client_file_receive(xfer->sg->client, xfer->sg->conn,
+					  &params, xfer->sg->public_key,
+					  xfer->sg->private_key,
 					  silcpurple_ftp_monitor, xfer,
 					  NULL, xfer->session_id,
 					  silcpurple_ftp_ask_name, xfer);
 	switch (status) {
 	case SILC_CLIENT_FILE_OK:
+		silc_free(local_ip);
+		silc_free(remote_ip);
 		return;
 		break;
 
@@ -227,6 +294,8 @@
 	purple_xfer_unref(xfer->xfer);
 	g_free(xfer->hostname);
 	silc_free(xfer);
+	silc_free(local_ip);
+	silc_free(remote_ip);
 }
 
 static void
@@ -236,8 +305,8 @@
 }
 
 void silcpurple_ftp_request(SilcClient client, SilcClientConnection conn,
-			  SilcClientEntry client_entry, SilcUInt32 session_id,
-			  const char *hostname, SilcUInt16 port)
+			    SilcClientEntry client_entry, SilcUInt32 session_id,
+			    const char *hostname, SilcUInt16 port)
 {
 	PurpleConnection *gc = client->application;
 	SilcPurple sg = gc->proto_data;
@@ -255,7 +324,7 @@
 	xfer->hostname = g_strdup(hostname);
 	xfer->port = port;
 	xfer->xfer = purple_xfer_new(xfer->sg->account, PURPLE_XFER_RECEIVE,
-				   xfer->client_entry->nickname);
+				     xfer->client_entry->nickname);
 	if (!xfer->xfer) {
 		silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id);
 		g_free(xfer->hostname);
@@ -277,10 +346,12 @@
 silcpurple_ftp_send_cancel(PurpleXfer *x)
 {
 	SilcPurpleXfer xfer = x->data;
+
+	if (!xfer)
+		return;
+
+	/* This call will free all resources */
 	silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id);
-	purple_xfer_unref(xfer->xfer);
-	g_free(xfer->hostname);
-	silc_free(xfer);
 }
 
 static void
@@ -290,19 +361,26 @@
 	const char *name;
 	char *local_ip = NULL, *remote_ip = NULL;
 	gboolean local = TRUE;
+	SilcClientConnectionParams params;
+	SilcSocket sock;
+
+	if (!xfer)
+		return;
 
 	name = purple_xfer_get_local_filename(x);
 
+	silc_socket_stream_get_info(silc_packet_stream_get_stream(xfer->sg->conn->stream),
+				    &sock, NULL, NULL, NULL);
+
 	/* Do the same magic what we do with key agreement (see silcpurple_buddy.c)
 	   to see if we are behind NAT. */
-	if (silc_net_check_local_by_sock(xfer->sg->conn->sock->sock,
-					 NULL, &local_ip)) {
+	if (silc_net_check_local_by_sock(sock, NULL, &local_ip)) {
 		/* Check if the IP is private */
 		if (silcpurple_ip_is_private(local_ip)) {
 			local = FALSE;
 			/* Local IP is private, resolve the remote server IP to see whether
 			   we are talking to Internet or just on LAN. */
-			if (silc_net_check_host_by_sock(xfer->sg->conn->sock->sock, NULL,
+			if (silc_net_check_host_by_sock(sock, NULL,
 							&remote_ip))
 				if (silcpurple_ip_is_private(remote_ip))
 					/* We assume we are in LAN.  Let's provide the connection point. */
@@ -313,10 +391,17 @@
 	if (local && !local_ip)
 		local_ip = silc_net_localip();
 
+	memset(&params, 0, sizeof(params));
+	params.timeout_secs = 60;
+	if (local)
+	  /* Provide connection point */
+	  params.local_ip = local_ip;
+
 	/* Send the file */
 	silc_client_file_send(xfer->sg->client, xfer->sg->conn,
+			      xfer->client_entry, &params,
+			      xfer->sg->public_key, xfer->sg->private_key,
 			      silcpurple_ftp_monitor, xfer,
-			      local_ip, 0, !local, xfer->client_entry,
 			      name, &xfer->session_id);
 
 	silc_free(local_ip);
@@ -325,10 +410,10 @@
 
 static void
 silcpurple_ftp_send_file_resolved(SilcClient client,
-				SilcClientConnection conn,
-				SilcClientEntry *clients,
-				SilcUInt32 clients_count,
-				void *context)
+				  SilcClientConnection conn,
+				  SilcStatus status,
+				  SilcDList clients,
+				  void *context)
 {
 	PurpleConnection *gc = client->application;
 	char tmp[256];
@@ -352,38 +437,29 @@
 	SilcPurple sg = gc->proto_data;
 	SilcClient client = sg->client;
 	SilcClientConnection conn = sg->conn;
-	SilcClientEntry *clients;
-	SilcUInt32 clients_count;
+	SilcDList clients;
 	SilcPurpleXfer xfer;
-	char *nickname;
 
 	g_return_val_if_fail(name != NULL, NULL);
 
-	if (!silc_parse_userfqdn(name, &nickname, NULL))
-		return NULL;
-
 	/* Find client entry */
-	clients = silc_client_get_clients_local(client, conn, nickname, name,
-											&clients_count);
+	clients = silc_client_get_clients_local(client, conn, name, FALSE);
 	if (!clients) {
-		silc_client_get_clients(client, conn, nickname, NULL,
-								silcpurple_ftp_send_file_resolved,
-								strdup(name));
-		silc_free(nickname);
+		silc_client_get_clients(client, conn, name, NULL,
+					silcpurple_ftp_send_file_resolved,
+					strdup(name));
 		return NULL;
 	}
+	silc_dlist_start(clients);
 
 	xfer = silc_calloc(1, sizeof(*xfer));
-
 	g_return_val_if_fail(xfer != NULL, NULL);
 
 	xfer->sg = sg;
-	xfer->client_entry = clients[0];
+	xfer->client_entry = silc_dlist_get(clients);
 	xfer->xfer = purple_xfer_new(xfer->sg->account, PURPLE_XFER_SEND,
-							   xfer->client_entry->nickname);
+				     xfer->client_entry->nickname);
 	if (!xfer->xfer) {
-		silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id);
-		g_free(xfer->hostname);
 		silc_free(xfer);
 		return NULL;
 	}
@@ -393,7 +469,6 @@
 	xfer->xfer->data = xfer;
 
 	silc_free(clients);
-	silc_free(nickname);
 
 	return xfer->xfer;
 }
--- a/libpurple/protocols/silc/ops.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/silc/ops.c	Tue Jun 12 21:21:37 2007 +0000
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 2004 Pekka Riikonen
+  Copyright (C) 2004 - 2007 Pekka Riikonen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
@@ -17,7 +17,7 @@
 
 */
 
-#include "silcincludes.h"
+#include "silc.h"
 #include "silcclient.h"
 #include "silcpurple.h"
 #include "imgstore.h"
@@ -26,14 +26,18 @@
 static void
 silc_channel_message(SilcClient client, SilcClientConnection conn,
 		     SilcClientEntry sender, SilcChannelEntry channel,
-		     SilcMessagePayload payload, SilcChannelPrivateKey key,
-		     SilcMessageFlags flags, const unsigned char *message,
+		     SilcMessagePayload payload,
+		     SilcChannelPrivateKey key, SilcMessageFlags flags,
+		     const unsigned char *message,
 		     SilcUInt32 message_len);
 static void
 silc_private_message(SilcClient client, SilcClientConnection conn,
 		     SilcClientEntry sender, SilcMessagePayload payload,
 		     SilcMessageFlags flags, const unsigned char *message,
 		     SilcUInt32 message_len);
+static void
+silc_ask_passphrase(SilcClient client, SilcClientConnection conn,
+		    SilcAskPassphrase completion, void *context);
 
 /* Message sent to the application by library. `conn' associates the
    message to a specific connection.  `conn', however, may be NULL.
@@ -41,23 +45,31 @@
    The application can for example filter the message according the
    type. */
 
-static void
-silc_say(SilcClient client, SilcClientConnection conn,
-	 SilcClientMessageType type, char *msg, ...)
+void silc_say(SilcClient client, SilcClientConnection conn,
+	      SilcClientMessageType type, char *msg, ...)
 {
-	/* Nothing */
+	if (type == SILC_CLIENT_MESSAGE_ERROR) {
+		char tmp[256];
+		va_list va;
+
+		va_start(va, msg);
+		silc_vsnprintf(tmp, sizeof(tmp), msg, va);
+		purple_notify_error(NULL, _("Error"), _("Error occurred"), tmp);
+
+		va_end(va);
+		return;
+	}
 }
 
-#ifdef HAVE_SILCMIME_H
 /* Processes incoming MIME message.  Can be private message or channel
-   message. */
+   message.  Returns TRUE if the message `mime' was displayed. */
 
-static void
+static SilcBool
 silcpurple_mime_message(SilcClient client, SilcClientConnection conn,
-		      SilcClientEntry sender, SilcChannelEntry channel,
-		      SilcMessagePayload payload, SilcChannelPrivateKey key,
-		      SilcMessageFlags flags, SilcMime mime,
-		      gboolean recursive)
+			SilcClientEntry sender, SilcChannelEntry channel,
+			SilcMessagePayload payload, SilcChannelPrivateKey key,
+			SilcMessageFlags flags, SilcMime mime,
+			gboolean recursive)
 {
 	PurpleConnection *gc = client->application;
 	SilcPurple sg = gc->proto_data;
@@ -66,9 +78,10 @@
 	SilcUInt32 data_len;
 	PurpleMessageFlags cflags = 0;
 	PurpleConversation *convo = NULL;
+	SilcBool ret = FALSE;
 
 	if (!mime)
-		return;
+		return FALSE;
 
 	/* Check for fragmented MIME message */
 	if (silc_mime_is_partial(mime)) {
@@ -79,12 +92,12 @@
 		mime = silc_mime_assemble(sg->mimeass, mime);
 		if (!mime)
 			/* More fragments to come */
-			return;
+			return FALSE;
 
 		/* Process the complete message */
-		silcpurple_mime_message(client, conn, sender, channel,
-				      payload, key, flags, mime, FALSE);
-		return;
+		return silcpurple_mime_message(client, conn, sender, channel,
+					       payload, key, flags, mime,
+					       FALSE);
 	}
 
 	/* Check for multipart message */
@@ -92,17 +105,33 @@
 		SilcMime p;
 		const char *mtype;
 		SilcDList parts = silc_mime_get_multiparts(mime, &mtype);
+		SilcBool ret;
 
-		/* Only "mixed" type supported */
-		if (strcmp(mtype, "mixed"))
-			goto out;
+		if (!strcmp(mtype, "mixed")) {
+			/* Contains multiple messages */
+			silc_dlist_start(parts);
+			while ((p = silc_dlist_get(parts)) != SILC_LIST_END) {
+			  /* Recursively process parts */
+			  ret = silcpurple_mime_message(client, conn, sender, channel,
+							payload, key, flags, p, TRUE);
+			}
+		}
 
-		silc_dlist_start(parts);
-		while ((p = silc_dlist_get(parts)) != SILC_LIST_END) {
-			/* Recursively process parts */
-			silcpurple_mime_message(client, conn, sender, channel,
-					      payload, key, flags, p, TRUE);
+		if (!strcmp(mtype, "alternative")) {
+			/* Same message in alternative formats.  Kopete sends
+			   these.  Go in order from last to first. */
+			silc_dlist_end(parts);
+			while ((p = silc_dlist_get(parts)) != SILC_LIST_END) {
+			  /* Go through the alternatives and display the first
+			     one we support. */
+			  if (silcpurple_mime_message(client, conn, sender, channel,
+						      payload, key, flags, p, TRUE)) {
+			    ret = TRUE;
+			    break;
+			  }
+			}
 		}
+
 		goto out;
 	}
 
@@ -124,13 +153,14 @@
 
 		if (channel)
 			silc_channel_message(client, conn, sender, channel,
-					     payload, key, 
+					     payload, key,
 					     SILC_MESSAGE_FLAG_UTF8, data,
 					     data_len);
 		else
 			silc_private_message(client, conn, sender, payload,
 					     SILC_MESSAGE_FLAG_UTF8, data,
 					     data_len);
+		ret = TRUE;
 		goto out;
 	}
 
@@ -157,7 +187,7 @@
 		}
 		if (channel && !convo)
 			convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
-								    channel->channel_name, sg->account);
+								      channel->channel_name, sg->account);
 		if (channel && !convo)
 			goto out;
 
@@ -165,11 +195,11 @@
 		if (imgid) {
 			cflags |= PURPLE_MESSAGE_IMAGES | PURPLE_MESSAGE_RECV;
 			g_snprintf(tmp, sizeof(tmp), "<IMG ID=\"%d\">", imgid);
-		  
+
 			if (channel)
 				serv_got_chat_in(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(convo)),
 				 		 sender->nickname ?
-				 		  sender->nickname : 
+				 		  sender->nickname :
 						 "<unknown>", cflags,
 						 tmp, time(NULL));
 			else
@@ -179,6 +209,7 @@
 
 			purple_imgstore_unref_by_id(imgid);
 			cflags = 0;
+			ret = TRUE;
 		}
 		goto out;
 	}
@@ -191,15 +222,16 @@
 					       payload, flags, data, data_len);
 		else
 			silcpurple_wb_receive(client, conn, sender, payload,
-					    flags, data, data_len);
+					      flags, data, data_len);
+		ret = TRUE;
 		goto out;
 	}
 
  out:
 	if (!recursive)
 		silc_mime_free(mime);
+	return ret;
 }
-#endif /* HAVE_SILCMIME_H */
 
 /* Message for a channel. The `sender' is the sender of the message
    The `channel' is the channel. The `message' is the message.  Note
@@ -210,8 +242,9 @@
 static void
 silc_channel_message(SilcClient client, SilcClientConnection conn,
 		     SilcClientEntry sender, SilcChannelEntry channel,
-		     SilcMessagePayload payload, SilcChannelPrivateKey key,
-		     SilcMessageFlags flags, const unsigned char *message,
+		     SilcMessagePayload payload,
+		     SilcChannelPrivateKey key, SilcMessageFlags flags,
+		     const unsigned char *message,
 		     SilcUInt32 message_len)
 {
 	PurpleConnection *gc = client->application;
@@ -236,7 +269,7 @@
 	}
 	if (!convo)
 		convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
-								channel->channel_name, sg->account);
+							      channel->channel_name, sg->account);
 	if (!convo)
 		return;
 
@@ -247,30 +280,10 @@
 
 	if (flags & SILC_MESSAGE_FLAG_DATA) {
 		/* Process MIME message */
-#ifdef HAVE_SILCMIME_H
 		SilcMime mime;
-		mime = silc_mime_decode(message, message_len);
+		mime = silc_mime_decode(NULL, message, message_len);
 		silcpurple_mime_message(client, conn, sender, channel, payload,
-				      key, flags, mime, FALSE);
-#else
-		char type[128], enc[128];
-		unsigned char *data;
-		SilcUInt32 data_len;
-
-		memset(type, 0, sizeof(type));
-		memset(enc, 0, sizeof(enc));
-
-		if (!silc_mime_parse(message, message_len, NULL, 0,
-		    type, sizeof(type) - 1, enc, sizeof(enc) - 1, &data,
-		    &data_len))
-			return;
-
-		if (!strcmp(type, "application/x-wb") &&
-		    !strcmp(enc, "binary") &&
-		    !purple_account_get_bool(sg->account, "block-wb", FALSE))
-			silcpurple_wb_receive_ch(client, conn, sender, channel,
-					       payload, flags, data, data_len);
-#endif
+					key, flags, mime, FALSE);
 		return;
 	}
 
@@ -283,9 +296,7 @@
 		tmp = g_markup_escape_text(msg, -1);
 		/* Send to Purple */
 		serv_got_chat_in(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(convo)),
-				 sender->nickname ?
-				 sender->nickname : "<unknown>", 0,
-				 tmp, time(NULL));
+				 sender->nickname, 0, tmp, time(NULL));
 		g_free(tmp);
 		g_free(msg);
 		return;
@@ -293,9 +304,7 @@
 
 	if (flags & SILC_MESSAGE_FLAG_NOTICE) {
 		msg = g_strdup_printf("(notice) <I>%s</I> %s",
-				      sender->nickname ?
-				      sender->nickname : "<unknown>",
-				      (const char *)message);
+				      sender->nickname, (const char *)message);
 		if (!msg)
 			return;
 
@@ -310,9 +319,7 @@
 		tmp = g_markup_escape_text((const char *)message, -1);
 		/* Send to Purple */
 		serv_got_chat_in(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(convo)),
-				 sender->nickname ?
-				 sender->nickname : "<unknown>", 0,
-				 tmp, time(NULL));
+				 sender->nickname, 0, tmp, time(NULL));
 		g_free(tmp);
 	}
 }
@@ -341,7 +348,7 @@
 	if (sender->nickname)
 		/* XXX - Should this be PURPLE_CONV_TYPE_IM? */
 		convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY,
-								sender->nickname, sg->account);
+							      sender->nickname, sg->account);
 
 	if (flags & SILC_MESSAGE_FLAG_SIGNED &&
 	    purple_account_get_bool(sg->account, "sign-verify", FALSE)) {
@@ -349,31 +356,11 @@
 	}
 
 	if (flags & SILC_MESSAGE_FLAG_DATA) {
-#ifdef HAVE_SILCMIME_H
 		/* Process MIME message */
 		SilcMime mime;
-		mime = silc_mime_decode(message, message_len);
+		mime = silc_mime_decode(NULL, message, message_len);
 		silcpurple_mime_message(client, conn, sender, NULL, payload,
 				      NULL, flags, mime, FALSE);
-#else
-		char type[128], enc[128];
-		unsigned char *data;
-		SilcUInt32 data_len;
-
-		memset(type, 0, sizeof(type));
-		memset(enc, 0, sizeof(enc));
-
-		if (!silc_mime_parse(message, message_len, NULL, 0,
-		    type, sizeof(type) - 1, enc, sizeof(enc) - 1, &data, 
-		    &data_len))
-			return;
-
-		if (!strcmp(type, "application/x-wb") &&
-		    !strcmp(enc, "binary") &&
-		    !purple_account_get_bool(sg->account, "block-wb", FALSE))
-			silcpurple_wb_receive(client, conn, sender, payload,
-					    flags, data, data_len);
-#endif
 		return;
 	}
 
@@ -383,11 +370,9 @@
 		if (!msg)
 			return;
 
+		/* Send to Purple */
 		tmp = g_markup_escape_text(msg, -1);
-		/* Send to Purple */
-		serv_got_im(gc, sender->nickname ?
-			    sender->nickname : "<unknown>",
-			    tmp, 0, time(NULL));
+		serv_got_im(gc, sender->nickname, tmp, 0, time(NULL));
 		g_free(msg);
 		g_free(tmp);
 		return;
@@ -395,15 +380,13 @@
 
 	if (flags & SILC_MESSAGE_FLAG_NOTICE && convo) {
 		msg = g_strdup_printf("(notice) <I>%s</I> %s",
-				      sender->nickname ?
-				      sender->nickname : "<unknown>",
-				      (const char *)message);
+				      sender->nickname, (const char *)message);
 		if (!msg)
 			return;
 
 		/* Send to Purple */
 		purple_conversation_write(convo, NULL, (const char *)msg,
-					PURPLE_MESSAGE_SYSTEM, time(NULL));
+					  PURPLE_MESSAGE_SYSTEM, time(NULL));
 		g_free(msg);
 		return;
 	}
@@ -411,9 +394,7 @@
 	if (flags & SILC_MESSAGE_FLAG_UTF8) {
 		tmp = g_markup_escape_text((const char *)message, -1);
 		/* Send to Purple */
-		serv_got_im(gc, sender->nickname ?
-			    sender->nickname : "<unknown>",
-			    tmp, 0, time(NULL));
+		serv_got_im(gc, sender->nickname, tmp, 0, time(NULL));
 		g_free(tmp);
 	}
 }
@@ -447,6 +428,7 @@
 	char buf[512], buf2[512], *tmp, *name;
 	SilcNotifyType notify;
 	PurpleBuddy *b;
+	SilcDList list;
 	int i;
 
 	va_start(va, type);
@@ -460,7 +442,7 @@
 	case SILC_NOTIFY_TYPE_INVITE:
 		{
 			GHashTable *components;
-			va_arg(va, SilcChannelEntry);
+			(void)va_arg(va, SilcChannelEntry);
 			name = va_arg(va, char *);
 			client_entry = va_arg(va, SilcClientEntry);
 
@@ -479,7 +461,7 @@
 			break;
 
 		convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
-								channel->channel_name, sg->account);
+							      channel->channel_name, sg->account);
 		if (!convo)
 			break;
 
@@ -487,7 +469,7 @@
 		g_snprintf(buf, sizeof(buf), "%s@%s",
 			   client_entry->username, client_entry->hostname);
 		purple_conv_chat_add_user(PURPLE_CONV_CHAT(convo),
-					g_strdup(client_entry->nickname), buf, PURPLE_CBFLAGS_NONE, TRUE);
+					  g_strdup(client_entry->nickname), buf, PURPLE_CBFLAGS_NONE, TRUE);
 
 		break;
 
@@ -496,13 +478,13 @@
 		channel = va_arg(va, SilcChannelEntry);
 
 		convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
-								channel->channel_name, sg->account);
+							      channel->channel_name, sg->account);
 		if (!convo)
 			break;
 
 		/* Remove user from channel */
 		purple_conv_chat_remove_user(PURPLE_CONV_CHAT(convo),
-					   client_entry->nickname, NULL);
+					     client_entry->nickname, NULL);
 
 		break;
 
@@ -510,19 +492,16 @@
 		client_entry = va_arg(va, SilcClientEntry);
 		tmp = va_arg(va, char *);
 
-		if (!client_entry->nickname)
-			break;
-
 		/* Remove from all channels */
 		silc_hash_table_list(client_entry->channels, &htl);
 		while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
 			convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
-									chu->channel->channel_name, sg->account);
+								      chu->channel->channel_name, sg->account);
 			if (!convo)
 				continue;
 			purple_conv_chat_remove_user(PURPLE_CONV_CHAT(convo),
-						   client_entry->nickname,
-						   tmp);
+						     client_entry->nickname,
+						     tmp);
 		}
 		silc_hash_table_list_reset(&htl);
 
@@ -537,7 +516,7 @@
 			channel = va_arg(va, SilcChannelEntry);
 
 			convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
-									channel->channel_name, sg->account);
+								      channel->channel_name, sg->account);
 			if (!convo)
 				break;
 
@@ -586,22 +565,22 @@
 		}
 	case SILC_NOTIFY_TYPE_NICK_CHANGE:
 		client_entry = va_arg(va, SilcClientEntry);
-		client_entry2 = va_arg(va, SilcClientEntry);
+		tmp = va_arg(va, char *);      /* Old nick */
+		name = va_arg(va, char *);     /* New nick */
 
-		if (!strcmp(client_entry->nickname, client_entry2->nickname))
+		if (!strcmp(tmp, name))
 			break;
 
 		/* Change nick on all channels */
-		silc_hash_table_list(client_entry2->channels, &htl);
+		silc_hash_table_list(client_entry->channels, &htl);
 		while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
 			convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
-									chu->channel->channel_name, sg->account);
+								      chu->channel->channel_name, sg->account);
 			if (!convo)
 				continue;
 			if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(convo), client_entry->nickname))
 				purple_conv_chat_rename_user(PURPLE_CONV_CHAT(convo),
-										   client_entry->nickname,
-										   client_entry2->nickname);
+							     tmp, name);
 		}
 		silc_hash_table_list_reset(&htl);
 
@@ -615,11 +594,11 @@
 		(void)va_arg(va, char *);
 		(void)va_arg(va, char *);
 		(void)va_arg(va, SilcPublicKey);
-		(void)va_arg(va, SilcBuffer);
+		(void)va_arg(va, SilcDList);
 		channel = va_arg(va, SilcChannelEntry);
 
 		convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
-								channel->channel_name, sg->account);
+							      channel->channel_name, sg->account);
 		if (!convo)
 			break;
 
@@ -643,7 +622,7 @@
 				   channel->channel_name);
 		}
 		purple_conv_chat_write(PURPLE_CONV_CHAT(convo), channel->channel_name,
-				     buf, PURPLE_MESSAGE_SYSTEM, time(NULL));
+				       buf, PURPLE_MESSAGE_SYSTEM, time(NULL));
 		break;
 
 	case SILC_NOTIFY_TYPE_CUMODE_CHANGE:
@@ -656,7 +635,7 @@
 			channel = va_arg(va, SilcChannelEntry);
 
 			convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
-									channel->channel_name, sg->account);
+								      channel->channel_name, sg->account);
 			if (!convo)
 				break;
 
@@ -672,19 +651,19 @@
 			if (mode) {
 				silcpurple_get_chumode_string(mode, buf2, sizeof(buf2));
 				g_snprintf(buf, sizeof(buf),
-						_("<I>%s</I> set <I>%s's</I> modes to: %s"), name,
-						client_entry2->nickname, buf2);
+					   _("<I>%s</I> set <I>%s's</I> modes to: %s"), name,
+					   client_entry2->nickname, buf2);
 				if (mode & SILC_CHANNEL_UMODE_CHANFO)
 					flags |= PURPLE_CBFLAGS_FOUNDER;
 				if (mode & SILC_CHANNEL_UMODE_CHANOP)
 					flags |= PURPLE_CBFLAGS_OP;
 			} else {
 				g_snprintf(buf, sizeof(buf),
-						_("<I>%s</I> removed all <I>%s's</I> modes"), name,
-						client_entry2->nickname);
+					   _("<I>%s</I> removed all <I>%s's</I> modes"), name,
+					   client_entry2->nickname);
 			}
 			purple_conv_chat_write(PURPLE_CONV_CHAT(convo), channel->channel_name,
-					buf, PURPLE_MESSAGE_SYSTEM, time(NULL));
+					       buf, PURPLE_MESSAGE_SYSTEM, time(NULL));
 			purple_conv_chat_user_set_flags(PURPLE_CONV_CHAT(convo), client_entry2->nickname, flags);
 			break;
 		}
@@ -702,7 +681,7 @@
 		channel = va_arg(va, SilcChannelEntry);
 
 		convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
-								channel->channel_name, sg->account);
+							      channel->channel_name, sg->account);
 		if (!convo)
 			break;
 
@@ -713,15 +692,15 @@
 				   channel->channel_name, client_entry2->nickname,
 				   tmp ? tmp : "");
 			purple_conv_chat_write(PURPLE_CONV_CHAT(convo), client_entry->nickname,
-					     buf, PURPLE_MESSAGE_SYSTEM, time(NULL));
+					       buf, PURPLE_MESSAGE_SYSTEM, time(NULL));
 			serv_got_chat_left(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(convo)));
 		} else {
 			/* Remove user from channel */
 			g_snprintf(buf, sizeof(buf), _("Kicked by %s (%s)"),
 				   client_entry2->nickname, tmp ? tmp : "");
 			purple_conv_chat_remove_user(PURPLE_CONV_CHAT(convo),
-						   client_entry->nickname,
-						   buf);
+						     client_entry->nickname,
+						     buf);
 		}
 
 		break;
@@ -732,9 +711,6 @@
 		idtype = va_arg(va, int);
 		entry = va_arg(va, SilcClientEntry);
 
-		if (!client_entry->nickname)
-			break;
-
 		if (client_entry == conn->local_entry) {
 			if (idtype == SILC_ID_CLIENT) {
 				client_entry2 = (SilcClientEntry)entry;
@@ -761,7 +737,7 @@
 				if (!convo)
 					continue;
 				purple_conv_chat_write(PURPLE_CONV_CHAT(convo), client_entry->nickname,
-						     buf, PURPLE_MESSAGE_SYSTEM, time(NULL));
+						       buf, PURPLE_MESSAGE_SYSTEM, time(NULL));
 				serv_got_chat_left(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(convo)));
 			}
 			silc_hash_table_list_reset(&htl);
@@ -792,7 +768,7 @@
 				if (!convo)
 					continue;
 				purple_conv_chat_remove_user(PURPLE_CONV_CHAT(convo),
-							   client_entry->nickname, tmp);
+							     client_entry->nickname, tmp);
 			}
 			silc_hash_table_list_reset(&htl);
 		}
@@ -803,33 +779,23 @@
 		break;
 
 	case SILC_NOTIFY_TYPE_SERVER_SIGNOFF:
-		{
-			int i;
-			SilcClientEntry *clients;
-			SilcUInt32 clients_count;
-
-			(void)va_arg(va, void *);
-			clients = va_arg(va, SilcClientEntry *);
-			clients_count = va_arg(va, SilcUInt32);
-
-			for (i = 0; i < clients_count; i++) {
-				if (!clients[i]->nickname)
-					break;
+		(void)va_arg(va, void *);
+		list = va_arg(va, SilcDList);
 
-				/* Remove from all channels */
-				silc_hash_table_list(clients[i]->channels, &htl);
-				while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
-					convo =
-						purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
-											chu->channel->channel_name, sg->account);
-					if (!convo)
-						continue;
-					purple_conv_chat_remove_user(PURPLE_CONV_CHAT(convo),
-								   clients[i]->nickname,
-								   _("Server signoff"));
-				}
-				silc_hash_table_list_reset(&htl);
+		silc_dlist_start(list);
+		while ((client_entry = silc_dlist_get(list))) {
+			/* Remove from all channels */
+			silc_hash_table_list(client_entry->channels, &htl);
+			while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+				convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+									      chu->channel->channel_name, sg->account);
+				if (!convo)
+					continue;
+				purple_conv_chat_remove_user(PURPLE_CONV_CHAT(convo),
+							     client_entry->nickname,
+							     _("Server signoff"));
 			}
+			silc_hash_table_list_reset(&htl);
 		}
 		break;
 
@@ -837,8 +803,8 @@
 		{
 			SilcStatus error = va_arg(va, int);
 			purple_notify_error(gc, "Error Notify",
-					  silc_get_status_message(error),
-					  NULL);
+					    silc_get_status_message(error),
+					    NULL);
 		}
 		break;
 
@@ -909,8 +875,8 @@
 			}
 
 			silc_free(b->proto_data);
-			b->proto_data = silc_memdup(client_entry->id,
-						    sizeof(*client_entry->id));
+			b->proto_data = silc_memdup(&client_entry->id,
+						    sizeof(client_entry->id));
 			if (notify == SILC_NOTIFY_TYPE_NICK_CHANGE) {
 				break;
 			} else if (notify == SILC_NOTIFY_TYPE_UMODE_CHANGE) {
@@ -955,19 +921,20 @@
 }
 
 
-/* Command handler. This function is called always in the command function.
-   If error occurs it will be called as well. `conn' is the associated
-   client connection. `cmd_context' is the command context that was
-   originally sent to the command. `success' is FALSE if error occurred
-   during command. `command' is the command being processed. It must be
-   noted that this is not reply from server. This is merely called just
-   after application has called the command. Just to tell application
-   that the command really was processed. */
+/* Command handler. This function is called always after application has
+   called a command.  It will be called to indicate that the command
+   was processed.  It will also be called if error occurs while processing
+   the command.  The `success' indicates whether the command was sent
+   or if error occurred.  The `status' indicates the actual error.
+   The `argc' and `argv' are the command line arguments sent to the
+   command by application.  Note that, this is not reply to the command
+   from server, this is merely and indication to application that the
+   command was processed. */
 
 static void
 silc_command(SilcClient client, SilcClientConnection conn,
-	     SilcClientCommandContext cmd_context, bool success,
-	     SilcCommand command, SilcStatus status)
+	     SilcBool success, SilcCommand command, SilcStatus status,
+	     SilcUInt32 argc, unsigned char **argv)
 {
 	PurpleConnection *gc = client->application;
 	SilcPurple sg = gc->proto_data;
@@ -975,8 +942,7 @@
 	switch (command) {
 
 	case SILC_COMMAND_CMODE:
-		if (cmd_context->argc == 3 &&
-		    !strcmp((char *)cmd_context->argv[2], "+C"))
+		if (argc == 3 && !strcmp((char *)argv[2], "+C"))
 			sg->chpk = TRUE;
 		else
 			sg->chpk = FALSE;
@@ -1090,53 +1056,96 @@
 }
 #endif
 
-/* Command reply handler. This function is called always in the command reply
-   function. If error occurs it will be called as well. Normal scenario
-   is that it will be called after the received command data has been parsed
-   and processed. The function is used to pass the received command data to
-   the application.
 
-   `conn' is the associated client connection. `cmd_payload' is the command
-   payload data received from server and it can be ignored. It is provided
-   if the application would like to re-parse the received command data,
-   however, it must be noted that the data is parsed already by the library
-   thus the payload can be ignored. `success' is FALSE if error occurred.
-   In this case arguments are not sent to the application. The `status' is
-   the command reply status server returned. The `command' is the command
-   reply being processed. The function has variable argument list and each
-   command defines the number and type of arguments it passes to the
-   application (on error they are not sent). */
+/* Command reply handler.  Delivers a reply to command that was sent
+   earlier.  The `conn' is the associated client connection.  The `command'
+   indicates the command reply type.  If the `status' other than
+   SILC_STATUS_OK an error occurred.  In this case the `error' will indicate
+   the error.  It is possible to receive list of command replies and list
+   of errors.  In this case the `status' will indicate it is an list entry
+   (the `status' is SILC_STATUS_LIST_START, SILC_STATUS_LIST_ITEM and/or
+   SILC_STATUS_LIST_END).
+
+   The arguments received in `ap' are command specific.  See a separate
+   documentation in the Toolkit Reference Manual for the command reply
+   arguments. */
 
 static void
 silc_command_reply(SilcClient client, SilcClientConnection conn,
-		   SilcCommandPayload cmd_payload, bool success,
-		   SilcCommand command, SilcStatus status, ...)
+		   SilcCommand command, SilcStatus status,
+		   SilcStatus error, va_list ap)
 {
 	PurpleConnection *gc = client->application;
 	SilcPurple sg = gc->proto_data;
 	PurpleConversation *convo;
-	va_list vp;
-
-	va_start(vp, status);
 
 	switch (command) {
 	case SILC_COMMAND_JOIN:
 		{
-			SilcChannelEntry channel_entry;
+			SilcChannelEntry channel;
+			PurpleConversation *convo;
+			SilcHashTableList *user_list;
+			SilcChannelUser chu;
+			GList *users = NULL, *flags = NULL;
+			char tmp[256], *topic;
 
-			if (!success) {
+			if (status != SILC_STATUS_OK) {
 				purple_notify_error(gc, _("Join Chat"), _("Cannot join channel"),
-						  silc_get_status_message(status));
+						    silc_get_status_message(error));
 				return;
 			}
 
-			(void)va_arg(vp, char *);
-			channel_entry = va_arg(vp, SilcChannelEntry);
+			(void)va_arg(ap, char *);
+			channel = va_arg(ap, SilcChannelEntry);
+			(void)va_arg(ap, SilcUInt32);
+			user_list = va_arg(ap, SilcHashTableList *);
+			topic = va_arg(ap, char *);
+
+			/* Add channel to Purple */
+			channel->context = SILC_32_TO_PTR(++sg->channel_ids);
+			serv_got_joined_chat(gc, sg->channel_ids, channel->channel_name);
+			convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+								      channel->channel_name, sg->account);
+			if (!convo)
+			  return;
+
+			/* Add all users to channel */
+			while (silc_hash_table_get(user_list, NULL, (void *)&chu)) {
+			  PurpleConvChatBuddyFlags f = PURPLE_CBFLAGS_NONE;
+			  chu->context = SILC_32_TO_PTR(sg->channel_ids);
+
+			  if (chu->mode & SILC_CHANNEL_UMODE_CHANFO)
+			    f |= PURPLE_CBFLAGS_FOUNDER;
+			  if (chu->mode & SILC_CHANNEL_UMODE_CHANOP)
+			    f |= PURPLE_CBFLAGS_OP;
+			  users = g_list_append(users, g_strdup(chu->client->nickname));
+			  flags = g_list_append(flags, GINT_TO_POINTER(f));
 
-			/* Resolve users on channel */
-			silc_client_get_clients_by_channel(client, conn, channel_entry,
-							   silcpurple_chat_join_done,
-							   channel_entry);
+			  if (chu->mode & SILC_CHANNEL_UMODE_CHANFO) {
+			    if (chu->client == conn->local_entry)
+				g_snprintf(tmp, sizeof(tmp),
+					   _("You are channel founder on <I>%s</I>"),
+					   channel->channel_name);
+			    else
+				g_snprintf(tmp, sizeof(tmp),
+					   _("Channel founder on <I>%s</I> is <I>%s</I>"),
+					   channel->channel_name, chu->client->nickname);
+
+			    purple_conversation_write(convo, NULL, tmp,
+						      PURPLE_MESSAGE_SYSTEM, time(NULL));
+			  }
+			}
+
+			purple_conv_chat_add_users(PURPLE_CONV_CHAT(convo), users, NULL, flags, FALSE);
+			g_list_free(users);
+			g_list_free(flags);
+
+			/* Set topic */
+			if (topic)
+			  purple_conv_chat_set_topic(PURPLE_CONV_CHAT(convo), NULL, topic);
+
+			/* Set nick */
+			purple_conv_chat_set_nick(PURPLE_CONV_CHAT(convo), conn->local_entry->nickname);
 		}
 		break;
 
@@ -1148,31 +1157,29 @@
 
 	case SILC_COMMAND_WHOIS:
 		{
-			SilcUInt32 idle, mode;
-			SilcBuffer channels, user_modes;
+			SilcUInt32 idle, *user_modes;
+			SilcDList channels;
 			SilcClientEntry client_entry;
 			char tmp[1024], *tmp2;
 			char *moodstr, *statusstr, *contactstr, *langstr, *devicestr, *tzstr, *geostr;
 			PurpleNotifyUserInfo *user_info;
 
-			if (!success) {
+			if (status != SILC_STATUS_OK) {
 				purple_notify_error(gc, _("User Information"),
 						_("Cannot get user information"),
-						silc_get_status_message(status));
+						silc_get_status_message(error));
 				break;
 			}
 
-			client_entry = va_arg(vp, SilcClientEntry);
-			if (!client_entry->nickname)
-				break;
-			(void)va_arg(vp, char *);
-			(void)va_arg(vp, char *);
-			(void)va_arg(vp, char *);
-			channels = va_arg(vp, SilcBuffer);
-			mode = va_arg(vp, SilcUInt32);
-			idle = va_arg(vp, SilcUInt32);
-			(void)va_arg(vp, unsigned char *);
-			user_modes = va_arg(vp, SilcBuffer);
+			client_entry = va_arg(ap, SilcClientEntry);
+			(void)va_arg(ap, char *);
+			(void)va_arg(ap, char *);
+			(void)va_arg(ap, char *);
+			channels = va_arg(ap, SilcDList);
+			(void)va_arg(ap, SilcUInt32);
+			idle = va_arg(ap, SilcUInt32);
+			(void)va_arg(ap, unsigned char *);
+			user_modes = va_arg(ap, SilcUInt32 *);
 
 			user_info = purple_notify_user_info_new();
 			tmp2 = g_markup_escape_text(client_entry->nickname, -1);
@@ -1183,22 +1190,20 @@
 				purple_notify_user_info_add_pair(user_info, _("Real Name"), tmp2);
 				g_free(tmp2);
 			}
-			if (client_entry->username) {
-				tmp2 = g_markup_escape_text(client_entry->username, -1);
-				if (client_entry->hostname) {
-					gchar *tmp3;
-					tmp3 = g_strdup_printf("%s@%s", tmp2, client_entry->hostname);
-					purple_notify_user_info_add_pair(user_info, _("Username"), tmp3);
-					g_free(tmp3);
-				} else
-					purple_notify_user_info_add_pair(user_info, _("Username"), tmp2);
-				g_free(tmp2);
-			}
+			tmp2 = g_markup_escape_text(client_entry->username, -1);
+			if (*client_entry->hostname) {
+				gchar *tmp3;
+				tmp3 = g_strdup_printf("%s@%s", tmp2, client_entry->hostname);
+				purple_notify_user_info_add_pair(user_info, _("Username"), tmp3);
+				g_free(tmp3);
+			} else
+				purple_notify_user_info_add_pair(user_info, _("Username"), tmp2);
+			g_free(tmp2);
 
 			if (client_entry->mode) {
 				memset(tmp, 0, sizeof(tmp));
 				silcpurple_get_umode_string(client_entry->mode,
-						tmp, sizeof(tmp) - strlen(tmp));
+							    tmp, sizeof(tmp) - strlen(tmp));
 				purple_notify_user_info_add_pair(user_info, _("User Modes"), tmp);
 			}
 
@@ -1240,39 +1245,28 @@
 				g_free(geostr);
 			}
 
-			if (client_entry->server)
+			if (*client_entry->server)
 				purple_notify_user_info_add_pair(user_info, _("Server"), client_entry->server);
 
 			if (channels && user_modes) {
-				SilcUInt32 *umodes;
-				SilcDList list =
-					silc_channel_payload_parse_list(channels->data,
-							channels->len);
-				if (list && silc_get_mode_list(user_modes,
-							silc_dlist_count(list),
-							&umodes)) {
-					SilcChannelPayload entry;
-					int i = 0;
+				SilcChannelPayload entry;
+				int i = 0;
 
-					memset(tmp, 0, sizeof(tmp));
-					silc_dlist_start(list);
-					while ((entry = silc_dlist_get(list))
-							!= SILC_LIST_END) {
-						SilcUInt32 name_len;
-						char *m = silc_client_chumode_char(umodes[i++]);
-						char *name = (char *)silc_channel_get_name(entry, &name_len);
-						if (m)
-							silc_strncat(tmp, sizeof(tmp) - 1, m, strlen(m));
-						silc_strncat(tmp, sizeof(tmp) - 1, name, name_len);
-						silc_strncat(tmp, sizeof(tmp) - 1, "  ", 1);
-						silc_free(m);
-
-					}
-					tmp2 = g_markup_escape_text(tmp, -1);
-					purple_notify_user_info_add_pair(user_info, _("Currently on"), tmp2);
-					g_free(tmp2);
-					silc_free(umodes);
+				memset(tmp, 0, sizeof(tmp));
+				silc_dlist_start(channels);
+				while ((entry = silc_dlist_get(channels))) {
+					SilcUInt32 name_len;
+					char *m = silc_client_chumode_char(user_modes[i++]);
+					char *name = (char *)silc_channel_get_name(entry, &name_len);
+					if (m)
+						silc_strncat(tmp, sizeof(tmp) - 1, m, strlen(m));
+					silc_strncat(tmp, sizeof(tmp) - 1, name, name_len);
+					silc_strncat(tmp, sizeof(tmp) - 1, "  ", 1);
+					silc_free(m);
 				}
+				tmp2 = g_markup_escape_text(tmp, -1);
+				purple_notify_user_info_add_pair(user_info, _("Currently on"), tmp2);
+				g_free(tmp2);
 			}
 
 			if (client_entry->public_key) {
@@ -1297,7 +1291,7 @@
 						_("OK"), G_CALLBACK(silcpurple_whois_more),
 						_("_More..."), G_CALLBACK(silcpurple_whois_more), gc->account, NULL, NULL);
 			else
-#endif
+#endif /* 0 */
 			purple_notify_userinfo(gc, client_entry->nickname, user_info, NULL, NULL);
 			purple_notify_user_info_destroy(user_info);
 		}
@@ -1309,17 +1303,17 @@
 			char *nickname, *realname, *username, *tmp;
 			PurpleNotifyUserInfo *user_info;
 
-			if (!success) {
+			if (status != SILC_STATUS_OK) {
 				purple_notify_error(gc, _("User Information"),
 						  _("Cannot get user information"),
-						  silc_get_status_message(status));
+						  silc_get_status_message(error));
 				break;
 			}
 
-			client_entry = va_arg(vp, SilcClientEntry);
-			nickname = va_arg(vp, char *);
-			username = va_arg(vp, char *);
-			realname = va_arg(vp, char *);
+			client_entry = va_arg(ap, SilcClientEntry);
+			nickname = va_arg(ap, char *);
+			username = va_arg(ap, char *);
+			realname = va_arg(ap, char *);
 			if (!nickname)
 				break;
 
@@ -1334,7 +1328,7 @@
 			}
 			if (username) {
 				tmp = g_markup_escape_text(username, -1);
-				if (client_entry && client_entry->hostname) {
+				if (client_entry && *client_entry->hostname) {
 					gchar *tmp3;
 					tmp3 = g_strdup_printf("%s@%s", tmp, client_entry->hostname);
 					purple_notify_user_info_add_pair(user_info, _("Username"), tmp3);
@@ -1343,7 +1337,7 @@
 					purple_notify_user_info_add_pair(user_info, _("Username"), tmp);
 				g_free(tmp);
 			}
-			if (client_entry && client_entry->server)
+			if (client_entry && *client_entry->server)
 				purple_notify_user_info_add_pair(user_info, _("Server"), client_entry->server);
 
 
@@ -1367,10 +1361,23 @@
 		break;
 
 	case SILC_COMMAND_DETACH:
-		if (!success) {
-			purple_notify_error(gc, _("Detach From Server"), _("Cannot detach"),
-					  silc_get_status_message(status));
-			return;
+		{
+			const char *file;
+			SilcBuffer detach_data;
+
+			if (status != SILC_STATUS_OK) {
+			  purple_notify_error(gc, _("Detach From Server"), _("Cannot detach"),
+					      silc_get_status_message(error));
+			  return;
+			}
+
+			detach_data = va_arg(ap, SilcBuffer);
+
+			/* Save the detachment data to file. */
+			file = silcpurple_session_file(purple_account_get_username(sg->account));
+			g_unlink(file);
+			silc_file_writefile(file, (const char *)silc_buffer_data(detach_data),
+					    silc_buffer_len(detach_data));
 		}
 		break;
 
@@ -1378,19 +1385,19 @@
 		{
 			SilcChannelEntry channel;
 
-			if (!success) {
+			if (status != SILC_STATUS_OK) {
 				purple_notify_error(gc, _("Topic"), _("Cannot set topic"),
-						  silc_get_status_message(status));
+						    silc_get_status_message(error));
 				return;
 			}
 
-			channel = va_arg(vp, SilcChannelEntry);
+			channel = va_arg(ap, SilcChannelEntry);
 
 			convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
-									channel->channel_name, sg->account);
+								      channel->channel_name, sg->account);
 			if (!convo) {
 				purple_debug_error("silc", "Got a topic for %s, which doesn't exist\n",
-								 channel->channel_name);
+						   channel->channel_name);
 				break;
 			}
 
@@ -1402,39 +1409,37 @@
 
 	case SILC_COMMAND_NICK:
 		{
-			/* I don't think we should need to do this because the server should
-			 * be sending a SILC_NOTIFY_TYPE_NICK_CHANGE when we change our own
-			 * nick, but it isn't, so we deal with it here instead. Stu. */
 			SilcClientEntry local_entry;
 			SilcHashTableList htl;
 			SilcChannelUser chu;
-			const char *oldnick;
+			const char *oldnick, *newnick;
 
-			if (!success) {
+			if (status != SILC_STATUS_OK) {
 				purple_notify_error(gc, _("Nick"), _("Failed to change nickname"),
-						silc_get_status_message(status));
+						    silc_get_status_message(error));
 				return;
 			}
 
-			local_entry = va_arg(vp, SilcClientEntry);
+			local_entry = va_arg(ap, SilcClientEntry);
+			newnick = va_arg(ap, char *);
 
 			/* Change nick on all channels */
 			silc_hash_table_list(local_entry->channels, &htl);
 			while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
 				convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
-									chu->channel->channel_name, sg->account);
+									      chu->channel->channel_name, sg->account);
 				if (!convo)
 					continue;
 				oldnick = purple_conv_chat_get_nick(PURPLE_CONV_CHAT(convo));
-				if (strcmp(oldnick, purple_normalize(purple_conversation_get_account(convo), local_entry->nickname))) {
+				if (strcmp(oldnick, purple_normalize(purple_conversation_get_account(convo), newnick))) {
 					purple_conv_chat_rename_user(PURPLE_CONV_CHAT(convo),
-							oldnick, local_entry->nickname);
-					purple_conv_chat_set_nick(PURPLE_CONV_CHAT(convo), local_entry->nickname);
+								     oldnick, newnick);
+					purple_conv_chat_set_nick(PURPLE_CONV_CHAT(convo), newnick);
 				}
 			}
 			silc_hash_table_list_reset(&htl);
 
-			purple_connection_set_display_name(gc, local_entry->nickname);
+			purple_connection_set_display_name(gc, newnick);
 		}
 		break;
 
@@ -1447,34 +1452,34 @@
 			if (sg->roomlist_canceled)
 				break;
 
-			if (!success) {
+			if (error != SILC_STATUS_OK) {
 				purple_notify_error(gc, _("Error"), _("Error retrieving room list"),
-						  silc_get_status_message(status));
+						    silc_get_status_message(error));
 				purple_roomlist_set_in_progress(sg->roomlist, FALSE);
 				purple_roomlist_unref(sg->roomlist);
 				sg->roomlist = NULL;
 				return;
 			}
 
-			(void)va_arg(vp, SilcChannelEntry);
-			name = va_arg(vp, char *);
+			(void)va_arg(ap, SilcChannelEntry);
+			name = va_arg(ap, char *);
 			if (!name) {
 				purple_notify_error(gc, _("Roomlist"), _("Cannot get room list"),
-						  silc_get_status_message(status));
+						    _("Network is empty"));
 				purple_roomlist_set_in_progress(sg->roomlist, FALSE);
 				purple_roomlist_unref(sg->roomlist);
 				sg->roomlist = NULL;
 				return;
 			}
-			topic = va_arg(vp, char *);
-			usercount = va_arg(vp, int);
+			topic = va_arg(ap, char *);
+			usercount = va_arg(ap, int);
 
 			room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, name, NULL);
 			purple_roomlist_room_add_field(sg->roomlist, room, name);
 			purple_roomlist_room_add_field(sg->roomlist, room,
-						     SILC_32_TO_PTR(usercount));
+						       SILC_32_TO_PTR(usercount));
 			purple_roomlist_room_add_field(sg->roomlist, room,
-						     topic ? topic : "");
+						       topic ? topic : "");
 			purple_roomlist_room_add(sg->roomlist, room);
 
 			if (status == SILC_STATUS_LIST_END ||
@@ -1490,21 +1495,21 @@
 		{
 			SilcPublicKey public_key;
 
-			if (!success) {
+			if (status != SILC_STATUS_OK) {
 				purple_notify_error(gc, _("Get Public Key"),
-						  _("Cannot fetch the public key"),
-						  silc_get_status_message(status));
+						    _("Cannot fetch the public key"),
+						    silc_get_status_message(error));
 				return;
 			}
 
-			(void)va_arg(vp, SilcUInt32);
-			(void)va_arg(vp, void *);
-			public_key = va_arg(vp, SilcPublicKey);
+			(void)va_arg(ap, SilcUInt32);
+			(void)va_arg(ap, void *);
+			public_key = va_arg(ap, SilcPublicKey);
 
 			if (!public_key)
 				purple_notify_error(gc, _("Get Public Key"),
-						  _("Cannot fetch the public key"),
-						  _("No public key was received"));
+						    _("Cannot fetch the public key"),
+						    _("No public key was received"));
 		}
 		break;
 
@@ -1515,16 +1520,16 @@
 			char *server_info;
 			char tmp[256];
 
-			if (!success) {
+			if (status != SILC_STATUS_OK) {
 				purple_notify_error(gc, _("Server Information"),
-						  _("Cannot get server information"),
-						  silc_get_status_message(status));
+						    _("Cannot get server information"),
+						    silc_get_status_message(error));
 				return;
 			}
 
-			(void)va_arg(vp, SilcServerEntry);
-			server_name = va_arg(vp, char *);
-			server_info = va_arg(vp, char *);
+			(void)va_arg(ap, SilcServerEntry);
+			server_name = va_arg(ap, char *);
+			server_info = va_arg(ap, char *);
 
 			if (server_name && server_info) {
 				g_snprintf(tmp, sizeof(tmp), "Server: %s\n%s",
@@ -1536,94 +1541,73 @@
 
 	case SILC_COMMAND_STATS:
 		{
-			SilcUInt32 starttime, uptime, my_clients, my_channels, my_server_ops,
-			my_router_ops, cell_clients, cell_channels, cell_servers,
-			clients, channels, servers, routers, server_ops, router_ops;
-			SilcUInt32 buffer_length;
-			SilcBufferStruct buf;
-
-			unsigned char *server_stats;
+			SilcClientStats *stats;
 			char *msg;
 
-			if (!success) {
+			if (status != SILC_STATUS_OK) {
 				purple_notify_error(gc, _("Server Statistics"),
-						_("Cannot get server statistics"),
-						silc_get_status_message(status));
+						    _("Cannot get server statistics"),
+						    silc_get_status_message(error));
 				return;
 			}
 
-			server_stats = va_arg(vp, unsigned char *);
-			buffer_length = va_arg(vp, SilcUInt32);
-			if (!server_stats || !buffer_length) {
-				purple_notify_error(gc, _("Server Statistics"),
-						_("No server statistics available"), NULL);
-				break;
-			}
-			silc_buffer_set(&buf, server_stats, buffer_length);
-			silc_buffer_unformat(&buf,
-					SILC_STR_UI_INT(&starttime),
-					SILC_STR_UI_INT(&uptime),
-					SILC_STR_UI_INT(&my_clients),
-					SILC_STR_UI_INT(&my_channels),
-					SILC_STR_UI_INT(&my_server_ops),
-					SILC_STR_UI_INT(&my_router_ops),
-					SILC_STR_UI_INT(&cell_clients),
-					SILC_STR_UI_INT(&cell_channels),
-					SILC_STR_UI_INT(&cell_servers),
-					SILC_STR_UI_INT(&clients),
-					SILC_STR_UI_INT(&channels),
-					SILC_STR_UI_INT(&servers),
-					SILC_STR_UI_INT(&routers),
-					SILC_STR_UI_INT(&server_ops),
-					SILC_STR_UI_INT(&router_ops),
-					SILC_STR_END);
+			stats = va_arg(ap, SilcClientStats *);
 
 			msg = g_strdup_printf(_("Local server start time: %s\n"
-					"Local server uptime: %s\n"
-					"Local server clients: %d\n"
-					"Local server channels: %d\n"
-					"Local server operators: %d\n"
-					"Local router operators: %d\n"
-					"Local cell clients: %d\n"
-					"Local cell channels: %d\n"
-					"Local cell servers: %d\n"
-					"Total clients: %d\n"
-					"Total channels: %d\n"
-					"Total servers: %d\n"
-					"Total routers: %d\n"
-					"Total server operators: %d\n"
-					"Total router operators: %d\n"),
-					silc_get_time(starttime),
-					purple_str_seconds_to_string((int)uptime),
-					(int)my_clients, (int)my_channels, (int)my_server_ops, (int)my_router_ops,
-					(int)cell_clients, (int)cell_channels, (int)cell_servers,
-					(int)clients, (int)channels, (int)servers, (int)routers,
-					(int)server_ops, (int)router_ops);
+						"Local server uptime: %s\n"
+						"Local server clients: %d\n"
+						"Local server channels: %d\n"
+						"Local server operators: %d\n"
+						"Local router operators: %d\n"
+						"Local cell clients: %d\n"
+						"Local cell channels: %d\n"
+						"Local cell servers: %d\n"
+						"Total clients: %d\n"
+						"Total channels: %d\n"
+						"Total servers: %d\n"
+						"Total routers: %d\n"
+						"Total server operators: %d\n"
+						"Total router operators: %d\n"),
+					      silc_time_string(stats->starttime),
+					      purple_str_seconds_to_string((int)stats->uptime),
+					      (int)stats->my_clients,
+					      (int)stats->my_channels,
+					      (int)stats->my_server_ops,
+					      (int)stats->my_router_ops,
+					      (int)stats->cell_clients,
+					      (int)stats->cell_channels,
+					      (int)stats->cell_servers,
+					      (int)stats->clients,
+					      (int)stats->channels,
+					      (int)stats->servers,
+					      (int)stats->routers,
+					      (int)stats->server_ops,
+					      (int)stats->router_ops);
 
 			purple_notify_info(gc, NULL,
-					_("Network Statistics"), msg);
+					   _("Network Statistics"), msg);
 			g_free(msg);
 		}
 		break;
 
 	case SILC_COMMAND_PING:
 		{
-			if (!success) {
+			if (status != SILC_STATUS_OK) {
 				purple_notify_error(gc, _("Ping"), _("Ping failed"),
-								  silc_get_status_message(status));
+						    silc_get_status_message(error));
 				return;
 			}
 
 			purple_notify_info(gc, _("Ping"), _("Ping reply received from server"),
-							 NULL);
+					   NULL);
 		}
 		break;
 
 	case SILC_COMMAND_KILL:
-		if (!success) {
+		if (status != SILC_STATUS_OK) {
 			purple_notify_error(gc, _("Kill User"),
-					  _("Could not kill user"),
-					  silc_get_status_message(status));
+					    _("Could not kill user"),
+					    silc_get_status_message(error));
 			return;
 		}
 		break;
@@ -1631,188 +1615,108 @@
 	case SILC_COMMAND_CMODE:
 		{
 			SilcChannelEntry channel_entry;
-			SilcBuffer channel_pubkeys;
+			SilcDList channel_pubkeys, list;
+			SilcArgumentDecodedList e;
 
-			if (!success)
+			if (status != SILC_STATUS_OK)
 				return;
 
-			channel_entry = va_arg(vp, SilcChannelEntry);
-			(void)va_arg(vp, SilcUInt32);
-			(void)va_arg(vp, SilcPublicKey);
-			channel_pubkeys = va_arg(vp, SilcBuffer);
+			channel_entry = va_arg(ap, SilcChannelEntry);
+			(void)va_arg(ap, SilcUInt32);
+			(void)va_arg(ap, SilcPublicKey);
+			channel_pubkeys = va_arg(ap, SilcDList);
+
+			if (!sg->chpk)
+				break;
+
+			list = silc_dlist_init();
 
-			if (sg->chpk)
-				silcpurple_chat_chauth_show(sg, channel_entry, channel_pubkeys);
+			if (channel_pubkeys) {
+			  silc_dlist_start(channel_pubkeys);
+			  while ((e = silc_dlist_get(channel_pubkeys))) {
+				if (e->arg_type == 0x00 ||
+				    e->arg_type == 0x03)
+				  silc_dlist_add(list, silc_pkcs_public_key_copy(e->argument));
+			  }
+			}
+			silcpurple_chat_chauth_show(sg, channel_entry, list);
+		}
+		break;
+
+	case SILC_COMMAND_WATCH:
+		if (status != SILC_STATUS_OK) {
+			purple_notify_error(gc, _("WATCH"), _("Cannot watch user"),
+					    silc_get_status_message(error));
+			return;
 		}
 		break;
 
 	default:
-		if (success)
+		if (status == SILC_STATUS_OK)
 			purple_debug_info("silc", "Unhandled command: %d (succeeded)\n", command);
 		else
 			purple_debug_info("silc", "Unhandled command: %d (failed: %s)\n", command,
-							silc_get_status_message(status));
+					  silc_get_status_message(error));
 		break;
 	}
-
-	va_end(vp);
 }
 
-
-/* Called to indicate that connection was either successfully established
-   or connecting failed.  This is also the first time application receives
-   the SilcClientConnection object which it should save somewhere.
-   If the `success' is FALSE the application must always call the function
-   silc_client_close_connection. */
-
-static void
-silc_connected(SilcClient client, SilcClientConnection conn,
-	       SilcClientConnectionStatus status)
-{
-	PurpleConnection *gc = client->application;
-	SilcPurple sg;
-	gboolean reject_watch, block_invites, block_ims;
-
-	if (gc == NULL) {
-		silc_client_close_connection(client, conn);
-		return;
-	}
-	sg = gc->proto_data;
-
-	switch (status) {
-	case SILC_CLIENT_CONN_SUCCESS:
-	case SILC_CLIENT_CONN_SUCCESS_RESUME:
-		purple_connection_set_state(gc, PURPLE_CONNECTED);
-
-		/* Send the server our buddy list */
-		silcpurple_send_buddylist(gc);
-
-		g_unlink(silcpurple_session_file(purple_account_get_username(sg->account)));
-
-		/* Send any UMODEs configured for account */
-		reject_watch = purple_account_get_bool(sg->account, "reject-watch", FALSE);
-		block_invites = purple_account_get_bool(sg->account, "block-invites", FALSE);
-		block_ims = purple_account_get_bool(sg->account, "block-ims", FALSE);
-		if (reject_watch || block_invites || block_ims) {
-			char m[5];
-			g_snprintf(m, sizeof(m), "+%s%s%s",
-					   reject_watch ? "w" : "",
-					   block_invites ? "I" : "",
-					   block_ims ? "P" : "");
-			silc_client_command_call(sg->client, sg->conn, NULL,
-					"UMODE", m, NULL);
-		}
+/* Generic command reply callback for silc_client_command_send.  Simply
+   calls the default command_reply client operation callback */
 
-		return;
-		break;
-	case SILC_CLIENT_CONN_ERROR:
-		purple_connection_error(gc, _("Error during connecting to SILC Server"));
-		g_unlink(silcpurple_session_file(purple_account_get_username(sg->account)));
-		break;
-
-	case SILC_CLIENT_CONN_ERROR_KE:
-		purple_connection_error(gc, _("Key Exchange failed"));
-		break;
-
-	case SILC_CLIENT_CONN_ERROR_AUTH:
-		purple_connection_error(gc, _("Authentication failed"));
-		break;
-
-	case SILC_CLIENT_CONN_ERROR_RESUME:
-		purple_connection_error(gc,
-				      _("Resuming detached session failed. "
-					"Press Reconnect to create new connection."));
-		g_unlink(silcpurple_session_file(purple_account_get_username(sg->account)));
-		break;
-
-	case SILC_CLIENT_CONN_ERROR_TIMEOUT:
-		purple_connection_error(gc, _("Connection Timeout"));
-		break;
-	}
-
-	/* Error */
-	sg->conn = NULL;
-	silc_client_close_connection(client, conn);
-}
-
-
-/* Called to indicate that connection was disconnected to the server.
-   The `status' may tell the reason of the disconnection, and if the
-   `message' is non-NULL it may include the disconnection message
-   received from server. */
-
-static void
-silc_disconnected(SilcClient client, SilcClientConnection conn,
-		  SilcStatus status, const char *message)
+SilcBool silcpurple_command_reply(SilcClient client, SilcClientConnection conn,
+				  SilcCommand command, SilcStatus status,
+				  SilcStatus error, void *context, va_list ap)
 {
-	PurpleConnection *gc = client->application;
-	SilcPurple sg = gc->proto_data;
-
-	if (sg->resuming && !sg->detaching)
-		g_unlink(silcpurple_session_file(purple_account_get_username(sg->account)));
-
-	sg->conn = NULL;
-
-	/* Close the connection */
-	if (!sg->detaching)
-		purple_connection_error(gc, _("Disconnected by server"));
-	else
-		/* TODO: Does this work correctly? Maybe we need to set wants_to_die? */
-		purple_account_disconnect(purple_connection_get_account(gc));
+  silc_command_reply(client, conn, command, status, error, ap);
+  return TRUE;
 }
 
 
 typedef struct {
-	SilcGetAuthMeth completion;
+        union {
+	  SilcAskPassphrase ask_pass;
+	  SilcGetAuthMeth get_auth;
+	} u;
 	void *context;
-} *SilcPurpleGetAuthMethod;
-
-/* Callback called when we've received the authentication method information
-   from the server after we've requested it. */
-
-static void silc_get_auth_method_callback(SilcClient client,
-					  SilcClientConnection conn,
-					  SilcAuthMethod auth_meth,
-					  void *context)
-{
-	SilcPurpleGetAuthMethod internal = context;
+} *SilcPurpleAskPassphrase;
 
-	switch (auth_meth) {
-	case SILC_AUTH_NONE:
-		/* No authentication required. */
-		(*internal->completion)(TRUE, auth_meth, NULL, 0, internal->context);
-		break;
+static void
+silc_ask_auth_password_cb(const unsigned char *passphrase,
+			  SilcUInt32 passphrase_len, void *context)
+{
+	SilcPurpleAskPassphrase internal = context;
 
-	case SILC_AUTH_PASSWORD:
-		/* By returning NULL here the library will ask the passphrase from us
-		   by calling the silc_ask_passphrase. */
-		(*internal->completion)(TRUE, auth_meth, NULL, 0, internal->context);
-		break;
-
-	case SILC_AUTH_PUBLIC_KEY:
-		/* Do not get the authentication data now, the library will generate
-		   it using our default key, if we do not provide it here. */
-		(*internal->completion)(TRUE, auth_meth, NULL, 0, internal->context);
-		break;
-	}
-
+	if (!passphrase || !(*passphrase))
+	  internal->u.get_auth(SILC_AUTH_NONE, NULL, 0, internal->context);
+	else
+	  internal->u.get_auth(SILC_AUTH_PASSWORD,
+			       (unsigned char *)passphrase,
+			       passphrase_len, internal->context);
 	silc_free(internal);
 }
 
 /* Find authentication method and authentication data by hostname and
-   port. The hostname may be IP address as well. When the authentication
-   method has been resolved the `completion' callback with the found
-   authentication method and authentication data is called. The `conn'
-   may be NULL. */
+   port. The hostname may be IP address as well. The `auth_method' is
+   the authentication method the remote connection requires.  It is
+   however possible that remote accepts also some other authentication
+   method.  Application should use the method that may have been
+   configured for this connection.  If none has been configured it should
+   use the required `auth_method'.  If the `auth_method' is
+   SILC_AUTH_NONE, server does not require any authentication or the
+   required authentication method is not known.  The `completion'
+   callback must be called to deliver the chosen authentication method
+   and data. The `conn' may be NULL. */
 
 static void
 silc_get_auth_method(SilcClient client, SilcClientConnection conn,
 		     char *hostname, SilcUInt16 port,
+		     SilcAuthMethod auth_method,
 		     SilcGetAuthMeth completion, void *context)
 {
 	PurpleConnection *gc = client->application;
 	SilcPurple sg = gc->proto_data;
-	SilcPurpleGetAuthMethod internal;
+	SilcPurpleAskPassphrase internal;
 	const char *password;
 
 	/* Progress */
@@ -1821,72 +1725,71 @@
 	else
 		purple_connection_update_progress(gc, _("Authenticating connection"), 4, 5);
 
-	/* Check configuration if we have this connection configured.  If we
-	   have then return that data immediately, as it's faster way. */
-	if (purple_account_get_bool(sg->account, "pubkey-auth", FALSE)) {
-		completion(TRUE, SILC_AUTH_PUBLIC_KEY, NULL, 0, context);
+	/* Check configuration if we have this connection configured. */
+	if (auth_method == SILC_AUTH_PUBLIC_KEY &&
+	    purple_account_get_bool(sg->account, "pubkey-auth", FALSE)) {
+		completion(SILC_AUTH_PUBLIC_KEY, NULL, 0, context);
 		return;
 	}
-	password = purple_connection_get_password(gc);
-	if (password && *password) {
-		completion(TRUE, SILC_AUTH_PASSWORD, (unsigned char *)password, strlen(password), context);
+	if (auth_method == SILC_AUTH_PASSWORD) {
+		password = purple_connection_get_password(gc);
+		if (password && *password) {
+		  completion(SILC_AUTH_PASSWORD, (unsigned char *)password, strlen(password), context);
+		  return;
+		}
+
+		/* Ask password from user */
+		internal = silc_calloc(1, sizeof(*internal));
+		if (!internal)
+		  return;
+		internal->u.get_auth = completion;
+		internal->context = context;
+		silc_ask_passphrase(client, conn, silc_ask_auth_password_cb,
+				    internal);
 		return;
 	}
 
-	/* Resolve the authentication method from server, as we may not know it. */
-	internal = silc_calloc(1, sizeof(*internal));
-	if (!internal)
-		return;
-	internal->completion = completion;
-	internal->context = context;
-	silc_client_request_authentication_method(client, conn,
-						  silc_get_auth_method_callback,
-						  internal);
+	completion(SILC_AUTH_NONE, NULL, 0, context);
 }
 
 
-/* Verifies received public key. The `conn_type' indicates which entity
-   (server, client etc.) has sent the public key. If user decides to trust
-   the application may save the key as trusted public key for later
-   use. The `completion' must be called after the public key has been
-   verified. */
+/* Called to verify received public key. The `conn_type' indicates which
+   entity (server or client) has sent the public key. If user decides to
+   trust the key the application may save the key as trusted public key for
+   later use. The `completion' must be called after the public key has
+   been verified. */
 
 static void
 silc_verify_public_key(SilcClient client, SilcClientConnection conn,
-		       SilcSocketType conn_type, unsigned char *pk,
-		       SilcUInt32 pk_len, SilcSKEPKType pk_type,
+		       SilcConnectionType conn_type,
+		       SilcPublicKey public_key,
 		       SilcVerifyPublicKey completion, void *context)
 {
 	PurpleConnection *gc = client->application;
 	SilcPurple sg = gc->proto_data;
 
-	if (!sg->conn && (conn_type == SILC_SOCKET_TYPE_SERVER ||
-			  conn_type == SILC_SOCKET_TYPE_ROUTER)) {
+	if (!sg->conn && (conn_type == SILC_CONN_SERVER ||
+			  conn_type == SILC_CONN_ROUTER)) {
 		/* Progress */
 		if (sg->resuming)
 			purple_connection_update_progress(gc, _("Resuming session"), 3, 5);
 		else
 			purple_connection_update_progress(gc, _("Verifying server public key"),
-							3, 5);
+							  3, 5);
 	}
 
 	/* Verify public key */
-	silcpurple_verify_public_key(client, conn, NULL, conn_type, pk,
-				   pk_len, pk_type, completion, context);
+	silcpurple_verify_public_key(client, conn, NULL, conn_type,
+				     public_key, completion, context);
 }
 
-typedef struct {
-	SilcAskPassphrase completion;
-	void *context;
-} *SilcPurpleAskPassphrase;
-
 static void
 silc_ask_passphrase_cb(SilcPurpleAskPassphrase internal, const char *passphrase)
 {
 	if (!passphrase || !(*passphrase))
-		internal->completion(NULL, 0, internal->context);
+		internal->u.ask_pass(NULL, 0, internal->context);
 	else
-		internal->completion((unsigned char *)passphrase,
+		internal->u.ask_pass((unsigned char *)passphrase,
 				     strlen(passphrase), internal->context);
 	silc_free(internal);
 }
@@ -1905,97 +1808,32 @@
 
 	if (!internal)
 		return;
-	internal->completion = completion;
+	internal->u.ask_pass = completion;
 	internal->context = context;
 	purple_request_input(gc, _("Passphrase"), NULL,
-			   _("Passphrase required"), NULL, FALSE, TRUE, NULL,
-			   _("OK"), G_CALLBACK(silc_ask_passphrase_cb),
-			   _("Cancel"), G_CALLBACK(silc_ask_passphrase_cb),
-			   purple_connection_get_account(gc), NULL, NULL, internal);
+			     _("Passphrase required"), NULL, FALSE, TRUE, NULL,
+			     _("OK"), G_CALLBACK(silc_ask_passphrase_cb),
+			     _("Cancel"), G_CALLBACK(silc_ask_passphrase_cb),
+			     purple_connection_get_account(gc), NULL, NULL, internal);
 }
 
 
-/* Notifies application that failure packet was received.  This is called
-   if there is some protocol active in the client.  The `protocol' is the
-   protocol context.  The `failure' is opaque pointer to the failure
-   indication.  Note, that the `failure' is protocol dependant and
-   application must explicitly cast it to correct type.  Usually `failure'
-   is 32 bit failure type (see protocol specs for all protocol failure
-   types). */
+/* Called to indicate that incoming key agreement request has been
+   received.  If the application wants to perform key agreement it may
+   call silc_client_perform_key_agreement to initiate key agreement or
+   silc_client_send_key_agreement to provide connection point to the
+   remote client in case the `hostname' is NULL.  If key agreement is
+   not desired this request can be ignored.  The `protocol' is either
+   value 0 for TCP or value 1 for UDP. */
 
 static void
-silc_failure(SilcClient client, SilcClientConnection conn,
-	     SilcProtocol protocol, void *failure)
+silc_key_agreement(SilcClient client, SilcClientConnection conn,
+		   SilcClientEntry client_entry,
+		   const char *hostname, SilcUInt16 protocol,
+		   SilcUInt16 port)
 {
-	PurpleConnection *gc = client->application;
-	char buf[128];
-
-	memset(buf, 0, sizeof(buf));
-
-	if (protocol->protocol->type == SILC_PROTOCOL_CLIENT_KEY_EXCHANGE) {
-		SilcSKEStatus status = (SilcSKEStatus)SILC_PTR_TO_32(failure);
-
-		if (status == SILC_SKE_STATUS_BAD_VERSION)
-			g_snprintf(buf, sizeof(buf),
-				   _("Failure: Version mismatch, upgrade your client"));
-		if (status == SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY)
-			g_snprintf(buf, sizeof(buf),
-				   _("Failure: Remote does not trust/support your public key"));
-		if (status == SILC_SKE_STATUS_UNKNOWN_GROUP)
-			g_snprintf(buf, sizeof(buf),
-				   _("Failure: Remote does not support proposed KE group"));
-		if (status == SILC_SKE_STATUS_UNKNOWN_CIPHER)
-			g_snprintf(buf, sizeof(buf),
-				   _("Failure: Remote does not support proposed cipher"));
-		if (status == SILC_SKE_STATUS_UNKNOWN_PKCS)
-			g_snprintf(buf, sizeof(buf),
-				   _("Failure: Remote does not support proposed PKCS"));
-		if (status == SILC_SKE_STATUS_UNKNOWN_HASH_FUNCTION)
-			g_snprintf(buf, sizeof(buf),
-				   _("Failure: Remote does not support proposed hash function"));
-		if (status == SILC_SKE_STATUS_UNKNOWN_HMAC)
-			g_snprintf(buf, sizeof(buf),
-				   _("Failure: Remote does not support proposed HMAC"));
-		if (status == SILC_SKE_STATUS_INCORRECT_SIGNATURE)
-			g_snprintf(buf, sizeof(buf), _("Failure: Incorrect signature"));
-		if (status == SILC_SKE_STATUS_INVALID_COOKIE)
-			g_snprintf(buf, sizeof(buf), _("Failure: Invalid cookie"));
-
-		/* Show the error on the progress bar.  A more generic error message
-		   is going to be showed to user after this in the silc_connected. */
-		purple_connection_update_progress(gc, buf, 2, 5);
-	}
-
-	if (protocol->protocol->type == SILC_PROTOCOL_CLIENT_CONNECTION_AUTH) {
-		SilcUInt32 err = SILC_PTR_TO_32(failure);
-
-		if (err == SILC_AUTH_FAILED)
-			g_snprintf(buf, sizeof(buf), _("Failure: Authentication failed"));
-
-		/* Show the error on the progress bar.  A more generic error message
-		   is going to be showed to user after this in the silc_connected. */
-		purple_connection_update_progress(gc, buf, 4, 5);
-	}
-}
-
-/* Asks whether the user would like to perform the key agreement protocol.
-   This is called after we have received an key agreement packet or an
-   reply to our key agreement packet. This returns TRUE if the user wants
-   the library to perform the key agreement protocol and FALSE if it is not
-   desired (application may start it later by calling the function
-   silc_client_perform_key_agreement). If TRUE is returned also the
-   `completion' and `context' arguments must be set by the application. */
-
-static bool
-silc_key_agreement(SilcClient client, SilcClientConnection conn,
-		   SilcClientEntry client_entry, const char *hostname,
-		   SilcUInt16 port, SilcKeyAgreementCallback *completion,
-		   void **context)
-{
-	silcpurple_buddy_keyagr_request(client, conn, client_entry, hostname, port);
-	*completion = NULL;
-	*context = NULL;
-	return FALSE;
+	silcpurple_buddy_keyagr_request(client, conn, client_entry,
+					hostname, port, protocol);
 }
 
 
@@ -2012,39 +1850,7 @@
 	 const char *hostname, SilcUInt16 port)
 {
 	silcpurple_ftp_request(client, conn, client_entry, session_id,
-			     hostname, port);
-}
-
-
-/* Delivers SILC session detachment data indicated by `detach_data' to the
-   application.  If application has issued SILC_COMMAND_DETACH command
-   the client session in the SILC network is not quit.  The client remains
-   in the network but is detached.  The detachment data may be used later
-   to resume the session in the SILC Network.  The appliation is
-   responsible of saving the `detach_data', to for example in a file.
-
-   The detachment data can be given as argument to the functions
-   silc_client_connect_to_server, or silc_client_add_connection when
-   creating connection to remote server, inside SilcClientConnectionParams
-   structure.  If it is provided the client library will attempt to resume
-   the session in the network.  After the connection is created
-   successfully, the application is responsible of setting the user
-   interface for user into the same state it was before detaching (showing
-   same channels, channel modes, etc).  It can do this by fetching the
-   information (like joined channels) from the client library. */
-
-static void
-silc_detach(SilcClient client, SilcClientConnection conn,
-	    const unsigned char *detach_data, SilcUInt32 detach_data_len)
-{
-	PurpleConnection *gc = client->application;
-	SilcPurple sg = gc->proto_data;
-	const char *file;
-
-	/* Save the detachment data to file. */
-	file = silcpurple_session_file(purple_account_get_username(sg->account));
-	g_unlink(file);
-	silc_file_writefile(file, (char *)detach_data, detach_data_len);
+			       hostname, port);
 }
 
 SilcClientOperations ops = {
@@ -2054,13 +1860,9 @@
 	silc_notify,
 	silc_command,
 	silc_command_reply,
-	silc_connected,
-	silc_disconnected,
 	silc_get_auth_method,
 	silc_verify_public_key,
 	silc_ask_passphrase,
-	silc_failure,
 	silc_key_agreement,
-	silc_ftp,
-	silc_detach
+	silc_ftp
 };
--- a/libpurple/protocols/silc/pk.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/silc/pk.c	Tue Jun 12 21:21:37 2007 +0000
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 2004 Pekka Riikonen
+  Copyright (C) 2004 - 2007 Pekka Riikonen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
@@ -17,7 +17,7 @@
 
 */
 
-#include "silcincludes.h"
+#include "silc.h"
 #include "silcclient.h"
 #include "silcpurple.h"
 
@@ -31,18 +31,16 @@
 	char *entity_name;
 	char *fingerprint;
 	char *babbleprint;
-	unsigned char *pk;
-	SilcUInt32 pk_len;
-	SilcSKEPKType pk_type;
+	SilcPublicKey public_key;
 	SilcVerifyPublicKey completion;
 	void *context;
 	gboolean changed;
 } *PublicKeyVerify;
 
 static void silcpurple_verify_ask(const char *entity,
-				const char *fingerprint,
-				const char *babbleprint,
-				PublicKeyVerify verify);
+				  const char *fingerprint,
+				  const char *babbleprint,
+				  PublicKeyVerify verify);
 
 static void silcpurple_verify_cb(PublicKeyVerify verify, gint id)
 {
@@ -54,8 +52,8 @@
 			verify->completion(TRUE, verify->context);
 
 		/* Save the key for future checking */
-		silc_pkcs_save_public_key_data(verify->filename, verify->pk,
-					       verify->pk_len, SILC_PKCS_FILE_PEM);
+		silc_pkcs_save_public_key(verify->filename, verify->public_key,
+					  SILC_PKCS_FILE_BASE64);
 	}
 
 	silc_free(verify->filename);
@@ -63,7 +61,7 @@
 	silc_free(verify->entity_name);
 	silc_free(verify->fingerprint);
 	silc_free(verify->babbleprint);
-	silc_free(verify->pk);
+	silc_pkcs_public_key_free(verify->public_key);
 	silc_free(verify);
 }
 
@@ -74,27 +72,23 @@
 	   should have option for the dialogs whether the buttons close them
 	   or not. */
 	silcpurple_verify_ask(verify->entity, verify->fingerprint,
-			    verify->babbleprint, verify);
+			      verify->babbleprint, verify);
 }
 
 static void silcpurple_verify_details(PublicKeyVerify verify, gint id)
 {
-	SilcPublicKey public_key;
 	PurpleConnection *gc = verify->client->application;
 	SilcPurple sg = gc->proto_data;
 
-	silc_pkcs_public_key_decode(verify->pk, verify->pk_len,
-				    &public_key);
-	silcpurple_show_public_key(sg, verify->entity_name, public_key,
-				 G_CALLBACK(silcpurple_verify_details_cb),
-				 verify);
-	silc_pkcs_public_key_free(public_key);
+	silcpurple_show_public_key(sg, verify->entity_name, verify->public_key,
+				   G_CALLBACK(silcpurple_verify_details_cb),
+				   verify);
 }
 
 static void silcpurple_verify_ask(const char *entity,
-				const char *fingerprint,
-				const char *babbleprint,
-				PublicKeyVerify verify)
+				  const char *fingerprint,
+				  const char *babbleprint,
+				  PublicKeyVerify verify)
 {
 	PurpleConnection *gc = verify->client->application;
 	char tmp[256], tmp2[256];
@@ -114,18 +108,17 @@
 		     "%s\n%s\n"), entity, fingerprint, babbleprint);
 
 	purple_request_action(gc, _("Verify Public Key"), tmp, tmp2,
-						PURPLE_DEFAULT_ACTION_NONE,
-						purple_connection_get_account(gc), entity, NULL, verify, 3,
-			    _("Yes"), G_CALLBACK(silcpurple_verify_cb),
-			    _("No"), G_CALLBACK(silcpurple_verify_cb),
-			    _("_View..."), G_CALLBACK(silcpurple_verify_details));
+			      PURPLE_DEFAULT_ACTION_NONE,
+			      purple_connection_get_account(gc), entity, NULL, verify, 3,
+			      _("Yes"), G_CALLBACK(silcpurple_verify_cb),
+			      _("No"), G_CALLBACK(silcpurple_verify_cb),
+			      _("_View..."), G_CALLBACK(silcpurple_verify_details));
 }
 
 void silcpurple_verify_public_key(SilcClient client, SilcClientConnection conn,
-				const char *name, SilcSocketType conn_type,
-				unsigned char *pk, SilcUInt32 pk_len,
-				SilcSKEPKType pk_type,
-				SilcVerifyPublicKey completion, void *context)
+				  const char *name, SilcConnectionType conn_type,
+				  SilcPublicKey public_key,
+				  SilcVerifyPublicKey completion, void *context)
 {
 	PurpleConnection *gc = client->application;
 	int i;
@@ -133,14 +126,18 @@
 	char *fingerprint, *babbleprint;
 	struct passwd *pw;
 	struct stat st;
-	char *entity = ((conn_type == SILC_SOCKET_TYPE_SERVER ||
-			 conn_type == SILC_SOCKET_TYPE_ROUTER) ?
+	char *entity = ((conn_type == SILC_CONN_SERVER ||
+			 conn_type == SILC_CONN_ROUTER) ?
 			"server" : "client");
 	PublicKeyVerify verify;
+	const char *ip, *hostname;
+	SilcUInt16 port;
+	unsigned char *pk;
+	SilcUInt32 pk_len;
 
-	if (pk_type != SILC_SKE_PK_TYPE_SILC) {
+	if (silc_pkcs_get_type(public_key) != SILC_PKCS_SILC) {
 		purple_notify_error(gc, _("Verify Public Key"),
-				  _("Unsupported public key type"), NULL);
+				    _("Unsupported public key type"), NULL);
 		if (completion)
 			completion(FALSE, context);
 		return;
@@ -157,17 +154,22 @@
 	memset(filename2, 0, sizeof(filename2));
 	memset(file, 0, sizeof(file));
 
-	if (conn_type == SILC_SOCKET_TYPE_SERVER ||
-	    conn_type == SILC_SOCKET_TYPE_ROUTER) {
+	silc_socket_stream_get_info(silc_packet_stream_get_stream(conn->stream),
+				    NULL, &hostname, &ip, &port);
+
+	pk = silc_pkcs_public_key_encode(public_key, &pk_len);
+
+	if (conn_type == SILC_CONN_SERVER ||
+	    conn_type == SILC_CONN_ROUTER) {
 		if (!name) {
 			g_snprintf(file, sizeof(file) - 1, "%skey_%s_%d.pub", entity,
-				   conn->sock->ip, conn->sock->port);
+				   ip, port);
 			g_snprintf(filename, sizeof(filename) - 1,
 				   "%s" G_DIR_SEPARATOR_S "%skeys" G_DIR_SEPARATOR_S "%s",
 				   silcpurple_silcdir(), entity, file);
 
 			g_snprintf(file, sizeof(file) - 1, "%skey_%s_%d.pub", entity,
-				   conn->sock->hostname, conn->sock->port);
+				   hostname, port);
 			g_snprintf(filename2, sizeof(filename2) - 1,
 				   "%s" G_DIR_SEPARATOR_S "%skeys" G_DIR_SEPARATOR_S "%s",
 				   silcpurple_silcdir(), entity, file);
@@ -176,7 +178,7 @@
 			hostf = filename2;
 		} else {
 			g_snprintf(file, sizeof(file) - 1, "%skey_%s_%d.pub", entity,
-				   name, conn->sock->port);
+				   name, port);
 			g_snprintf(filename, sizeof(filename) - 1,
 				   "%s" G_DIR_SEPARATOR_S "%skeys" G_DIR_SEPARATOR_S "%s",
 				   silcpurple_silcdir(), entity, file);
@@ -206,12 +208,10 @@
 	verify->conn = conn;
 	verify->filename = strdup(ipf);
 	verify->entity = strdup(entity);
-	verify->entity_name = (conn_type != SILC_SOCKET_TYPE_CLIENT ?
-			       (name ? strdup(name) : strdup(conn->sock->hostname))
+	verify->entity_name = (conn_type != SILC_CONN_CLIENT ?
+			       (name ? strdup(name) : strdup(hostname))
 			       : NULL);
-	verify->pk = silc_memdup(pk, pk_len);
-	verify->pk_len = pk_len;
-	verify->pk_type = pk_type;
+	verify->public_key = silc_pkcs_public_key_copy(public_key);
 	verify->completion = completion;
 	verify->context = context;
 	fingerprint = verify->fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
@@ -221,7 +221,7 @@
 	if (g_stat(ipf, &st) < 0 && (!hostf || g_stat(hostf, &st) < 0)) {
 		/* Key does not exist, ask user to verify the key and save it */
 		silcpurple_verify_ask(name ? name : entity,
-				    fingerprint, babbleprint, verify);
+				      fingerprint, babbleprint, verify);
 		return;
 	} else {
 		/* The key already exists, verify it. */
@@ -230,14 +230,8 @@
 		SilcUInt32 encpk_len;
 
 		/* Load the key file, try for both IP filename and hostname filename */
-		if (!silc_pkcs_load_public_key(ipf, &public_key,
-					       SILC_PKCS_FILE_PEM) &&
-		    !silc_pkcs_load_public_key(ipf, &public_key,
-					       SILC_PKCS_FILE_BIN) &&
-		    (!hostf || (!silc_pkcs_load_public_key(hostf, &public_key,
-							   SILC_PKCS_FILE_PEM) &&
-				!silc_pkcs_load_public_key(hostf, &public_key,
-							   SILC_PKCS_FILE_BIN)))) {
+		if (!silc_pkcs_load_public_key(ipf, &public_key) &&
+		    (!hostf || (!silc_pkcs_load_public_key(hostf, &public_key)))) {
 			silcpurple_verify_ask(name ? name : entity,
 					    fingerprint, babbleprint, verify);
 			return;
@@ -266,9 +260,9 @@
 		silc_free(verify->filename);
 		silc_free(verify->entity);
 		silc_free(verify->entity_name);
-		silc_free(verify->pk);
 		silc_free(verify->fingerprint);
 		silc_free(verify->babbleprint);
+		silc_pkcs_public_key_free(verify->public_key);
 		silc_free(verify);
 	}
 }
--- a/libpurple/protocols/silc/silc.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/silc/silc.c	Tue Jun 12 21:21:37 2007 +0000
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 2004 - 2005 Pekka Riikonen
+  Copyright (C) 2004 - 2007 Pekka Riikonen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
@@ -17,7 +17,7 @@
 
 */
 
-#include "silcincludes.h"
+#include "silc.h"
 #include "silcclient.h"
 #include "silcpurple.h"
 #include "version.h"
@@ -26,6 +26,15 @@
 extern SilcClientOperations ops;
 static PurplePlugin *silc_plugin = NULL;
 
+/* Error log message callback */
+
+static SilcBool silcpurple_log_error(SilcLogType type, char *message,
+				     void *context)
+{
+	silc_say(NULL, NULL, SILC_CLIENT_MESSAGE_ERROR, message);
+	return TRUE;
+}
+
 static const char *
 silcpurple_list_icon(PurpleAccount *a, PurpleBuddy *b)
 {
@@ -102,8 +111,8 @@
 	idp = silc_id_payload_encode(sg->conn->local_id, SILC_ID_CLIENT);
 	SILC_PUT32_MSB(mode, mb);
 	silc_client_command_send(sg->client, sg->conn, SILC_COMMAND_UMODE,
-				 ++sg->conn->cmd_ident, 2,
-				 1, idp->data, idp->len,
+				 silcpurple_command_reply, NULL, 2,
+				 1, idp->data, silc_buffer_len(idp),
 				 2, mb, sizeof(mb));
 	silc_buffer_free(idp);
 }
@@ -115,91 +124,54 @@
 silcpurple_keepalive(PurpleConnection *gc)
 {
 	SilcPurple sg = gc->proto_data;
-	silc_client_send_packet(sg->client, sg->conn, SILC_PACKET_HEARTBEAT,
-				NULL, 0);
+	silc_packet_send(sg->conn->stream, SILC_PACKET_HEARTBEAT, 0,
+			 NULL, 0);
 }
 
 static gboolean
 silcpurple_scheduler(gpointer *context)
 {
-	SilcPurple sg = (SilcPurple)context;
-	silc_client_run_one(sg->client);
+	SilcClient client = (SilcClient)context;
+	silc_client_run_one(client);
 	return TRUE;
 }
 
 static void
-silcpurple_nickname_parse(const char *nickname,
-			char **ret_nickname)
-{
-	silc_parse_userfqdn(nickname, ret_nickname, NULL);
-}
-
-static void
-silcpurple_login_connected(gpointer data, gint source, const gchar *error_message)
+silcpurple_connect_cb(SilcClient client, SilcClientConnection conn,
+		      SilcClientConnectionStatus status, SilcStatus error,
+		      const char *message, void *context)
 {
-	PurpleConnection *gc = data;
+	PurpleConnection *gc = context;
 	SilcPurple sg;
-	SilcClient client;
-	SilcClientConnection conn;
-	PurpleAccount *account;
-	SilcClientConnectionParams params;
-	const char *dfile;
-
-	g_return_if_fail(gc != NULL);
+	SilcUInt32 mask;
+	char tz[16];
+	PurpleStoredImage *img;
+#ifdef HAVE_SYS_UTSNAME_H
+	struct utsname u;
+#endif
 
 	sg = gc->proto_data;
 
-	if (source < 0) {
-		purple_connection_error(gc, _("Connection failed"));
-		return;
-	}
+	switch (status) {
+	case SILC_CLIENT_CONN_SUCCESS:
+	case SILC_CLIENT_CONN_SUCCESS_RESUME:
+		sg->conn = conn;
 
-	client = sg->client;
-	account = sg->account;
-
-	/* Get session detachment data, if available */
-	memset(&params, 0, sizeof(params));
-	dfile = silcpurple_session_file(purple_account_get_username(sg->account));
-	params.detach_data = (unsigned char *)silc_file_readfile(dfile, &params.detach_data_len);
-	if (params.detach_data)
-		params.detach_data[params.detach_data_len] = 0;
+		/* Connection created successfully */
+		purple_connection_set_state(gc, PURPLE_CONNECTED);
 
-	/* Add connection to SILC client library */
-	conn = silc_client_add_connection(
-			  sg->client, &params,
-			  (char *)purple_account_get_string(account, "server",
-							  "silc.silcnet.org"),
-			  purple_account_get_int(account, "port", 706), sg);
-	if (!conn) {
-		purple_connection_error(gc, _("Cannot initialize SILC Client connection"));
-		gc->proto_data = NULL;
-		return;
-	}
-	sg->conn = conn;
+		/* Send the server our buddy list */
+		silcpurple_send_buddylist(gc);
+
+		g_unlink(silcpurple_session_file(purple_account_get_username(sg->account)));
 
-	/* Progress */
-	if (params.detach_data) {
-		purple_connection_update_progress(gc, _("Resuming session"), 2, 5);
-		sg->resuming = TRUE;
-	} else {
-		purple_connection_update_progress(gc, _("Performing key exchange"), 2, 5);
-	}
-
-	/* Perform SILC Key Exchange.  The "silc_connected" will be called
-	   eventually. */
-	silc_client_start_key_exchange(sg->client, sg->conn, source);
+		/* Send any UMODEs configured for account */
+		if (purple_account_get_bool(sg->account, "block-ims", FALSE)) {
+			silc_client_command_call(sg->client, sg->conn, NULL,
+						 "UMODE", "+P", NULL);
+		}
 
-	/* Set default attributes */
-	if (!purple_account_get_bool(account, "reject-attrs", FALSE)) {
-		SilcUInt32 mask;
-		const char *tmp;
-#ifdef SILC_ATTRIBUTE_USER_ICON
-		PurpleStoredImage *img;
-#endif
-#ifdef HAVE_SYS_UTSNAME_H
-		struct utsname u;
-#endif
-
+		/* Set default attributes */
 		mask = SILC_ATTRIBUTE_MOOD_NORMAL;
 		silc_client_attribute_add(client, conn,
 					  SILC_ATTRIBUTE_STATUS_MOOD,
@@ -222,36 +194,191 @@
 						  (void *)&dev, sizeof(dev));
 		}
 #endif
-#ifdef _WIN32
-		tmp = _tzname[0];
-#else
-		tmp = tzname[0];
-#endif
+		silc_timezone(tz, sizeof(tz));
 		silc_client_attribute_add(client, conn,
 					  SILC_ATTRIBUTE_TIMEZONE,
-					  (void *)tmp, strlen(tmp));
+					  (void *)tz, strlen(tz));
 
-#ifdef SILC_ATTRIBUTE_USER_ICON
 		/* Set our buddy icon */
-		img = purple_buddy_icons_find_account_icon(account);
+		img = purple_buddy_icons_find_account_icon(sg->account);
 		silcpurple_buddy_set_icon(gc, img);
 		purple_imgstore_unref(img);
-#endif
+
+		return;
+		break;
+
+	case SILC_CLIENT_CONN_DISCONNECTED:
+		/* Disconnected */
+		if (sg->resuming && !sg->detaching)
+		  g_unlink(silcpurple_session_file(purple_account_get_username(sg->account)));
+
+		/* Close the connection */
+		if (!sg->detaching)
+		  purple_connection_error(gc, _("Disconnected by server"));
+		else
+		  /* TODO: Does this work correctly? Maybe we need to set wants_to_die? */
+		  purple_account_disconnect(purple_connection_get_account(gc));
+		break;
+
+	case SILC_CLIENT_CONN_ERROR:
+		purple_connection_error(gc, _("Error during connecting to SILC Server"));
+		g_unlink(silcpurple_session_file(purple_account_get_username(sg->account)));
+		break;
+
+	case SILC_CLIENT_CONN_ERROR_KE:
+		purple_connection_error(gc, _("Key Exchange failed"));
+		break;
+
+	case SILC_CLIENT_CONN_ERROR_AUTH:
+		purple_connection_error(gc, _("Authentication failed"));
+		break;
+
+	case SILC_CLIENT_CONN_ERROR_RESUME:
+		purple_connection_error(gc,
+				      _("Resuming detached session failed. "
+					"Press Reconnect to create new connection."));
+		g_unlink(silcpurple_session_file(purple_account_get_username(sg->account)));
+		break;
+
+	case SILC_CLIENT_CONN_ERROR_TIMEOUT:
+		purple_connection_error(gc, _("Connection Timeout"));
+		break;
 	}
 
+	/* Error */
+	sg->conn = NULL;
+}
+
+static void
+silcpurple_stream_created(SilcSocketStreamStatus status, SilcStream stream,
+			  void *context)
+{
+	PurpleConnection *gc = context;
+	SilcPurple sg;
+	SilcClient client;
+	SilcClientConnectionParams params;
+	const char *dfile;
+
+	sg = gc->proto_data;
+
+	if (status != SILC_SOCKET_OK) {
+		purple_connection_error(gc, _("Connection failed"));
+		silc_pkcs_public_key_free(sg->public_key);
+		silc_pkcs_private_key_free(sg->private_key);
+		silc_free(sg);
+		gc->proto_data = NULL;
+		return;
+	}
+
+	client = sg->client;
+
+	/* Progress */
+	if (params.detach_data) {
+		purple_connection_update_progress(gc, _("Resuming session"), 2, 5);
+		sg->resuming = TRUE;
+	} else {
+		purple_connection_update_progress(gc, _("Performing key exchange"), 2, 5);
+	}
+
+	/* Get session detachment data, if available */
+	memset(&params, 0, sizeof(params));
+	dfile = silcpurple_session_file(purple_account_get_username(sg->account));
+	params.detach_data = (unsigned char *)silc_file_readfile(dfile, &params.detach_data_len);
+	if (params.detach_data)
+		params.detach_data[params.detach_data_len] = 0;
+	params.ignore_requested_attributes = FALSE;
+	params.pfs = purple_account_get_bool(sg->account, "pfs", FALSE);
+
+	/* Perform SILC Key Exchange. */
+	silc_client_key_exchange(sg->client, &params, sg->public_key,
+				 sg->private_key, stream, SILC_CONN_SERVER,
+				 silcpurple_connect_cb, gc);
+
 	silc_free(params.detach_data);
 }
 
 static void
+silcpurple_login_connected(gpointer data, gint source, const gchar *error_message)
+{
+	PurpleConnection *gc = data;
+	SilcPurple sg;
+
+	g_return_if_fail(gc != NULL);
+
+	sg = gc->proto_data;
+
+	if (source < 0) {
+		purple_connection_error(gc, _("Connection failed"));
+		silc_pkcs_public_key_free(sg->public_key);
+		silc_pkcs_private_key_free(sg->private_key);
+		silc_free(sg);
+		gc->proto_data = NULL;
+		return;
+	}
+
+	/* Wrap socket to TCP stream */
+	silc_socket_tcp_stream_create(source, TRUE, FALSE,
+				      sg->client->schedule,
+				      silcpurple_stream_created, gc);
+}
+
+static void silcpurple_running(SilcClient client, void *context)
+{
+	PurpleAccount *account = context;
+	PurpleConnection *gc = account->gc;
+	SilcPurple sg;
+	char pkd[256], prd[256];
+
+	sg = silc_calloc(1, sizeof(*sg));
+	if (!sg)
+		return;
+	memset(sg, 0, sizeof(*sg));
+	sg->client = client;
+	sg->gc = gc;
+	sg->account = account;
+	sg->scheduler = SILC_PTR_TO_32(gc->proto_data);
+	gc->proto_data = sg;
+
+	/* Progress */
+	purple_connection_update_progress(gc, _("Connecting to SILC Server"), 1, 5);
+
+	/* Load SILC key pair */
+	g_snprintf(pkd, sizeof(pkd), "%s" G_DIR_SEPARATOR_S "public_key.pub", silcpurple_silcdir());
+	g_snprintf(prd, sizeof(prd), "%s" G_DIR_SEPARATOR_S "private_key.prv", silcpurple_silcdir());
+	if (!silc_load_key_pair((char *)purple_account_get_string(account, "public-key", pkd),
+				(char *)purple_account_get_string(account, "private-key", prd),
+				(gc->password == NULL) ? "" : gc->password,
+				&sg->public_key, &sg->private_key)) {
+		g_snprintf(pkd, sizeof(pkd), _("Could not load SILC key pair"));
+		purple_connection_error(gc, pkd);
+		gc->proto_data = NULL;
+		silc_free(sg);
+		return;
+	}
+
+	/* Connect to the SILC server */
+	if (purple_proxy_connect(gc, account,
+				 purple_account_get_string(account, "server",
+							   "silc.silcnet.org"),
+				 purple_account_get_int(account, "port", 706),
+				 silcpurple_login_connected, gc) == NULL)
+	{
+		purple_connection_error(gc, _("Unable to create connection"));
+		gc->proto_data = NULL;
+		silc_free(sg);
+		return;
+	}
+}
+
+static void
 silcpurple_login(PurpleAccount *account)
 {
-	SilcPurple sg;
 	SilcClient client;
-	SilcClientParams params;
 	PurpleConnection *gc;
-	char pkd[256], prd[256];
+	SilcClientParams params;
 	const char *cipher, *hmac;
-	char *realname;
+	char *username, *hostname, *realname, **up;
+	guint scheduler;
 	int i;
 
 	gc = account->gc;
@@ -260,10 +387,7 @@
 	gc->proto_data = NULL;
 
 	memset(&params, 0, sizeof(params));
-	strcat(params.nickname_format, "%n@%h%a");
-	params.nickname_parse = silcpurple_nickname_parse;
-	params.ignore_requested_attributes =
-		purple_account_get_bool(account, "reject-attrs", FALSE);
+	strcat(params.nickname_format, "%n#a");
 
 	/* Allocate SILC client */
 	client = silc_client_alloc(&ops, &params, gc, NULL);
@@ -273,32 +397,28 @@
 	}
 
 	/* Get username, real name and local hostname for SILC library */
-	if (purple_account_get_username(account)) {
-		const char *u = purple_account_get_username(account);
-		char **up = g_strsplit(u, "@", 2);
-		client->username = strdup(up[0]);
-		g_strfreev(up);
-	} else {
-		client->username = silc_get_username();
-		purple_account_set_username(account, client->username);
+	if (!purple_account_get_username(account))
+		purple_account_set_username(account, silc_get_username());
+
+	username = (char *)purple_account_get_username(account);
+	up = g_strsplit(username, "@", 2);
+	username = strdup(up[0]);
+	g_strfreev(up);
+
+	if (!purple_account_get_user_info(account)) {
+		purple_account_set_user_info(account, silc_get_real_name());
+		if (!purple_account_get_user_info(account))
+			purple_account_set_user_info(account,
+						     "John T. Noname");
 	}
-	realname = silc_get_real_name();
-	if (purple_account_get_user_info(account)) {
-		client->realname = strdup(purple_account_get_user_info(account));
-		free(realname);
-	} else if ((silc_get_real_name() != NULL) && (*realname != '\0')) {
-		client->realname = realname;
-		purple_account_set_user_info(account, client->realname);
-	} else {
-		free(realname);
-		client->realname = strdup(_("John Noname"));
-	}
-	client->hostname = silc_net_localhost();
+	realname = (char *)purple_account_get_user_info(account);
+	hostname = silc_net_localhost();
 
-	purple_connection_set_display_name(gc, client->username);
+	purple_connection_set_display_name(gc, username);
 
 	/* Register requested cipher and HMAC */
-	cipher = purple_account_get_string(account, "cipher", SILC_DEFAULT_CIPHER);
+	cipher = purple_account_get_string(account, "cipher",
+					   SILC_DEFAULT_CIPHER);
 	for (i = 0; silc_default_ciphers[i].name; i++)
 		if (!strcmp(silc_default_ciphers[i].name, cipher)) {
 			silc_cipher_register(&(silc_default_ciphers[i]));
@@ -312,7 +432,8 @@
 		}
 
 	/* Init SILC client */
-	if (!silc_client_init(client)) {
+	if (!silc_client_init(client, username, hostname, realname,
+			      silcpurple_running, account)) {
 		gc->wants_to_die = TRUE;
 		purple_connection_error(gc, _("Cannot initialize SILC protocol"));
 		return;
@@ -321,59 +442,23 @@
 	/* Check the ~/.silc dir and create it, and new key pair if necessary. */
 	if (!silcpurple_check_silc_dir(gc)) {
 		gc->wants_to_die = TRUE;
-		purple_connection_error(gc, _("Cannot find/access ~/.silc directory"));
-		return;
-	}
-
-	/* Progress */
-	purple_connection_update_progress(gc, _("Connecting to SILC Server"), 1, 5);
-
-	/* Load SILC key pair */
-	g_snprintf(pkd, sizeof(pkd), "%s" G_DIR_SEPARATOR_S "public_key.pub", silcpurple_silcdir());
-	g_snprintf(prd, sizeof(prd), "%s" G_DIR_SEPARATOR_S "private_key.prv", silcpurple_silcdir());
-	if (!silc_load_key_pair((char *)purple_account_get_string(account, "public-key", pkd),
-							(char *)purple_account_get_string(account, "private-key", prd),
-				(gc->password == NULL) ? "" : gc->password, &client->pkcs,
-				&client->public_key, &client->private_key)) {
-		g_snprintf(pkd, sizeof(pkd), _("Could not load SILC key pair: %s"), strerror(errno));
-		purple_connection_error(gc, pkd);
-		return;
-	}
-
-	sg = silc_calloc(1, sizeof(*sg));
-	if (!sg)
-		return;
-	memset(sg, 0, sizeof(*sg));
-	sg->client = client;
-	sg->gc = gc;
-	sg->account = account;
-	gc->proto_data = sg;
-
-	/* Connect to the SILC server */
-	if (purple_proxy_connect(gc, account,
-			       purple_account_get_string(account, "server",
-						       "silc.silcnet.org"),
-			       purple_account_get_int(account, "port", 706),
-			       silcpurple_login_connected, gc) == NULL)
-	{
-		purple_connection_error(gc, _("Unable to create connection"));
+		purple_connection_error(gc, _("Error loading SILC key pair"));
 		return;
 	}
 
 	/* Schedule SILC using Glib's event loop */
-	sg->scheduler = purple_timeout_add(300, (GSourceFunc)silcpurple_scheduler, sg);
+	scheduler = purple_timeout_add(300, (GSourceFunc)silcpurple_scheduler, client);
+	gc->proto_data = SILC_32_TO_PTR(scheduler);
 }
 
 static int
 silcpurple_close_final(gpointer *context)
 {
 	SilcPurple sg = (SilcPurple)context;
-	silc_client_stop(sg->client);
+	silc_client_stop(sg->client, NULL, NULL);
 	silc_client_free(sg->client);
-#ifdef HAVE_SILCMIME_H
 	if (sg->mimeass)
 		silc_mime_assembler_free(sg->mimeass);
-#endif
 	silc_free(sg);
 	return 0;
 }
@@ -387,7 +472,7 @@
 
 	/* Send QUIT */
 	silc_client_command_call(sg->client, sg->conn, NULL,
-				 "QUIT", "Download this: " PURPLE_WEBSITE, NULL);
+				 "QUIT", "Download Pidgin: " PURPLE_WEBSITE, NULL);
 
 	if (sg->conn)
 		silc_client_close_connection(sg->client, sg->conn);
@@ -595,7 +680,7 @@
 	gboolean cemail = FALSE, ccall = FALSE, csms = FALSE,
 		cmms = FALSE, cchat = TRUE, cvideo = FALSE;
 	gboolean device = TRUE;
-	char status[1024];
+	char status[1024], tz[16];
 
 	sg = gc->proto_data;
 	if (!sg)
@@ -722,11 +807,9 @@
 					  purple_account_get_string(sg->account, "vcard", ""),
 					  FALSE);
 	purple_request_field_group_add_field(g, f);
-#ifdef _WIN32
-	f = purple_request_field_string_new("timezone", _("Timezone"), _tzname[0], FALSE);
-#else
-	f = purple_request_field_string_new("timezone", _("Timezone"), tzname[0], FALSE);
-#endif
+
+	silc_timezone(tz, sizeof(tz));
+	f = purple_request_field_string_new("timezone", _("Timezone (UTC)"), tz, FALSE);
 	purple_request_field_group_add_field(g, f);
 	purple_request_fields_add_group(fields, g);
 
@@ -861,12 +944,14 @@
 	if (f)
 		c = purple_request_field_string_get_value(f);
 
-	identifier = silc_pkcs_encode_identifier((char *)un, (char *)hn,
-						 (char *)rn, (char *)e, (char *)o, (char *)c);
+	identifier = silc_pkcs_silc_encode_identifier((char *)un, (char *)hn,
+						      (char *)rn, (char *)e,
+						      (char *)o, (char *)c,
+						      NULL);
 
 	/* Create the key pair */
 	if (!silc_create_key_pair(SILCPURPLE_DEF_PKCS, keylen, pkfile, prfile,
-				  identifier, pass1, NULL, &public_key, NULL,
+				  identifier, pass1, &public_key, NULL,
 				  FALSE)) {
 		purple_notify_error(
 		     gc, _("Create New SILC Key Pair"), _("Key Pair Generation failed"), NULL);
@@ -941,10 +1026,10 @@
 	purple_request_fields_add_group(fields, g);
 
 	purple_request_fields(gc, _("Create New SILC Key Pair"),
-			    _("Create New SILC Key Pair"), NULL, fields,
-			    _("Generate Key Pair"), G_CALLBACK(silcpurple_create_keypair_cb),
-			    _("Cancel"), G_CALLBACK(silcpurple_create_keypair_cancel),
-				gc->account, NULL, NULL, gc);
+			      _("Create New SILC Key Pair"), NULL, fields,
+			      _("Generate Key Pair"), G_CALLBACK(silcpurple_create_keypair_cb),
+			      _("Cancel"), G_CALLBACK(silcpurple_create_keypair_cancel),
+			      gc->account, NULL, NULL, gc);
 
 	g_strfreev(u);
 	silc_free(hostname);
@@ -963,8 +1048,8 @@
         char prd[256];
 	g_snprintf(prd, sizeof(prd), "%s" G_DIR_SEPARATOR_S "private_key.pub", silcpurple_silcdir());
 	silc_change_private_key_passphrase(purple_account_get_string(gc->account,
-								   "private-key",
-								   prd), old, new);
+								     "private-key",
+								     prd), old, new);
 }
 
 static void
@@ -982,15 +1067,12 @@
 static GList *
 silcpurple_actions(PurplePlugin *plugin, gpointer context)
 {
-	PurpleConnection *gc = context;
 	GList *list = NULL;
 	PurplePluginAction *act;
 
-	if (!purple_account_get_bool(gc->account, "reject-attrs", FALSE)) {
-		act = purple_plugin_action_new(_("Online Status"),
-				silcpurple_attrs);
-		list = g_list_append(list, act);
-	}
+	act = purple_plugin_action_new(_("Online Status"),
+			silcpurple_attrs);
+	list = g_list_append(list, act);
 
 	act = purple_plugin_action_new(_("Detach From Server"),
 			silcpurple_detach);
@@ -1028,49 +1110,43 @@
 
 static void
 silcpurple_send_im_resolved(SilcClient client,
-			  SilcClientConnection conn,
-			  SilcClientEntry *clients,
-			  SilcUInt32 clients_count,
-			  void *context)
+			    SilcClientConnection conn,
+			    SilcStatus status,
+			    SilcDList clients,
+			    void *context)
 {
 	PurpleConnection *gc = client->application;
 	SilcPurple sg = gc->proto_data;
 	SilcPurpleIM im = context;
 	PurpleConversation *convo;
-	char tmp[256], *nickname = NULL;
+	char tmp[256];
 	SilcClientEntry client_entry;
-#ifdef HAVE_SILCMIME_H
 	SilcDList list;
-#endif
 
 	convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, im->nick,
-							sg->account);
+						      sg->account);
 	if (!convo)
 		return;
 
 	if (!clients)
 		goto err;
 
-	if (clients_count > 1) {
-		silc_parse_userfqdn(im->nick, &nickname, NULL);
-
+	if (silc_dlist_count(clients) > 1) {
 		/* Find the correct one. The im->nick might be a formatted nick
 		   so this will find the correct one. */
 		clients = silc_client_get_clients_local(client, conn,
-							nickname, im->nick,
-							&clients_count);
+							im->nick, FALSE);
 		if (!clients)
 			goto err;
-		client_entry = clients[0];
-		silc_free(clients);
-	} else {
-		client_entry = clients[0];
 	}
 
-#ifdef HAVE_SILCMIME_H
+	silc_dlist_start(clients);
+	client_entry = silc_dlist_get(clients);
+
 	/* Check for images */
 	if (im->gflags & PURPLE_MESSAGE_IMAGES) {
-		list = silcpurple_image_message(im->message, (SilcUInt32 *)&im->flags);
+		list = silcpurple_image_message(im->message,
+						(SilcUInt32 *)(void *)&im->flags);
 		if (list) {
 			/* Send one or more MIME message.  If more than one, they
 			   are MIME fragments due to over large message */
@@ -1079,22 +1155,21 @@
 			silc_dlist_start(list);
 			while ((buf = silc_dlist_get(list)) != SILC_LIST_END)
 				silc_client_send_private_message(client, conn,
-								 client_entry, im->flags,
-								 buf->data, buf->len,
-								 TRUE);
+								 client_entry, im->flags, NULL,
+								 buf->data,
+								 silc_buffer_len(buf));
 			silc_mime_partial_free(list);
 			purple_conv_im_write(PURPLE_CONV_IM(convo), conn->local_entry->nickname,
-				   im->message, 0, time(NULL));
+					     im->message, 0, time(NULL));
 			goto out;
 		}
 	}
-#endif
 
 	/* Send the message */
 	silc_client_send_private_message(client, conn, client_entry, im->flags,
-					 (unsigned char *)im->message, im->message_len, TRUE);
+					 NULL, (unsigned char *)im->message, im->message_len);
 	purple_conv_im_write(PURPLE_CONV_IM(convo), conn->local_entry->nickname,
-			   im->message, 0, time(NULL));
+			     im->message, 0, time(NULL));
 	goto out;
 
  err:
@@ -1106,24 +1181,22 @@
 	g_free(im->nick);
 	g_free(im->message);
 	silc_free(im);
-	silc_free(nickname);
 }
 
 static int
 silcpurple_send_im(PurpleConnection *gc, const char *who, const char *message,
-		 PurpleMessageFlags flags)
+		   PurpleMessageFlags flags)
 {
 	SilcPurple sg = gc->proto_data;
 	SilcClient client = sg->client;
 	SilcClientConnection conn = sg->conn;
-	SilcClientEntry *clients;
-	SilcUInt32 clients_count, mflags;
-	char *nickname, *msg, *tmp;
+	SilcDList clients;
+	SilcClientEntry client_entry;
+	SilcUInt32 mflags;
+	char *msg, *tmp;
 	int ret = 0;
 	gboolean sign = purple_account_get_bool(sg->account, "sign-verify", FALSE);
-#ifdef HAVE_SILCMIME_H
 	SilcDList list;
-#endif
 
 	if (!who || !message)
 		return 0;
@@ -1141,14 +1214,9 @@
 		mflags |= SILC_MESSAGE_FLAG_ACTION;
 	} else if (strlen(msg) > 1 && msg[0] == '/') {
 		if (!silc_client_command_call(client, conn, msg + 1))
-			purple_notify_error(gc, _("Call Command"), _("Cannot call command"),
-					_("Unknown command"));
-		g_free(tmp);
-		return 0;
-	}
-
-
-	if (!silc_parse_userfqdn(who, &nickname, NULL)) {
+			purple_notify_error(gc, _("Call Command"),
+					    _("Cannot call command"),
+					    _("Unknown command"));
 		g_free(tmp);
 		return 0;
 	}
@@ -1157,8 +1225,7 @@
 		mflags |= SILC_MESSAGE_FLAG_SIGNED;
 
 	/* Find client entry */
-	clients = silc_client_get_clients_local(client, conn, nickname, who,
-						&clients_count);
+	clients = silc_client_get_clients_local(client, conn, who, FALSE);
 	if (!clients) {
 		/* Resolve unknown user */
 		SilcPurpleIM im = silc_calloc(1, sizeof(*im));
@@ -1171,14 +1238,15 @@
 		im->message_len = strlen(im->message);
 		im->flags = mflags;
 		im->gflags = flags;
-		silc_client_get_clients(client, conn, nickname, NULL,
+		silc_client_get_clients(client, conn, who, NULL,
 					silcpurple_send_im_resolved, im);
-		silc_free(nickname);
 		g_free(tmp);
 		return 0;
 	}
 
-#ifdef HAVE_SILCMIME_H
+	silc_dlist_start(clients);
+	client_entry = silc_dlist_get(clients);
+
 	/* Check for images */
 	if (flags & PURPLE_MESSAGE_IMAGES) {
 		list = silcpurple_image_message(message, &mflags);
@@ -1191,27 +1259,24 @@
 			while ((buf = silc_dlist_get(list)) != SILC_LIST_END)
 				ret =
 			 	silc_client_send_private_message(client, conn,
-								 clients[0], mflags,
-								 buf->data, buf->len,
-								 TRUE);
+								 client_entry, mflags, NULL,
+								 buf->data,
+								 silc_buffer_len(buf));
 			silc_mime_partial_free(list);
 			g_free(tmp);
-			silc_free(nickname);
-			silc_free(clients);
+			silc_client_list_free(client, conn, clients);
 			return ret;
 		}
 	}
-#endif
 
 	/* Send private message directly */
-	ret = silc_client_send_private_message(client, conn, clients[0],
-					       mflags,
+	ret = silc_client_send_private_message(client, conn, client_entry,
+					       mflags, NULL,
 					       (unsigned char *)msg,
-					       strlen(msg), TRUE);
+					       strlen(msg));
 
 	g_free(tmp);
-	silc_free(nickname);
-	silc_free(clients);
+	silc_client_list_free(client, conn, clients);
 	return ret;
 }
 
@@ -1219,7 +1284,6 @@
 static GList *silcpurple_blist_node_menu(PurpleBlistNode *node) {
 	/* split this single menu building function back into the two
 	   original: one for buddies and one for chats */
-
 	if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
 		return silcpurple_chat_menu((PurpleChat *) node);
 	} else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
@@ -1548,7 +1612,7 @@
 		return PURPLE_CMD_RET_FAILED;
 
 	silc_client_command_call(sg->client, sg->conn, NULL,
-				 "QUIT", (args && args[0]) ? args[0] : "Download this: " PURPLE_WEBSITE, NULL);
+				 "QUIT", (args && args[0]) ? args[0] : "Download Pidgin: " PURPLE_WEBSITE, NULL);
 
 	return PURPLE_CMD_RET_OK;
 }
@@ -1722,82 +1786,70 @@
 
 static PurplePluginProtocolInfo prpl_info =
 {
-#ifdef HAVE_SILCMIME_H
 	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME |
-	OPT_PROTO_PASSWORD_OPTIONAL | OPT_PROTO_IM_IMAGE,
-#else
-	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME |
-	OPT_PROTO_PASSWORD_OPTIONAL,
-#endif
-	NULL,						/* user_splits */
-	NULL,						/* protocol_options */
-#ifdef SILC_ATTRIBUTE_USER_ICON
+	OPT_PROTO_PASSWORD_OPTIONAL | OPT_PROTO_IM_IMAGE |
+	OPT_PROTO_SLASH_COMMANDS_NATIVE,
+	NULL,					/* user_splits */
+	NULL,					/* protocol_options */
 	{"jpeg,gif,png,bmp", 0, 0, 96, 96, 0, PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */
-#else
-	NO_BUDDY_ICONS,
-#endif
-	silcpurple_list_icon,			/* list_icon */
-	NULL,				/* list_emblems */
-	silcpurple_status_text,		/* status_text */
+	silcpurple_list_icon,	                /* list_icon */
+	NULL,					/* list_emblems */
+	silcpurple_status_text,			/* status_text */
 	silcpurple_tooltip_text,		/* tooltip_text */
-	silcpurple_away_states,		/* away_states */
-	silcpurple_blist_node_menu,	/* blist_node_menu */
+	silcpurple_away_states,			/* away_states */
+	silcpurple_blist_node_menu,		/* blist_node_menu */
 	silcpurple_chat_info,			/* chat_info */
-	silcpurple_chat_info_defaults,/* chat_info_defaults */
-	silcpurple_login,				/* login */
-	silcpurple_close,				/* close */
+	silcpurple_chat_info_defaults,	        /* chat_info_defaults */
+	silcpurple_login,			/* login */
+	silcpurple_close,			/* close */
 	silcpurple_send_im,			/* send_im */
 	silcpurple_set_info,			/* set_info */
-	NULL,						/* send_typing */
+	NULL,					/* send_typing */
 	silcpurple_get_info,			/* get_info */
-	silcpurple_set_status,		/* set_status */
+	silcpurple_set_status,			/* set_status */
 	silcpurple_idle_set,			/* set_idle */
 	silcpurple_change_passwd,		/* change_passwd */
 	silcpurple_add_buddy,			/* add_buddy */
-	NULL,						/* add_buddies */
+	NULL,					/* add_buddies */
 	silcpurple_remove_buddy,		/* remove_buddy */
-	NULL,						/* remove_buddies */
-	NULL,						/* add_permit */
-	NULL,						/* add_deny */
-	NULL,						/* rem_permit */
-	NULL,						/* rem_deny */
-	NULL,						/* set_permit_deny */
+	NULL,					/* remove_buddies */
+	NULL,					/* add_permit */
+	NULL,					/* add_deny */
+	NULL,					/* rem_permit */
+	NULL,					/* rem_deny */
+	NULL,					/* set_permit_deny */
 	silcpurple_chat_join,			/* join_chat */
-	NULL,						/* reject_chat */
+	NULL,					/* reject_chat */
 	silcpurple_get_chat_name,		/* get_chat_name */
-	silcpurple_chat_invite,		/* chat_invite */
-	silcpurple_chat_leave,		/* chat_leave */
-	NULL,						/* chat_whisper */
+	silcpurple_chat_invite,			/* chat_invite */
+	silcpurple_chat_leave,			/* chat_leave */
+	NULL,					/* chat_whisper */
 	silcpurple_chat_send,			/* chat_send */
 	silcpurple_keepalive,			/* keepalive */
-	NULL,						/* register_user */
-	NULL,						/* get_cb_info */
-	NULL,						/* get_cb_away */
-	NULL,						/* alias_buddy */
-	NULL,						/* group_buddy */
-	NULL,						/* rename_group */
-	NULL,						/* buddy_free */
-	NULL,						/* convo_closed */
-	NULL,						/* normalize */
-#ifdef SILC_ATTRIBUTE_USER_ICON
-	silcpurple_buddy_set_icon,			/* set_buddy_icon */
-#else
-	NULL,
-#endif
-	NULL,						/* remove_group */
-	NULL,						/* get_cb_real_name */
-	silcpurple_chat_set_topic,	/* set_chat_topic */
-	NULL,						/* find_blist_chat */
-	silcpurple_roomlist_get_list,	/* roomlist_get_list */
-	silcpurple_roomlist_cancel,	/* roomlist_cancel */
-	NULL,						/* roomlist_expand_category */
-	NULL,						/* can_receive_file */
+	NULL,					/* register_user */
+	NULL,					/* get_cb_info */
+	NULL,					/* get_cb_away */
+	NULL,					/* alias_buddy */
+	NULL,					/* group_buddy */
+	NULL,					/* rename_group */
+	NULL,					/* buddy_free */
+	NULL,					/* convo_closed */
+	NULL,					/* normalize */
+	silcpurple_buddy_set_icon,		/* set_buddy_icon */
+	NULL,					/* remove_group */
+	NULL,					/* get_cb_real_name */
+	silcpurple_chat_set_topic,		/* set_chat_topic */
+	NULL,					/* find_blist_chat */
+	silcpurple_roomlist_get_list,	        /* roomlist_get_list */
+	silcpurple_roomlist_cancel,		/* roomlist_cancel */
+	NULL,				        /* roomlist_expand_category */
+	NULL,					/* can_receive_file */
 	silcpurple_ftp_send_file,		/* send_file */
 	silcpurple_ftp_new_xfer,		/* new_xfer */
-	NULL,						/* offline_message */
+	NULL,					/* offline_message */
 	&silcpurple_wb_ops,			/* whiteboard_prpl_ops */
-	NULL,                       /* send_raw */
-	NULL,                       /* roomlist_room_serialize */
+	NULL,					/* send_raw */
+	NULL,				        /* roomlist_room_serialize */
 
 	/* padding */
 	NULL,
@@ -1811,29 +1863,29 @@
 	PURPLE_PLUGIN_MAGIC,
 	PURPLE_MAJOR_VERSION,
 	PURPLE_MINOR_VERSION,
-	PURPLE_PLUGIN_PROTOCOL,                             /**< type           */
-	NULL,                                             /**< ui_requirement */
-	0,                                                /**< flags          */
-	NULL,                                             /**< dependencies   */
-	PURPLE_PRIORITY_DEFAULT,                            /**< priority       */
+	PURPLE_PLUGIN_PROTOCOL,			/**< type           */
+	NULL,					/**< ui_requirement */
+	0,					/**< flags          */
+	NULL,					/**< dependencies   */
+	PURPLE_PRIORITY_DEFAULT,		/**< priority       */
 
-	"prpl-silc",                                      /**< id             */
-	"SILC",                                           /**< name           */
-	"1.0",                                            /**< version        */
+	"prpl-silc",				/**< id             */
+	"SILC",					/**< name           */
+	"1.1",					/**< version        */
 	/**  summary        */
 	N_("SILC Protocol Plugin"),
 	/**  description    */
 	N_("Secure Internet Live Conferencing (SILC) Protocol"),
-	"Pekka Riikonen",                                 /**< author         */
-	"http://silcnet.org/",                            /**< homepage       */
+	"Pekka Riikonen",			/**< author         */
+	"http://silcnet.org/",			/**< homepage       */
 
-	NULL,                                             /**< load           */
-	NULL,                                             /**< unload         */
-	NULL,                                             /**< destroy        */
+	NULL,					/**< load           */
+	NULL,					/**< unload         */
+	NULL,					/**< destroy        */
 
-	NULL,                                             /**< ui_info        */
-	&prpl_info,                                       /**< extra_info     */
-	NULL,                                             /**< prefs_info     */
+	NULL,					/**< ui_info        */
+	&prpl_info,				/**< extra_info     */
+	NULL,					/**< prefs_info     */
 	silcpurple_actions,
 
 	/* padding */
@@ -1860,18 +1912,18 @@
 
 	/* Account options */
 	option = purple_account_option_string_new(_("Connect server"),
-						"server",
-						"silc.silcnet.org");
+						  "server",
+						  "silc.silcnet.org");
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 	option = purple_account_option_int_new(_("Port"), "port", 706);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 	g_snprintf(tmp, sizeof(tmp), "%s" G_DIR_SEPARATOR_S "public_key.pub", silcpurple_silcdir());
 	option = purple_account_option_string_new(_("Public Key file"),
-						"public-key", tmp);
+						  "public-key", tmp);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 	g_snprintf(tmp, sizeof(tmp), "%s" G_DIR_SEPARATOR_S "private_key.prv", silcpurple_silcdir());
 	option = purple_account_option_string_new(_("Private Key file"),
-						"private-key", tmp);
+						  "private-key", tmp);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 
 	for (i = 0; silc_default_ciphers[i].name; i++) {
@@ -1893,35 +1945,36 @@
 	option = purple_account_option_list_new(_("HMAC"), "hmac", list);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 
-	option = purple_account_option_bool_new(_("Public key authentication"),
-					      "pubkey-auth", FALSE);
+	option = purple_account_option_bool_new(_("Use Perfect Forward Secrecy"),
+						"pfs", FALSE);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
-	option = purple_account_option_bool_new(_("Reject watching by other users"),
-					      "reject-watch", FALSE);
-	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
-	option = purple_account_option_bool_new(_("Block invites"),
-					      "block-invites", FALSE);
+
+	option = purple_account_option_bool_new(_("Public key authentication"),
+						"pubkey-auth", FALSE);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 	option = purple_account_option_bool_new(_("Block IMs without Key Exchange"),
-					      "block-ims", FALSE);
-	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
-	option = purple_account_option_bool_new(_("Reject online status attribute requests"),
-					      "reject-attrs", FALSE);
+						"block-ims", FALSE);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 	option = purple_account_option_bool_new(_("Block messages to whiteboard"),
-					      "block-wb", FALSE);
+						"block-wb", FALSE);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 	option = purple_account_option_bool_new(_("Automatically open whiteboard"),
-					      "open-wb", FALSE);
+						"open-wb", FALSE);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 	option = purple_account_option_bool_new(_("Digitally sign and verify all messages"),
-					      "sign-verify", FALSE);
+						"sign-verify", FALSE);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 
 	purple_prefs_remove("/plugins/prpl/silc");
 
+	silc_log_set_callback(SILC_LOG_ERROR, silcpurple_log_error, NULL);
 	silcpurple_register_commands();
 
+#if 0
+silc_log_debug(TRUE);
+silc_log_set_debug_string("*client*");
+#endif
+
 #ifdef _WIN32
 	silc_net_win32_init();
 #endif
--- a/libpurple/protocols/silc/silcpurple.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/silc/silcpurple.h	Tue Jun 12 21:21:37 2007 +0000
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 2004 Pekka Riikonen
+  Copyright (C) 2004 - 2007 Pekka Riikonen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
@@ -66,6 +66,8 @@
 typedef struct SilcPurpleStruct {
 	SilcClient client;
 	SilcClientConnection conn;
+	SilcPublicKey public_key;
+	SilcPrivateKey private_key;
 
 	guint scheduler;
 	PurpleConnection *gc;
@@ -75,9 +77,7 @@
 
 	char *motd;
 	PurpleRoomlist *roomlist;
-#ifdef HAVE_SILCMIME_H
 	SilcMimeAssembler mimeass;
-#endif
 	unsigned int detaching            : 1;
 	unsigned int resuming             : 1;
 	unsigned int roomlist_canceled    : 1;
@@ -85,27 +85,29 @@
 } *SilcPurple;
 
 
+void silc_say(SilcClient client, SilcClientConnection conn,
+	      SilcClientMessageType type, char *msg, ...);
+SilcBool silcpurple_command_reply(SilcClient client, SilcClientConnection conn,
+				  SilcCommand command, SilcStatus status,
+				  SilcStatus error, void *context, va_list ap);
 gboolean silcpurple_check_silc_dir(PurpleConnection *gc);
-void silcpurple_chat_join_done(SilcClient client,
-			     SilcClientConnection conn,
-			     SilcClientEntry *clients,
-			     SilcUInt32 clients_count,
-			     void *context);
 const char *silcpurple_silcdir(void);
 const char *silcpurple_session_file(const char *account);
 void silcpurple_verify_public_key(SilcClient client, SilcClientConnection conn,
-				const char *name, SilcSocketType conn_type,
-				unsigned char *pk, SilcUInt32 pk_len,
-				SilcSKEPKType pk_type,
-				SilcVerifyPublicKey completion, void *context);
+				  const char *name,
+				  SilcConnectionType conn_type,
+				  SilcPublicKey public_key,
+				  SilcVerifyPublicKey completion,
+				  void *context);
 GList *silcpurple_buddy_menu(PurpleBuddy *buddy);
 void silcpurple_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group);
 void silcpurple_send_buddylist(PurpleConnection *gc);
 void silcpurple_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group);
 void silcpurple_buddy_keyagr_request(SilcClient client,
-				   SilcClientConnection conn,
-				   SilcClientEntry client_entry,
-				   const char *hostname, SilcUInt16 port);
+				     SilcClientConnection conn,
+				     SilcClientEntry client_entry,
+				     const char *hostname, SilcUInt16 port,
+				     SilcUInt16 protocol);
 void silcpurple_idle_set(PurpleConnection *gc, int idle);
 void silcpurple_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full);
 char *silcpurple_status_text(PurpleBuddy *b);
@@ -140,17 +142,13 @@
 PurpleRoomlist *silcpurple_roomlist_get_list(PurpleConnection *gc);
 void silcpurple_roomlist_cancel(PurpleRoomlist *list);
 void silcpurple_chat_chauth_show(SilcPurple sg, SilcChannelEntry channel,
-			       SilcBuffer channel_pubkeys);
+				 SilcDList channel_pubkeys);
 void silcpurple_parse_attrs(SilcDList attrs, char **moodstr, char **statusstr,
 					 char **contactstr, char **langstr, char **devicestr,
 					 char **tzstr, char **geostr);
-#ifdef SILC_ATTRIBUTE_USER_ICON
 void silcpurple_buddy_set_icon(PurpleConnection *gc, PurpleStoredImage *img);
-#endif
-#ifdef HAVE_SILCMIME_H
 char *silcpurple_file2mime(const char *filename);
 SilcDList silcpurple_image_message(const char *msg, SilcUInt32 *mflags);
-#endif
 
 #ifdef _WIN32
 typedef int uid_t;
--- a/libpurple/protocols/silc/util.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/silc/util.c	Tue Jun 12 21:21:37 2007 +0000
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 2004 - 2005 Pekka Riikonen
+  Copyright (C) 2004 - 2007 Pekka Riikonen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
@@ -17,7 +17,7 @@
 
 */
 
-#include "silcincludes.h"
+#include "silc.h"
 #include "silcclient.h"
 #include "silcpurple.h"
 #include "imgstore.h"
@@ -206,22 +206,24 @@
 		if (errno == ENOENT) {
 			purple_connection_update_progress(gc, _("Creating SILC key pair..."), 1, 5);
 			if (!silc_create_key_pair(SILCPURPLE_DEF_PKCS,
-					     SILCPURPLE_DEF_PKCS_LEN,
-					     file_public_key, file_private_key, NULL,
-					     (gc->password == NULL) ? "" : gc->password,
-						 NULL, NULL, NULL, FALSE)) {
-				purple_debug_error("silc", "Couldn't create key pair\n");
+						  SILCPURPLE_DEF_PKCS_LEN,
+						  file_public_key,
+						  file_private_key, NULL,
+						  (gc->password == NULL)
+						  ? "" : gc->password,
+						  NULL, NULL, FALSE)) {
+				purple_connection_error(gc, _("Cannot create SILC key pair\n"));
 				return FALSE;
 			}
 
 			if ((g_stat(file_public_key, &st)) == -1) {
 				purple_debug_error("silc", "Couldn't stat '%s' public key, error: %s\n",
-					file_public_key, strerror(errno));
+						   file_public_key, strerror(errno));
 				return FALSE;
 			}
 		} else {
 			purple_debug_error("silc", "Couldn't stat '%s' public key, error: %s\n",
-							 file_public_key, strerror(errno));
+					   file_public_key, strerror(errno));
 			return FALSE;
 		}
 	}
@@ -237,7 +239,7 @@
 	if ((fd = g_open(file_private_key, O_RDONLY, 0)) != -1) {
 		if ((fstat(fd, &st)) == -1) {
 			purple_debug_error("silc", "Couldn't stat '%s' private key, error: %s\n",
-							 file_private_key, strerror(errno));
+					   file_private_key, strerror(errno));
 			close(fd);
 			return FALSE;
 		}
@@ -246,18 +248,20 @@
 		if (errno == ENOENT) {
 			purple_connection_update_progress(gc, _("Creating SILC key pair..."), 1, 5);
 			if (!silc_create_key_pair(SILCPURPLE_DEF_PKCS,
-					     SILCPURPLE_DEF_PKCS_LEN,
-					     file_public_key, file_private_key, NULL,
-					     (gc->password == NULL) ? "" : gc->password,
-						 NULL, NULL, NULL, FALSE)) {
-				purple_debug_error("silc", "Couldn't create key pair\n");
+						  SILCPURPLE_DEF_PKCS_LEN,
+						  file_public_key,
+						  file_private_key, NULL,
+						  (gc->password == NULL)
+						  ? "" : gc->password,
+						  NULL, NULL, FALSE)) {
+				purple_connection_error(gc, _("Cannot create SILC key pair\n"));
 				return FALSE;
 			}
 
 			if ((fd = g_open(file_private_key, O_RDONLY, 0)) != -1) {
 				if ((fstat(fd, &st)) == -1) {
 					purple_debug_error("silc", "Couldn't stat '%s' private key, error: %s\n",
-							 file_private_key, strerror(errno));
+							   file_private_key, strerror(errno));
 					close(fd);
 					return FALSE;
 				}
@@ -266,12 +270,12 @@
 			 * will set the permissions */
 			else if ((g_stat(file_private_key, &st)) == -1) {
 				purple_debug_error("silc", "Couldn't stat '%s' private key, error: %s\n",
-					file_private_key, strerror(errno));
+						   file_private_key, strerror(errno));
 				return FALSE;
 			}
 		} else {
 			purple_debug_error("silc", "Couldn't stat '%s' private key, error: %s\n",
-							 file_private_key, strerror(errno));
+					   file_private_key, strerror(errno));
 			return FALSE;
 		}
 	}
@@ -323,30 +327,29 @@
 #endif
 
 void silcpurple_show_public_key(SilcPurple sg,
-			      const char *name, SilcPublicKey public_key,
-			      GCallback callback, void *context)
+				const char *name, SilcPublicKey public_key,
+				GCallback callback, void *context)
 {
 	SilcPublicKeyIdentifier ident;
-	SilcPKCS pkcs;
+	SilcSILCPublicKey silc_pubkey;
 	char *fingerprint, *babbleprint;
 	unsigned char *pk;
 	SilcUInt32 pk_len, key_len = 0;
 	GString *s;
 	char *buf;
 
-	ident = silc_pkcs_decode_identifier(public_key->identifier);
-	if (!ident)
-		return;
+	/* We support showing only SILC public keys for now */
+	if (silc_pkcs_get_type(public_key) != SILC_PKCS_SILC)
+	  return;
+
+	silc_pubkey = silc_pkcs_get_context(SILC_PKCS_SILC, public_key);
+	ident = &silc_pubkey->identifier;
+	key_len = silc_pkcs_public_key_get_len(public_key);
 
 	pk = silc_pkcs_public_key_encode(public_key, &pk_len);
 	fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
 	babbleprint = silc_hash_babbleprint(NULL, pk, pk_len);
 
-	if (silc_pkcs_alloc((unsigned char *)public_key->name, &pkcs)) {
-		key_len = silc_pkcs_public_key_set(pkcs, public_key);
-		silc_pkcs_free(pkcs);
-	}
-
 	s = g_string_new("");
 	if (ident->realname)
 		/* Hint for translators: Please check the tabulator width here and in
@@ -363,8 +366,10 @@
 		g_string_append_printf(s, _("Organization: \t%s\n"), ident->org);
 	if (ident->country)
 		g_string_append_printf(s, _("Country: \t%s\n"), ident->country);
-	g_string_append_printf(s, _("Algorithm: \t%s\n"), public_key->name);
+	g_string_append_printf(s, _("Algorithm: \t%s\n"), silc_pubkey->pkcs->name);
 	g_string_append_printf(s, _("Key Length: \t%d bits\n"), (int)key_len);
+	if (ident->version)
+	  g_string_append_printf(s, _("Version: \t%s\n"), ident->version);
 	g_string_append_printf(s, "\n");
 	g_string_append_printf(s, _("Public Key Fingerprint:\n%s\n\n"), fingerprint);
 	g_string_append_printf(s, _("Public Key Babbleprint:\n%s"), babbleprint);
@@ -372,15 +377,14 @@
 	buf = g_string_free(s, FALSE);
 
 	purple_request_action(sg->gc, _("Public Key Information"),
-			    _("Public Key Information"),
-			    buf, 0, purple_connection_get_account(sg->gc),
-				NULL, NULL, context, 1, _("Close"), callback);
+			      _("Public Key Information"),
+			      buf, 0, purple_connection_get_account(sg->gc),
+			      NULL, NULL, context, 1, _("Close"), callback);
 
 	g_free(buf);
 	silc_free(fingerprint);
 	silc_free(babbleprint);
 	silc_free(pk);
-	silc_pkcs_free_identifier(ident);
 }
 
 SilcAttributePayload
@@ -400,7 +404,7 @@
 }
 
 void silcpurple_get_umode_string(SilcUInt32 mode, char *buf,
-			       SilcUInt32 buf_size)
+				 SilcUInt32 buf_size)
 {
 	memset(buf, 0, buf_size);
 	if ((mode & SILC_UMODE_SERVER_OPERATOR) ||
@@ -435,7 +439,7 @@
 }
 
 void silcpurple_get_chmode_string(SilcUInt32 mode, char *buf,
-				SilcUInt32 buf_size)
+				  SilcUInt32 buf_size)
 {
 	memset(buf, 0, buf_size);
 	if (mode & SILC_CHANNEL_MODE_FOUNDER_AUTH)
@@ -482,8 +486,8 @@
 
 void
 silcpurple_parse_attrs(SilcDList attrs, char **moodstr, char **statusstr,
-					 char **contactstr, char **langstr, char **devicestr,
-					 char **tzstr, char **geostr)
+		       char **contactstr, char **langstr, char **devicestr,
+		       char **tzstr, char **geostr)
 {
 	SilcAttributePayload attr;
 	SilcAttributeMood mood = 0;
@@ -610,7 +614,6 @@
 				geo.accuracy ? geo.accuracy : "");
 }
 
-#ifdef HAVE_SILCMIME_H
 /* Returns MIME type of filetype */
 
 char *silcpurple_file2mime(const char *filename)
@@ -620,23 +623,23 @@
 	ct = strrchr(filename, '.');
 	if (!ct)
 		return NULL;
-	else if (!g_ascii_strcasecmp(".png", ct))
+	else if (!strcasecmp(".png", ct))
 		return strdup("image/png");
-	else if (!g_ascii_strcasecmp(".jpg", ct))
+	else if (!strcasecmp(".jpg", ct))
 		return strdup("image/jpeg");
-	else if (!g_ascii_strcasecmp(".jpeg", ct))
+	else if (!strcasecmp(".jpeg", ct))
 		return strdup("image/jpeg");
-	else if (!g_ascii_strcasecmp(".gif", ct))
+	else if (!strcasecmp(".gif", ct))
 		return strdup("image/gif");
-	else if (!g_ascii_strcasecmp(".tiff", ct))
+	else if (!strcasecmp(".tiff", ct))
 		return strdup("image/tiff");
-	
+
 	return NULL;
 }
 
-/* Checks if message has images, and assembles MIME message if it has. 
-   If only one image is present, creates simple MIME image message.  If 
-   there are multiple images and/or text with images multipart MIME 
+/* Checks if message has images, and assembles MIME message if it has.
+   If only one image is present, creates simple MIME image message.  If
+   there are multiple images and/or text with images multipart MIME
    message is created. */
 
 SilcDList silcpurple_image_message(const char *msg, SilcUInt32 *mflags)
@@ -666,8 +669,9 @@
 			tmp = g_strndup(last, start - last);
 			text = purple_unescape_html(tmp);
 			g_free(tmp);
+
 			/* Add text */
-			silc_mime_add_data(p, text, strlen(text));
+			silc_mime_add_data(p, (const unsigned char *)text, strlen(text));
 			g_free(text);
 
 			if (!parts)
@@ -720,7 +724,7 @@
 				    "text/plain; charset=utf-8");
 
 		/* Add text */
-		silc_mime_add_data(p, tmp, strlen(tmp));
+		silc_mime_add_data(p, (const unsigned char *)tmp, strlen(tmp));
 		g_free(tmp);
 
 		if (!parts)
@@ -742,7 +746,7 @@
 		silc_mime_add_field(mime, "MIME-Version", "1.0");
 		g_snprintf(b, sizeof(b), "b%4X%4X",
 			   (unsigned int)time(NULL),
-			   silc_dlist_count(parts)); 
+			   silc_dlist_count(parts));
 		silc_mime_set_multipart(mime, "mixed", b);
 		silc_dlist_start(parts);
 		while ((p = silc_dlist_get(parts)) != SILC_LIST_END)
@@ -767,5 +771,3 @@
 
 	return list;
 }
-
-#endif /* HAVE_SILCMIME_H */
--- a/libpurple/protocols/silc/wb.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/silc/wb.c	Tue Jun 12 21:21:37 2007 +0000
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 2005 Pekka Riikonen
+  Copyright (C) 2005 - 2007 Pekka Riikonen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
@@ -17,7 +17,7 @@
 
 */
 
-#include "silcincludes.h"
+#include "silc.h"
 #include "silcclient.h"
 #include "silcpurple.h"
 #include "wb.h"
@@ -30,7 +30,7 @@
   2 bytes	height
   4 bytes	brush color
   2 bytes	brush size
-  n bytes	data 
+  n bytes	data
 
   Data:
 
@@ -204,7 +204,7 @@
 		silc_buffer_pull(&buf, 8);
 		x = dx;
 		y = dy;
-		while (buf.len > 0) {
+		while (silc_buffer_len(&buf) > 0) {
 			ret = silc_buffer_unformat(&buf,
 						   SILC_STR_UI_INT(&dx),
 						   SILC_STR_UI_INT(&dy),
@@ -214,7 +214,7 @@
 			silc_buffer_pull(&buf, 8);
 
 			purple_whiteboard_draw_line(wb, x, y, x + dx, y + dy,
-						  brush_color, brush_size);
+						    brush_color, brush_size);
 			x += dx;
 			y += dy;
 		}
@@ -253,8 +253,8 @@
 }
 
 static void
-silcpurple_wb_request(SilcClient client, const unsigned char *message, 
-		    SilcUInt32 message_len, SilcClientEntry sender, 
+silcpurple_wb_request(SilcClient client, const unsigned char *message,
+		    SilcUInt32 message_len, SilcClientEntry sender,
 		    SilcChannelEntry channel)
 {
 	char tmp[128];
@@ -406,16 +406,16 @@
 	/* Send the message */
 	if (wbs->type == 0) {
 		/* Private message */
-		silc_client_send_private_message(sg->client, sg->conn, 
-						 wbs->u.client, 
-						 SILC_MESSAGE_FLAG_DATA,
-						 packet->head, len, TRUE);
+		silc_client_send_private_message(sg->client, sg->conn,
+						 wbs->u.client,
+						 SILC_MESSAGE_FLAG_DATA, NULL,
+						 packet->head, len);
 	} else if (wbs->type == 1) {
 		/* Channel message. Channel private keys are not supported. */
 		silc_client_send_channel_message(sg->client, sg->conn,
 						 wbs->u.channel, NULL,
-						 SILC_MESSAGE_FLAG_DATA,
-						 packet->head, len, TRUE);
+						 SILC_MESSAGE_FLAG_DATA, NULL,
+						 packet->head, len);
 	}
 
 	silc_buffer_free(packet);
@@ -501,16 +501,16 @@
 	/* Send the message */
 	if (wbs->type == 0) {
 		/* Private message */
-		silc_client_send_private_message(sg->client, sg->conn, 
-						 wbs->u.client, 
-						 SILC_MESSAGE_FLAG_DATA,
-						 packet->head, len, TRUE);
+		silc_client_send_private_message(sg->client, sg->conn,
+						 wbs->u.client,
+						 SILC_MESSAGE_FLAG_DATA, NULL,
+						 packet->head, len);
 	} else if (wbs->type == 1) {
 		/* Channel message */
 		silc_client_send_channel_message(sg->client, sg->conn,
 						 wbs->u.channel, NULL,
-						 SILC_MESSAGE_FLAG_DATA,
-						 packet->head, len, TRUE);
+						 SILC_MESSAGE_FLAG_DATA, NULL,
+						 packet->head, len);
 	}
 
 	silc_buffer_free(packet);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/silc10/Makefile.am	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,36 @@
+EXTRA_DIST = README TODO Makefile.mingw
+
+pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION)
+
+SILCSOURCES = silc.c silcpurple.h buddy.c chat.c ft.c ops.c pk.c util.c wb.c wb.h
+
+AM_CFLAGS = $(st)
+
+libsilcpurple_la_LDFLAGS = -module -avoid-version
+
+if STATIC_SILC
+
+st = -DPURPLE_STATIC_PRPL $(SILC_CFLAGS)
+noinst_LIBRARIES = libsilcpurple.a
+pkg_LTLIBRARIES =
+
+libsilcpurple_a_SOURCES = $(SILCSOURCES)
+libsilcpurple_a_CFLAGS  = $(AM_CFLAGS)
+libsilcpurple_a_LIBADD  = $(SILC_LIBS)
+
+else
+
+st = $(SILC_CFLAGS)
+pkg_LTLIBRARIES = libsilcpurple.la
+noinst_LIBRARIES =
+
+libsilcpurple_la_SOURCES = $(SILCSOURCES)
+libsilcpurple_la_LIBADD  = $(GLIB_LIBS) $(SILC_LIBS)
+
+endif
+
+AM_CPPFLAGS = \
+	-I$(top_srcdir)/libpurple \
+	-I$(top_builddir)/libpurple \
+	$(GLIB_CFLAGS) \
+	$(DEBUG_CFLAGS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/silc10/Makefile.mingw	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,91 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for win32 (mingw) version of libsilc protocol plugin
+#
+
+PIDGIN_TREE_TOP := ../../..
+include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
+
+TARGET = libsilc
+NEEDED_DLLS =		$(SILC_TOOLKIT)/lib/silc.dll \
+			$(SILC_TOOLKIT)/lib/silcclient.dll
+TYPE = PLUGIN
+
+# Static or Plugin...
+ifeq ($(TYPE),STATIC)
+  DEFINES += -DSTATIC
+  DLL_INSTALL_DIR =	$(PURPLE_INSTALL_DIR)
+else
+ifeq ($(TYPE),PLUGIN)
+  DLL_INSTALL_DIR =	$(PURPLE_INSTALL_PLUGINS_DIR)
+endif
+endif
+
+##
+## INCLUDE PATHS
+##
+INCLUDE_PATHS +=	-I. \
+			-I$(GTK_TOP)/include \
+			-I$(GTK_TOP)/include/glib-2.0 \
+			-I$(GTK_TOP)/lib/glib-2.0/include \
+			-I$(PURPLE_TOP) \
+			-I$(PURPLE_TOP)/win32 \
+			-I$(PIDGIN_TREE_TOP) \
+			-I$(SILC_TOOLKIT)/include
+
+LIB_PATHS +=		-L$(GTK_TOP)/lib \
+			-L$(PURPLE_TOP) \
+			-L$(SILC_TOOLKIT)/lib
+
+##
+##  SOURCES, OBJECTS
+##
+C_SRC =			silc.c \
+			buddy.c \
+			chat.c \
+			ft.c \
+			ops.c \
+			pk.c \
+			util.c \
+			wb.c
+
+OBJECTS = $(C_SRC:%.c=%.o)
+
+##
+## LIBRARIES
+##
+LIBS = \
+			-lglib-2.0 \
+			-lws2_32 \
+			-lintl \
+			-lpurple \
+			-lsilc \
+			-lsilcclient
+
+include $(PIDGIN_COMMON_RULES)
+
+##
+## TARGET DEFINITIONS
+##
+.PHONY: all install clean
+
+all: $(TARGET).dll
+
+install: all $(DLL_INSTALL_DIR) $(PURPLE_INSTALL_DIR)
+	cp $(TARGET).dll $(DLL_INSTALL_DIR)
+	cp $(NEEDED_DLLS) $(PURPLE_INSTALL_DIR)
+
+$(OBJECTS): $(PURPLE_CONFIG_H)
+
+$(TARGET).dll: $(PURPLE_DLL).a $(OBJECTS)
+	$(CC) -shared $(OBJECTS) $(LIB_PATHS) $(LIBS) $(DLL_LD_FLAGS) -Wl,--image-base,0x64000000 -o $(TARGET).dll
+
+##
+## CLEAN RULES
+##
+clean:
+	rm -f $(OBJECTS)
+	rm -f $(TARGET).dll
+
+include $(PIDGIN_COMMON_TARGETS)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/silc10/README	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,31 @@
+SILC Purple Plugin
+==================
+
+This is the Purple protocol plugin of the protocol called Secure Internet
+Live Conferencing (SILC).  The implementation will use the SILC Toolkit, 
+freely available from the http://silcnet.org/ site, for the actual SILC 
+protocol implementation.
+
+To include SILC into Purple, one needs to first compile and install 
+the SILC Toolkit.  It is done as follows:
+
+	./configure --enable-shared
+	make
+	make install
+
+This will compile shared libraries of the SILC Toolkit.  If the --prefix 
+is not given to ./configure, the binaries are installed into the 
+/usr/local/silc directory.
+
+Once the Toolkit is installed one needs to tell Purple's ./configure
+script where the SILC Toolkit is located.  It is done as simply as:
+
+	./configure
+
+if pkg-config is installed in your system.  If it is isn't it's done as:
+
+	./configure --with-silc-libs=/path/to/silc/lib
+		    --with-silc-includes=/path/to/silc/include
+
+If the SILC Toolkit cannot be found then the SILC protocol plugin will
+not be compiled.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/silc10/TODO	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,8 @@
+Features TODO (maybe)
+=====================
+
+Preferences
+	- Add joined channels to buddy list automatically (during
+	  session)
+	- Add joined channels to buddy list automatically permanently
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/silc10/buddy.c	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,1728 @@
+/*
+
+  silcpurple_buddy.c
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  Copyright (C) 2004 Pekka Riikonen
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; version 2 of the License.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+*/
+
+#include "silcincludes.h"
+#include "silcclient.h"
+#include "silcpurple.h"
+#include "wb.h"
+
+/***************************** Key Agreement *********************************/
+
+static void
+silcpurple_buddy_keyagr(PurpleBlistNode *node, gpointer data);
+
+static void
+silcpurple_buddy_keyagr_do(PurpleConnection *gc, const char *name,
+			 			 gboolean force_local);
+
+typedef struct {
+	char *nick;
+	PurpleConnection *gc;
+} *SilcPurpleResolve;
+
+static void
+silcpurple_buddy_keyagr_resolved(SilcClient client,
+			       SilcClientConnection conn,
+			       SilcClientEntry *clients,
+			       SilcUInt32 clients_count,
+			       void *context)
+{
+	PurpleConnection *gc = client->application;
+	SilcPurpleResolve r = context;
+	char tmp[256];
+
+	if (!clients) {
+		g_snprintf(tmp, sizeof(tmp),
+			   _("User %s is not present in the network"), r->nick);
+		purple_notify_error(gc, _("Key Agreement"),
+				  _("Cannot perform the key agreement"), tmp);
+		silc_free(r->nick);
+		silc_free(r);
+		return;
+	}
+
+	silcpurple_buddy_keyagr_do(gc, r->nick, FALSE);
+	silc_free(r->nick);
+	silc_free(r);
+}
+
+typedef struct {
+	gboolean responder;
+} *SilcPurpleKeyAgr;
+
+static void
+silcpurple_buddy_keyagr_cb(SilcClient client,
+			 SilcClientConnection conn,
+			 SilcClientEntry client_entry,
+			 SilcKeyAgreementStatus status,
+			 SilcSKEKeyMaterial *key,
+			 void *context)
+{
+	PurpleConnection *gc = client->application;
+	SilcPurple sg = gc->proto_data;
+	SilcPurpleKeyAgr a = context;
+
+	if (!sg->conn)
+		return;
+
+	switch (status) {
+	case SILC_KEY_AGREEMENT_OK:
+		{
+			PurpleConversation *convo;
+			char tmp[128];
+
+			/* Set the private key for this client */
+			silc_client_del_private_message_key(client, conn, client_entry);
+			silc_client_add_private_message_key_ske(client, conn, client_entry,
+								NULL, NULL, key, a->responder);
+			silc_ske_free_key_material(key);
+
+			
+			/* Open IM window */
+			convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
+									client_entry->nickname, sg->account);
+			if (convo) {
+				/* we don't have windows in the core anymore...but we may want to
+				 * provide some method for asking the UI to show the window
+				purple_conv_window_show(purple_conversation_get_window(convo));
+				 */
+			} else {
+				convo = purple_conversation_new(PURPLE_CONV_TYPE_IM, sg->account,
+							      client_entry->nickname);
+			}
+			g_snprintf(tmp, sizeof(tmp), "%s [private key]", client_entry->nickname);
+			purple_conversation_set_title(convo, tmp);
+		}
+		break;
+
+	case SILC_KEY_AGREEMENT_ERROR:
+		purple_notify_error(gc, _("Key Agreement"),
+				  _("Error occurred during key agreement"), NULL);
+		break;
+
+	case SILC_KEY_AGREEMENT_FAILURE:
+		purple_notify_error(gc, _("Key Agreement"), _("Key Agreement failed"), NULL);
+		break;
+
+	case SILC_KEY_AGREEMENT_TIMEOUT:
+		purple_notify_error(gc, _("Key Agreement"),
+				  _("Timeout during key agreement"), NULL);
+		break;
+
+	case SILC_KEY_AGREEMENT_ABORTED:
+		purple_notify_error(gc, _("Key Agreement"),
+				  _("Key agreement was aborted"), NULL);
+		break;
+
+	case SILC_KEY_AGREEMENT_ALREADY_STARTED:
+		purple_notify_error(gc, _("Key Agreement"),
+				  _("Key agreement is already started"), NULL);
+		break;
+
+	case SILC_KEY_AGREEMENT_SELF_DENIED:
+		purple_notify_error(gc, _("Key Agreement"),
+				  _("Key agreement cannot be started with yourself"),
+				  NULL);
+		break;
+
+	default:
+		break;
+	}
+
+	silc_free(a);
+}
+
+static void
+silcpurple_buddy_keyagr_do(PurpleConnection *gc, const char *name,
+			 gboolean force_local)
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClientEntry *clients;
+	SilcUInt32 clients_count;
+	char *local_ip = NULL, *remote_ip = NULL;
+	gboolean local = TRUE;
+	char *nickname;
+	SilcPurpleKeyAgr a;
+
+	if (!sg->conn || !name)
+		return;
+
+	if (!silc_parse_userfqdn(name, &nickname, NULL))
+		return;
+
+	/* Find client entry */
+	clients = silc_client_get_clients_local(sg->client, sg->conn, nickname, name,
+						&clients_count);
+	if (!clients) {
+		/* Resolve unknown user */
+		SilcPurpleResolve r = silc_calloc(1, sizeof(*r));
+		if (!r)
+			return;
+		r->nick = g_strdup(name);
+		r->gc = gc;
+		silc_client_get_clients(sg->client, sg->conn, nickname, NULL,
+					silcpurple_buddy_keyagr_resolved, r);
+		silc_free(nickname);
+		return;
+	}
+
+	/* Resolve the local IP from the outgoing socket connection.  We resolve
+	   it to check whether we have a private range IP address or public IP
+	   address.  If we have public then we will assume that we are not behind
+	   NAT and will provide automatically the point of connection to the
+	   agreement.  If we have private range address we assume that we are
+	   behind NAT and we let the responder provide the point of connection.
+
+	   The algorithm also checks the remote IP address of server connection.
+	   If it is private range address and we have private range address we
+	   assume that we are chatting in LAN and will provide the point of
+	   connection.
+
+	   Naturally this algorithm does not always get things right. */
+
+	if (silc_net_check_local_by_sock(sg->conn->sock->sock, NULL, &local_ip)) {
+		/* Check if the IP is private */
+		if (!force_local && silcpurple_ip_is_private(local_ip)) {
+			local = FALSE;
+
+			/* Local IP is private, resolve the remote server IP to see whether
+			   we are talking to Internet or just on LAN. */
+			if (silc_net_check_host_by_sock(sg->conn->sock->sock, NULL,
+							&remote_ip))
+				if (silcpurple_ip_is_private(remote_ip))
+					/* We assume we are in LAN.  Let's provide
+					   the connection point. */
+					local = TRUE;
+		}
+	}
+
+	if (force_local)
+		local = TRUE;
+
+	if (local && !local_ip)
+		local_ip = silc_net_localip();
+
+	a = silc_calloc(1, sizeof(*a));
+	if (!a)
+		return;
+	a->responder = local;
+
+	/* Send the key agreement request */
+	silc_client_send_key_agreement(sg->client, sg->conn, clients[0],
+				       local ? local_ip : NULL, NULL, 0, 60,
+				       silcpurple_buddy_keyagr_cb, a);
+
+	silc_free(local_ip);
+	silc_free(remote_ip);
+	silc_free(clients);
+}
+
+typedef struct {
+	SilcClient client;
+	SilcClientConnection conn;
+	SilcClientID client_id;
+	char *hostname;
+	SilcUInt16 port;
+} *SilcPurpleKeyAgrAsk;
+
+static void
+silcpurple_buddy_keyagr_request_cb(SilcPurpleKeyAgrAsk a, gint id)
+{
+	SilcPurpleKeyAgr ai;
+	SilcClientEntry client_entry;
+
+	if (id != 1)
+		goto out;
+
+	/* Get the client entry. */
+	client_entry = silc_client_get_client_by_id(a->client, a->conn,
+						    &a->client_id);
+	if (!client_entry) {
+		purple_notify_error(a->client->application, _("Key Agreement"),
+				  _("The remote user is not present in the network any more"),
+				  NULL);
+		goto out;
+	}
+
+	/* If the hostname was provided by the requestor perform the key agreement
+	   now.  Otherwise, we will send him a request to connect to us. */
+	if (a->hostname) {
+		ai = silc_calloc(1, sizeof(*ai));
+		if (!ai)
+			goto out;
+		ai->responder = FALSE;
+		silc_client_perform_key_agreement(a->client, a->conn, client_entry,
+						  a->hostname, a->port,
+						  silcpurple_buddy_keyagr_cb, ai);
+	} else {
+		/* Send request.  Force us as the point of connection since requestor
+		   did not provide the point of connection. */
+		silcpurple_buddy_keyagr_do(a->client->application,
+					 client_entry->nickname, TRUE);
+	}
+
+ out:
+	silc_free(a->hostname);
+	silc_free(a);
+}
+
+void silcpurple_buddy_keyagr_request(SilcClient client,
+				   SilcClientConnection conn,
+				   SilcClientEntry client_entry,
+				   const char *hostname, SilcUInt16 port)
+{
+	char tmp[128], tmp2[128];
+	SilcPurpleKeyAgrAsk a;
+	PurpleConnection *gc = client->application;
+
+	g_snprintf(tmp, sizeof(tmp),
+		   _("Key agreement request received from %s. Would you like to "
+		     "perform the key agreement?"), client_entry->nickname);
+	if (hostname)
+		g_snprintf(tmp2, sizeof(tmp2),
+			   _("The remote user is waiting key agreement on:\n"
+			     "Remote host: %s\nRemote port: %d"), hostname, port);
+
+	a = silc_calloc(1, sizeof(*a));
+	if (!a)
+		return;
+	a->client = client;
+	a->conn = conn;
+	a->client_id = *client_entry->id;
+	if (hostname)
+		a->hostname = strdup(hostname);
+	a->port = port;
+
+	purple_request_action(client->application, _("Key Agreement Request"), tmp,
+			    hostname ? tmp2 : NULL, 1, gc->account, client_entry->nickname,
+				NULL, a, 2, _("Yes"), G_CALLBACK(silcpurple_buddy_keyagr_request_cb),
+			    _("No"), G_CALLBACK(silcpurple_buddy_keyagr_request_cb));
+}
+
+static void
+silcpurple_buddy_keyagr(PurpleBlistNode *node, gpointer data)
+{
+	PurpleBuddy *buddy;
+
+	buddy = (PurpleBuddy *)node;
+	silcpurple_buddy_keyagr_do(buddy->account->gc, buddy->name, FALSE);
+}
+
+
+/**************************** Static IM Key **********************************/
+
+static void
+silcpurple_buddy_resetkey(PurpleBlistNode *node, gpointer data)
+{
+	PurpleBuddy *b;
+	PurpleConnection *gc;
+        SilcPurple sg;
+	char *nickname;
+	SilcClientEntry *clients;
+	SilcUInt32 clients_count;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
+
+	b = (PurpleBuddy *) node;
+	gc = purple_account_get_connection(b->account);
+	sg = gc->proto_data;
+
+	if (!silc_parse_userfqdn(b->name, &nickname, NULL))
+		return;
+
+	/* Find client entry */
+	clients = silc_client_get_clients_local(sg->client, sg->conn,
+						nickname, b->name,
+						&clients_count);
+	if (!clients) {
+		silc_free(nickname);
+		return;
+	}
+
+	clients[0]->prv_resp = FALSE;
+	silc_client_del_private_message_key(sg->client, sg->conn,
+					    clients[0]);
+	silc_free(clients);
+	silc_free(nickname);
+}
+
+typedef struct {
+	SilcClient client;
+	SilcClientConnection conn;
+	SilcClientID client_id;
+} *SilcPurplePrivkey;
+
+static void
+silcpurple_buddy_privkey(PurpleConnection *gc, const char *name);
+
+static void
+silcpurple_buddy_privkey_cb(SilcPurplePrivkey p, const char *passphrase)
+{
+	SilcClientEntry client_entry;
+
+        if (!passphrase || !(*passphrase)) {
+                silc_free(p);
+                return;
+        }
+
+	/* Get the client entry. */
+	client_entry = silc_client_get_client_by_id(p->client, p->conn,
+						    &p->client_id);
+	if (!client_entry) {
+		purple_notify_error(p->client->application, _("IM With Password"),
+				  _("The remote user is not present in the network any more"),
+				  NULL);
+		silc_free(p);
+		return;
+	}
+
+	/* Set the private message key */
+	silc_client_del_private_message_key(p->client, p->conn,
+					    client_entry);
+	silc_client_add_private_message_key(p->client, p->conn,
+					    client_entry, NULL, NULL,
+					    (unsigned char *)passphrase,
+					    strlen(passphrase), FALSE,
+					    client_entry->prv_resp);
+	if (!client_entry->prv_resp)
+		silc_client_send_private_message_key_request(p->client,
+							     p->conn,
+							     client_entry);
+        silc_free(p);
+}
+
+static void
+silcpurple_buddy_privkey_resolved(SilcClient client,
+				SilcClientConnection conn,
+				SilcClientEntry *clients,
+				SilcUInt32 clients_count,
+				void *context)
+{
+	char tmp[256];
+
+	if (!clients) {
+		g_snprintf(tmp, sizeof(tmp),
+			   _("User %s is not present in the network"),
+			   (const char *)context);
+		purple_notify_error(client->application, _("IM With Password"),
+				  _("Cannot set IM key"), tmp);
+		g_free(context);
+		return;
+	}
+
+	silcpurple_buddy_privkey(client->application, context);
+	silc_free(context);
+}
+
+static void
+silcpurple_buddy_privkey(PurpleConnection *gc, const char *name)
+{
+	SilcPurple sg = gc->proto_data;
+	char *nickname;
+	SilcPurplePrivkey p;
+	SilcClientEntry *clients;
+	SilcUInt32 clients_count;
+
+	if (!name)
+		return;
+	if (!silc_parse_userfqdn(name, &nickname, NULL))
+		return;
+
+	/* Find client entry */
+	clients = silc_client_get_clients_local(sg->client, sg->conn,
+						nickname, name,
+						&clients_count);
+	if (!clients) {
+		silc_client_get_clients(sg->client, sg->conn, nickname, NULL,
+					silcpurple_buddy_privkey_resolved,
+					g_strdup(name));
+		silc_free(nickname);
+		return;
+	}
+
+	p = silc_calloc(1, sizeof(*p));
+	if (!p)
+		return;
+	p->client = sg->client;
+	p->conn = sg->conn;
+	p->client_id = *clients[0]->id;
+	purple_request_input(gc, _("IM With Password"), NULL,
+	                     _("Set IM Password"), NULL, FALSE, TRUE, NULL,
+	                     _("OK"), G_CALLBACK(silcpurple_buddy_privkey_cb),
+	                     _("Cancel"), G_CALLBACK(silcpurple_buddy_privkey_cb),
+	                     gc->account, NULL, NULL, p);
+
+	silc_free(clients);
+	silc_free(nickname);
+}
+
+static void
+silcpurple_buddy_privkey_menu(PurpleBlistNode *node, gpointer data)
+{
+	PurpleBuddy *buddy;
+	PurpleConnection *gc;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
+
+	buddy = (PurpleBuddy *) node;
+	gc = purple_account_get_connection(buddy->account);
+
+	silcpurple_buddy_privkey(gc, buddy->name);
+}
+
+
+/**************************** Get Public Key *********************************/
+
+typedef struct {
+	SilcClient client;
+	SilcClientConnection conn;
+	SilcClientID client_id;
+} *SilcPurpleBuddyGetkey;
+
+static void
+silcpurple_buddy_getkey(PurpleConnection *gc, const char *name);
+
+static void
+silcpurple_buddy_getkey_cb(SilcPurpleBuddyGetkey g,
+			 SilcClientCommandReplyContext cmd)
+{
+	SilcClientEntry client_entry;
+	unsigned char *pk;
+	SilcUInt32 pk_len;
+
+	/* Get the client entry. */
+	client_entry = silc_client_get_client_by_id(g->client, g->conn,
+						    &g->client_id);
+	if (!client_entry) {
+		purple_notify_error(g->client->application, _("Get Public Key"),
+				  _("The remote user is not present in the network any more"),
+				  NULL);
+		silc_free(g);
+		return;
+	}
+
+	if (!client_entry->public_key) {
+		silc_free(g);
+		return;
+	}
+
+	/* Now verify the public key */
+	pk = silc_pkcs_public_key_encode(client_entry->public_key, &pk_len);
+	silcpurple_verify_public_key(g->client, g->conn, client_entry->nickname,
+				   SILC_SOCKET_TYPE_CLIENT,
+				   pk, pk_len, SILC_SKE_PK_TYPE_SILC,
+				   NULL, NULL);
+	silc_free(pk);
+	silc_free(g);
+}
+
+static void
+silcpurple_buddy_getkey_resolved(SilcClient client,
+			       SilcClientConnection conn,
+			       SilcClientEntry *clients,
+			       SilcUInt32 clients_count,
+			       void *context)
+{
+	char tmp[256];
+
+	if (!clients) {
+		g_snprintf(tmp, sizeof(tmp),
+			   _("User %s is not present in the network"),
+			   (const char *)context);
+		purple_notify_error(client->application, _("Get Public Key"),
+				  _("Cannot fetch the public key"), tmp);
+		g_free(context);
+		return;
+	}
+
+	silcpurple_buddy_getkey(client->application, context);
+	silc_free(context);
+}
+
+static void
+silcpurple_buddy_getkey(PurpleConnection *gc, const char *name)
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcClientEntry *clients;
+	SilcUInt32 clients_count;
+	SilcPurpleBuddyGetkey g;
+	char *nickname;
+
+	if (!name)
+		return;
+
+	if (!silc_parse_userfqdn(name, &nickname, NULL))
+		return;
+
+	/* Find client entry */
+	clients = silc_client_get_clients_local(client, conn, nickname, name,
+						&clients_count);
+	if (!clients) {
+		silc_client_get_clients(client, conn, nickname, NULL,
+					silcpurple_buddy_getkey_resolved,
+					g_strdup(name));
+		silc_free(nickname);
+		return;
+	}
+
+	/* Call GETKEY */
+	g = silc_calloc(1, sizeof(*g));
+	if (!g)
+		return;
+	g->client = client;
+	g->conn = conn;
+	g->client_id = *clients[0]->id;
+	silc_client_command_call(client, conn, NULL, "GETKEY",
+				 clients[0]->nickname, NULL);
+	silc_client_command_pending(conn, SILC_COMMAND_GETKEY,
+				    conn->cmd_ident,
+				    (SilcCommandCb)silcpurple_buddy_getkey_cb, g);
+	silc_free(clients);
+	silc_free(nickname);
+}
+
+static void
+silcpurple_buddy_getkey_menu(PurpleBlistNode *node, gpointer data)
+{
+	PurpleBuddy *buddy;
+	PurpleConnection *gc;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
+
+	buddy = (PurpleBuddy *) node;
+	gc = purple_account_get_connection(buddy->account);
+
+	silcpurple_buddy_getkey(gc, buddy->name);
+}
+
+static void
+silcpurple_buddy_showkey(PurpleBlistNode *node, gpointer data)
+{
+	PurpleBuddy *b;
+	PurpleConnection *gc;
+	SilcPurple sg;
+	SilcPublicKey public_key;
+	const char *pkfile;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
+
+	b = (PurpleBuddy *) node;
+	gc = purple_account_get_connection(b->account);
+	sg = gc->proto_data;
+
+	pkfile = purple_blist_node_get_string(node, "public-key");
+	if (!silc_pkcs_load_public_key(pkfile, &public_key, SILC_PKCS_FILE_PEM) &&
+	    !silc_pkcs_load_public_key(pkfile, &public_key, SILC_PKCS_FILE_BIN)) {
+		purple_notify_error(gc,
+				  _("Show Public Key"),
+				  _("Could not load public key"), NULL);
+		return;
+	}
+
+	silcpurple_show_public_key(sg, b->name, public_key, NULL, NULL);
+	silc_pkcs_public_key_free(public_key);
+}
+
+
+/**************************** Buddy routines *********************************/
+
+/* The buddies are implemented by using the WHOIS and WATCH commands that
+   can be used to search users by their public key.  Since nicknames aren't
+   unique in SILC we cannot trust the buddy list using their nickname.  We
+   associate public keys to buddies and use those to search and watch
+   in the network.
+
+   The problem is that Purple does not return PurpleBuddy contexts to the
+   callbacks but the buddy names.  Naturally, this is not going to work
+   with SILC.  But, for now, we have to do what we can... */
+
+typedef struct {
+	SilcClient client;
+	SilcClientConnection conn;
+	SilcClientID client_id;
+	PurpleBuddy *b;
+	unsigned char *offline_pk;
+	SilcUInt32 offline_pk_len;
+	unsigned int offline        : 1;
+	unsigned int pubkey_search  : 1;
+	unsigned int init           : 1;
+} *SilcPurpleBuddyRes;
+
+static void
+silcpurple_add_buddy_ask_pk_cb(SilcPurpleBuddyRes r, gint id);
+static void
+silcpurple_add_buddy_resolved(SilcClient client,
+			    SilcClientConnection conn,
+			    SilcClientEntry *clients,
+			    SilcUInt32 clients_count,
+			    void *context);
+
+void silcpurple_get_info(PurpleConnection *gc, const char *who)
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcClientEntry client_entry;
+	PurpleBuddy *b;
+	const char *filename, *nick = who;
+	char tmp[256];
+
+	if (!who)
+		return;
+	if (strlen(who) > 1 && who[0] == '@')
+		nick = who + 1;
+	if (strlen(who) > 1 && who[0] == '*')
+		nick = who + 1;
+	if (strlen(who) > 2 && who[0] == '*' && who[1] == '@')
+		nick = who + 2;
+
+	b = purple_find_buddy(gc->account, nick);
+	if (b) {
+		/* See if we have this buddy's public key.  If we do use that
+		   to search the details. */
+		filename = purple_blist_node_get_string((PurpleBlistNode *)b, "public-key");
+		if (filename) {
+			/* Call WHOIS.  The user info is displayed in the WHOIS
+			   command reply. */
+			silc_client_command_call(client, conn, NULL, "WHOIS",
+						 "-details", "-pubkey", filename, NULL);
+			return;
+		}
+
+		if (!b->proto_data) {
+			g_snprintf(tmp, sizeof(tmp),
+				   _("User %s is not present in the network"), b->name);
+			purple_notify_error(gc, _("User Information"),
+					  _("Cannot get user information"), tmp);
+			return;
+		}
+
+		client_entry = silc_client_get_client_by_id(client, conn, b->proto_data);
+		if (client_entry) {
+			/* Call WHOIS.  The user info is displayed in the WHOIS
+			   command reply. */
+			silc_client_command_call(client, conn, NULL, "WHOIS",
+						 client_entry->nickname, "-details", NULL);
+		}
+	} else {
+		/* Call WHOIS just with nickname. */
+		silc_client_command_call(client, conn, NULL, "WHOIS", nick, NULL);
+	}
+}
+
+static void
+silcpurple_add_buddy_pk_no(SilcPurpleBuddyRes r)
+{
+	char tmp[512];
+	g_snprintf(tmp, sizeof(tmp), _("The %s buddy is not trusted"),
+		   r->b->name);
+	purple_notify_error(r->client->application, _("Add Buddy"), tmp,
+			  _("You cannot receive buddy notifications until you "
+			    "import his/her public key.  You can use the Get Public Key "
+			    "command to get the public key."));
+	purple_prpl_got_user_status(purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), SILCPURPLE_STATUS_ID_OFFLINE, NULL);
+}
+
+static void
+silcpurple_add_buddy_save(bool success, void *context)
+{
+	SilcPurpleBuddyRes r = context;
+	PurpleBuddy *b = r->b;
+	SilcClient client = r->client;
+	SilcClientEntry client_entry;
+	SilcAttributePayload attr;
+	SilcAttribute attribute;
+	SilcVCardStruct vcard;
+	SilcAttributeObjMime message, extension;
+#ifdef SILC_ATTRIBUTE_USER_ICON
+	SilcAttributeObjMime usericon;
+#endif
+	SilcAttributeObjPk serverpk, usersign, serversign;
+	gboolean usign_success = TRUE, ssign_success = TRUE;
+	char filename[512], filename2[512], *fingerprint = NULL, *tmp;
+	SilcUInt32 len;
+	int i;
+
+	if (!success) {
+		/* The user did not trust the public key. */
+		silcpurple_add_buddy_pk_no(r);
+		silc_free(r);
+		return;
+	}
+
+	if (r->offline) {
+		/* User is offline.  Associate the imported public key with
+		   this user. */
+		fingerprint = silc_hash_fingerprint(NULL, r->offline_pk,
+						    r->offline_pk_len);
+		for (i = 0; i < strlen(fingerprint); i++)
+			if (fingerprint[i] == ' ')
+				fingerprint[i] = '_';
+		g_snprintf(filename, sizeof(filename) - 1,
+			   "%s" G_DIR_SEPARATOR_S "clientkeys" G_DIR_SEPARATOR_S "clientkey_%s.pub",
+			   silcpurple_silcdir(), fingerprint);
+		purple_blist_node_set_string((PurpleBlistNode *)b, "public-key", filename);
+		purple_prpl_got_user_status(purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), SILCPURPLE_STATUS_ID_OFFLINE, NULL);
+		silc_free(fingerprint);
+		silc_free(r->offline_pk);
+		silc_free(r);
+		return;
+	}
+
+	/* Get the client entry. */
+	client_entry = silc_client_get_client_by_id(r->client, r->conn,
+						    &r->client_id);
+	if (!client_entry) {
+		silc_free(r);
+		return;
+	}
+
+	memset(&vcard, 0, sizeof(vcard));
+	memset(&message, 0, sizeof(message));
+	memset(&extension, 0, sizeof(extension));
+#ifdef SILC_ATTRIBUTE_USER_ICON
+	memset(&usericon, 0, sizeof(usericon));
+#endif
+	memset(&serverpk, 0, sizeof(serverpk));
+	memset(&usersign, 0, sizeof(usersign));
+	memset(&serversign, 0, sizeof(serversign));
+
+	/* Now that we have the public key and we trust it now we
+	   save the attributes of the buddy and update its status. */
+
+	if (client_entry->attrs) {
+		silc_dlist_start(client_entry->attrs);
+		while ((attr = silc_dlist_get(client_entry->attrs))
+		       != SILC_LIST_END) {
+			attribute = silc_attribute_get_attribute(attr);
+
+			switch (attribute) {
+			case SILC_ATTRIBUTE_USER_INFO:
+				if (!silc_attribute_get_object(attr, (void *)&vcard,
+							       sizeof(vcard)))
+					continue;
+				break;
+
+			case SILC_ATTRIBUTE_STATUS_MESSAGE:
+				if (!silc_attribute_get_object(attr, (void *)&message,
+							       sizeof(message)))
+					continue;
+				break;
+
+			case SILC_ATTRIBUTE_EXTENSION:
+				if (!silc_attribute_get_object(attr, (void *)&extension,
+							       sizeof(extension)))
+					continue;
+				break;
+
+#ifdef SILC_ATTRIBUTE_USER_ICON
+			case SILC_ATTRIBUTE_USER_ICON:
+				if (!silc_attribute_get_object(attr, (void *)&usericon,
+							       sizeof(usericon)))
+					continue;
+				break;
+#endif
+
+			case SILC_ATTRIBUTE_SERVER_PUBLIC_KEY:
+				if (serverpk.type)
+					continue;
+				if (!silc_attribute_get_object(attr, (void *)&serverpk,
+							       sizeof(serverpk)))
+					continue;
+				break;
+
+			case SILC_ATTRIBUTE_USER_DIGITAL_SIGNATURE:
+				if (usersign.data)
+					continue;
+				if (!silc_attribute_get_object(attr, (void *)&usersign,
+							       sizeof(usersign)))
+					continue;
+				break;
+
+			case SILC_ATTRIBUTE_SERVER_DIGITAL_SIGNATURE:
+				if (serversign.data)
+					continue;
+				if (!silc_attribute_get_object(attr, (void *)&serversign,
+							       sizeof(serversign)))
+					continue;
+				break;
+
+			default:
+				break;
+			}
+		}
+	}
+
+	/* Verify the attribute signatures */
+
+	if (usersign.data) {
+		SilcPKCS pkcs;
+		unsigned char *verifyd;
+		SilcUInt32 verify_len;
+
+		silc_pkcs_alloc((unsigned char*)"rsa", &pkcs);
+		verifyd = silc_attribute_get_verify_data(client_entry->attrs,
+							 FALSE, &verify_len);
+		if (verifyd && silc_pkcs_public_key_set(pkcs, client_entry->public_key)){
+			if (!silc_pkcs_verify_with_hash(pkcs, client->sha1hash,
+							usersign.data,
+							usersign.data_len,
+							verifyd, verify_len))
+				usign_success = FALSE;
+		}
+		silc_free(verifyd);
+	}
+
+	if (serversign.data && !strcmp(serverpk.type, "silc-rsa")) {
+		SilcPublicKey public_key;
+		SilcPKCS pkcs;
+		unsigned char *verifyd;
+		SilcUInt32 verify_len;
+
+		if (silc_pkcs_public_key_decode(serverpk.data, serverpk.data_len,
+						&public_key)) {
+			silc_pkcs_alloc((unsigned char *)"rsa", &pkcs);
+			verifyd = silc_attribute_get_verify_data(client_entry->attrs,
+								 TRUE, &verify_len);
+			if (verifyd && silc_pkcs_public_key_set(pkcs, public_key)) {
+				if (!silc_pkcs_verify_with_hash(pkcs, client->sha1hash,
+							       serversign.data,
+							       serversign.data_len,
+							       verifyd, verify_len))
+					ssign_success = FALSE;
+			}
+			silc_pkcs_public_key_free(public_key);
+			silc_free(verifyd);
+		}
+	}
+
+	fingerprint = silc_fingerprint(client_entry->fingerprint,
+				       client_entry->fingerprint_len);
+	for (i = 0; i < strlen(fingerprint); i++)
+		if (fingerprint[i] == ' ')
+			fingerprint[i] = '_';
+
+	if (usign_success || ssign_success) {
+		struct passwd *pw;
+		struct stat st;
+
+		memset(filename2, 0, sizeof(filename2));
+
+		/* Filename for dir */
+		tmp = fingerprint + strlen(fingerprint) - 9;
+		g_snprintf(filename, sizeof(filename) - 1,
+			   "%s" G_DIR_SEPARATOR_S "friends" G_DIR_SEPARATOR_S "%s",
+			   silcpurple_silcdir(), tmp);
+
+		pw = getpwuid(getuid());
+		if (!pw)
+			return;
+
+		/* Create dir if it doesn't exist */
+		if ((g_stat(filename, &st)) == -1) {
+			if (errno == ENOENT) {
+				if (pw->pw_uid == geteuid())
+					g_mkdir(filename, 0755);
+			}
+		}
+
+		/* Save VCard */
+		g_snprintf(filename2, sizeof(filename2) - 1,
+			   "%s" G_DIR_SEPARATOR_S "vcard", filename);
+		if (vcard.full_name) {
+			tmp = (char *)silc_vcard_encode(&vcard, &len);
+			silc_file_writefile(filename2, tmp, len);
+			silc_free(tmp);
+		}
+
+		/* Save status message */
+		if (message.mime) {
+			memset(filename2, 0, sizeof(filename2));
+			g_snprintf(filename2, sizeof(filename2) - 1,
+				   "%s" G_DIR_SEPARATOR_S "status_message.mime",
+				   filename);
+			silc_file_writefile(filename2, (char *)message.mime,
+					    message.mime_len);
+		}
+
+		/* Save extension data */
+		if (extension.mime) {
+			memset(filename2, 0, sizeof(filename2));
+			g_snprintf(filename2, sizeof(filename2) - 1,
+				   "%s" G_DIR_SEPARATOR_S "extension.mime",
+				   filename);
+			silc_file_writefile(filename2, (char *)extension.mime,
+					    extension.mime_len);
+		}
+
+#ifdef SILC_ATTRIBUTE_USER_ICON
+		/* Save user icon */
+		if (usericon.mime) {
+			SilcMime m = silc_mime_decode(usericon.mime,
+						      usericon.mime_len);
+			if (m) {
+				const char *type = silc_mime_get_field(m, "Content-Type");
+				if (!strcmp(type, "image/jpeg") ||
+				    !strcmp(type, "image/gif") ||
+				    !strcmp(type, "image/bmp") ||
+				    !strcmp(type, "image/png")) {
+					const unsigned char *data;
+					SilcUInt32 data_len;
+					data = silc_mime_get_data(m, &data_len);
+					if (data) {
+						/* TODO: Check if SILC gives us something to use as the checksum instead */
+						purple_buddy_icons_set_for_user(purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), g_memdup(data, data_len), data_len, NULL);
+					}
+				}
+				silc_mime_free(m);
+			}
+		}
+#endif
+	}
+
+	/* Save the public key path to buddy properties, as it is used
+	   to identify the buddy in the network (and not the nickname). */
+	memset(filename, 0, sizeof(filename));
+	g_snprintf(filename, sizeof(filename) - 1,
+		   "%s" G_DIR_SEPARATOR_S "clientkeys" G_DIR_SEPARATOR_S "clientkey_%s.pub",
+		   silcpurple_silcdir(), fingerprint);
+	purple_blist_node_set_string((PurpleBlistNode *)b, "public-key", filename);
+
+	/* Update online status */
+	purple_prpl_got_user_status(purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), SILCPURPLE_STATUS_ID_AVAILABLE, NULL);
+
+	/* Finally, start watching this user so we receive its status
+	   changes from the server */
+	g_snprintf(filename2, sizeof(filename2) - 1, "+%s", filename);
+	silc_client_command_call(r->client, r->conn, NULL, "WATCH", "-pubkey",
+				 filename2, NULL);
+
+	silc_free(fingerprint);
+	silc_free(r);
+}
+
+static void
+silcpurple_add_buddy_ask_import(void *user_data, const char *name)
+{
+	SilcPurpleBuddyRes r = (SilcPurpleBuddyRes)user_data;
+	SilcPublicKey public_key;
+
+	/* Load the public key */
+	if (!silc_pkcs_load_public_key(name, &public_key, SILC_PKCS_FILE_PEM) &&
+	    !silc_pkcs_load_public_key(name, &public_key, SILC_PKCS_FILE_BIN)) {
+		silcpurple_add_buddy_ask_pk_cb(r, 0);
+		purple_notify_error(r->client->application,
+				  _("Add Buddy"), _("Could not load public key"), NULL);
+		return;
+	}
+
+	/* Now verify the public key */
+	r->offline_pk = silc_pkcs_public_key_encode(public_key, &r->offline_pk_len);
+	silcpurple_verify_public_key(r->client, r->conn, r->b->name,
+				   SILC_SOCKET_TYPE_CLIENT,
+				   r->offline_pk, r->offline_pk_len,
+				   SILC_SKE_PK_TYPE_SILC,
+				   silcpurple_add_buddy_save, r);
+}
+
+static void
+silcpurple_add_buddy_ask_pk_cancel(void *user_data, const char *name)
+{
+	SilcPurpleBuddyRes r = (SilcPurpleBuddyRes)user_data;
+
+	/* The user did not import public key.  The buddy is unusable. */
+	silcpurple_add_buddy_pk_no(r);
+	silc_free(r);
+}
+
+static void
+silcpurple_add_buddy_ask_pk_cb(SilcPurpleBuddyRes r, gint id)
+{
+	if (id != 0) {
+		/* The user did not import public key.  The buddy is unusable. */
+		silcpurple_add_buddy_pk_no(r);
+		silc_free(r);
+		return;
+	}
+
+	/* Open file selector to select the public key. */
+	purple_request_file(r->client->application, _("Open..."), NULL, FALSE,
+			  G_CALLBACK(silcpurple_add_buddy_ask_import),
+			  G_CALLBACK(silcpurple_add_buddy_ask_pk_cancel),
+			  purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), NULL, r);
+
+}
+
+static void
+silcpurple_add_buddy_ask_pk(SilcPurpleBuddyRes r)
+{
+	char tmp[512];
+	g_snprintf(tmp, sizeof(tmp), _("The %s buddy is not present in the network"),
+		   r->b->name);
+	purple_request_action(r->client->application, _("Add Buddy"), tmp,
+			    _("To add the buddy you must import his/her public key. "
+			      "Press Import to import a public key."), 0,
+				  purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), NULL, r, 2,
+			    _("Cancel"), G_CALLBACK(silcpurple_add_buddy_ask_pk_cb),
+			    _("_Import..."), G_CALLBACK(silcpurple_add_buddy_ask_pk_cb));
+}
+
+static void
+silcpurple_add_buddy_getkey_cb(SilcPurpleBuddyRes r,
+			     SilcClientCommandReplyContext cmd)
+{
+	SilcClientEntry client_entry;
+	unsigned char *pk;
+	SilcUInt32 pk_len;
+
+	/* Get the client entry. */
+	client_entry = silc_client_get_client_by_id(r->client, r->conn,
+						    &r->client_id);
+	if (!client_entry || !client_entry->public_key) {
+		/* The buddy is offline/nonexistent. We will require user
+		   to associate a public key with the buddy or the buddy
+		   cannot be added. */
+		r->offline = TRUE;
+		silcpurple_add_buddy_ask_pk(r);
+		return;
+	}
+
+	/* Now verify the public key */
+	pk = silc_pkcs_public_key_encode(client_entry->public_key, &pk_len);
+	silcpurple_verify_public_key(r->client, r->conn, client_entry->nickname,
+				   SILC_SOCKET_TYPE_CLIENT,
+				   pk, pk_len, SILC_SKE_PK_TYPE_SILC,
+				   silcpurple_add_buddy_save, r);
+	silc_free(pk);
+}
+
+static void
+silcpurple_add_buddy_select_cb(SilcPurpleBuddyRes r, PurpleRequestFields *fields)
+{
+	PurpleRequestField *f;
+	const GList *list;
+	SilcClientEntry client_entry;
+
+	f = purple_request_fields_get_field(fields, "list");
+	list = purple_request_field_list_get_selected(f);
+	if (!list) {
+		/* The user did not select any user. */
+		silcpurple_add_buddy_pk_no(r);
+		silc_free(r);
+		return;
+	}
+
+	client_entry = purple_request_field_list_get_data(f, list->data);
+	silcpurple_add_buddy_resolved(r->client, r->conn, &client_entry, 1, r);
+}
+
+static void
+silcpurple_add_buddy_select_cancel(SilcPurpleBuddyRes r, PurpleRequestFields *fields)
+{
+	/* The user did not select any user. */
+	silcpurple_add_buddy_pk_no(r);
+	silc_free(r);
+}
+
+static void
+silcpurple_add_buddy_select(SilcPurpleBuddyRes r,
+			  SilcClientEntry *clients,
+			  SilcUInt32 clients_count)
+{
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *g;
+	PurpleRequestField *f;
+	char tmp[512], tmp2[128];
+	int i;
+	char *fingerprint;
+
+	fields = purple_request_fields_new();
+	g = purple_request_field_group_new(NULL);
+	f = purple_request_field_list_new("list", NULL);
+	purple_request_field_group_add_field(g, f);
+	purple_request_field_list_set_multi_select(f, FALSE);
+	purple_request_fields_add_group(fields, g);
+
+	for (i = 0; i < clients_count; i++) {
+		fingerprint = NULL;
+		if (clients[i]->fingerprint) {
+			fingerprint = silc_fingerprint(clients[i]->fingerprint,
+						       clients[i]->fingerprint_len);
+			g_snprintf(tmp2, sizeof(tmp2), "\n%s", fingerprint);
+		}
+		g_snprintf(tmp, sizeof(tmp), "%s - %s (%s@%s)%s",
+			   clients[i]->realname, clients[i]->nickname,
+			   clients[i]->username, clients[i]->hostname ?
+			   clients[i]->hostname : "",
+			   fingerprint ? tmp2 : "");
+		purple_request_field_list_add(f, tmp, clients[i]);
+		silc_free(fingerprint);
+	}
+
+	purple_request_fields(r->client->application, _("Add Buddy"),
+				_("Select correct user"),
+				r->pubkey_search
+					? _("More than one user was found with the same public key. Select "
+						"the correct user from the list to add to the buddy list.")
+					: _("More than one user was found with the same name. Select "
+						"the correct user from the list to add to the buddy list."),
+				fields,
+				_("OK"), G_CALLBACK(silcpurple_add_buddy_select_cb),
+				_("Cancel"), G_CALLBACK(silcpurple_add_buddy_select_cancel),
+				purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), NULL, r);
+}
+
+static void
+silcpurple_add_buddy_resolved(SilcClient client,
+			    SilcClientConnection conn,
+			    SilcClientEntry *clients,
+			    SilcUInt32 clients_count,
+			    void *context)
+{
+	SilcPurpleBuddyRes r = context;
+	PurpleBuddy *b = r->b;
+	SilcAttributePayload pub;
+	SilcAttributeObjPk userpk;
+	unsigned char *pk;
+	SilcUInt32 pk_len;
+	const char *filename;
+
+	filename = purple_blist_node_get_string((PurpleBlistNode *)b, "public-key");
+
+	/* If the buddy is offline/nonexistent, we will require user
+	   to associate a public key with the buddy or the buddy
+	   cannot be added. */
+	if (!clients_count) {
+		if (r->init) {
+			silc_free(r);
+			return;
+		}
+
+		r->offline = TRUE;
+		/* If the user has already associated a public key, try loading it
+		 * before prompting the user to load it again */
+		if (filename != NULL)
+			silcpurple_add_buddy_ask_import(r, filename);
+		else
+			silcpurple_add_buddy_ask_pk(r);
+		return;
+	}
+
+	/* If more than one client was found with nickname, we need to verify
+	   from user which one is the correct. */
+	if (clients_count > 1 && !r->pubkey_search) {
+		if (r->init) {
+			silc_free(r);
+			return;
+		}
+
+		silcpurple_add_buddy_select(r, clients, clients_count);
+		return;
+	}
+
+	/* If we searched using public keys and more than one entry was found
+	   the same person is logged on multiple times. */
+	if (clients_count > 1 && r->pubkey_search && b->name) {
+		if (r->init) {
+			/* Find the entry that closest matches to the
+			   buddy nickname. */
+			int i;
+			for (i = 0; i < clients_count; i++) {
+				if (!strncasecmp(b->name, clients[i]->nickname,
+						 strlen(b->name))) {
+					clients[0] = clients[i];
+					break;
+				}
+			}
+		} else {
+			/* Verify from user which one is correct */
+			silcpurple_add_buddy_select(r, clients, clients_count);
+			return;
+		}
+	}
+
+	/* The client was found.  Now get its public key and verify
+	   that before adding the buddy. */
+	memset(&userpk, 0, sizeof(userpk));
+	b->proto_data = silc_memdup(clients[0]->id, sizeof(*clients[0]->id));
+	r->client_id = *clients[0]->id;
+
+	/* Get the public key from attributes, if not present then
+	   resolve it with GETKEY unless we have it cached already. */
+	if (clients[0]->attrs && !clients[0]->public_key) {
+		pub = silcpurple_get_attr(clients[0]->attrs,
+					SILC_ATTRIBUTE_USER_PUBLIC_KEY);
+		if (!pub || !silc_attribute_get_object(pub, (void *)&userpk,
+						       sizeof(userpk))) {
+			/* Get public key with GETKEY */
+			silc_client_command_call(client, conn, NULL,
+						 "GETKEY", clients[0]->nickname, NULL);
+			silc_client_command_pending(conn, SILC_COMMAND_GETKEY,
+						    conn->cmd_ident,
+						    (SilcCommandCb)silcpurple_add_buddy_getkey_cb,
+						    r);
+			return;
+		}
+		if (!silc_pkcs_public_key_decode(userpk.data, userpk.data_len,
+						 &clients[0]->public_key))
+			return;
+		silc_free(userpk.data);
+	} else if (filename && !clients[0]->public_key) {
+		if (!silc_pkcs_load_public_key(filename, &clients[0]->public_key,
+					       SILC_PKCS_FILE_PEM) &&
+		    !silc_pkcs_load_public_key(filename, &clients[0]->public_key,
+					       SILC_PKCS_FILE_BIN)) {
+			/* Get public key with GETKEY */
+			silc_client_command_call(client, conn, NULL,
+						 "GETKEY", clients[0]->nickname, NULL);
+			silc_client_command_pending(conn, SILC_COMMAND_GETKEY,
+						    conn->cmd_ident,
+						    (SilcCommandCb)silcpurple_add_buddy_getkey_cb,
+						    r);
+			return;
+		}
+	} else if (!clients[0]->public_key) {
+		/* Get public key with GETKEY */
+		silc_client_command_call(client, conn, NULL,
+					 "GETKEY", clients[0]->nickname, NULL);
+		silc_client_command_pending(conn, SILC_COMMAND_GETKEY,
+					    conn->cmd_ident,
+					    (SilcCommandCb)silcpurple_add_buddy_getkey_cb,
+					    r);
+		return;
+	}
+
+	/* We have the public key, verify it. */
+	pk = silc_pkcs_public_key_encode(clients[0]->public_key, &pk_len);
+	silcpurple_verify_public_key(client, conn, clients[0]->nickname,
+				   SILC_SOCKET_TYPE_CLIENT,
+				   pk, pk_len, SILC_SKE_PK_TYPE_SILC,
+				   silcpurple_add_buddy_save, r);
+	silc_free(pk);
+}
+
+static void
+silcpurple_add_buddy_i(PurpleConnection *gc, PurpleBuddy *b, gboolean init)
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcPurpleBuddyRes r;
+	SilcBuffer attrs;
+	const char *filename, *name = b->name;
+
+	r = silc_calloc(1, sizeof(*r));
+	if (!r)
+		return;
+	r->client = client;
+	r->conn = conn;
+	r->b = b;
+	r->init = init;
+
+	/* See if we have this buddy's public key.  If we do use that
+	   to search the details. */
+	filename = purple_blist_node_get_string((PurpleBlistNode *)b, "public-key");
+	if (filename) {
+		SilcPublicKey public_key;
+		SilcAttributeObjPk userpk;
+
+		if (!silc_pkcs_load_public_key(filename, &public_key,
+					       SILC_PKCS_FILE_PEM) &&
+		    !silc_pkcs_load_public_key(filename, &public_key,
+					       SILC_PKCS_FILE_BIN))
+			return;
+
+		/* Get all attributes, and use the public key to search user */
+		name = NULL;
+		attrs = silc_client_attributes_request(SILC_ATTRIBUTE_USER_INFO,
+						       SILC_ATTRIBUTE_SERVICE,
+						       SILC_ATTRIBUTE_STATUS_MOOD,
+						       SILC_ATTRIBUTE_STATUS_FREETEXT,
+						       SILC_ATTRIBUTE_STATUS_MESSAGE,
+						       SILC_ATTRIBUTE_PREFERRED_LANGUAGE,
+						       SILC_ATTRIBUTE_PREFERRED_CONTACT,
+						       SILC_ATTRIBUTE_TIMEZONE,
+						       SILC_ATTRIBUTE_GEOLOCATION,
+#ifdef SILC_ATTRIBUTE_USER_ICON
+						       SILC_ATTRIBUTE_USER_ICON,
+#endif
+						       SILC_ATTRIBUTE_DEVICE_INFO, 0);
+		userpk.type = "silc-rsa";
+		userpk.data = silc_pkcs_public_key_encode(public_key, &userpk.data_len);
+		attrs = silc_attribute_payload_encode(attrs,
+						      SILC_ATTRIBUTE_USER_PUBLIC_KEY,
+						      SILC_ATTRIBUTE_FLAG_VALID,
+						      &userpk, sizeof(userpk));
+		silc_free(userpk.data);
+		silc_pkcs_public_key_free(public_key);
+		r->pubkey_search = TRUE;
+	} else {
+		/* Get all attributes */
+		attrs = silc_client_attributes_request(0);
+	}
+
+	/* Resolve */
+	silc_client_get_clients_whois(client, conn, name, NULL, attrs,
+				      silcpurple_add_buddy_resolved, r);
+	silc_buffer_free(attrs);
+}
+
+void silcpurple_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
+{
+	silcpurple_add_buddy_i(gc, buddy, FALSE);
+}
+
+void silcpurple_send_buddylist(PurpleConnection *gc)
+{
+	PurpleBuddyList *blist;
+	PurpleBlistNode *gnode, *cnode, *bnode;
+	PurpleBuddy *buddy;
+	PurpleAccount *account;
+
+	account = purple_connection_get_account(gc);
+
+	if ((blist = purple_get_blist()) != NULL)
+	{
+		for (gnode = blist->root; gnode != NULL; gnode = gnode->next)
+		{
+			if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
+				continue;
+			for (cnode = gnode->child; cnode != NULL; cnode = cnode->next)
+			{
+				if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
+					continue;
+				for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
+				{
+					if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
+						continue;
+					buddy = (PurpleBuddy *)bnode;
+					if (purple_buddy_get_account(buddy) == account)
+						silcpurple_add_buddy_i(gc, buddy, TRUE);
+				}
+			}
+		}
+	}
+}
+
+void silcpurple_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
+			   PurpleGroup *group)
+{
+	silc_free(buddy->proto_data);
+}
+
+void silcpurple_idle_set(PurpleConnection *gc, int idle)
+
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcAttributeObjService service;
+	const char *server;
+	int port;
+
+	server = purple_account_get_string(sg->account, "server",
+					 "silc.silcnet.org");
+	port = purple_account_get_int(sg->account, "port", 706),
+
+	memset(&service, 0, sizeof(service));
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_SERVICE, NULL);
+	service.port = port;
+	g_snprintf(service.address, sizeof(service.address), "%s", server);
+	service.idle = idle;
+	silc_client_attribute_add(client, conn, SILC_ATTRIBUTE_SERVICE,
+				  &service, sizeof(service));
+}
+
+char *silcpurple_status_text(PurpleBuddy *b)
+{
+	SilcPurple sg = b->account->gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcClientID *client_id = b->proto_data;
+	SilcClientEntry client_entry;
+	SilcAttributePayload attr;
+	SilcAttributeMood mood = 0;
+
+	/* Get the client entry. */
+	client_entry = silc_client_get_client_by_id(client, conn, client_id);
+	if (!client_entry)
+		return NULL;
+
+	/* If user is online, we show the mood status, if available.
+	   If user is offline or away that status is indicated. */
+
+	if (client_entry->mode & SILC_UMODE_DETACHED)
+		return g_strdup(_("Detached"));
+	if (client_entry->mode & SILC_UMODE_GONE)
+		return g_strdup(_("Away"));
+	if (client_entry->mode & SILC_UMODE_INDISPOSED)
+		return g_strdup(_("Indisposed"));
+	if (client_entry->mode & SILC_UMODE_BUSY)
+		return g_strdup(_("Busy"));
+	if (client_entry->mode & SILC_UMODE_PAGE)
+		return g_strdup(_("Wake Me Up"));
+	if (client_entry->mode & SILC_UMODE_HYPER)
+		return g_strdup(_("Hyper Active"));
+	if (client_entry->mode & SILC_UMODE_ROBOT)
+		return g_strdup(_("Robot"));
+
+	attr = silcpurple_get_attr(client_entry->attrs, SILC_ATTRIBUTE_STATUS_MOOD);
+	if (attr && silc_attribute_get_object(attr, &mood, sizeof(mood))) {
+		/* The mood is a bit mask, so we could show multiple moods,
+		   but let's show only one for now. */
+		if (mood & SILC_ATTRIBUTE_MOOD_HAPPY)
+			return g_strdup(_("Happy"));
+		if (mood & SILC_ATTRIBUTE_MOOD_SAD)
+			return g_strdup(_("Sad"));
+		if (mood & SILC_ATTRIBUTE_MOOD_ANGRY)
+			return g_strdup(_("Angry"));
+		if (mood & SILC_ATTRIBUTE_MOOD_JEALOUS)
+			return g_strdup(_("Jealous"));
+		if (mood & SILC_ATTRIBUTE_MOOD_ASHAMED)
+			return g_strdup(_("Ashamed"));
+		if (mood & SILC_ATTRIBUTE_MOOD_INVINCIBLE)
+			return g_strdup(_("Invincible"));
+		if (mood & SILC_ATTRIBUTE_MOOD_INLOVE)
+			return g_strdup(_("In Love"));
+		if (mood & SILC_ATTRIBUTE_MOOD_SLEEPY)
+			return g_strdup(_("Sleepy"));
+		if (mood & SILC_ATTRIBUTE_MOOD_BORED)
+			return g_strdup(_("Bored"));
+		if (mood & SILC_ATTRIBUTE_MOOD_EXCITED)
+			return g_strdup(_("Excited"));
+		if (mood & SILC_ATTRIBUTE_MOOD_ANXIOUS)
+			return g_strdup(_("Anxious"));
+	}
+
+	return NULL;
+}
+
+void silcpurple_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full)
+{
+	SilcPurple sg = b->account->gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcClientID *client_id = b->proto_data;
+	SilcClientEntry client_entry;
+	char *moodstr, *statusstr, *contactstr, *langstr, *devicestr, *tzstr, *geostr;
+	char tmp[256];
+
+	/* Get the client entry. */
+	client_entry = silc_client_get_client_by_id(client, conn, client_id);
+	if (!client_entry)
+		return;
+
+	if (client_entry->nickname)
+		purple_notify_user_info_add_pair(user_info, _("Nickname"),
+					       client_entry->nickname);
+	if (client_entry->username && client_entry->hostname) {
+		g_snprintf(tmp, sizeof(tmp), "%s@%s", client_entry->username, client_entry->hostname);
+		purple_notify_user_info_add_pair(user_info, _("Username"), tmp);
+	}
+	if (client_entry->mode) {
+		memset(tmp, 0, sizeof(tmp));
+		silcpurple_get_umode_string(client_entry->mode,
+					  tmp, sizeof(tmp) - strlen(tmp));
+		purple_notify_user_info_add_pair(user_info, _("User Modes"), tmp);
+	}
+
+	silcpurple_parse_attrs(client_entry->attrs, &moodstr, &statusstr, &contactstr, &langstr, &devicestr, &tzstr, &geostr);
+
+	if (statusstr) {
+		purple_notify_user_info_add_pair(user_info, _("Message"), statusstr);
+		g_free(statusstr);
+	}
+
+	if (full) {
+		if (moodstr) {
+			purple_notify_user_info_add_pair(user_info, _("Mood"), moodstr);
+			g_free(moodstr);
+		}
+
+		if (contactstr) {
+			purple_notify_user_info_add_pair(user_info, _("Preferred Contact"), contactstr);
+			g_free(contactstr);
+		}
+
+		if (langstr) {
+			purple_notify_user_info_add_pair(user_info, _("Preferred Language"), langstr);
+			g_free(langstr);
+		}
+
+		if (devicestr) {
+			purple_notify_user_info_add_pair(user_info, _("Device"), devicestr);
+			g_free(devicestr);
+		}
+
+		if (tzstr) {
+			purple_notify_user_info_add_pair(user_info, _("Timezone"), tzstr);
+			g_free(tzstr);
+		}
+
+		if (geostr) {
+			purple_notify_user_info_add_pair(user_info, _("Geolocation"), geostr);
+			g_free(geostr);
+		}
+	}
+}
+
+static void
+silcpurple_buddy_kill(PurpleBlistNode *node, gpointer data)
+{
+	PurpleBuddy *b;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
+
+	b = (PurpleBuddy *) node;
+	gc = purple_account_get_connection(b->account);
+	sg = gc->proto_data;
+
+	/* Call KILL */
+	silc_client_command_call(sg->client, sg->conn, NULL, "KILL",
+				 b->name, "Killed by operator", NULL);
+}
+
+typedef struct {
+	SilcPurple sg;
+	SilcClientEntry client_entry;
+} *SilcPurpleBuddyWb;
+
+static void
+silcpurple_buddy_wb(PurpleBlistNode *node, gpointer data)
+{
+	SilcPurpleBuddyWb wb = data;
+	silcpurple_wb_init(wb->sg, wb->client_entry);
+	silc_free(wb);
+}
+
+GList *silcpurple_buddy_menu(PurpleBuddy *buddy)
+{
+	PurpleConnection *gc = purple_account_get_connection(buddy->account);
+	SilcPurple sg = gc->proto_data;
+	SilcClientConnection conn = sg->conn;
+	const char *pkfile = NULL;
+	SilcClientEntry client_entry = NULL;
+	PurpleMenuAction *act;
+	GList *m = NULL;
+	SilcPurpleBuddyWb wb;
+
+	pkfile = purple_blist_node_get_string((PurpleBlistNode *) buddy, "public-key");
+	client_entry = silc_client_get_client_by_id(sg->client,
+						    sg->conn,
+						    buddy->proto_data);
+
+	if (client_entry && client_entry->send_key) {
+		act = purple_menu_action_new(_("Reset IM Key"),
+		                           PURPLE_CALLBACK(silcpurple_buddy_resetkey),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+
+	} else {
+		act = purple_menu_action_new(_("IM with Key Exchange"),
+		                           PURPLE_CALLBACK(silcpurple_buddy_keyagr),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+
+		act = purple_menu_action_new(_("IM with Password"),
+		                           PURPLE_CALLBACK(silcpurple_buddy_privkey_menu),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+	}
+
+	if (pkfile) {
+		act = purple_menu_action_new(_("Show Public Key"),
+		                           PURPLE_CALLBACK(silcpurple_buddy_showkey),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+
+	} else {
+		act = purple_menu_action_new(_("Get Public Key..."),
+		                           PURPLE_CALLBACK(silcpurple_buddy_getkey_menu),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+	}
+
+	if (conn && conn->local_entry->mode & SILC_UMODE_ROUTER_OPERATOR) {
+		act = purple_menu_action_new(_("Kill User"),
+		                           PURPLE_CALLBACK(silcpurple_buddy_kill),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+	}
+
+	if (client_entry) {
+		wb = silc_calloc(1, sizeof(*wb));
+		wb->sg = sg;
+		wb->client_entry = client_entry;
+		act = purple_menu_action_new(_("Draw On Whiteboard"),
+		                           PURPLE_CALLBACK(silcpurple_buddy_wb),
+		                           (void *)wb, NULL);
+		m = g_list_append(m, act);
+	}
+	return m;
+}
+
+#ifdef SILC_ATTRIBUTE_USER_ICON
+void silcpurple_buddy_set_icon(PurpleConnection *gc, PurpleStoredImage *img)
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcMime mime;
+	char type[32];
+	unsigned char *icon;
+	const char *t;
+	SilcAttributeObjMime obj;
+
+	/* Remove */
+	if (!img) {
+		silc_client_attribute_del(client, conn,
+					  SILC_ATTRIBUTE_USER_ICON, NULL);
+		return;
+	}
+
+	/* Add */
+	mime = silc_mime_alloc();
+	if (!mime)
+		return;
+
+	t = purple_imgstore_get_extension(img);
+	if (!t || !strcmp(t, "icon")) {
+		silc_mime_free(mime);
+		return;
+	}
+	if (!strcmp(t, "jpg"))
+		t = "jpeg";
+	g_snprintf(type, sizeof(type), "image/%s", t);
+	silc_mime_add_field(mime, "Content-Type", type);
+	silc_mime_add_data(mime, purple_imgstore_get_data(img), purple_imgstore_get_size(img));
+
+	obj.mime = icon = silc_mime_encode(mime, &obj.mime_len);
+	if (obj.mime)
+		silc_client_attribute_add(client, conn, 
+					  SILC_ATTRIBUTE_USER_ICON, &obj, sizeof(obj));
+
+	silc_free(icon);
+	silc_mime_free(mime);
+}
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/silc10/chat.c	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,1456 @@
+/*
+
+  silcpurple_chat.c
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  Copyright (C) 2004 Pekka Riikonen
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; version 2 of the License.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+*/
+
+#include "silcincludes.h"
+#include "silcclient.h"
+#include "silcpurple.h"
+#include "wb.h"
+
+/***************************** Channel Routines ******************************/
+
+GList *silcpurple_chat_info(PurpleConnection *gc)
+{
+	GList *ci = NULL;
+	struct proto_chat_entry *pce;
+
+	pce = g_new0(struct proto_chat_entry, 1);
+	pce->label = _("_Channel:");
+	pce->identifier = "channel";
+	pce->required = TRUE;
+	ci = g_list_append(ci, pce);
+
+	pce = g_new0(struct proto_chat_entry, 1);
+	pce->label = _("_Passphrase:");
+	pce->identifier = "passphrase";
+	pce->secret = TRUE;
+	ci = g_list_append(ci, pce);
+
+	return ci;
+}
+
+GHashTable *silcpurple_chat_info_defaults(PurpleConnection *gc, const char *chat_name)
+{
+	GHashTable *defaults;
+
+	defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
+
+	if (chat_name != NULL)
+		g_hash_table_insert(defaults, "channel", g_strdup(chat_name));
+
+	return defaults;
+}
+
+static void
+silcpurple_chat_getinfo(PurpleConnection *gc, GHashTable *components);
+
+static void
+silcpurple_chat_getinfo_res(SilcClient client,
+			  SilcClientConnection conn,
+			  SilcChannelEntry *channels,
+			  SilcUInt32 channels_count,
+			  void *context)
+{
+	GHashTable *components = context;
+	PurpleConnection *gc = client->application;
+	const char *chname;
+	char tmp[256];
+
+	chname = g_hash_table_lookup(components, "channel");
+	if (!chname)
+		return;
+
+	if (!channels) {
+		g_snprintf(tmp, sizeof(tmp),
+			   _("Channel %s does not exist in the network"), chname);
+		purple_notify_error(gc, _("Channel Information"),
+				  _("Cannot get channel information"), tmp);
+		return;
+	}
+
+	silcpurple_chat_getinfo(gc, components);
+}
+
+
+static void
+silcpurple_chat_getinfo(PurpleConnection *gc, GHashTable *components)
+{
+	SilcPurple sg = gc->proto_data;
+	const char *chname;
+	char *buf, tmp[256], *tmp2;
+	GString *s;
+	SilcChannelEntry channel;
+	SilcHashTableList htl;
+	SilcChannelUser chu;
+
+	if (!components)
+		return;
+
+	chname = g_hash_table_lookup(components, "channel");
+	if (!chname)
+		return;
+	channel = silc_client_get_channel(sg->client, sg->conn,
+					  (char *)chname);
+	if (!channel) {
+		silc_client_get_channel_resolve(sg->client, sg->conn,
+						(char *)chname,
+						silcpurple_chat_getinfo_res,
+						components);
+		return;
+	}
+
+	s = g_string_new("");
+	tmp2 = g_markup_escape_text(channel->channel_name, -1);
+	g_string_append_printf(s, _("<b>Channel Name:</b> %s"), tmp2);
+	g_free(tmp2);
+	if (channel->user_list && silc_hash_table_count(channel->user_list))
+		g_string_append_printf(s, _("<br><b>User Count:</b> %d"),
+				       (int)silc_hash_table_count(channel->user_list));
+
+	silc_hash_table_list(channel->user_list, &htl);
+	while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+		if (chu->mode & SILC_CHANNEL_UMODE_CHANFO) {
+			tmp2 = g_markup_escape_text(chu->client->nickname, -1);
+			g_string_append_printf(s, _("<br><b>Channel Founder:</b> %s"),
+					       tmp2);
+			g_free(tmp2);
+			break;
+		}
+	}
+	silc_hash_table_list_reset(&htl);
+
+	if (channel->channel_key)
+		g_string_append_printf(s, _("<br><b>Channel Cipher:</b> %s"),
+				       silc_cipher_get_name(channel->channel_key));
+	if (channel->hmac)
+		/* Definition of HMAC: http://en.wikipedia.org/wiki/HMAC */
+		g_string_append_printf(s, _("<br><b>Channel HMAC:</b> %s"),
+				       silc_hmac_get_name(channel->hmac));
+
+	if (channel->topic) {
+		tmp2 = g_markup_escape_text(channel->topic, -1);
+		g_string_append_printf(s, _("<br><b>Channel Topic:</b><br>%s"), tmp2);
+		g_free(tmp2);
+	}
+
+	if (channel->mode) {
+		g_string_append_printf(s, _("<br><b>Channel Modes:</b> "));
+		silcpurple_get_chmode_string(channel->mode, tmp, sizeof(tmp));
+		g_string_append(s, tmp);
+	}
+
+	if (channel->founder_key) {
+		char *fingerprint, *babbleprint;
+		unsigned char *pk;
+		SilcUInt32 pk_len;
+		pk = silc_pkcs_public_key_encode(channel->founder_key, &pk_len);
+		fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+		babbleprint = silc_hash_babbleprint(NULL, pk, pk_len);
+
+		g_string_append_printf(s, _("<br><b>Founder Key Fingerprint:</b><br>%s"), fingerprint);
+		g_string_append_printf(s, _("<br><b>Founder Key Babbleprint:</b><br>%s"), babbleprint);
+
+		silc_free(fingerprint);
+		silc_free(babbleprint);
+		silc_free(pk);
+	}
+
+	buf = g_string_free(s, FALSE);
+	purple_notify_formatted(gc, NULL, _("Channel Information"), NULL, buf, NULL, NULL);
+	g_free(buf);
+}
+
+
+static void
+silcpurple_chat_getinfo_menu(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat = (PurpleChat *)node;
+	silcpurple_chat_getinfo(chat->account->gc, chat->components);
+}
+
+
+#if 0   /* XXX For now these are not implemented.  We need better
+	   listview dialog from Purple for these. */
+/************************** Channel Invite List ******************************/
+
+static void
+silcpurple_chat_invitelist(PurpleBlistNode *node, gpointer data);
+{
+
+}
+
+
+/**************************** Channel Ban List *******************************/
+
+static void
+silcpurple_chat_banlist(PurpleBlistNode *node, gpointer data);
+{
+
+}
+#endif
+
+
+/************************* Channel Authentication ****************************/
+
+typedef struct {
+	SilcPurple sg;
+	SilcChannelEntry channel;
+	PurpleChat *c;
+	SilcBuffer pubkeys;
+} *SilcPurpleChauth;
+
+static void
+silcpurple_chat_chpk_add(void *user_data, const char *name)
+{
+	SilcPurpleChauth sgc = (SilcPurpleChauth)user_data;
+	SilcPurple sg = sgc->sg;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcPublicKey public_key;
+	SilcBuffer chpks, pk, chidp;
+	unsigned char mode[4];
+	SilcUInt32 m;
+
+	/* Load the public key */
+	if (!silc_pkcs_load_public_key(name, &public_key, SILC_PKCS_FILE_PEM) &&
+	    !silc_pkcs_load_public_key(name, &public_key, SILC_PKCS_FILE_BIN)) {
+		silcpurple_chat_chauth_show(sgc->sg, sgc->channel, sgc->pubkeys);
+		silc_buffer_free(sgc->pubkeys);
+		silc_free(sgc);
+		purple_notify_error(client->application,
+				  _("Add Channel Public Key"),
+				  _("Could not load public key"), NULL);
+		return;
+	}
+
+	pk = silc_pkcs_public_key_payload_encode(public_key);
+	chpks = silc_buffer_alloc_size(2);
+	SILC_PUT16_MSB(1, chpks->head);
+	chpks = silc_argument_payload_encode_one(chpks, pk->data,
+						 pk->len, 0x00);
+	silc_buffer_free(pk);
+
+	m = sgc->channel->mode;
+	m |= SILC_CHANNEL_MODE_CHANNEL_AUTH;
+
+	/* Send CMODE */
+	SILC_PUT32_MSB(m, mode);
+	chidp = silc_id_payload_encode(sgc->channel->id, SILC_ID_CHANNEL);
+	silc_client_command_send(client, conn, SILC_COMMAND_CMODE,
+				 ++conn->cmd_ident, 3,
+				 1, chidp->data, chidp->len,
+				 2, mode, sizeof(mode),
+				 9, chpks->data, chpks->len);
+	silc_buffer_free(chpks);
+	silc_buffer_free(chidp);
+	silc_buffer_free(sgc->pubkeys);
+	silc_free(sgc);
+}
+
+static void
+silcpurple_chat_chpk_cancel(void *user_data, const char *name)
+{
+	SilcPurpleChauth sgc = (SilcPurpleChauth)user_data;
+	silcpurple_chat_chauth_show(sgc->sg, sgc->channel, sgc->pubkeys);
+	silc_buffer_free(sgc->pubkeys);
+	silc_free(sgc);
+}
+
+static void
+silcpurple_chat_chpk_cb(SilcPurpleChauth sgc, PurpleRequestFields *fields)
+{
+	SilcPurple sg = sgc->sg;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	PurpleRequestField *f;
+	const GList *list;
+	SilcPublicKey public_key;
+	SilcBuffer chpks, pk, chidp;
+	SilcUInt16 c = 0, ct;
+	unsigned char mode[4];
+	SilcUInt32 m;
+
+	f = purple_request_fields_get_field(fields, "list");
+	if (!purple_request_field_list_get_selected(f)) {
+		/* Add new public key */
+		purple_request_file(sg->gc, _("Open Public Key..."), NULL, FALSE,
+				  G_CALLBACK(silcpurple_chat_chpk_add),
+				  G_CALLBACK(silcpurple_chat_chpk_cancel),
+				  purple_connection_get_account(sg->gc), NULL, NULL, sgc);
+		return;
+	}
+
+	list = purple_request_field_list_get_items(f);
+	chpks = silc_buffer_alloc_size(2);
+
+	for (ct = 0; list; list = list->next, ct++) {
+		public_key = purple_request_field_list_get_data(f, list->data);
+		if (purple_request_field_list_is_selected(f, list->data)) {
+			/* Delete this public key */
+			pk = silc_pkcs_public_key_payload_encode(public_key);
+			chpks = silc_argument_payload_encode_one(chpks, pk->data,
+								 pk->len, 0x01);
+			silc_buffer_free(pk);
+			c++;
+		}
+		silc_pkcs_public_key_free(public_key);
+	}
+	if (!c) {
+		silc_buffer_free(chpks);
+		return;
+	}
+	SILC_PUT16_MSB(c, chpks->head);
+
+	m = sgc->channel->mode;
+	if (ct == c)
+		m &= ~SILC_CHANNEL_MODE_CHANNEL_AUTH;
+
+	/* Send CMODE */
+	SILC_PUT32_MSB(m, mode);
+	chidp = silc_id_payload_encode(sgc->channel->id, SILC_ID_CHANNEL);
+	silc_client_command_send(client, conn, SILC_COMMAND_CMODE,
+				 ++conn->cmd_ident, 3,
+				 1, chidp->data, chidp->len,
+				 2, mode, sizeof(mode),
+				 9, chpks->data, chpks->len);
+	silc_buffer_free(chpks);
+	silc_buffer_free(chidp);
+	silc_buffer_free(sgc->pubkeys);
+	silc_free(sgc);
+}
+
+static void
+silcpurple_chat_chauth_ok(SilcPurpleChauth sgc, PurpleRequestFields *fields)
+{
+	SilcPurple sg = sgc->sg;
+	PurpleRequestField *f;
+	const char *curpass, *val;
+	int set;
+
+	f = purple_request_fields_get_field(fields, "passphrase");
+	val = purple_request_field_string_get_value(f);
+	curpass = purple_blist_node_get_string((PurpleBlistNode *)sgc->c, "passphrase");
+
+	if (!val && curpass)
+		set = 0;
+	else if (val && !curpass)
+		set = 1;
+	else if (val && curpass && strcmp(val, curpass))
+		set = 1;
+	else
+		set = -1;
+
+	if (set == 1) {
+		silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+					 sgc->channel->channel_name, "+a", val, NULL);
+		purple_blist_node_set_string((PurpleBlistNode *)sgc->c, "passphrase", val);
+	} else if (set == 0) {
+		silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+					 sgc->channel->channel_name, "-a", NULL);
+		purple_blist_node_remove_setting((PurpleBlistNode *)sgc->c, "passphrase");
+	}
+
+	silc_buffer_free(sgc->pubkeys);
+	silc_free(sgc);
+}
+
+void silcpurple_chat_chauth_show(SilcPurple sg, SilcChannelEntry channel,
+			       SilcBuffer channel_pubkeys)
+{
+	SilcUInt16 argc;
+	SilcArgumentPayload chpks;
+	unsigned char *pk;
+	SilcUInt32 pk_len, type;
+	char *fingerprint, *babbleprint;
+	SilcPublicKey pubkey;
+	SilcPublicKeyIdentifier ident;
+	char tmp2[1024], t[512];
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *g;
+	PurpleRequestField *f;
+	SilcPurpleChauth sgc;
+	const char *curpass = NULL;
+
+	sgc = silc_calloc(1, sizeof(*sgc));
+	if (!sgc)
+		return;
+	sgc->sg = sg;
+	sgc->channel = channel;
+
+	fields = purple_request_fields_new();
+
+	if (sgc->c)
+	  curpass = purple_blist_node_get_string((PurpleBlistNode *)sgc->c, "passphrase");
+
+	g = purple_request_field_group_new(NULL);
+	f = purple_request_field_string_new("passphrase", _("Channel Passphrase"),
+					  curpass, FALSE);
+	purple_request_field_string_set_masked(f, TRUE);
+	purple_request_field_group_add_field(g, f);
+	purple_request_fields_add_group(fields, g);
+
+	g = purple_request_field_group_new(NULL);
+	f = purple_request_field_label_new("l1", _("Channel Public Keys List"));
+	purple_request_field_group_add_field(g, f);
+	purple_request_fields_add_group(fields, g);
+
+	g_snprintf(t, sizeof(t),
+		   _("Channel authentication is used to secure the channel from "
+		     "unauthorized access. The authentication may be based on "
+		     "passphrase and digital signatures. If passphrase is set, it "
+		     "is required to be able to join. If channel public keys are set "
+		     "then only users whose public keys are listed are able to join."));
+
+	if (!channel_pubkeys) {
+		f = purple_request_field_list_new("list", NULL);
+		purple_request_field_group_add_field(g, f);
+		purple_request_fields(sg->gc, _("Channel Authentication"),
+				    _("Channel Authentication"), t, fields,
+				    _("Add / Remove"), G_CALLBACK(silcpurple_chat_chpk_cb),
+				    _("OK"), G_CALLBACK(silcpurple_chat_chauth_ok),
+					purple_connection_get_account(sg->gc), NULL, NULL, sgc);
+		return;
+	}
+	sgc->pubkeys = silc_buffer_copy(channel_pubkeys);
+
+	g = purple_request_field_group_new(NULL);
+	f = purple_request_field_list_new("list", NULL);
+	purple_request_field_group_add_field(g, f);
+	purple_request_fields_add_group(fields, g);
+
+	SILC_GET16_MSB(argc, channel_pubkeys->data);
+	chpks = silc_argument_payload_parse(channel_pubkeys->data + 2,
+					    channel_pubkeys->len - 2, argc);
+	if (!chpks)
+		return;
+
+	pk = silc_argument_get_first_arg(chpks, &type, &pk_len);
+	while (pk) {
+		fingerprint = silc_hash_fingerprint(NULL, pk + 4, pk_len - 4);
+		babbleprint = silc_hash_babbleprint(NULL, pk + 4, pk_len - 4);
+		silc_pkcs_public_key_payload_decode(pk, pk_len, &pubkey);
+		ident = silc_pkcs_decode_identifier(pubkey->identifier);
+
+		g_snprintf(tmp2, sizeof(tmp2), "%s\n  %s\n  %s",
+			   ident->realname ? ident->realname : ident->username ?
+			   ident->username : "", fingerprint, babbleprint);
+		purple_request_field_list_add(f, tmp2, pubkey);
+
+		silc_free(fingerprint);
+		silc_free(babbleprint);
+		silc_pkcs_free_identifier(ident);
+		pk = silc_argument_get_next_arg(chpks, &type, &pk_len);
+	}
+
+	purple_request_field_list_set_multi_select(f, FALSE);
+	purple_request_fields(sg->gc, _("Channel Authentication"),
+			    _("Channel Authentication"), t, fields,
+			    _("Add / Remove"), G_CALLBACK(silcpurple_chat_chpk_cb),
+			    _("OK"), G_CALLBACK(silcpurple_chat_chauth_ok),
+				purple_connection_get_account(sg->gc), NULL, NULL, sgc);
+
+	silc_argument_payload_free(chpks);
+}
+
+static void
+silcpurple_chat_chauth(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(chat->components, "channel"),
+				 "+C", NULL);
+}
+
+
+/************************** Channel Private Groups **************************/
+
+/* Private groups are "virtual" channels.  They are groups inside a channel.
+   This is implemented by using channel private keys.  By knowing a channel
+   private key user becomes part of that group and is able to talk on that
+   group.  Other users, on the same channel, won't be able to see the
+   messages of that group.  It is possible to have multiple groups inside
+   a channel - and thus having multiple private keys on the channel. */
+
+typedef struct {
+	SilcPurple sg;
+	PurpleChat *c;
+	const char *channel;
+} *SilcPurpleCharPrv;
+
+static void
+silcpurple_chat_prv_add(SilcPurpleCharPrv p, PurpleRequestFields *fields)
+{
+	SilcPurple sg = p->sg;
+	char tmp[512];
+	PurpleRequestField *f;
+	const char *name, *passphrase, *alias;
+	GHashTable *comp;
+	PurpleGroup *g;
+	PurpleChat *cn;
+
+	f = purple_request_fields_get_field(fields, "name");
+	name = purple_request_field_string_get_value(f);
+	if (!name) {
+		silc_free(p);
+		return;
+	}
+	f = purple_request_fields_get_field(fields, "passphrase");
+	passphrase = purple_request_field_string_get_value(f);
+	f = purple_request_fields_get_field(fields, "alias");
+	alias = purple_request_field_string_get_value(f);
+
+	/* Add private group to buddy list */
+	g_snprintf(tmp, sizeof(tmp), "%s [Private Group]", name);
+	comp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+	g_hash_table_replace(comp, g_strdup("channel"), g_strdup(tmp));
+	g_hash_table_replace(comp, g_strdup("passphrase"), g_strdup(passphrase));
+
+	cn = purple_chat_new(sg->account, alias, comp);
+	g = (PurpleGroup *)p->c->node.parent;
+	purple_blist_add_chat(cn, g, (PurpleBlistNode *)p->c);
+
+	/* Associate to a real channel */
+	purple_blist_node_set_string((PurpleBlistNode *)cn, "parentch", p->channel);
+
+	/* Join the group */
+	silcpurple_chat_join(sg->gc, comp);
+
+	silc_free(p);
+}
+
+static void
+silcpurple_chat_prv_cancel(SilcPurpleCharPrv p, PurpleRequestFields *fields)
+{
+	silc_free(p);
+}
+
+static void
+silcpurple_chat_prv(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	SilcPurpleCharPrv p;
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *g;
+	PurpleRequestField *f;
+	char tmp[512];
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	p = silc_calloc(1, sizeof(*p));
+	if (!p)
+		return;
+	p->sg = sg;
+
+	p->channel = g_hash_table_lookup(chat->components, "channel");
+	p->c = purple_blist_find_chat(sg->account, p->channel);
+
+	fields = purple_request_fields_new();
+
+	g = purple_request_field_group_new(NULL);
+	f = purple_request_field_string_new("name", _("Group Name"),
+					  NULL, FALSE);
+	purple_request_field_group_add_field(g, f);
+
+	f = purple_request_field_string_new("passphrase", _("Passphrase"),
+					  NULL, FALSE);
+	purple_request_field_string_set_masked(f, TRUE);
+	purple_request_field_group_add_field(g, f);
+
+	f = purple_request_field_string_new("alias", _("Alias"),
+					  NULL, FALSE);
+	purple_request_field_group_add_field(g, f);
+	purple_request_fields_add_group(fields, g);
+
+	g_snprintf(tmp, sizeof(tmp),
+		   _("Please enter the %s channel private group name and passphrase."),
+		   p->channel);
+	purple_request_fields(gc, _("Add Channel Private Group"), NULL, tmp, fields,
+			    _("Add"), G_CALLBACK(silcpurple_chat_prv_add),
+			    _("Cancel"), G_CALLBACK(silcpurple_chat_prv_cancel),
+				purple_connection_get_account(gc), NULL, NULL, p);
+}
+
+
+/****************************** Channel Modes ********************************/
+
+static void
+silcpurple_chat_permanent_reset(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(chat->components, "channel"),
+				 "-f", NULL);
+}
+
+static void
+silcpurple_chat_permanent(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+	const char *channel;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	if (!sg->conn)
+		return;
+
+	/* XXX we should have ability to define which founder
+	   key to use.  Now we use the user's own public key
+	   (default key). */
+
+	/* Call CMODE */
+	channel = g_hash_table_lookup(chat->components, "channel");
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE", channel,
+				 "+f", NULL);
+}
+
+typedef struct {
+	SilcPurple sg;
+	const char *channel;
+} *SilcPurpleChatInput;
+
+static void
+silcpurple_chat_ulimit_cb(SilcPurpleChatInput s, const char *limit)
+{
+	SilcChannelEntry channel;
+	int ulimit = 0;
+
+	channel = silc_client_get_channel(s->sg->client, s->sg->conn,
+					  (char *)s->channel);
+	if (!channel)
+		return;
+	if (limit)
+		ulimit = atoi(limit);
+
+	if (!limit || !(*limit) || *limit == '0') {
+		if (limit && ulimit == channel->user_limit) {
+			silc_free(s);
+			return;
+		}
+		silc_client_command_call(s->sg->client, s->sg->conn, NULL, "CMODE",
+					 s->channel, "-l", NULL);
+
+		silc_free(s);
+		return;
+	}
+
+	if (ulimit == channel->user_limit) {
+		silc_free(s);
+		return;
+	}
+
+	/* Call CMODE */
+	silc_client_command_call(s->sg->client, s->sg->conn, NULL, "CMODE",
+				 s->channel, "+l", limit, NULL);
+
+	silc_free(s);
+}
+
+static void
+silcpurple_chat_ulimit(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	SilcPurpleChatInput s;
+	SilcChannelEntry channel;
+	const char *ch;
+	char tmp[32];
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	if (!sg->conn)
+		return;
+
+	ch = g_strdup(g_hash_table_lookup(chat->components, "channel"));
+	channel = silc_client_get_channel(sg->client, sg->conn, (char *)ch);
+	if (!channel)
+		return;
+
+	s = silc_calloc(1, sizeof(*s));
+	if (!s)
+		return;
+	s->channel = ch;
+	s->sg = sg;
+	g_snprintf(tmp, sizeof(tmp), "%d", (int)channel->user_limit);
+	purple_request_input(gc, _("User Limit"), NULL,
+			   _("Set user limit on channel. Set to zero to reset user limit."),
+			   tmp, FALSE, FALSE, NULL,
+			   _("OK"), G_CALLBACK(silcpurple_chat_ulimit_cb),
+			   _("Cancel"), G_CALLBACK(silcpurple_chat_ulimit_cb),
+			   purple_connection_get_account(gc), NULL, NULL, s);
+}
+
+static void
+silcpurple_chat_resettopic(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(chat->components, "channel"),
+				 "-t", NULL);
+}
+
+static void
+silcpurple_chat_settopic(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(chat->components, "channel"),
+				 "+t", NULL);
+}
+
+static void
+silcpurple_chat_resetprivate(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(chat->components, "channel"),
+				 "-p", NULL);
+}
+
+static void
+silcpurple_chat_setprivate(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(chat->components, "channel"),
+				 "+p", NULL);
+}
+
+static void
+silcpurple_chat_resetsecret(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(chat->components, "channel"),
+				 "-s", NULL);
+}
+
+static void
+silcpurple_chat_setsecret(PurpleBlistNode *node, gpointer data)
+{
+	PurpleChat *chat;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node));
+
+	chat = (PurpleChat *) node;
+	gc = purple_account_get_connection(chat->account);
+	sg = gc->proto_data;
+
+	silc_client_command_call(sg->client, sg->conn, NULL, "CMODE",
+				 g_hash_table_lookup(chat->components, "channel"),
+				 "+s", NULL);
+}
+
+typedef struct {
+	SilcPurple sg;
+	SilcChannelEntry channel;
+} *SilcPurpleChatWb;
+
+static void
+silcpurple_chat_wb(PurpleBlistNode *node, gpointer data)
+{
+	SilcPurpleChatWb wb = data;
+	silcpurple_wb_init_ch(wb->sg, wb->channel);
+	silc_free(wb);
+}
+
+GList *silcpurple_chat_menu(PurpleChat *chat)
+{
+	GHashTable *components = chat->components;
+	PurpleConnection *gc = purple_account_get_connection(chat->account);
+	SilcPurple sg = gc->proto_data;
+	SilcClientConnection conn = sg->conn;
+	const char *chname = NULL;
+	SilcChannelEntry channel = NULL;
+	SilcChannelUser chu = NULL;
+	SilcUInt32 mode = 0;
+
+	GList *m = NULL;
+	PurpleMenuAction *act;
+
+	if (components)
+		chname = g_hash_table_lookup(components, "channel");
+	if (chname)
+		channel = silc_client_get_channel(sg->client, sg->conn,
+						  (char *)chname);
+	if (channel) {
+		chu = silc_client_on_channel(channel, conn->local_entry);
+		if (chu)
+			mode = chu->mode;
+	}
+
+	if (strstr(chname, "[Private Group]"))
+		return NULL;
+
+	act = purple_menu_action_new(_("Get Info"),
+	                           PURPLE_CALLBACK(silcpurple_chat_getinfo_menu),
+	                           NULL, NULL);
+	m = g_list_append(m, act);
+
+#if 0   /* XXX For now these are not implemented.  We need better
+	   listview dialog from Purple for these. */
+	if (mode & SILC_CHANNEL_UMODE_CHANOP) {
+		act = purple_menu_action_new(_("Invite List"),
+		                           PURPLE_CALLBACK(silcpurple_chat_invitelist),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+
+		act = purple_menu_action_new(_("Ban List"),
+		                           PURPLE_CALLBACK(silcpurple_chat_banlist),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+	}
+#endif
+
+	if (chu) {
+		act = purple_menu_action_new(_("Add Private Group"),
+		                           PURPLE_CALLBACK(silcpurple_chat_prv),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+	}
+
+	if (mode & SILC_CHANNEL_UMODE_CHANFO) {
+		act = purple_menu_action_new(_("Channel Authentication"),
+		                           PURPLE_CALLBACK(silcpurple_chat_chauth),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+
+		if (channel->mode & SILC_CHANNEL_MODE_FOUNDER_AUTH) {
+			act = purple_menu_action_new(_("Reset Permanent"),
+			                           PURPLE_CALLBACK(silcpurple_chat_permanent_reset),
+			                           NULL, NULL);
+			m = g_list_append(m, act);
+		} else {
+			act = purple_menu_action_new(_("Set Permanent"),
+			                           PURPLE_CALLBACK(silcpurple_chat_permanent),
+			                           NULL, NULL);
+			m = g_list_append(m, act);
+		}
+	}
+
+	if (mode & SILC_CHANNEL_UMODE_CHANOP) {
+		act = purple_menu_action_new(_("Set User Limit"),
+		                           PURPLE_CALLBACK(silcpurple_chat_ulimit),
+		                           NULL, NULL);
+		m = g_list_append(m, act);
+
+		if (channel->mode & SILC_CHANNEL_MODE_TOPIC) {
+			act = purple_menu_action_new(_("Reset Topic Restriction"),
+			                           PURPLE_CALLBACK(silcpurple_chat_resettopic),
+			                           NULL, NULL);
+			m = g_list_append(m, act);
+		} else {
+			act = purple_menu_action_new(_("Set Topic Restriction"),
+			                           PURPLE_CALLBACK(silcpurple_chat_settopic),
+			                           NULL, NULL);
+			m = g_list_append(m, act);
+		}
+
+		if (channel->mode & SILC_CHANNEL_MODE_PRIVATE) {
+			act = purple_menu_action_new(_("Reset Private Channel"),
+			                           PURPLE_CALLBACK(silcpurple_chat_resetprivate),
+			                           NULL, NULL);
+			m = g_list_append(m, act);
+		} else {
+			act = purple_menu_action_new(_("Set Private Channel"),
+			                           PURPLE_CALLBACK(silcpurple_chat_setprivate),
+			                           NULL, NULL);
+			m = g_list_append(m, act);
+		}
+
+		if (channel->mode & SILC_CHANNEL_MODE_SECRET) {
+			act = purple_menu_action_new(_("Reset Secret Channel"),
+			                           PURPLE_CALLBACK(silcpurple_chat_resetsecret),
+			                           NULL, NULL);
+			m = g_list_append(m, act);
+		} else {
+			act = purple_menu_action_new(_("Set Secret Channel"),
+			                           PURPLE_CALLBACK(silcpurple_chat_setsecret),
+			                           NULL, NULL);
+			m = g_list_append(m, act);
+		}
+	}
+
+	if (channel) {
+		SilcPurpleChatWb wb;
+		wb = silc_calloc(1, sizeof(*wb));
+		wb->sg = sg;
+		wb->channel = channel;
+		act = purple_menu_action_new(_("Draw On Whiteboard"),
+		                           PURPLE_CALLBACK(silcpurple_chat_wb),
+		                           (void *)wb, NULL);
+		m = g_list_append(m, act);
+	}
+
+	return m;
+}
+
+
+/******************************* Joining Etc. ********************************/
+
+void silcpurple_chat_join_done(SilcClient client,
+			     SilcClientConnection conn,
+			     SilcClientEntry *clients,
+			     SilcUInt32 clients_count,
+			     void *context)
+{
+	PurpleConnection *gc = client->application;
+	SilcPurple sg = gc->proto_data;
+	SilcChannelEntry channel = context;
+	PurpleConversation *convo;
+	SilcUInt32 retry = SILC_PTR_TO_32(channel->context);
+	SilcHashTableList htl;
+	SilcChannelUser chu;
+	GList *users = NULL, *flags = NULL;
+	char tmp[256];
+
+	if (!clients && retry < 1) {
+		/* Resolving users failed, try again. */
+		channel->context = SILC_32_TO_PTR(retry + 1);
+		silc_client_get_clients_by_channel(client, conn, channel,
+						   silcpurple_chat_join_done, channel);
+		return;
+	}
+
+	/* Add channel to Purple */
+	channel->context = SILC_32_TO_PTR(++sg->channel_ids);
+	serv_got_joined_chat(gc, sg->channel_ids, channel->channel_name);
+	convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+							channel->channel_name, sg->account);
+	if (!convo)
+		return;
+
+	/* Add all users to channel */
+	silc_hash_table_list(channel->user_list, &htl);
+	while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+		PurpleConvChatBuddyFlags f = PURPLE_CBFLAGS_NONE;
+		if (!chu->client->nickname)
+			continue;
+		chu->context = SILC_32_TO_PTR(sg->channel_ids);
+
+		if (chu->mode & SILC_CHANNEL_UMODE_CHANFO)
+			f |= PURPLE_CBFLAGS_FOUNDER;
+		if (chu->mode & SILC_CHANNEL_UMODE_CHANOP)
+			f |= PURPLE_CBFLAGS_OP;
+		users = g_list_append(users, g_strdup(chu->client->nickname));
+		flags = g_list_append(flags, GINT_TO_POINTER(f));
+
+		if (chu->mode & SILC_CHANNEL_UMODE_CHANFO) {
+			if (chu->client == conn->local_entry)
+				g_snprintf(tmp, sizeof(tmp),
+					   _("You are channel founder on <I>%s</I>"),
+					   channel->channel_name);
+			else
+				g_snprintf(tmp, sizeof(tmp),
+					   _("Channel founder on <I>%s</I> is <I>%s</I>"),
+					   channel->channel_name, chu->client->nickname);
+
+			purple_conversation_write(convo, NULL, tmp,
+						PURPLE_MESSAGE_SYSTEM, time(NULL));
+
+		}
+	}
+	silc_hash_table_list_reset(&htl);
+
+	purple_conv_chat_add_users(PURPLE_CONV_CHAT(convo), users, NULL, flags, FALSE);
+	g_list_free(users);
+	g_list_free(flags);
+
+	/* Set topic */
+	if (channel->topic)
+		purple_conv_chat_set_topic(PURPLE_CONV_CHAT(convo), NULL, channel->topic);
+
+	/* Set nick */
+	purple_conv_chat_set_nick(PURPLE_CONV_CHAT(convo), conn->local_entry->nickname);
+}
+
+char *silcpurple_get_chat_name(GHashTable *data)
+{
+	return g_strdup(g_hash_table_lookup(data, "channel"));
+}	
+
+void silcpurple_chat_join(PurpleConnection *gc, GHashTable *data)
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	const char *channel, *passphrase, *parentch;
+
+	if (!conn)
+		return;
+
+	channel = g_hash_table_lookup(data, "channel");
+	passphrase = g_hash_table_lookup(data, "passphrase");
+
+	/* Check if we are joining a private group.  Handle it
+	   purely locally as it's not a real channel */
+	if (strstr(channel, "[Private Group]")) {
+		SilcChannelEntry channel_entry;
+		SilcChannelPrivateKey key;
+		PurpleChat *c;
+		SilcPurplePrvgrp grp;
+
+		c = purple_blist_find_chat(sg->account, channel);
+		parentch = purple_blist_node_get_string((PurpleBlistNode *)c, "parentch");
+		if (!parentch)
+			return;
+
+		channel_entry = silc_client_get_channel(sg->client, sg->conn,
+							(char *)parentch);
+		if (!channel_entry ||
+		    !silc_client_on_channel(channel_entry, sg->conn->local_entry)) {
+			char tmp[512];
+			g_snprintf(tmp, sizeof(tmp),
+				   _("You have to join the %s channel before you are "
+				     "able to join the private group"), parentch);
+			purple_notify_error(gc, _("Join Private Group"),
+					  _("Cannot join private group"), tmp);
+			return;
+		}
+
+		/* Add channel private key */
+		if (!silc_client_add_channel_private_key(client, conn,
+							 channel_entry, channel,
+							 NULL, NULL,
+							 (unsigned char *)passphrase,
+							 strlen(passphrase), &key))
+			return;
+
+		/* Join the group */
+		grp = silc_calloc(1, sizeof(*grp));
+		if (!grp)
+			return;
+		grp->id = ++sg->channel_ids + SILCPURPLE_PRVGRP;
+		grp->chid = SILC_PTR_TO_32(channel_entry->context);
+		grp->parentch = parentch;
+		grp->channel = channel;
+		grp->key = key;
+		sg->grps = g_list_append(sg->grps, grp);
+		serv_got_joined_chat(gc, grp->id, channel);
+		return;
+	}
+
+	/* XXX We should have other properties here as well:
+	   1. whether to try to authenticate to the channel
+	     1a. with default key,
+	     1b. with specific key.
+	   2. whether to try to authenticate to become founder.
+	     2a. with default key,
+	     2b. with specific key.
+
+	   Since now such variety is not possible in the join dialog
+	   we always use -founder and -auth options, which try to
+	   do both 1 and 2 with default keys. */
+
+	/* Call JOIN */
+	if ((passphrase != NULL) && (*passphrase != '\0'))
+		silc_client_command_call(client, conn, NULL, "JOIN",
+					 channel, passphrase, "-auth", "-founder", NULL);
+	else
+		silc_client_command_call(client, conn, NULL, "JOIN",
+					 channel, "-auth", "-founder", NULL);
+}
+
+void silcpurple_chat_invite(PurpleConnection *gc, int id, const char *msg,
+			  const char *name)
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcHashTableList htl;
+	SilcChannelUser chu;
+	gboolean found = FALSE;
+
+	if (!conn)
+		return;
+
+	/* See if we are inviting on a private group.  Invite
+	   to the actual channel */
+	if (id > SILCPURPLE_PRVGRP) {
+		GList *l;
+		SilcPurplePrvgrp prv;
+
+		for (l = sg->grps; l; l = l->next)
+			if (((SilcPurplePrvgrp)l->data)->id == id)
+				break;
+		if (!l)
+			return;
+		prv = l->data;
+		id = prv->chid;
+	}
+
+	/* Find channel by id */
+	silc_hash_table_list(conn->local_entry->channels, &htl);
+	while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+		if (SILC_PTR_TO_32(chu->channel->context) == id ) {
+			found = TRUE;
+			break;
+		}
+	}
+	silc_hash_table_list_reset(&htl);
+	if (!found)
+		return;
+
+	/* Call INVITE */
+	silc_client_command_call(client, conn, NULL, "INVITE",
+				 chu->channel->channel_name,
+				 name, NULL);
+}
+
+void silcpurple_chat_leave(PurpleConnection *gc, int id)
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcHashTableList htl;
+	SilcChannelUser chu;
+	gboolean found = FALSE;
+	GList *l;
+	SilcPurplePrvgrp prv;
+
+	if (!conn)
+		return;
+
+	/* See if we are leaving a private group */
+	if (id > SILCPURPLE_PRVGRP) {
+		SilcChannelEntry channel;
+
+		for (l = sg->grps; l; l = l->next)
+			if (((SilcPurplePrvgrp)l->data)->id == id)
+				break;
+		if (!l)
+			return;
+		prv = l->data;
+		channel = silc_client_get_channel(sg->client, sg->conn,
+						  (char *)prv->parentch);
+		if (!channel)
+			return;
+		silc_client_del_channel_private_key(client, conn,
+						    channel, prv->key);
+		silc_free(prv);
+		sg->grps = g_list_remove(sg->grps, prv);
+		serv_got_chat_left(gc, id);
+		return;
+	}
+
+	/* Find channel by id */
+	silc_hash_table_list(conn->local_entry->channels, &htl);
+	while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+		if (SILC_PTR_TO_32(chu->channel->context) == id ) {
+			found = TRUE;
+			break;
+		}
+	}
+	silc_hash_table_list_reset(&htl);
+	if (!found)
+		return;
+
+	/* Call LEAVE */
+	silc_client_command_call(client, conn, NULL, "LEAVE",
+				 chu->channel->channel_name, NULL);
+
+	serv_got_chat_left(gc, id);
+
+	/* Leave from private groups on this channel as well */
+	for (l = sg->grps; l; l = l->next)
+		if (((SilcPurplePrvgrp)l->data)->chid == id) {
+			prv = l->data;
+			silc_client_del_channel_private_key(client, conn,
+							    chu->channel,
+							    prv->key);
+			serv_got_chat_left(gc, prv->id);
+			silc_free(prv);
+			sg->grps = g_list_remove(sg->grps, prv);
+			if (!sg->grps)
+				break;
+		}
+}
+
+int silcpurple_chat_send(PurpleConnection *gc, int id, const char *msg, PurpleMessageFlags msgflags)
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcHashTableList htl;
+	SilcChannelUser chu;
+	SilcChannelEntry channel = NULL;
+	SilcChannelPrivateKey key = NULL;
+	SilcUInt32 flags;
+	int ret;
+	char *msg2, *tmp;
+	gboolean found = FALSE;
+	gboolean sign = purple_account_get_bool(sg->account, "sign-verify", FALSE);
+
+	if (!msg || !conn)
+		return 0;
+
+	flags = SILC_MESSAGE_FLAG_UTF8;
+
+	tmp = msg2 = purple_unescape_html(msg);
+
+	if (!g_ascii_strncasecmp(msg2, "/me ", 4))
+	{
+		msg2 += 4;
+		if (!*msg2) {
+			g_free(tmp);
+			return 0;
+		}
+		flags |= SILC_MESSAGE_FLAG_ACTION;
+	} else if (strlen(msg) > 1 && msg[0] == '/') {
+		if (!silc_client_command_call(client, conn, msg + 1))
+			purple_notify_error(gc, _("Call Command"), _("Cannot call command"),
+							  _("Unknown command"));
+		g_free(tmp);
+		return 0;
+	}
+
+
+	if (sign)
+		flags |= SILC_MESSAGE_FLAG_SIGNED;
+
+	/* Get the channel private key if we are sending on
+	   private group */
+	if (id > SILCPURPLE_PRVGRP) {
+		GList *l;
+		SilcPurplePrvgrp prv;
+
+		for (l = sg->grps; l; l = l->next)
+			if (((SilcPurplePrvgrp)l->data)->id == id)
+				break;
+		if (!l) {
+			g_free(tmp);
+			return 0;
+		}
+		prv = l->data;
+		channel = silc_client_get_channel(sg->client, sg->conn,
+						  (char *)prv->parentch);
+		if (!channel) {
+			g_free(tmp);
+			return 0;
+		}
+		key = prv->key;
+	}
+
+	if (!channel) {
+		/* Find channel by id */
+		silc_hash_table_list(conn->local_entry->channels, &htl);
+		while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+			if (SILC_PTR_TO_32(chu->channel->context) == id ) {
+				found = TRUE;
+				break;
+			}
+		}
+		silc_hash_table_list_reset(&htl);
+		if (!found) {
+			g_free(tmp);
+			return 0;
+		}
+		channel = chu->channel;
+	}
+
+	/* Send channel message */
+	ret = silc_client_send_channel_message(client, conn, channel, key,
+					       flags, (unsigned char *)msg2,
+					       strlen(msg2), TRUE);
+	if (ret) {
+		serv_got_chat_in(gc, id, purple_connection_get_display_name(gc), 0, msg,
+				 time(NULL));
+	}
+	g_free(tmp);
+
+	return ret;
+}
+
+void silcpurple_chat_set_topic(PurpleConnection *gc, int id, const char *topic)
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcHashTableList htl;
+	SilcChannelUser chu;
+	gboolean found = FALSE;
+
+	if (!conn)
+		return;
+
+	/* See if setting topic on private group.  Set it
+	   on the actual channel */
+	if (id > SILCPURPLE_PRVGRP) {
+		GList *l;
+		SilcPurplePrvgrp prv;
+
+		for (l = sg->grps; l; l = l->next)
+			if (((SilcPurplePrvgrp)l->data)->id == id)
+				break;
+		if (!l)
+			return;
+		prv = l->data;
+		id = prv->chid;
+	}
+
+	/* Find channel by id */
+	silc_hash_table_list(conn->local_entry->channels, &htl);
+	while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+		if (SILC_PTR_TO_32(chu->channel->context) == id ) {
+			found = TRUE;
+			break;
+		}
+	}
+	silc_hash_table_list_reset(&htl);
+	if (!found)
+		return;
+
+	/* Call TOPIC */
+	silc_client_command_call(client, conn, NULL, "TOPIC",
+				 chu->channel->channel_name, topic, NULL);
+}
+
+PurpleRoomlist *silcpurple_roomlist_get_list(PurpleConnection *gc)
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	GList *fields = NULL;
+	PurpleRoomlistField *f;
+
+	if (!conn)
+		return NULL;
+
+	if (sg->roomlist)
+		purple_roomlist_unref(sg->roomlist);
+
+	sg->roomlist_canceled = FALSE;
+
+	sg->roomlist = purple_roomlist_new(purple_connection_get_account(gc));
+	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING, "", "channel", TRUE);
+	fields = g_list_append(fields, f);
+	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_INT,
+				    _("Users"), "users", FALSE);
+	fields = g_list_append(fields, f);
+	f = purple_roomlist_field_new(PURPLE_ROOMLIST_FIELD_STRING,
+				    _("Topic"), "topic", FALSE);
+	fields = g_list_append(fields, f);
+	purple_roomlist_set_fields(sg->roomlist, fields);
+
+	/* Call LIST */
+	silc_client_command_call(client, conn, "LIST");
+
+	purple_roomlist_set_in_progress(sg->roomlist, TRUE);
+
+	return sg->roomlist;
+}
+
+void silcpurple_roomlist_cancel(PurpleRoomlist *list)
+{
+	PurpleConnection *gc = purple_account_get_connection(list->account);
+	SilcPurple sg;
+
+	if (!gc)
+		return;
+	sg = gc->proto_data;
+
+	purple_roomlist_set_in_progress(list, FALSE);
+	if (sg->roomlist == list) {
+		purple_roomlist_unref(sg->roomlist);
+		sg->roomlist = NULL;
+		sg->roomlist_canceled = TRUE;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/silc10/ft.c	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,412 @@
+/*
+
+  silcpurple_ft.c
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  Copyright (C) 2004 Pekka Riikonen
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; version 2 of the License.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+*/
+
+#include "silcincludes.h"
+#include "silcclient.h"
+#include "silcpurple.h"
+
+/****************************** File Transfer ********************************/
+
+/* This implements the secure file transfer protocol (SFTP) using the SILC
+   SFTP library implementation.  The API we use from the SILC Toolkit is the
+   SILC Client file transfer API, as it provides a simple file transfer we
+   need in this case.  We could use the SILC SFTP API directly, but it would
+   be an overkill since we'd effectively re-implement the file transfer what
+   the SILC Client's file transfer API already provides.
+
+   From Purple we do NOT use the FT API to do the transfer as it is very limiting.
+   In fact it does not suite to file transfers like SFTP at all.  For example,
+   it assumes that read operations are synchronous what they are not in SFTP.
+   It also assumes that the file transfer socket is to be handled by the Purple
+   eventloop, and this naturally is something we don't want to do in case of
+   SILC Toolkit.  The FT API suites well to purely stream based file transfers
+   like HTTP GET and similar.
+
+   For this reason, we directly access the Purple GKT FT API and hack the FT
+   API to merely provide the user interface experience and all the magic
+   is done in the SILC Toolkit.  Ie. we update the statistics information in
+   the FT API for user interface, and that's it.  A bit dirty but until the
+   FT API gets better this is the way to go.  Good thing that FT API allowed
+   us to do this. */
+
+typedef struct {
+	SilcPurple sg;
+	SilcClientEntry client_entry;
+	SilcUInt32 session_id;
+	char *hostname;
+	SilcUInt16 port;
+	PurpleXfer *xfer;
+
+	SilcClientFileName completion;
+	void *completion_context;
+} *SilcPurpleXfer;
+
+static void
+silcpurple_ftp_monitor(SilcClient client,
+		     SilcClientConnection conn,
+		     SilcClientMonitorStatus status,
+		     SilcClientFileError error,
+		     SilcUInt64 offset,
+		     SilcUInt64 filesize,
+		     SilcClientEntry client_entry,
+		     SilcUInt32 session_id,
+		     const char *filepath,
+		     void *context)
+{
+	SilcPurpleXfer xfer = context;
+	PurpleConnection *gc = xfer->sg->gc;
+	char tmp[256];
+
+	if (status == SILC_CLIENT_FILE_MONITOR_CLOSED) {
+		purple_xfer_unref(xfer->xfer);
+		silc_free(xfer);
+		return;
+	}
+
+	if (status == SILC_CLIENT_FILE_MONITOR_KEY_AGREEMENT)
+		return;
+
+	if (status == SILC_CLIENT_FILE_MONITOR_ERROR) {
+		if (error == SILC_CLIENT_FILE_NO_SUCH_FILE) {
+			g_snprintf(tmp, sizeof(tmp), "No such file %s",
+				   filepath ? filepath : "[N/A]");
+			purple_notify_error(gc, _("Secure File Transfer"),
+					  _("Error during file transfer"), tmp);
+		} else if (error == SILC_CLIENT_FILE_PERMISSION_DENIED) {
+			purple_notify_error(gc, _("Secure File Transfer"),
+					  _("Error during file transfer"),
+					  _("Permission denied"));
+		} else if (error == SILC_CLIENT_FILE_KEY_AGREEMENT_FAILED) {
+			purple_notify_error(gc, _("Secure File Transfer"),
+					  _("Error during file transfer"),
+					  _("Key agreement failed"));
+		} else if (error == SILC_CLIENT_FILE_UNKNOWN_SESSION) {
+			purple_notify_error(gc, _("Secure File Transfer"),
+					  _("Error during file transfer"),
+					  _("File transfer session does not exist"));
+		} else {
+			purple_notify_error(gc, _("Secure File Transfer"),
+					  _("Error during file transfer"), NULL);
+		}
+		silc_client_file_close(client, conn, session_id);
+		purple_xfer_unref(xfer->xfer);
+		silc_free(xfer);
+		return;
+	}
+
+	/* Update file transfer UI */
+	if (!offset && filesize)
+		purple_xfer_set_size(xfer->xfer, filesize);
+	if (offset && filesize) {
+		xfer->xfer->bytes_sent = offset;
+		xfer->xfer->bytes_remaining = filesize - offset;
+	}
+	purple_xfer_update_progress(xfer->xfer);
+
+	if (status == SILC_CLIENT_FILE_MONITOR_SEND ||
+	    status == SILC_CLIENT_FILE_MONITOR_RECEIVE) {
+		if (offset == filesize) {
+			/* Download finished */
+			purple_xfer_set_completed(xfer->xfer, TRUE);
+			silc_client_file_close(client, conn, session_id);
+		}
+	}
+}
+
+static void
+silcpurple_ftp_cancel(PurpleXfer *x)
+{
+	SilcPurpleXfer xfer = x->data;
+	xfer->xfer->status = PURPLE_XFER_STATUS_CANCEL_LOCAL;
+	purple_xfer_update_progress(xfer->xfer);
+	silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id);
+}
+
+static void
+silcpurple_ftp_ask_name_cancel(PurpleXfer *x)
+{
+	SilcPurpleXfer xfer = x->data;
+
+	/* Cancel the transmission */
+	xfer->completion(NULL, xfer->completion_context);
+	silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id);
+}
+
+static void
+silcpurple_ftp_ask_name_ok(PurpleXfer *x)
+{
+	SilcPurpleXfer xfer = x->data;
+	const char *name;
+
+	name = purple_xfer_get_local_filename(x);
+	g_unlink(name);
+	xfer->completion(name, xfer->completion_context);
+}
+
+static void
+silcpurple_ftp_ask_name(SilcClient client,
+		      SilcClientConnection conn,
+		      SilcUInt32 session_id,
+		      const char *remote_filename,
+		      SilcClientFileName completion,
+		      void *completion_context,
+		      void *context)
+{
+	SilcPurpleXfer xfer = context;
+
+	xfer->completion = completion;
+	xfer->completion_context = completion_context;
+
+	purple_xfer_set_init_fnc(xfer->xfer, silcpurple_ftp_ask_name_ok);
+	purple_xfer_set_request_denied_fnc(xfer->xfer, silcpurple_ftp_ask_name_cancel);
+
+	/* Request to save the file */
+	purple_xfer_set_filename(xfer->xfer, remote_filename);
+	purple_xfer_request(xfer->xfer);
+}
+
+static void
+silcpurple_ftp_request_result(PurpleXfer *x)
+{
+	SilcPurpleXfer xfer = x->data;
+	SilcClientFileError status;
+	PurpleConnection *gc = xfer->sg->gc;
+
+	if (purple_xfer_get_status(x) != PURPLE_XFER_STATUS_ACCEPTED)
+		return;
+
+	/* Start the file transfer */
+	status = silc_client_file_receive(xfer->sg->client, xfer->sg->conn,
+					  silcpurple_ftp_monitor, xfer,
+					  NULL, xfer->session_id,
+					  silcpurple_ftp_ask_name, xfer);
+	switch (status) {
+	case SILC_CLIENT_FILE_OK:
+		return;
+		break;
+
+	case SILC_CLIENT_FILE_UNKNOWN_SESSION:
+		purple_notify_error(gc, _("Secure File Transfer"),
+				  _("No file transfer session active"), NULL);
+		break;
+
+	case SILC_CLIENT_FILE_ALREADY_STARTED:
+		purple_notify_error(gc, _("Secure File Transfer"),
+				  _("File transfer already started"), NULL);
+		break;
+
+	case SILC_CLIENT_FILE_KEY_AGREEMENT_FAILED:
+		purple_notify_error(gc, _("Secure File Transfer"),
+				  _("Could not perform key agreement for file transfer"),
+				  NULL);
+		break;
+
+	default:
+		purple_notify_error(gc, _("Secure File Transfer"),
+				  _("Could not start the file transfer"), NULL);
+		break;
+	}
+
+	/* Error */
+	purple_xfer_unref(xfer->xfer);
+	g_free(xfer->hostname);
+	silc_free(xfer);
+}
+
+static void
+silcpurple_ftp_request_denied(PurpleXfer *x)
+{
+
+}
+
+void silcpurple_ftp_request(SilcClient client, SilcClientConnection conn,
+			  SilcClientEntry client_entry, SilcUInt32 session_id,
+			  const char *hostname, SilcUInt16 port)
+{
+	PurpleConnection *gc = client->application;
+	SilcPurple sg = gc->proto_data;
+	SilcPurpleXfer xfer;
+
+	xfer = silc_calloc(1, sizeof(*xfer));
+	if (!xfer) {
+		silc_client_file_close(sg->client, sg->conn, session_id);
+		return;
+	}
+
+	xfer->sg = sg;
+	xfer->client_entry = client_entry;
+	xfer->session_id = session_id;
+	xfer->hostname = g_strdup(hostname);
+	xfer->port = port;
+	xfer->xfer = purple_xfer_new(xfer->sg->account, PURPLE_XFER_RECEIVE,
+				   xfer->client_entry->nickname);
+	if (!xfer->xfer) {
+		silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id);
+		g_free(xfer->hostname);
+		silc_free(xfer);
+		return;
+	}
+	purple_xfer_set_init_fnc(xfer->xfer, silcpurple_ftp_request_result);
+	purple_xfer_set_request_denied_fnc(xfer->xfer, silcpurple_ftp_request_denied);
+	purple_xfer_set_cancel_recv_fnc(xfer->xfer, silcpurple_ftp_cancel);
+	xfer->xfer->remote_ip = g_strdup(hostname);
+	xfer->xfer->remote_port = port;
+	xfer->xfer->data = xfer;
+
+	/* File transfer request */
+	purple_xfer_request(xfer->xfer);
+}
+
+static void
+silcpurple_ftp_send_cancel(PurpleXfer *x)
+{
+	SilcPurpleXfer xfer = x->data;
+	silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id);
+	purple_xfer_unref(xfer->xfer);
+	g_free(xfer->hostname);
+	silc_free(xfer);
+}
+
+static void
+silcpurple_ftp_send(PurpleXfer *x)
+{
+	SilcPurpleXfer xfer = x->data;
+	const char *name;
+	char *local_ip = NULL, *remote_ip = NULL;
+	gboolean local = TRUE;
+
+	name = purple_xfer_get_local_filename(x);
+
+	/* Do the same magic what we do with key agreement (see silcpurple_buddy.c)
+	   to see if we are behind NAT. */
+	if (silc_net_check_local_by_sock(xfer->sg->conn->sock->sock,
+					 NULL, &local_ip)) {
+		/* Check if the IP is private */
+		if (silcpurple_ip_is_private(local_ip)) {
+			local = FALSE;
+			/* Local IP is private, resolve the remote server IP to see whether
+			   we are talking to Internet or just on LAN. */
+			if (silc_net_check_host_by_sock(xfer->sg->conn->sock->sock, NULL,
+							&remote_ip))
+				if (silcpurple_ip_is_private(remote_ip))
+					/* We assume we are in LAN.  Let's provide the connection point. */
+					local = TRUE;
+		}
+	}
+
+	if (local && !local_ip)
+		local_ip = silc_net_localip();
+
+	/* Send the file */
+	silc_client_file_send(xfer->sg->client, xfer->sg->conn,
+			      silcpurple_ftp_monitor, xfer,
+			      local_ip, 0, !local, xfer->client_entry,
+			      name, &xfer->session_id);
+
+	silc_free(local_ip);
+	silc_free(remote_ip);
+}
+
+static void
+silcpurple_ftp_send_file_resolved(SilcClient client,
+				SilcClientConnection conn,
+				SilcClientEntry *clients,
+				SilcUInt32 clients_count,
+				void *context)
+{
+	PurpleConnection *gc = client->application;
+	char tmp[256];
+
+	if (!clients) {
+		g_snprintf(tmp, sizeof(tmp),
+			   _("User %s is not present in the network"),
+			   (const char *)context);
+		purple_notify_error(gc, _("Secure File Transfer"),
+				  _("Cannot send file"), tmp);
+		silc_free(context);
+		return;
+	}
+
+	silcpurple_ftp_send_file(client->application, (const char *)context, NULL);
+	silc_free(context);
+}
+
+PurpleXfer *silcpurple_ftp_new_xfer(PurpleConnection *gc, const char *name)
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcClientEntry *clients;
+	SilcUInt32 clients_count;
+	SilcPurpleXfer xfer;
+	char *nickname;
+
+	g_return_val_if_fail(name != NULL, NULL);
+
+	if (!silc_parse_userfqdn(name, &nickname, NULL))
+		return NULL;
+
+	/* Find client entry */
+	clients = silc_client_get_clients_local(client, conn, nickname, name,
+											&clients_count);
+	if (!clients) {
+		silc_client_get_clients(client, conn, nickname, NULL,
+								silcpurple_ftp_send_file_resolved,
+								strdup(name));
+		silc_free(nickname);
+		return NULL;
+	}
+
+	xfer = silc_calloc(1, sizeof(*xfer));
+
+	g_return_val_if_fail(xfer != NULL, NULL);
+
+	xfer->sg = sg;
+	xfer->client_entry = clients[0];
+	xfer->xfer = purple_xfer_new(xfer->sg->account, PURPLE_XFER_SEND,
+							   xfer->client_entry->nickname);
+	if (!xfer->xfer) {
+		silc_client_file_close(xfer->sg->client, xfer->sg->conn, xfer->session_id);
+		g_free(xfer->hostname);
+		silc_free(xfer);
+		return NULL;
+	}
+	purple_xfer_set_init_fnc(xfer->xfer, silcpurple_ftp_send);
+	purple_xfer_set_request_denied_fnc(xfer->xfer, silcpurple_ftp_request_denied);
+	purple_xfer_set_cancel_send_fnc(xfer->xfer, silcpurple_ftp_send_cancel);
+	xfer->xfer->data = xfer;
+
+	silc_free(clients);
+	silc_free(nickname);
+
+	return xfer->xfer;
+}
+
+void silcpurple_ftp_send_file(PurpleConnection *gc, const char *name, const char *file)
+{
+	PurpleXfer *xfer = silcpurple_ftp_new_xfer(gc, name);
+
+	g_return_if_fail(xfer != NULL);
+
+	/* Choose file to send */
+	if (file)
+		purple_xfer_request_accepted(xfer, file);
+	else
+		purple_xfer_request(xfer);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/silc10/ops.c	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,2057 @@
+/*
+
+  silcpurple_ops.c
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  Copyright (C) 2004 Pekka Riikonen
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; version 2 of the License.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+*/
+
+#include "silcincludes.h"
+#include "silcclient.h"
+#include "silcpurple.h"
+#include "imgstore.h"
+#include "wb.h"
+
+static void
+silc_channel_message(SilcClient client, SilcClientConnection conn,
+		     SilcClientEntry sender, SilcChannelEntry channel,
+		     SilcMessagePayload payload, SilcChannelPrivateKey key,
+		     SilcMessageFlags flags, const unsigned char *message,
+		     SilcUInt32 message_len);
+static void
+silc_private_message(SilcClient client, SilcClientConnection conn,
+		     SilcClientEntry sender, SilcMessagePayload payload,
+		     SilcMessageFlags flags, const unsigned char *message,
+		     SilcUInt32 message_len);
+
+/* Message sent to the application by library. `conn' associates the
+   message to a specific connection.  `conn', however, may be NULL.
+   The `type' indicates the type of the message sent by the library.
+   The application can for example filter the message according the
+   type. */
+
+static void
+silc_say(SilcClient client, SilcClientConnection conn,
+	 SilcClientMessageType type, char *msg, ...)
+{
+	/* Nothing */
+}
+
+#ifdef HAVE_SILCMIME_H
+/* Processes incoming MIME message.  Can be private message or channel
+   message. */
+
+static void
+silcpurple_mime_message(SilcClient client, SilcClientConnection conn,
+		      SilcClientEntry sender, SilcChannelEntry channel,
+		      SilcMessagePayload payload, SilcChannelPrivateKey key,
+		      SilcMessageFlags flags, SilcMime mime,
+		      gboolean recursive)
+{
+	PurpleConnection *gc = client->application;
+	SilcPurple sg = gc->proto_data;
+	const char *type;
+	const unsigned char *data;
+	SilcUInt32 data_len;
+	PurpleMessageFlags cflags = 0;
+	PurpleConversation *convo = NULL;
+
+	if (!mime)
+		return;
+
+	/* Check for fragmented MIME message */
+	if (silc_mime_is_partial(mime)) {
+		if (!sg->mimeass)
+			sg->mimeass = silc_mime_assembler_alloc();
+
+		/* Defragment */
+		mime = silc_mime_assemble(sg->mimeass, mime);
+		if (!mime)
+			/* More fragments to come */
+			return;
+
+		/* Process the complete message */
+		silcpurple_mime_message(client, conn, sender, channel,
+				      payload, key, flags, mime, FALSE);
+		return;
+	}
+
+	/* Check for multipart message */
+	if (silc_mime_is_multipart(mime)) {
+		SilcMime p;
+		const char *mtype;
+		SilcDList parts = silc_mime_get_multiparts(mime, &mtype);
+
+		/* Only "mixed" type supported */
+		if (strcmp(mtype, "mixed"))
+			goto out;
+
+		silc_dlist_start(parts);
+		while ((p = silc_dlist_get(parts)) != SILC_LIST_END) {
+			/* Recursively process parts */
+			silcpurple_mime_message(client, conn, sender, channel,
+					      payload, key, flags, p, TRUE);
+		}
+		goto out;
+	}
+
+	/* Get content type and MIME data */
+	type = silc_mime_get_field(mime, "Content-Type");
+	if (!type)
+		goto out;
+	data = silc_mime_get_data(mime, &data_len);
+	if (!data)
+		goto out;
+
+	/* Process according to content type */
+
+	/* Plain text */
+	if (strstr(type, "text/plain")) {
+		/* Default is UTF-8, don't check for other charsets */
+		if (!strstr(type, "utf-8"))
+			goto out;
+
+		if (channel)
+			silc_channel_message(client, conn, sender, channel,
+					     payload, key, 
+					     SILC_MESSAGE_FLAG_UTF8, data,
+					     data_len);
+		else
+			silc_private_message(client, conn, sender, payload,
+					     SILC_MESSAGE_FLAG_UTF8, data,
+					     data_len);
+		goto out;
+	}
+
+	/* Image */
+	if (strstr(type, "image/png") ||
+	    strstr(type, "image/jpeg") ||
+	    strstr(type, "image/gif") ||
+	    strstr(type, "image/tiff")) {
+		char tmp[32];
+		int imgid;
+
+		/* Get channel convo (if message is for channel) */
+		if (key && channel) {
+			GList *l;
+			SilcPurplePrvgrp prv;
+
+			for (l = sg->grps; l; l = l->next)
+				if (((SilcPurplePrvgrp)l->data)->key == key) {
+					prv = l->data;
+					convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+							prv->channel, sg->account);
+					break;
+				}
+		}
+		if (channel && !convo)
+			convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+								    channel->channel_name, sg->account);
+		if (channel && !convo)
+			goto out;
+
+		imgid = purple_imgstore_add_with_id(g_memdup(data, data_len), data_len, "");
+		if (imgid) {
+			cflags |= PURPLE_MESSAGE_IMAGES | PURPLE_MESSAGE_RECV;
+			g_snprintf(tmp, sizeof(tmp), "<IMG ID=\"%d\">", imgid);
+		  
+			if (channel)
+				serv_got_chat_in(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(convo)),
+				 		 sender->nickname ?
+				 		  sender->nickname : 
+						 "<unknown>", cflags,
+						 tmp, time(NULL));
+			else
+				serv_got_im(gc, sender->nickname ?
+					    sender->nickname : "<unknown>",
+					    tmp, cflags, time(NULL));
+
+			purple_imgstore_unref_by_id(imgid);
+			cflags = 0;
+		}
+		goto out;
+	}
+
+	/* Whiteboard message */
+	if (strstr(type, "application/x-wb") &&
+	    !purple_account_get_bool(sg->account, "block-wb", FALSE)) {
+		if (channel)
+			silcpurple_wb_receive_ch(client, conn, sender, channel,
+					       payload, flags, data, data_len);
+		else
+			silcpurple_wb_receive(client, conn, sender, payload,
+					    flags, data, data_len);
+		goto out;
+	}
+
+ out:
+	if (!recursive)
+		silc_mime_free(mime);
+}
+#endif /* HAVE_SILCMIME_H */
+
+/* Message for a channel. The `sender' is the sender of the message
+   The `channel' is the channel. The `message' is the message.  Note
+   that `message' maybe NULL.  The `flags' indicates message flags
+   and it is used to determine how the message can be interpreted
+   (like it may tell the message is multimedia message). */
+
+static void
+silc_channel_message(SilcClient client, SilcClientConnection conn,
+		     SilcClientEntry sender, SilcChannelEntry channel,
+		     SilcMessagePayload payload, SilcChannelPrivateKey key,
+		     SilcMessageFlags flags, const unsigned char *message,
+		     SilcUInt32 message_len)
+{
+	PurpleConnection *gc = client->application;
+	SilcPurple sg = gc->proto_data;
+	PurpleConversation *convo = NULL;
+	char *msg, *tmp;
+
+	if (!message)
+		return;
+
+	if (key) {
+		GList *l;
+		SilcPurplePrvgrp prv;
+
+		for (l = sg->grps; l; l = l->next)
+			if (((SilcPurplePrvgrp)l->data)->key == key) {
+				prv = l->data;
+				convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+										prv->channel, sg->account);
+				break;
+			}
+	}
+	if (!convo)
+		convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+								channel->channel_name, sg->account);
+	if (!convo)
+		return;
+
+	if (flags & SILC_MESSAGE_FLAG_SIGNED &&
+	    purple_account_get_bool(sg->account, "sign-verify", FALSE)) {
+		/* XXX */
+	}
+
+	if (flags & SILC_MESSAGE_FLAG_DATA) {
+		/* Process MIME message */
+#ifdef HAVE_SILCMIME_H
+		SilcMime mime;
+		mime = silc_mime_decode(message, message_len);
+		silcpurple_mime_message(client, conn, sender, channel, payload,
+				      key, flags, mime, FALSE);
+#else
+		char type[128], enc[128];
+		unsigned char *data;
+		SilcUInt32 data_len;
+
+		memset(type, 0, sizeof(type));
+		memset(enc, 0, sizeof(enc));
+
+		if (!silc_mime_parse(message, message_len, NULL, 0,
+		    type, sizeof(type) - 1, enc, sizeof(enc) - 1, &data,
+		    &data_len))
+			return;
+
+		if (!strcmp(type, "application/x-wb") &&
+		    !strcmp(enc, "binary") &&
+		    !purple_account_get_bool(sg->account, "block-wb", FALSE))
+			silcpurple_wb_receive_ch(client, conn, sender, channel,
+					       payload, flags, data, data_len);
+#endif
+		return;
+	}
+
+	if (flags & SILC_MESSAGE_FLAG_ACTION) {
+		msg = g_strdup_printf("/me %s",
+				      (const char *)message);
+		if (!msg)
+			return;
+
+		tmp = g_markup_escape_text(msg, -1);
+		/* Send to Purple */
+		serv_got_chat_in(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(convo)),
+				 sender->nickname ?
+				 sender->nickname : "<unknown>", 0,
+				 tmp, time(NULL));
+		g_free(tmp);
+		g_free(msg);
+		return;
+	}
+
+	if (flags & SILC_MESSAGE_FLAG_NOTICE) {
+		msg = g_strdup_printf("(notice) <I>%s</I> %s",
+				      sender->nickname ?
+				      sender->nickname : "<unknown>",
+				      (const char *)message);
+		if (!msg)
+			return;
+
+		/* Send to Purple */
+		purple_conversation_write(convo, NULL, (const char *)msg,
+					PURPLE_MESSAGE_SYSTEM, time(NULL));
+		g_free(msg);
+		return;
+	}
+
+	if (flags & SILC_MESSAGE_FLAG_UTF8) {
+		tmp = g_markup_escape_text((const char *)message, -1);
+		/* Send to Purple */
+		serv_got_chat_in(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(convo)),
+				 sender->nickname ?
+				 sender->nickname : "<unknown>", 0,
+				 tmp, time(NULL));
+		g_free(tmp);
+	}
+}
+
+
+/* Private message to the client. The `sender' is the sender of the
+   message. The message is `message'and maybe NULL.  The `flags'
+   indicates message flags  and it is used to determine how the message
+   can be interpreted (like it may tell the message is multimedia
+   message). */
+
+static void
+silc_private_message(SilcClient client, SilcClientConnection conn,
+		     SilcClientEntry sender, SilcMessagePayload payload,
+		     SilcMessageFlags flags, const unsigned char *message,
+		     SilcUInt32 message_len)
+{
+	PurpleConnection *gc = client->application;
+	SilcPurple sg = gc->proto_data;
+	PurpleConversation *convo = NULL;
+	char *msg, *tmp;
+
+	if (!message)
+		return;
+
+	if (sender->nickname)
+		/* XXX - Should this be PURPLE_CONV_TYPE_IM? */
+		convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY,
+								sender->nickname, sg->account);
+
+	if (flags & SILC_MESSAGE_FLAG_SIGNED &&
+	    purple_account_get_bool(sg->account, "sign-verify", FALSE)) {
+		/* XXX */
+	}
+
+	if (flags & SILC_MESSAGE_FLAG_DATA) {
+#ifdef HAVE_SILCMIME_H
+		/* Process MIME message */
+		SilcMime mime;
+		mime = silc_mime_decode(message, message_len);
+		silcpurple_mime_message(client, conn, sender, NULL, payload,
+				      NULL, flags, mime, FALSE);
+#else
+		char type[128], enc[128];
+		unsigned char *data;
+		SilcUInt32 data_len;
+
+		memset(type, 0, sizeof(type));
+		memset(enc, 0, sizeof(enc));
+
+		if (!silc_mime_parse(message, message_len, NULL, 0,
+		    type, sizeof(type) - 1, enc, sizeof(enc) - 1, &data, 
+		    &data_len))
+			return;
+
+		if (!strcmp(type, "application/x-wb") &&
+		    !strcmp(enc, "binary") &&
+		    !purple_account_get_bool(sg->account, "block-wb", FALSE))
+			silcpurple_wb_receive(client, conn, sender, payload,
+					    flags, data, data_len);
+#endif
+		return;
+	}
+
+	if (flags & SILC_MESSAGE_FLAG_ACTION && convo) {
+		msg = g_strdup_printf("/me %s",
+				      (const char *)message);
+		if (!msg)
+			return;
+
+		tmp = g_markup_escape_text(msg, -1);
+		/* Send to Purple */
+		serv_got_im(gc, sender->nickname ?
+			    sender->nickname : "<unknown>",
+			    tmp, 0, time(NULL));
+		g_free(msg);
+		g_free(tmp);
+		return;
+	}
+
+	if (flags & SILC_MESSAGE_FLAG_NOTICE && convo) {
+		msg = g_strdup_printf("(notice) <I>%s</I> %s",
+				      sender->nickname ?
+				      sender->nickname : "<unknown>",
+				      (const char *)message);
+		if (!msg)
+			return;
+
+		/* Send to Purple */
+		purple_conversation_write(convo, NULL, (const char *)msg,
+					PURPLE_MESSAGE_SYSTEM, time(NULL));
+		g_free(msg);
+		return;
+	}
+
+	if (flags & SILC_MESSAGE_FLAG_UTF8) {
+		tmp = g_markup_escape_text((const char *)message, -1);
+		/* Send to Purple */
+		serv_got_im(gc, sender->nickname ?
+			    sender->nickname : "<unknown>",
+			    tmp, 0, time(NULL));
+		g_free(tmp);
+	}
+}
+
+
+/* Notify message to the client. The notify arguments are sent in the
+   same order as servers sends them. The arguments are same as received
+   from the server except for ID's.  If ID is received application receives
+   the corresponding entry to the ID. For example, if Client ID is received
+   application receives SilcClientEntry.  Also, if the notify type is
+   for channel the channel entry is sent to application (even if server
+   does not send it because client library gets the channel entry from
+   the Channel ID in the packet's header). */
+
+static void
+silc_notify(SilcClient client, SilcClientConnection conn,
+	    SilcNotifyType type, ...)
+{
+	va_list va;
+	PurpleConnection *gc = client->application;
+	SilcPurple sg = gc->proto_data;
+	PurpleConversation *convo;
+	SilcClientEntry client_entry, client_entry2;
+	SilcChannelEntry channel;
+	SilcServerEntry server_entry;
+	SilcIdType idtype;
+	void *entry;
+	SilcUInt32 mode;
+	SilcHashTableList htl;
+	SilcChannelUser chu;
+	char buf[512], buf2[512], *tmp, *name;
+	SilcNotifyType notify;
+	PurpleBuddy *b;
+	int i;
+
+	va_start(va, type);
+	memset(buf, 0, sizeof(buf));
+
+	switch (type) {
+
+	case SILC_NOTIFY_TYPE_NONE:
+		break;
+
+	case SILC_NOTIFY_TYPE_INVITE:
+		{
+			GHashTable *components;
+			va_arg(va, SilcChannelEntry);
+			name = va_arg(va, char *);
+			client_entry = va_arg(va, SilcClientEntry);
+
+			components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+			g_hash_table_insert(components, strdup("channel"), strdup(name));
+			serv_got_chat_invite(gc, name, client_entry->nickname, NULL, components);
+		}
+		break;
+
+	case SILC_NOTIFY_TYPE_JOIN:
+		client_entry = va_arg(va, SilcClientEntry);
+		channel = va_arg(va, SilcChannelEntry);
+
+		/* If we joined channel, do nothing */
+		if (client_entry == conn->local_entry)
+			break;
+
+		convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+								channel->channel_name, sg->account);
+		if (!convo)
+			break;
+
+		/* Join user to channel */
+		g_snprintf(buf, sizeof(buf), "%s@%s",
+			   client_entry->username, client_entry->hostname);
+		purple_conv_chat_add_user(PURPLE_CONV_CHAT(convo),
+					g_strdup(client_entry->nickname), buf, PURPLE_CBFLAGS_NONE, TRUE);
+
+		break;
+
+	case SILC_NOTIFY_TYPE_LEAVE:
+		client_entry = va_arg(va, SilcClientEntry);
+		channel = va_arg(va, SilcChannelEntry);
+
+		convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+								channel->channel_name, sg->account);
+		if (!convo)
+			break;
+
+		/* Remove user from channel */
+		purple_conv_chat_remove_user(PURPLE_CONV_CHAT(convo),
+					   client_entry->nickname, NULL);
+
+		break;
+
+	case SILC_NOTIFY_TYPE_SIGNOFF:
+		client_entry = va_arg(va, SilcClientEntry);
+		tmp = va_arg(va, char *);
+
+		if (!client_entry->nickname)
+			break;
+
+		/* Remove from all channels */
+		silc_hash_table_list(client_entry->channels, &htl);
+		while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+			convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+									chu->channel->channel_name, sg->account);
+			if (!convo)
+				continue;
+			purple_conv_chat_remove_user(PURPLE_CONV_CHAT(convo),
+						   client_entry->nickname,
+						   tmp);
+		}
+		silc_hash_table_list_reset(&htl);
+
+		break;
+
+	case SILC_NOTIFY_TYPE_TOPIC_SET:
+		{
+			char *esc, *tmp2;
+			idtype = va_arg(va, int);
+			entry = va_arg(va, void *);
+			tmp = va_arg(va, char *);
+			channel = va_arg(va, SilcChannelEntry);
+
+			convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+									channel->channel_name, sg->account);
+			if (!convo)
+				break;
+
+			if (!tmp)
+				break;
+
+			esc = g_markup_escape_text(tmp, -1);
+			tmp2 = purple_markup_linkify(esc);
+			g_free(esc);
+
+			if (idtype == SILC_ID_CLIENT) {
+				client_entry = (SilcClientEntry)entry;
+				g_snprintf(buf, sizeof(buf),
+						_("%s has changed the topic of <I>%s</I> to: %s"),
+						client_entry->nickname, channel->channel_name, tmp2);
+				purple_conv_chat_write(PURPLE_CONV_CHAT(convo), client_entry->nickname,
+						buf, PURPLE_MESSAGE_SYSTEM, time(NULL));
+				purple_conv_chat_set_topic(PURPLE_CONV_CHAT(convo),
+						client_entry->nickname, tmp);
+			} else if (idtype == SILC_ID_SERVER) {
+				server_entry = (SilcServerEntry)entry;
+				g_snprintf(buf, sizeof(buf),
+						_("%s has changed the topic of <I>%s</I> to: %s"),
+						server_entry->server_name, channel->channel_name, tmp2);
+				purple_conv_chat_write(PURPLE_CONV_CHAT(convo), server_entry->server_name,
+						buf, PURPLE_MESSAGE_SYSTEM, time(NULL));
+				purple_conv_chat_set_topic(PURPLE_CONV_CHAT(convo),
+						server_entry->server_name, tmp);
+			} else if (idtype == SILC_ID_CHANNEL) {
+				channel = (SilcChannelEntry)entry;
+				g_snprintf(buf, sizeof(buf),
+						_("%s has changed the topic of <I>%s</I> to: %s"),
+						channel->channel_name, channel->channel_name, tmp2);
+				purple_conv_chat_write(PURPLE_CONV_CHAT(convo), channel->channel_name,
+						buf, PURPLE_MESSAGE_SYSTEM, time(NULL));
+				purple_conv_chat_set_topic(PURPLE_CONV_CHAT(convo),
+						channel->channel_name, tmp);
+			} else {
+				purple_conv_chat_set_topic(PURPLE_CONV_CHAT(convo), NULL, tmp);
+			}
+
+			g_free(tmp2);
+
+			break;
+
+		}
+	case SILC_NOTIFY_TYPE_NICK_CHANGE:
+		client_entry = va_arg(va, SilcClientEntry);
+		client_entry2 = va_arg(va, SilcClientEntry);
+
+		if (!strcmp(client_entry->nickname, client_entry2->nickname))
+			break;
+
+		/* Change nick on all channels */
+		silc_hash_table_list(client_entry2->channels, &htl);
+		while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+			convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+									chu->channel->channel_name, sg->account);
+			if (!convo)
+				continue;
+			if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(convo), client_entry->nickname))
+				purple_conv_chat_rename_user(PURPLE_CONV_CHAT(convo),
+										   client_entry->nickname,
+										   client_entry2->nickname);
+		}
+		silc_hash_table_list_reset(&htl);
+
+		break;
+
+	case SILC_NOTIFY_TYPE_CMODE_CHANGE:
+		idtype = va_arg(va, int);
+		entry = va_arg(va, void *);
+		mode = va_arg(va, SilcUInt32);
+		(void)va_arg(va, char *);
+		(void)va_arg(va, char *);
+		(void)va_arg(va, char *);
+		(void)va_arg(va, SilcPublicKey);
+		(void)va_arg(va, SilcBuffer);
+		channel = va_arg(va, SilcChannelEntry);
+
+		convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+								channel->channel_name, sg->account);
+		if (!convo)
+			break;
+
+		if (idtype == SILC_ID_CLIENT)
+			name = ((SilcClientEntry)entry)->nickname;
+		else if (idtype == SILC_ID_SERVER)
+			name = ((SilcServerEntry)entry)->server_name;
+		else
+			name = ((SilcChannelEntry)entry)->channel_name;
+		if (!name)
+			break;
+
+		if (mode) {
+			silcpurple_get_chmode_string(mode, buf2, sizeof(buf2));
+			g_snprintf(buf, sizeof(buf),
+				   _("<I>%s</I> set channel <I>%s</I> modes to: %s"), name,
+				   channel->channel_name, buf2);
+		} else {
+			g_snprintf(buf, sizeof(buf),
+				   _("<I>%s</I> removed all channel <I>%s</I> modes"), name,
+				   channel->channel_name);
+		}
+		purple_conv_chat_write(PURPLE_CONV_CHAT(convo), channel->channel_name,
+				     buf, PURPLE_MESSAGE_SYSTEM, time(NULL));
+		break;
+
+	case SILC_NOTIFY_TYPE_CUMODE_CHANGE:
+		{
+			PurpleConvChatBuddyFlags flags = PURPLE_CBFLAGS_NONE;
+			idtype = va_arg(va, int);
+			entry = va_arg(va, void *);
+			mode = va_arg(va, SilcUInt32);
+			client_entry2 = va_arg(va, SilcClientEntry);
+			channel = va_arg(va, SilcChannelEntry);
+
+			convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+									channel->channel_name, sg->account);
+			if (!convo)
+				break;
+
+			if (idtype == SILC_ID_CLIENT)
+				name = ((SilcClientEntry)entry)->nickname;
+			else if (idtype == SILC_ID_SERVER)
+				name = ((SilcServerEntry)entry)->server_name;
+			else
+				name = ((SilcChannelEntry)entry)->channel_name;
+			if (!name)
+				break;
+
+			if (mode) {
+				silcpurple_get_chumode_string(mode, buf2, sizeof(buf2));
+				g_snprintf(buf, sizeof(buf),
+						_("<I>%s</I> set <I>%s's</I> modes to: %s"), name,
+						client_entry2->nickname, buf2);
+				if (mode & SILC_CHANNEL_UMODE_CHANFO)
+					flags |= PURPLE_CBFLAGS_FOUNDER;
+				if (mode & SILC_CHANNEL_UMODE_CHANOP)
+					flags |= PURPLE_CBFLAGS_OP;
+			} else {
+				g_snprintf(buf, sizeof(buf),
+						_("<I>%s</I> removed all <I>%s's</I> modes"), name,
+						client_entry2->nickname);
+			}
+			purple_conv_chat_write(PURPLE_CONV_CHAT(convo), channel->channel_name,
+					buf, PURPLE_MESSAGE_SYSTEM, time(NULL));
+			purple_conv_chat_user_set_flags(PURPLE_CONV_CHAT(convo), client_entry2->nickname, flags);
+			break;
+		}
+
+	case SILC_NOTIFY_TYPE_MOTD:
+		tmp = va_arg(va, char *);
+		silc_free(sg->motd);
+		sg->motd = silc_memdup(tmp, strlen(tmp));
+		break;
+
+	case SILC_NOTIFY_TYPE_KICKED:
+		client_entry = va_arg(va, SilcClientEntry);
+		tmp = va_arg(va, char *);
+		client_entry2 = va_arg(va, SilcClientEntry);
+		channel = va_arg(va, SilcChannelEntry);
+
+		convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+								channel->channel_name, sg->account);
+		if (!convo)
+			break;
+
+		if (client_entry == conn->local_entry) {
+			/* Remove us from channel */
+			g_snprintf(buf, sizeof(buf),
+				   _("You have been kicked off <I>%s</I> by <I>%s</I> (%s)"),
+				   channel->channel_name, client_entry2->nickname,
+				   tmp ? tmp : "");
+			purple_conv_chat_write(PURPLE_CONV_CHAT(convo), client_entry->nickname,
+					     buf, PURPLE_MESSAGE_SYSTEM, time(NULL));
+			serv_got_chat_left(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(convo)));
+		} else {
+			/* Remove user from channel */
+			g_snprintf(buf, sizeof(buf), _("Kicked by %s (%s)"),
+				   client_entry2->nickname, tmp ? tmp : "");
+			purple_conv_chat_remove_user(PURPLE_CONV_CHAT(convo),
+						   client_entry->nickname,
+						   buf);
+		}
+
+		break;
+
+	case SILC_NOTIFY_TYPE_KILLED:
+		client_entry = va_arg(va, SilcClientEntry);
+		tmp = va_arg(va, char *);
+		idtype = va_arg(va, int);
+		entry = va_arg(va, SilcClientEntry);
+
+		if (!client_entry->nickname)
+			break;
+
+		if (client_entry == conn->local_entry) {
+			if (idtype == SILC_ID_CLIENT) {
+				client_entry2 = (SilcClientEntry)entry;
+				g_snprintf(buf, sizeof(buf),
+					   _("You have been killed by %s (%s)"),
+					   client_entry2->nickname, tmp ? tmp : "");
+			} else if (idtype == SILC_ID_SERVER) {
+				server_entry = (SilcServerEntry)entry;
+				g_snprintf(buf, sizeof(buf),
+					   _("You have been killed by %s (%s)"),
+					   server_entry->server_name, tmp ? tmp : "");
+			} else if (idtype == SILC_ID_CHANNEL) {
+				channel = (SilcChannelEntry)entry;
+				g_snprintf(buf, sizeof(buf),
+					   _("You have been killed by %s (%s)"),
+					   channel->channel_name, tmp ? tmp : "");
+			}
+
+			/* Remove us from all channels */
+			silc_hash_table_list(client_entry->channels, &htl);
+			while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+				convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+										chu->channel->channel_name, sg->account);
+				if (!convo)
+					continue;
+				purple_conv_chat_write(PURPLE_CONV_CHAT(convo), client_entry->nickname,
+						     buf, PURPLE_MESSAGE_SYSTEM, time(NULL));
+				serv_got_chat_left(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(convo)));
+			}
+			silc_hash_table_list_reset(&htl);
+
+		} else {
+			if (idtype == SILC_ID_CLIENT) {
+				client_entry2 = (SilcClientEntry)entry;
+				g_snprintf(buf, sizeof(buf),
+					   _("Killed by %s (%s)"),
+					   client_entry2->nickname, tmp ? tmp : "");
+			} else if (idtype == SILC_ID_SERVER) {
+				server_entry = (SilcServerEntry)entry;
+				g_snprintf(buf, sizeof(buf),
+					   _("Killed by %s (%s)"),
+					   server_entry->server_name, tmp ? tmp : "");
+			} else if (idtype == SILC_ID_CHANNEL) {
+				channel = (SilcChannelEntry)entry;
+				g_snprintf(buf, sizeof(buf),
+					   _("Killed by %s (%s)"),
+					   channel->channel_name, tmp ? tmp : "");
+			}
+
+			/* Remove user from all channels */
+			silc_hash_table_list(client_entry->channels, &htl);
+			while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+				convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+										chu->channel->channel_name, sg->account);
+				if (!convo)
+					continue;
+				purple_conv_chat_remove_user(PURPLE_CONV_CHAT(convo),
+							   client_entry->nickname, tmp);
+			}
+			silc_hash_table_list_reset(&htl);
+		}
+
+		break;
+
+	case SILC_NOTIFY_TYPE_CHANNEL_CHANGE:
+		break;
+
+	case SILC_NOTIFY_TYPE_SERVER_SIGNOFF:
+		{
+			int i;
+			SilcClientEntry *clients;
+			SilcUInt32 clients_count;
+
+			(void)va_arg(va, void *);
+			clients = va_arg(va, SilcClientEntry *);
+			clients_count = va_arg(va, SilcUInt32);
+
+			for (i = 0; i < clients_count; i++) {
+				if (!clients[i]->nickname)
+					break;
+
+				/* Remove from all channels */
+				silc_hash_table_list(clients[i]->channels, &htl);
+				while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+					convo =
+						purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+											chu->channel->channel_name, sg->account);
+					if (!convo)
+						continue;
+					purple_conv_chat_remove_user(PURPLE_CONV_CHAT(convo),
+								   clients[i]->nickname,
+								   _("Server signoff"));
+				}
+				silc_hash_table_list_reset(&htl);
+			}
+		}
+		break;
+
+	case SILC_NOTIFY_TYPE_ERROR:
+		{
+			SilcStatus error = va_arg(va, int);
+			purple_notify_error(gc, "Error Notify",
+					  silc_get_status_message(error),
+					  NULL);
+		}
+		break;
+
+	case SILC_NOTIFY_TYPE_WATCH:
+		{
+			SilcPublicKey public_key;
+			unsigned char *pk;
+			SilcUInt32 pk_len;
+			char *fingerprint;
+
+			client_entry = va_arg(va, SilcClientEntry);
+			(void)va_arg(va, char *);
+			mode = va_arg(va, SilcUInt32);
+			notify = va_arg(va, int);
+			public_key = va_arg(va, SilcPublicKey);
+
+			b = NULL;
+			if (public_key) {
+				PurpleBlistNode *gnode, *cnode, *bnode;
+				const char *f;
+
+				pk = silc_pkcs_public_key_encode(public_key, &pk_len);
+				if (!pk)
+					break;
+				fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+				for (i = 0; i < strlen(fingerprint); i++)
+					if (fingerprint[i] == ' ')
+						fingerprint[i] = '_';
+				g_snprintf(buf, sizeof(buf) - 1,
+					   "%s" G_DIR_SEPARATOR_S "clientkeys"
+					   G_DIR_SEPARATOR_S "clientkey_%s.pub",
+					   silcpurple_silcdir(), fingerprint);
+				silc_free(fingerprint);
+				silc_free(pk);
+
+				/* Find buddy by associated public key */
+				for (gnode = purple_get_blist()->root; gnode;
+				     gnode = gnode->next) {
+					if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
+						continue;
+					for (cnode = gnode->child; cnode; cnode = cnode->next) {
+						if( !PURPLE_BLIST_NODE_IS_CONTACT(cnode))
+							continue;
+						for (bnode = cnode->child; bnode;
+						     bnode = bnode->next) {
+							if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
+								continue;
+							b = (PurpleBuddy *)bnode;
+							if (b->account != gc->account)
+								continue;
+							f = purple_blist_node_get_string(bnode, "public-key");
+							if (f && !strcmp(f, buf))
+								goto cont;
+							b = NULL;
+						}
+					}
+				}
+			}
+		cont:
+			if (!b) {
+				/* Find buddy by nickname */
+				b = purple_find_buddy(sg->account, client_entry->nickname);
+				if (!b) {
+					purple_debug_warning("silc", "WATCH for %s, unknown buddy",
+						client_entry->nickname);
+					break;
+				}
+			}
+
+			silc_free(b->proto_data);
+			b->proto_data = silc_memdup(client_entry->id,
+						    sizeof(*client_entry->id));
+			if (notify == SILC_NOTIFY_TYPE_NICK_CHANGE) {
+				break;
+			} else if (notify == SILC_NOTIFY_TYPE_UMODE_CHANGE) {
+				/* See if client was away and is now present */
+				if (!(mode & (SILC_UMODE_GONE | SILC_UMODE_INDISPOSED |
+					      SILC_UMODE_BUSY | SILC_UMODE_PAGE |
+					      SILC_UMODE_DETACHED)) &&
+				    (client_entry->mode & SILC_UMODE_GONE ||
+				     client_entry->mode & SILC_UMODE_INDISPOSED ||
+				     client_entry->mode & SILC_UMODE_BUSY ||
+				     client_entry->mode & SILC_UMODE_PAGE ||
+				     client_entry->mode & SILC_UMODE_DETACHED)) {
+					client_entry->mode = mode;
+					purple_prpl_got_user_status(purple_buddy_get_account(b), purple_buddy_get_name(b), SILCPURPLE_STATUS_ID_AVAILABLE, NULL);
+				}
+				else if ((mode & SILC_UMODE_GONE) ||
+					 (mode & SILC_UMODE_INDISPOSED) ||
+					 (mode & SILC_UMODE_BUSY) ||
+					 (mode & SILC_UMODE_PAGE) ||
+					 (mode & SILC_UMODE_DETACHED)) {
+					client_entry->mode = mode;
+					purple_prpl_got_user_status(purple_buddy_get_account(b), purple_buddy_get_name(b), SILCPURPLE_STATUS_ID_OFFLINE, NULL);
+				}
+			} else if (notify == SILC_NOTIFY_TYPE_SIGNOFF ||
+				   notify == SILC_NOTIFY_TYPE_SERVER_SIGNOFF ||
+				   notify == SILC_NOTIFY_TYPE_KILLED) {
+				client_entry->mode = mode;
+				purple_prpl_got_user_status(purple_buddy_get_account(b), purple_buddy_get_name(b), SILCPURPLE_STATUS_ID_OFFLINE, NULL);
+			} else if (notify == SILC_NOTIFY_TYPE_NONE) {
+				client_entry->mode = mode;
+				purple_prpl_got_user_status(purple_buddy_get_account(b), purple_buddy_get_name(b), SILCPURPLE_STATUS_ID_AVAILABLE, NULL);
+			}
+		}
+		break;
+
+	default:
+		purple_debug_info("silc", "Unhandled notification: %d\n", type);
+		break;
+	}
+
+	va_end(va);
+}
+
+
+/* Command handler. This function is called always in the command function.
+   If error occurs it will be called as well. `conn' is the associated
+   client connection. `cmd_context' is the command context that was
+   originally sent to the command. `success' is FALSE if error occurred
+   during command. `command' is the command being processed. It must be
+   noted that this is not reply from server. This is merely called just
+   after application has called the command. Just to tell application
+   that the command really was processed. */
+
+static void
+silc_command(SilcClient client, SilcClientConnection conn,
+	     SilcClientCommandContext cmd_context, bool success,
+	     SilcCommand command, SilcStatus status)
+{
+	PurpleConnection *gc = client->application;
+	SilcPurple sg = gc->proto_data;
+
+	switch (command) {
+
+	case SILC_COMMAND_CMODE:
+		if (cmd_context->argc == 3 &&
+		    !strcmp((char *)cmd_context->argv[2], "+C"))
+			sg->chpk = TRUE;
+		else
+			sg->chpk = FALSE;
+		break;
+
+	default:
+		break;
+	}
+}
+
+#if 0
+static void
+silcpurple_whois_more(SilcClientEntry client_entry, gint id)
+{
+	SilcAttributePayload attr;
+	SilcAttribute attribute;
+	char *buf;
+	GString *s;
+	SilcVCardStruct vcard;
+	int i;
+
+	if (id != 0)
+		return;
+
+	memset(&vcard, 0, sizeof(vcard));
+
+	s = g_string_new("");
+
+	silc_dlist_start(client_entry->attrs);
+	while ((attr = silc_dlist_get(client_entry->attrs)) != SILC_LIST_END) {
+		attribute = silc_attribute_get_attribute(attr);
+		switch (attribute) {
+
+		case SILC_ATTRIBUTE_USER_INFO:
+			if (!silc_attribute_get_object(attr, (void *)&vcard,
+						       sizeof(vcard)))
+				continue;
+			g_string_append_printf(s, "%s:\n\n", _("Personal Information"));
+			if (vcard.full_name)
+				g_string_append_printf(s, "%s:\t\t%s\n",
+						       _("Full Name"),
+						       vcard.full_name);
+			if (vcard.first_name)
+				g_string_append_printf(s, "%s:\t%s\n",
+						       _("First Name"),
+						       vcard.first_name);
+			if (vcard.middle_names)
+				g_string_append_printf(s, "%s:\t%s\n",
+						       _("Middle Name"),
+						       vcard.middle_names);
+			if (vcard.family_name)
+				g_string_append_printf(s, "%s:\t%s\n",
+						       _("Family Name"),
+						       vcard.family_name);
+			if (vcard.nickname)
+				g_string_append_printf(s, "%s:\t\t%s\n",
+						       _("Nickname"),
+						       vcard.nickname);
+			if (vcard.bday)
+				g_string_append_printf(s, "%s:\t\t%s\n",
+						       _("Birth Day"),
+						       vcard.bday);
+			if (vcard.title)
+				g_string_append_printf(s, "%s:\t\t%s\n",
+						       _("Job Title"),
+						       vcard.title);
+			if (vcard.role)
+				g_string_append_printf(s, "%s:\t\t%s\n",
+						       _("Job Role"),
+						       vcard.role);
+			if (vcard.org_name)
+				g_string_append_printf(s, "%s:\t%s\n",
+						       _("Organization"),
+						       vcard.org_name);
+			if (vcard.org_unit)
+				g_string_append_printf(s, "%s:\t\t%s\n",
+						       _("Unit"),
+						       vcard.org_unit);
+			if (vcard.url)
+				g_string_append_printf(s, "%s:\t%s\n",
+						       _("Homepage"),
+						       vcard.url);
+			if (vcard.label)
+				g_string_append_printf(s, "%s:\t%s\n",
+						       _("Address"),
+						       vcard.label);
+			for (i = 0; i < vcard.num_tels; i++) {
+				if (vcard.tels[i].telnum)
+					g_string_append_printf(s, "%s:\t\t\t%s\n",
+							       _("Phone"),
+							       vcard.tels[i].telnum);
+			}
+			for (i = 0; i < vcard.num_emails; i++) {
+				if (vcard.emails[i].address)
+					g_string_append_printf(s, "%s:\t\t%s\n",
+							       _("E-Mail"),
+							       vcard.emails[i].address);
+			}
+			if (vcard.note)
+				g_string_append_printf(s, "\n%s:\t\t%s\n",
+						       _("Note"),
+						       vcard.note);
+			break;
+		}
+	}
+
+	buf = g_string_free(s, FALSE);
+	purple_notify_info(NULL, _("User Information"), _("User Information"),
+			 buf);
+	g_free(buf);
+}
+#endif
+
+/* Command reply handler. This function is called always in the command reply
+   function. If error occurs it will be called as well. Normal scenario
+   is that it will be called after the received command data has been parsed
+   and processed. The function is used to pass the received command data to
+   the application.
+
+   `conn' is the associated client connection. `cmd_payload' is the command
+   payload data received from server and it can be ignored. It is provided
+   if the application would like to re-parse the received command data,
+   however, it must be noted that the data is parsed already by the library
+   thus the payload can be ignored. `success' is FALSE if error occurred.
+   In this case arguments are not sent to the application. The `status' is
+   the command reply status server returned. The `command' is the command
+   reply being processed. The function has variable argument list and each
+   command defines the number and type of arguments it passes to the
+   application (on error they are not sent). */
+
+static void
+silc_command_reply(SilcClient client, SilcClientConnection conn,
+		   SilcCommandPayload cmd_payload, bool success,
+		   SilcCommand command, SilcStatus status, ...)
+{
+	PurpleConnection *gc = client->application;
+	SilcPurple sg = gc->proto_data;
+	PurpleConversation *convo;
+	va_list vp;
+
+	va_start(vp, status);
+
+	switch (command) {
+	case SILC_COMMAND_JOIN:
+		{
+			SilcChannelEntry channel_entry;
+
+			if (!success) {
+				purple_notify_error(gc, _("Join Chat"), _("Cannot join channel"),
+						  silc_get_status_message(status));
+				return;
+			}
+
+			(void)va_arg(vp, char *);
+			channel_entry = va_arg(vp, SilcChannelEntry);
+
+			/* Resolve users on channel */
+			silc_client_get_clients_by_channel(client, conn, channel_entry,
+							   silcpurple_chat_join_done,
+							   channel_entry);
+		}
+		break;
+
+	case SILC_COMMAND_LEAVE:
+		break;
+
+	case SILC_COMMAND_USERS:
+		break;
+
+	case SILC_COMMAND_WHOIS:
+		{
+			SilcUInt32 idle, mode;
+			SilcBuffer channels, user_modes;
+			SilcClientEntry client_entry;
+			char tmp[1024], *tmp2;
+			char *moodstr, *statusstr, *contactstr, *langstr, *devicestr, *tzstr, *geostr;
+			PurpleNotifyUserInfo *user_info;
+
+			if (!success) {
+				purple_notify_error(gc, _("User Information"),
+						_("Cannot get user information"),
+						silc_get_status_message(status));
+				break;
+			}
+
+			client_entry = va_arg(vp, SilcClientEntry);
+			if (!client_entry->nickname)
+				break;
+			(void)va_arg(vp, char *);
+			(void)va_arg(vp, char *);
+			(void)va_arg(vp, char *);
+			channels = va_arg(vp, SilcBuffer);
+			mode = va_arg(vp, SilcUInt32);
+			idle = va_arg(vp, SilcUInt32);
+			(void)va_arg(vp, unsigned char *);
+			user_modes = va_arg(vp, SilcBuffer);
+
+			user_info = purple_notify_user_info_new();
+			tmp2 = g_markup_escape_text(client_entry->nickname, -1);
+			purple_notify_user_info_add_pair(user_info, _("Nickname"), tmp2);
+			g_free(tmp2);
+			if (client_entry->realname) {
+				tmp2 = g_markup_escape_text(client_entry->realname, -1);
+				purple_notify_user_info_add_pair(user_info, _("Real Name"), tmp2);
+				g_free(tmp2);
+			}
+			if (client_entry->username) {
+				tmp2 = g_markup_escape_text(client_entry->username, -1);
+				if (client_entry->hostname) {
+					gchar *tmp3;
+					tmp3 = g_strdup_printf("%s@%s", tmp2, client_entry->hostname);
+					purple_notify_user_info_add_pair(user_info, _("Username"), tmp3);
+					g_free(tmp3);
+				} else
+					purple_notify_user_info_add_pair(user_info, _("Username"), tmp2);
+				g_free(tmp2);
+			}
+
+			if (client_entry->mode) {
+				memset(tmp, 0, sizeof(tmp));
+				silcpurple_get_umode_string(client_entry->mode,
+						tmp, sizeof(tmp) - strlen(tmp));
+				purple_notify_user_info_add_pair(user_info, _("User Modes"), tmp);
+			}
+
+			silcpurple_parse_attrs(client_entry->attrs, &moodstr, &statusstr, &contactstr, &langstr, &devicestr, &tzstr, &geostr);
+			if (moodstr) {
+				purple_notify_user_info_add_pair(user_info, _("Mood"), moodstr);
+				g_free(moodstr);
+			}
+
+			if (statusstr) {
+				tmp2 = g_markup_escape_text(statusstr, -1);
+				purple_notify_user_info_add_pair(user_info, _("Status Text"), tmp2);
+				g_free(statusstr);
+				g_free(tmp2);
+			}
+
+			if (contactstr) {
+				purple_notify_user_info_add_pair(user_info, _("Preferred Contact"), contactstr);
+				g_free(contactstr);
+			}
+
+			if (langstr) {
+				purple_notify_user_info_add_pair(user_info, _("Preferred Language"), langstr);
+				g_free(langstr);
+			}
+
+			if (devicestr) {
+				purple_notify_user_info_add_pair(user_info, _("Device"), devicestr);
+				g_free(devicestr);
+			}
+
+			if (tzstr) {
+				purple_notify_user_info_add_pair(user_info, _("Timezone"), tzstr);
+				g_free(tzstr);
+			}
+
+			if (geostr) {
+				purple_notify_user_info_add_pair(user_info, _("Geolocation"), geostr);
+				g_free(geostr);
+			}
+
+			if (client_entry->server)
+				purple_notify_user_info_add_pair(user_info, _("Server"), client_entry->server);
+
+			if (channels && user_modes) {
+				SilcUInt32 *umodes;
+				SilcDList list =
+					silc_channel_payload_parse_list(channels->data,
+							channels->len);
+				if (list && silc_get_mode_list(user_modes,
+							silc_dlist_count(list),
+							&umodes)) {
+					SilcChannelPayload entry;
+					int i = 0;
+
+					memset(tmp, 0, sizeof(tmp));
+					silc_dlist_start(list);
+					while ((entry = silc_dlist_get(list))
+							!= SILC_LIST_END) {
+						SilcUInt32 name_len;
+						char *m = silc_client_chumode_char(umodes[i++]);
+						char *name = (char *)silc_channel_get_name(entry, &name_len);
+						if (m)
+							silc_strncat(tmp, sizeof(tmp) - 1, m, strlen(m));
+						silc_strncat(tmp, sizeof(tmp) - 1, name, name_len);
+						silc_strncat(tmp, sizeof(tmp) - 1, "  ", 1);
+						silc_free(m);
+
+					}
+					tmp2 = g_markup_escape_text(tmp, -1);
+					purple_notify_user_info_add_pair(user_info, _("Currently on"), tmp2);
+					g_free(tmp2);
+					silc_free(umodes);
+				}
+			}
+
+			if (client_entry->public_key) {
+				char *fingerprint, *babbleprint;
+				unsigned char *pk;
+				SilcUInt32 pk_len;
+				pk = silc_pkcs_public_key_encode(client_entry->public_key, &pk_len);
+				fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+				babbleprint = silc_hash_babbleprint(NULL, pk, pk_len);
+				purple_notify_user_info_add_pair(user_info, _("Public Key Fingerprint"), fingerprint);
+				purple_notify_user_info_add_pair(user_info, _("Public Key Babbleprint"), babbleprint);
+				silc_free(fingerprint);
+				silc_free(babbleprint);
+				silc_free(pk);
+			}
+
+#if 0 /* XXX for now, let's not show attrs here */
+			if (client_entry->attrs)
+				purple_request_action(gc, _("User Information"),
+						_("User Information"),
+						buf, 1, client_entry, 2,
+						_("OK"), G_CALLBACK(silcpurple_whois_more),
+						_("_More..."), G_CALLBACK(silcpurple_whois_more), gc->account, NULL, NULL);
+			else
+#endif
+			purple_notify_userinfo(gc, client_entry->nickname, user_info, NULL, NULL);
+			purple_notify_user_info_destroy(user_info);
+		}
+		break;
+
+	case SILC_COMMAND_WHOWAS:
+		{
+			SilcClientEntry client_entry;
+			char *nickname, *realname, *username, *tmp;
+			PurpleNotifyUserInfo *user_info;
+
+			if (!success) {
+				purple_notify_error(gc, _("User Information"),
+						  _("Cannot get user information"),
+						  silc_get_status_message(status));
+				break;
+			}
+
+			client_entry = va_arg(vp, SilcClientEntry);
+			nickname = va_arg(vp, char *);
+			username = va_arg(vp, char *);
+			realname = va_arg(vp, char *);
+			if (!nickname)
+				break;
+
+			user_info = purple_notify_user_info_new();
+			tmp = g_markup_escape_text(nickname, -1);
+			purple_notify_user_info_add_pair(user_info, _("Nickname"), tmp);
+			g_free(tmp);
+			if (realname) {
+				tmp = g_markup_escape_text(realname, -1);
+				purple_notify_user_info_add_pair(user_info, _("Real Name"), tmp);
+				g_free(tmp);
+			}
+			if (username) {
+				tmp = g_markup_escape_text(username, -1);
+				if (client_entry && client_entry->hostname) {
+					gchar *tmp3;
+					tmp3 = g_strdup_printf("%s@%s", tmp, client_entry->hostname);
+					purple_notify_user_info_add_pair(user_info, _("Username"), tmp3);
+					g_free(tmp3);
+				} else
+					purple_notify_user_info_add_pair(user_info, _("Username"), tmp);
+				g_free(tmp);
+			}
+			if (client_entry && client_entry->server)
+				purple_notify_user_info_add_pair(user_info, _("Server"), client_entry->server);
+
+
+			if (client_entry && client_entry->public_key) {
+				char *fingerprint, *babbleprint;
+				unsigned char *pk;
+				SilcUInt32 pk_len;
+				pk = silc_pkcs_public_key_encode(client_entry->public_key, &pk_len);
+				fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+				babbleprint = silc_hash_babbleprint(NULL, pk, pk_len);
+				purple_notify_user_info_add_pair(user_info, _("Public Key Fingerprint"), fingerprint);
+				purple_notify_user_info_add_pair(user_info, _("Public Key Babbleprint"), babbleprint);
+				silc_free(fingerprint);
+				silc_free(babbleprint);
+				silc_free(pk);
+			}
+
+			purple_notify_userinfo(gc, nickname, user_info, NULL, NULL);
+			purple_notify_user_info_destroy(user_info);
+		}
+		break;
+
+	case SILC_COMMAND_DETACH:
+		if (!success) {
+			purple_notify_error(gc, _("Detach From Server"), _("Cannot detach"),
+					  silc_get_status_message(status));
+			return;
+		}
+		break;
+
+	case SILC_COMMAND_TOPIC:
+		{
+			SilcChannelEntry channel;
+
+			if (!success) {
+				purple_notify_error(gc, _("Topic"), _("Cannot set topic"),
+						  silc_get_status_message(status));
+				return;
+			}
+
+			channel = va_arg(vp, SilcChannelEntry);
+
+			convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+									channel->channel_name, sg->account);
+			if (!convo) {
+				purple_debug_error("silc", "Got a topic for %s, which doesn't exist\n",
+								 channel->channel_name);
+				break;
+			}
+
+			/* Set topic */
+			if (channel->topic)
+				purple_conv_chat_set_topic(PURPLE_CONV_CHAT(convo), NULL, channel->topic);
+		}
+		break;
+
+	case SILC_COMMAND_NICK:
+		{
+			/* I don't think we should need to do this because the server should
+			 * be sending a SILC_NOTIFY_TYPE_NICK_CHANGE when we change our own
+			 * nick, but it isn't, so we deal with it here instead. Stu. */
+			SilcClientEntry local_entry;
+			SilcHashTableList htl;
+			SilcChannelUser chu;
+			const char *oldnick;
+
+			if (!success) {
+				purple_notify_error(gc, _("Nick"), _("Failed to change nickname"),
+						silc_get_status_message(status));
+				return;
+			}
+
+			local_entry = va_arg(vp, SilcClientEntry);
+
+			/* Change nick on all channels */
+			silc_hash_table_list(local_entry->channels, &htl);
+			while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
+				convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
+									chu->channel->channel_name, sg->account);
+				if (!convo)
+					continue;
+				oldnick = purple_conv_chat_get_nick(PURPLE_CONV_CHAT(convo));
+				if (strcmp(oldnick, purple_normalize(purple_conversation_get_account(convo), local_entry->nickname))) {
+					purple_conv_chat_rename_user(PURPLE_CONV_CHAT(convo),
+							oldnick, local_entry->nickname);
+					purple_conv_chat_set_nick(PURPLE_CONV_CHAT(convo), local_entry->nickname);
+				}
+			}
+			silc_hash_table_list_reset(&htl);
+
+			purple_connection_set_display_name(gc, local_entry->nickname);
+		}
+		break;
+
+	case SILC_COMMAND_LIST:
+		{
+			char *topic, *name;
+			int usercount;
+			PurpleRoomlistRoom *room;
+
+			if (sg->roomlist_canceled)
+				break;
+
+			if (!success) {
+				purple_notify_error(gc, _("Error"), _("Error retrieving room list"),
+						  silc_get_status_message(status));
+				purple_roomlist_set_in_progress(sg->roomlist, FALSE);
+				purple_roomlist_unref(sg->roomlist);
+				sg->roomlist = NULL;
+				return;
+			}
+
+			(void)va_arg(vp, SilcChannelEntry);
+			name = va_arg(vp, char *);
+			if (!name) {
+				purple_notify_error(gc, _("Roomlist"), _("Cannot get room list"),
+						  silc_get_status_message(status));
+				purple_roomlist_set_in_progress(sg->roomlist, FALSE);
+				purple_roomlist_unref(sg->roomlist);
+				sg->roomlist = NULL;
+				return;
+			}
+			topic = va_arg(vp, char *);
+			usercount = va_arg(vp, int);
+
+			room = purple_roomlist_room_new(PURPLE_ROOMLIST_ROOMTYPE_ROOM, name, NULL);
+			purple_roomlist_room_add_field(sg->roomlist, room, name);
+			purple_roomlist_room_add_field(sg->roomlist, room,
+						     SILC_32_TO_PTR(usercount));
+			purple_roomlist_room_add_field(sg->roomlist, room,
+						     topic ? topic : "");
+			purple_roomlist_room_add(sg->roomlist, room);
+
+			if (status == SILC_STATUS_LIST_END ||
+			    status == SILC_STATUS_OK) {
+				purple_roomlist_set_in_progress(sg->roomlist, FALSE);
+				purple_roomlist_unref(sg->roomlist);
+				sg->roomlist = NULL;
+			}
+		}
+		break;
+
+	case SILC_COMMAND_GETKEY:
+		{
+			SilcPublicKey public_key;
+
+			if (!success) {
+				purple_notify_error(gc, _("Get Public Key"),
+						  _("Cannot fetch the public key"),
+						  silc_get_status_message(status));
+				return;
+			}
+
+			(void)va_arg(vp, SilcUInt32);
+			(void)va_arg(vp, void *);
+			public_key = va_arg(vp, SilcPublicKey);
+
+			if (!public_key)
+				purple_notify_error(gc, _("Get Public Key"),
+						  _("Cannot fetch the public key"),
+						  _("No public key was received"));
+		}
+		break;
+
+	case SILC_COMMAND_INFO:
+		{
+
+			char *server_name;
+			char *server_info;
+			char tmp[256];
+
+			if (!success) {
+				purple_notify_error(gc, _("Server Information"),
+						  _("Cannot get server information"),
+						  silc_get_status_message(status));
+				return;
+			}
+
+			(void)va_arg(vp, SilcServerEntry);
+			server_name = va_arg(vp, char *);
+			server_info = va_arg(vp, char *);
+
+			if (server_name && server_info) {
+				g_snprintf(tmp, sizeof(tmp), "Server: %s\n%s",
+					   server_name, server_info);
+				purple_notify_info(gc, NULL, _("Server Information"), tmp);
+			}
+		}
+		break;
+
+	case SILC_COMMAND_STATS:
+		{
+			SilcUInt32 starttime, uptime, my_clients, my_channels, my_server_ops,
+			my_router_ops, cell_clients, cell_channels, cell_servers,
+			clients, channels, servers, routers, server_ops, router_ops;
+			SilcUInt32 buffer_length;
+			SilcBufferStruct buf;
+
+			unsigned char *server_stats;
+			char *msg;
+
+			if (!success) {
+				purple_notify_error(gc, _("Server Statistics"),
+						_("Cannot get server statistics"),
+						silc_get_status_message(status));
+				return;
+			}
+
+			server_stats = va_arg(vp, unsigned char *);
+			buffer_length = va_arg(vp, SilcUInt32);
+			if (!server_stats || !buffer_length) {
+				purple_notify_error(gc, _("Server Statistics"),
+						_("No server statistics available"), NULL);
+				break;
+			}
+			silc_buffer_set(&buf, server_stats, buffer_length);
+			silc_buffer_unformat(&buf,
+					SILC_STR_UI_INT(&starttime),
+					SILC_STR_UI_INT(&uptime),
+					SILC_STR_UI_INT(&my_clients),
+					SILC_STR_UI_INT(&my_channels),
+					SILC_STR_UI_INT(&my_server_ops),
+					SILC_STR_UI_INT(&my_router_ops),
+					SILC_STR_UI_INT(&cell_clients),
+					SILC_STR_UI_INT(&cell_channels),
+					SILC_STR_UI_INT(&cell_servers),
+					SILC_STR_UI_INT(&clients),
+					SILC_STR_UI_INT(&channels),
+					SILC_STR_UI_INT(&servers),
+					SILC_STR_UI_INT(&routers),
+					SILC_STR_UI_INT(&server_ops),
+					SILC_STR_UI_INT(&router_ops),
+					SILC_STR_END);
+
+			msg = g_strdup_printf(_("Local server start time: %s\n"
+					"Local server uptime: %s\n"
+					"Local server clients: %d\n"
+					"Local server channels: %d\n"
+					"Local server operators: %d\n"
+					"Local router operators: %d\n"
+					"Local cell clients: %d\n"
+					"Local cell channels: %d\n"
+					"Local cell servers: %d\n"
+					"Total clients: %d\n"
+					"Total channels: %d\n"
+					"Total servers: %d\n"
+					"Total routers: %d\n"
+					"Total server operators: %d\n"
+					"Total router operators: %d\n"),
+					silc_get_time(starttime),
+					purple_str_seconds_to_string((int)uptime),
+					(int)my_clients, (int)my_channels, (int)my_server_ops, (int)my_router_ops,
+					(int)cell_clients, (int)cell_channels, (int)cell_servers,
+					(int)clients, (int)channels, (int)servers, (int)routers,
+					(int)server_ops, (int)router_ops);
+
+			purple_notify_info(gc, NULL,
+					_("Network Statistics"), msg);
+			g_free(msg);
+		}
+		break;
+
+	case SILC_COMMAND_PING:
+		{
+			if (!success) {
+				purple_notify_error(gc, _("Ping"), _("Ping failed"),
+								  silc_get_status_message(status));
+				return;
+			}
+
+			purple_notify_info(gc, _("Ping"), _("Ping reply received from server"),
+							 NULL);
+		}
+		break;
+
+	case SILC_COMMAND_KILL:
+		if (!success) {
+			purple_notify_error(gc, _("Kill User"),
+					  _("Could not kill user"),
+					  silc_get_status_message(status));
+			return;
+		}
+		break;
+
+	case SILC_COMMAND_CMODE:
+		{
+			SilcChannelEntry channel_entry;
+			SilcBuffer channel_pubkeys;
+
+			if (!success)
+				return;
+
+			channel_entry = va_arg(vp, SilcChannelEntry);
+			(void)va_arg(vp, SilcUInt32);
+			(void)va_arg(vp, SilcPublicKey);
+			channel_pubkeys = va_arg(vp, SilcBuffer);
+
+			if (sg->chpk)
+				silcpurple_chat_chauth_show(sg, channel_entry, channel_pubkeys);
+		}
+		break;
+
+	default:
+		if (success)
+			purple_debug_info("silc", "Unhandled command: %d (succeeded)\n", command);
+		else
+			purple_debug_info("silc", "Unhandled command: %d (failed: %s)\n", command,
+							silc_get_status_message(status));
+		break;
+	}
+
+	va_end(vp);
+}
+
+
+/* Called to indicate that connection was either successfully established
+   or connecting failed.  This is also the first time application receives
+   the SilcClientConnection object which it should save somewhere.
+   If the `success' is FALSE the application must always call the function
+   silc_client_close_connection. */
+
+static void
+silc_connected(SilcClient client, SilcClientConnection conn,
+	       SilcClientConnectionStatus status)
+{
+	PurpleConnection *gc = client->application;
+	SilcPurple sg;
+
+	if (gc == NULL) {
+		silc_client_close_connection(client, conn);
+		return;
+	}
+	sg = gc->proto_data;
+
+	switch (status) {
+	case SILC_CLIENT_CONN_SUCCESS:
+	case SILC_CLIENT_CONN_SUCCESS_RESUME:
+		purple_connection_set_state(gc, PURPLE_CONNECTED);
+
+		/* Send the server our buddy list */
+		silcpurple_send_buddylist(gc);
+
+		g_unlink(silcpurple_session_file(purple_account_get_username(sg->account)));
+
+		/* Send any UMODEs configured for account */
+		if (purple_account_get_bool(sg->account, "block-ims", FALSE)) {
+			silc_client_command_call(sg->client, sg->conn, NULL,
+					"UMODE", "+P", NULL);
+		}
+
+		return;
+		break;
+	case SILC_CLIENT_CONN_ERROR:
+		purple_connection_error(gc, _("Error during connecting to SILC Server"));
+		g_unlink(silcpurple_session_file(purple_account_get_username(sg->account)));
+		break;
+
+	case SILC_CLIENT_CONN_ERROR_KE:
+		purple_connection_error(gc, _("Key Exchange failed"));
+		break;
+
+	case SILC_CLIENT_CONN_ERROR_AUTH:
+		purple_connection_error(gc, _("Authentication failed"));
+		break;
+
+	case SILC_CLIENT_CONN_ERROR_RESUME:
+		purple_connection_error(gc,
+				      _("Resuming detached session failed. "
+					"Press Reconnect to create new connection."));
+		g_unlink(silcpurple_session_file(purple_account_get_username(sg->account)));
+		break;
+
+	case SILC_CLIENT_CONN_ERROR_TIMEOUT:
+		purple_connection_error(gc, _("Connection Timeout"));
+		break;
+	}
+
+	/* Error */
+	sg->conn = NULL;
+	silc_client_close_connection(client, conn);
+}
+
+
+/* Called to indicate that connection was disconnected to the server.
+   The `status' may tell the reason of the disconnection, and if the
+   `message' is non-NULL it may include the disconnection message
+   received from server. */
+
+static void
+silc_disconnected(SilcClient client, SilcClientConnection conn,
+		  SilcStatus status, const char *message)
+{
+	PurpleConnection *gc = client->application;
+	SilcPurple sg = gc->proto_data;
+
+	if (sg->resuming && !sg->detaching)
+		g_unlink(silcpurple_session_file(purple_account_get_username(sg->account)));
+
+	sg->conn = NULL;
+
+	/* Close the connection */
+	if (!sg->detaching)
+		purple_connection_error(gc, _("Disconnected by server"));
+	else
+		/* TODO: Does this work correctly? Maybe we need to set wants_to_die? */
+		purple_account_disconnect(purple_connection_get_account(gc));
+}
+
+
+typedef struct {
+	SilcGetAuthMeth completion;
+	void *context;
+} *SilcPurpleGetAuthMethod;
+
+/* Callback called when we've received the authentication method information
+   from the server after we've requested it. */
+
+static void silc_get_auth_method_callback(SilcClient client,
+					  SilcClientConnection conn,
+					  SilcAuthMethod auth_meth,
+					  void *context)
+{
+	SilcPurpleGetAuthMethod internal = context;
+
+	switch (auth_meth) {
+	case SILC_AUTH_NONE:
+		/* No authentication required. */
+		(*internal->completion)(TRUE, auth_meth, NULL, 0, internal->context);
+		break;
+
+	case SILC_AUTH_PASSWORD:
+		/* By returning NULL here the library will ask the passphrase from us
+		   by calling the silc_ask_passphrase. */
+		(*internal->completion)(TRUE, auth_meth, NULL, 0, internal->context);
+		break;
+
+	case SILC_AUTH_PUBLIC_KEY:
+		/* Do not get the authentication data now, the library will generate
+		   it using our default key, if we do not provide it here. */
+		(*internal->completion)(TRUE, auth_meth, NULL, 0, internal->context);
+		break;
+	}
+
+	silc_free(internal);
+}
+
+/* Find authentication method and authentication data by hostname and
+   port. The hostname may be IP address as well. When the authentication
+   method has been resolved the `completion' callback with the found
+   authentication method and authentication data is called. The `conn'
+   may be NULL. */
+
+static void
+silc_get_auth_method(SilcClient client, SilcClientConnection conn,
+		     char *hostname, SilcUInt16 port,
+		     SilcGetAuthMeth completion, void *context)
+{
+	PurpleConnection *gc = client->application;
+	SilcPurple sg = gc->proto_data;
+	SilcPurpleGetAuthMethod internal;
+	const char *password;
+
+	/* Progress */
+	if (sg->resuming)
+		purple_connection_update_progress(gc, _("Resuming session"), 4, 5);
+	else
+		purple_connection_update_progress(gc, _("Authenticating connection"), 4, 5);
+
+	/* Check configuration if we have this connection configured.  If we
+	   have then return that data immediately, as it's faster way. */
+	if (purple_account_get_bool(sg->account, "pubkey-auth", FALSE)) {
+		completion(TRUE, SILC_AUTH_PUBLIC_KEY, NULL, 0, context);
+		return;
+	}
+	password = purple_connection_get_password(gc);
+	if (password && *password) {
+		completion(TRUE, SILC_AUTH_PASSWORD, (unsigned char *)password, strlen(password), context);
+		return;
+	}
+
+	/* Resolve the authentication method from server, as we may not know it. */
+	internal = silc_calloc(1, sizeof(*internal));
+	if (!internal)
+		return;
+	internal->completion = completion;
+	internal->context = context;
+	silc_client_request_authentication_method(client, conn,
+						  silc_get_auth_method_callback,
+						  internal);
+}
+
+
+/* Verifies received public key. The `conn_type' indicates which entity
+   (server, client etc.) has sent the public key. If user decides to trust
+   the application may save the key as trusted public key for later
+   use. The `completion' must be called after the public key has been
+   verified. */
+
+static void
+silc_verify_public_key(SilcClient client, SilcClientConnection conn,
+		       SilcSocketType conn_type, unsigned char *pk,
+		       SilcUInt32 pk_len, SilcSKEPKType pk_type,
+		       SilcVerifyPublicKey completion, void *context)
+{
+	PurpleConnection *gc = client->application;
+	SilcPurple sg = gc->proto_data;
+
+	if (!sg->conn && (conn_type == SILC_SOCKET_TYPE_SERVER ||
+			  conn_type == SILC_SOCKET_TYPE_ROUTER)) {
+		/* Progress */
+		if (sg->resuming)
+			purple_connection_update_progress(gc, _("Resuming session"), 3, 5);
+		else
+			purple_connection_update_progress(gc, _("Verifying server public key"),
+							3, 5);
+	}
+
+	/* Verify public key */
+	silcpurple_verify_public_key(client, conn, NULL, conn_type, pk,
+				   pk_len, pk_type, completion, context);
+}
+
+typedef struct {
+	SilcAskPassphrase completion;
+	void *context;
+} *SilcPurpleAskPassphrase;
+
+static void
+silc_ask_passphrase_cb(SilcPurpleAskPassphrase internal, const char *passphrase)
+{
+	if (!passphrase || !(*passphrase))
+		internal->completion(NULL, 0, internal->context);
+	else
+		internal->completion((unsigned char *)passphrase,
+				     strlen(passphrase), internal->context);
+	silc_free(internal);
+}
+
+/* Ask (interact, that is) a passphrase from user. The passphrase is
+   returned to the library by calling the `completion' callback with
+   the `context'. The returned passphrase SHOULD be in UTF-8 encoded,
+   if not then the library will attempt to encode. */
+
+static void
+silc_ask_passphrase(SilcClient client, SilcClientConnection conn,
+		    SilcAskPassphrase completion, void *context)
+{
+	PurpleConnection *gc = client->application;
+	SilcPurpleAskPassphrase internal = silc_calloc(1, sizeof(*internal));
+
+	if (!internal)
+		return;
+	internal->completion = completion;
+	internal->context = context;
+	purple_request_input(gc, _("Passphrase"), NULL,
+			   _("Passphrase required"), NULL, FALSE, TRUE, NULL,
+			   _("OK"), G_CALLBACK(silc_ask_passphrase_cb),
+			   _("Cancel"), G_CALLBACK(silc_ask_passphrase_cb),
+			   purple_connection_get_account(gc), NULL, NULL, internal);
+}
+
+
+/* Notifies application that failure packet was received.  This is called
+   if there is some protocol active in the client.  The `protocol' is the
+   protocol context.  The `failure' is opaque pointer to the failure
+   indication.  Note, that the `failure' is protocol dependant and
+   application must explicitly cast it to correct type.  Usually `failure'
+   is 32 bit failure type (see protocol specs for all protocol failure
+   types). */
+
+static void
+silc_failure(SilcClient client, SilcClientConnection conn,
+	     SilcProtocol protocol, void *failure)
+{
+	PurpleConnection *gc = client->application;
+	char buf[128];
+
+	memset(buf, 0, sizeof(buf));
+
+	if (protocol->protocol->type == SILC_PROTOCOL_CLIENT_KEY_EXCHANGE) {
+		SilcSKEStatus status = (SilcSKEStatus)SILC_PTR_TO_32(failure);
+
+		if (status == SILC_SKE_STATUS_BAD_VERSION)
+			g_snprintf(buf, sizeof(buf),
+				   _("Failure: Version mismatch, upgrade your client"));
+		if (status == SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY)
+			g_snprintf(buf, sizeof(buf),
+				   _("Failure: Remote does not trust/support your public key"));
+		if (status == SILC_SKE_STATUS_UNKNOWN_GROUP)
+			g_snprintf(buf, sizeof(buf),
+				   _("Failure: Remote does not support proposed KE group"));
+		if (status == SILC_SKE_STATUS_UNKNOWN_CIPHER)
+			g_snprintf(buf, sizeof(buf),
+				   _("Failure: Remote does not support proposed cipher"));
+		if (status == SILC_SKE_STATUS_UNKNOWN_PKCS)
+			g_snprintf(buf, sizeof(buf),
+				   _("Failure: Remote does not support proposed PKCS"));
+		if (status == SILC_SKE_STATUS_UNKNOWN_HASH_FUNCTION)
+			g_snprintf(buf, sizeof(buf),
+				   _("Failure: Remote does not support proposed hash function"));
+		if (status == SILC_SKE_STATUS_UNKNOWN_HMAC)
+			g_snprintf(buf, sizeof(buf),
+				   _("Failure: Remote does not support proposed HMAC"));
+		if (status == SILC_SKE_STATUS_INCORRECT_SIGNATURE)
+			g_snprintf(buf, sizeof(buf), _("Failure: Incorrect signature"));
+		if (status == SILC_SKE_STATUS_INVALID_COOKIE)
+			g_snprintf(buf, sizeof(buf), _("Failure: Invalid cookie"));
+
+		/* Show the error on the progress bar.  A more generic error message
+		   is going to be showed to user after this in the silc_connected. */
+		purple_connection_update_progress(gc, buf, 2, 5);
+	}
+
+	if (protocol->protocol->type == SILC_PROTOCOL_CLIENT_CONNECTION_AUTH) {
+		SilcUInt32 err = SILC_PTR_TO_32(failure);
+
+		if (err == SILC_AUTH_FAILED)
+			g_snprintf(buf, sizeof(buf), _("Failure: Authentication failed"));
+
+		/* Show the error on the progress bar.  A more generic error message
+		   is going to be showed to user after this in the silc_connected. */
+		purple_connection_update_progress(gc, buf, 4, 5);
+	}
+}
+
+/* Asks whether the user would like to perform the key agreement protocol.
+   This is called after we have received an key agreement packet or an
+   reply to our key agreement packet. This returns TRUE if the user wants
+   the library to perform the key agreement protocol and FALSE if it is not
+   desired (application may start it later by calling the function
+   silc_client_perform_key_agreement). If TRUE is returned also the
+   `completion' and `context' arguments must be set by the application. */
+
+static bool
+silc_key_agreement(SilcClient client, SilcClientConnection conn,
+		   SilcClientEntry client_entry, const char *hostname,
+		   SilcUInt16 port, SilcKeyAgreementCallback *completion,
+		   void **context)
+{
+	silcpurple_buddy_keyagr_request(client, conn, client_entry, hostname, port);
+	*completion = NULL;
+	*context = NULL;
+	return FALSE;
+}
+
+
+/* Notifies application that file transfer protocol session is being
+   requested by the remote client indicated by the `client_entry' from
+   the `hostname' and `port'. The `session_id' is the file transfer
+   session and it can be used to either accept or reject the file
+   transfer request, by calling the silc_client_file_receive or
+   silc_client_file_close, respectively. */
+
+static void
+silc_ftp(SilcClient client, SilcClientConnection conn,
+	 SilcClientEntry client_entry, SilcUInt32 session_id,
+	 const char *hostname, SilcUInt16 port)
+{
+	silcpurple_ftp_request(client, conn, client_entry, session_id,
+			     hostname, port);
+}
+
+
+/* Delivers SILC session detachment data indicated by `detach_data' to the
+   application.  If application has issued SILC_COMMAND_DETACH command
+   the client session in the SILC network is not quit.  The client remains
+   in the network but is detached.  The detachment data may be used later
+   to resume the session in the SILC Network.  The appliation is
+   responsible of saving the `detach_data', to for example in a file.
+
+   The detachment data can be given as argument to the functions
+   silc_client_connect_to_server, or silc_client_add_connection when
+   creating connection to remote server, inside SilcClientConnectionParams
+   structure.  If it is provided the client library will attempt to resume
+   the session in the network.  After the connection is created
+   successfully, the application is responsible of setting the user
+   interface for user into the same state it was before detaching (showing
+   same channels, channel modes, etc).  It can do this by fetching the
+   information (like joined channels) from the client library. */
+
+static void
+silc_detach(SilcClient client, SilcClientConnection conn,
+	    const unsigned char *detach_data, SilcUInt32 detach_data_len)
+{
+	PurpleConnection *gc = client->application;
+	SilcPurple sg = gc->proto_data;
+	const char *file;
+
+	/* Save the detachment data to file. */
+	file = silcpurple_session_file(purple_account_get_username(sg->account));
+	g_unlink(file);
+	silc_file_writefile(file, (char *)detach_data, detach_data_len);
+}
+
+SilcClientOperations ops = {
+	silc_say,
+	silc_channel_message,
+	silc_private_message,
+	silc_notify,
+	silc_command,
+	silc_command_reply,
+	silc_connected,
+	silc_disconnected,
+	silc_get_auth_method,
+	silc_verify_public_key,
+	silc_ask_passphrase,
+	silc_failure,
+	silc_key_agreement,
+	silc_ftp,
+	silc_detach
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/silc10/pk.c	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,274 @@
+/*
+
+  silcpurple_pk.c
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  Copyright (C) 2004 Pekka Riikonen
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; version 2 of the License.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+*/
+
+#include "silcincludes.h"
+#include "silcclient.h"
+#include "silcpurple.h"
+
+/************************* Public Key Verification ***************************/
+
+typedef struct {
+	SilcClient client;
+	SilcClientConnection conn;
+	char *filename;
+	char *entity;
+	char *entity_name;
+	char *fingerprint;
+	char *babbleprint;
+	unsigned char *pk;
+	SilcUInt32 pk_len;
+	SilcSKEPKType pk_type;
+	SilcVerifyPublicKey completion;
+	void *context;
+	gboolean changed;
+} *PublicKeyVerify;
+
+static void silcpurple_verify_ask(const char *entity,
+				const char *fingerprint,
+				const char *babbleprint,
+				PublicKeyVerify verify);
+
+static void silcpurple_verify_cb(PublicKeyVerify verify, gint id)
+{
+	if (id != 2) {
+		if (verify->completion)
+			verify->completion(FALSE, verify->context);
+	} else {
+		if (verify->completion)
+			verify->completion(TRUE, verify->context);
+
+		/* Save the key for future checking */
+		silc_pkcs_save_public_key_data(verify->filename, verify->pk,
+					       verify->pk_len, SILC_PKCS_FILE_PEM);
+	}
+
+	silc_free(verify->filename);
+	silc_free(verify->entity);
+	silc_free(verify->entity_name);
+	silc_free(verify->fingerprint);
+	silc_free(verify->babbleprint);
+	silc_free(verify->pk);
+	silc_free(verify);
+}
+
+static void silcpurple_verify_details_cb(PublicKeyVerify verify)
+{
+	/* What a hack.  We have to display the accept dialog _again_
+	   because Purple closes the dialog after you press the button.  Purple
+	   should have option for the dialogs whether the buttons close them
+	   or not. */
+	silcpurple_verify_ask(verify->entity, verify->fingerprint,
+			    verify->babbleprint, verify);
+}
+
+static void silcpurple_verify_details(PublicKeyVerify verify, gint id)
+{
+	SilcPublicKey public_key;
+	PurpleConnection *gc = verify->client->application;
+	SilcPurple sg = gc->proto_data;
+
+	silc_pkcs_public_key_decode(verify->pk, verify->pk_len,
+				    &public_key);
+	silcpurple_show_public_key(sg, verify->entity_name, public_key,
+				 G_CALLBACK(silcpurple_verify_details_cb),
+				 verify);
+	silc_pkcs_public_key_free(public_key);
+}
+
+static void silcpurple_verify_ask(const char *entity,
+				const char *fingerprint,
+				const char *babbleprint,
+				PublicKeyVerify verify)
+{
+	PurpleConnection *gc = verify->client->application;
+	char tmp[256], tmp2[256];
+
+	if (verify->changed) {
+		g_snprintf(tmp, sizeof(tmp),
+			   _("Received %s's public key. Your local copy does not match this "
+			     "key. Would you still like to accept this public key?"),
+			   entity);
+	} else {
+		g_snprintf(tmp, sizeof(tmp),
+			   _("Received %s's public key. Would you like to accept this "
+			     "public key?"), entity);
+	}
+	g_snprintf(tmp2, sizeof(tmp2),
+		   _("Fingerprint and babbleprint for the %s key are:\n\n"
+		     "%s\n%s\n"), entity, fingerprint, babbleprint);
+
+	purple_request_action(gc, _("Verify Public Key"), tmp, tmp2,
+						PURPLE_DEFAULT_ACTION_NONE,
+						purple_connection_get_account(gc), entity, NULL, verify, 3,
+			    _("Yes"), G_CALLBACK(silcpurple_verify_cb),
+			    _("No"), G_CALLBACK(silcpurple_verify_cb),
+			    _("_View..."), G_CALLBACK(silcpurple_verify_details));
+}
+
+void silcpurple_verify_public_key(SilcClient client, SilcClientConnection conn,
+				const char *name, SilcSocketType conn_type,
+				unsigned char *pk, SilcUInt32 pk_len,
+				SilcSKEPKType pk_type,
+				SilcVerifyPublicKey completion, void *context)
+{
+	PurpleConnection *gc = client->application;
+	int i;
+	char file[256], filename[256], filename2[256], *ipf, *hostf = NULL;
+	char *fingerprint, *babbleprint;
+	struct passwd *pw;
+	struct stat st;
+	char *entity = ((conn_type == SILC_SOCKET_TYPE_SERVER ||
+			 conn_type == SILC_SOCKET_TYPE_ROUTER) ?
+			"server" : "client");
+	PublicKeyVerify verify;
+
+	if (pk_type != SILC_SKE_PK_TYPE_SILC) {
+		purple_notify_error(gc, _("Verify Public Key"),
+				  _("Unsupported public key type"), NULL);
+		if (completion)
+			completion(FALSE, context);
+		return;
+	}
+
+	pw = getpwuid(getuid());
+	if (!pw) {
+		if (completion)
+			completion(FALSE, context);
+		return;
+	}
+
+	memset(filename, 0, sizeof(filename));
+	memset(filename2, 0, sizeof(filename2));
+	memset(file, 0, sizeof(file));
+
+	if (conn_type == SILC_SOCKET_TYPE_SERVER ||
+	    conn_type == SILC_SOCKET_TYPE_ROUTER) {
+		if (!name) {
+			g_snprintf(file, sizeof(file) - 1, "%skey_%s_%d.pub", entity,
+				   conn->sock->ip, conn->sock->port);
+			g_snprintf(filename, sizeof(filename) - 1,
+				   "%s" G_DIR_SEPARATOR_S "%skeys" G_DIR_SEPARATOR_S "%s",
+				   silcpurple_silcdir(), entity, file);
+
+			g_snprintf(file, sizeof(file) - 1, "%skey_%s_%d.pub", entity,
+				   conn->sock->hostname, conn->sock->port);
+			g_snprintf(filename2, sizeof(filename2) - 1,
+				   "%s" G_DIR_SEPARATOR_S "%skeys" G_DIR_SEPARATOR_S "%s",
+				   silcpurple_silcdir(), entity, file);
+
+			ipf = filename;
+			hostf = filename2;
+		} else {
+			g_snprintf(file, sizeof(file) - 1, "%skey_%s_%d.pub", entity,
+				   name, conn->sock->port);
+			g_snprintf(filename, sizeof(filename) - 1,
+				   "%s" G_DIR_SEPARATOR_S "%skeys" G_DIR_SEPARATOR_S "%s",
+				   silcpurple_silcdir(), entity, file);
+
+			ipf = filename;
+		}
+	} else {
+		/* Replace all whitespaces with `_'. */
+		fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+		for (i = 0; i < strlen(fingerprint); i++)
+			if (fingerprint[i] == ' ')
+				fingerprint[i] = '_';
+
+		g_snprintf(file, sizeof(file) - 1, "%skey_%s.pub", entity, fingerprint);
+		g_snprintf(filename, sizeof(filename) - 1,
+			   "%s" G_DIR_SEPARATOR_S "%skeys" G_DIR_SEPARATOR_S "%s",
+			   silcpurple_silcdir(), entity, file);
+		silc_free(fingerprint);
+
+		ipf = filename;
+	}
+
+	verify = silc_calloc(1, sizeof(*verify));
+	if (!verify)
+		return;
+	verify->client = client;
+	verify->conn = conn;
+	verify->filename = strdup(ipf);
+	verify->entity = strdup(entity);
+	verify->entity_name = (conn_type != SILC_SOCKET_TYPE_CLIENT ?
+			       (name ? strdup(name) : strdup(conn->sock->hostname))
+			       : NULL);
+	verify->pk = silc_memdup(pk, pk_len);
+	verify->pk_len = pk_len;
+	verify->pk_type = pk_type;
+	verify->completion = completion;
+	verify->context = context;
+	fingerprint = verify->fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+	babbleprint = verify->babbleprint = silc_hash_babbleprint(NULL, pk, pk_len);
+
+	/* Check whether this key already exists */
+	if (g_stat(ipf, &st) < 0 && (!hostf || g_stat(hostf, &st) < 0)) {
+		/* Key does not exist, ask user to verify the key and save it */
+		silcpurple_verify_ask(name ? name : entity,
+				    fingerprint, babbleprint, verify);
+		return;
+	} else {
+		/* The key already exists, verify it. */
+		SilcPublicKey public_key;
+		unsigned char *encpk;
+		SilcUInt32 encpk_len;
+
+		/* Load the key file, try for both IP filename and hostname filename */
+		if (!silc_pkcs_load_public_key(ipf, &public_key,
+					       SILC_PKCS_FILE_PEM) &&
+		    !silc_pkcs_load_public_key(ipf, &public_key,
+					       SILC_PKCS_FILE_BIN) &&
+		    (!hostf || (!silc_pkcs_load_public_key(hostf, &public_key,
+							   SILC_PKCS_FILE_PEM) &&
+				!silc_pkcs_load_public_key(hostf, &public_key,
+							   SILC_PKCS_FILE_BIN)))) {
+			silcpurple_verify_ask(name ? name : entity,
+					    fingerprint, babbleprint, verify);
+			return;
+		}
+
+		/* Encode the key data */
+		encpk = silc_pkcs_public_key_encode(public_key, &encpk_len);
+		if (!encpk) {
+			silcpurple_verify_ask(name ? name : entity,
+					    fingerprint, babbleprint, verify);
+			return;
+		}
+
+		/* Compare the keys */
+		if (memcmp(encpk, pk, encpk_len)) {
+			/* Ask user to verify the key and save it */
+			verify->changed = TRUE;
+			silcpurple_verify_ask(name ? name : entity,
+					    fingerprint, babbleprint, verify);
+			return;
+		}
+
+		/* Local copy matched */
+		if (completion)
+			completion(TRUE, context);
+		silc_free(verify->filename);
+		silc_free(verify->entity);
+		silc_free(verify->entity_name);
+		silc_free(verify->pk);
+		silc_free(verify->fingerprint);
+		silc_free(verify->babbleprint);
+		silc_free(verify);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/silc10/silc.c	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,1916 @@
+/*
+
+  silcpurple.c
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  Copyright (C) 2004 - 2005 Pekka Riikonen
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; version 2 of the License.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+*/
+
+#include "silcincludes.h"
+#include "silcclient.h"
+#include "silcpurple.h"
+#include "version.h"
+#include "wb.h"
+
+extern SilcClientOperations ops;
+static PurplePlugin *silc_plugin = NULL;
+
+static const char *
+silcpurple_list_icon(PurpleAccount *a, PurpleBuddy *b)
+{
+	return (const char *)"silc";
+}
+
+static GList *
+silcpurple_away_states(PurpleAccount *account)
+{
+	PurpleStatusType *type;
+	GList *types = NULL;
+
+	type = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, SILCPURPLE_STATUS_ID_AVAILABLE, NULL, FALSE, TRUE, FALSE);
+	types = g_list_append(types, type);
+	type = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, SILCPURPLE_STATUS_ID_HYPER, _("Hyper Active"), FALSE, TRUE, FALSE);
+	types = g_list_append(types, type);
+	type = purple_status_type_new_full(PURPLE_STATUS_AWAY, SILCPURPLE_STATUS_ID_AWAY, NULL, FALSE, TRUE, FALSE);
+	types = g_list_append(types, type);
+	type = purple_status_type_new_full(PURPLE_STATUS_UNAVAILABLE, SILCPURPLE_STATUS_ID_BUSY, _("Busy"), FALSE, TRUE, FALSE);
+	types = g_list_append(types, type);
+	type = purple_status_type_new_full(PURPLE_STATUS_AWAY, SILCPURPLE_STATUS_ID_INDISPOSED, _("Indisposed"), FALSE, TRUE, FALSE);
+	types = g_list_append(types, type);
+	type = purple_status_type_new_full(PURPLE_STATUS_AWAY, SILCPURPLE_STATUS_ID_PAGE, _("Wake Me Up"), FALSE, TRUE, FALSE);
+	types = g_list_append(types, type);
+	type = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, SILCPURPLE_STATUS_ID_OFFLINE, NULL, FALSE, TRUE, FALSE);
+	types = g_list_append(types, type);
+
+	return types;
+}
+
+static void
+silcpurple_set_status(PurpleAccount *account, PurpleStatus *status)
+{
+	PurpleConnection *gc = purple_account_get_connection(account);
+	SilcPurple sg = NULL;
+	SilcUInt32 mode;
+	SilcBuffer idp;
+	unsigned char mb[4];
+	const char *state;
+
+	if (gc != NULL)
+		sg = gc->proto_data;
+
+	if (status == NULL)
+		return;
+
+	state = purple_status_get_id(status);
+
+	if (state == NULL)
+		return;
+
+	if ((sg == NULL) || (sg->conn == NULL))
+		return;
+
+	mode = sg->conn->local_entry->mode;
+	mode &= ~(SILC_UMODE_GONE |
+		  SILC_UMODE_HYPER |
+		  SILC_UMODE_BUSY |
+		  SILC_UMODE_INDISPOSED |
+		  SILC_UMODE_PAGE);
+
+	if (!strcmp(state, "hyper"))
+		mode |= SILC_UMODE_HYPER;
+	else if (!strcmp(state, "away"))
+		mode |= SILC_UMODE_GONE;
+	else if (!strcmp(state, "busy"))
+		mode |= SILC_UMODE_BUSY;
+	else if (!strcmp(state, "indisposed"))
+		mode |= SILC_UMODE_INDISPOSED;
+	else if (!strcmp(state, "page"))
+		mode |= SILC_UMODE_PAGE;
+
+	/* Send UMODE */
+	idp = silc_id_payload_encode(sg->conn->local_id, SILC_ID_CLIENT);
+	SILC_PUT32_MSB(mode, mb);
+	silc_client_command_send(sg->client, sg->conn, SILC_COMMAND_UMODE,
+				 ++sg->conn->cmd_ident, 2,
+				 1, idp->data, idp->len,
+				 2, mb, sizeof(mb));
+	silc_buffer_free(idp);
+}
+
+
+/*************************** Connection Routines *****************************/
+
+static void
+silcpurple_keepalive(PurpleConnection *gc)
+{
+	SilcPurple sg = gc->proto_data;
+	silc_client_send_packet(sg->client, sg->conn, SILC_PACKET_HEARTBEAT,
+				NULL, 0);
+}
+
+static gboolean
+silcpurple_scheduler(gpointer *context)
+{
+	SilcPurple sg = (SilcPurple)context;
+	silc_client_run_one(sg->client);
+	return TRUE;
+}
+
+static void
+silcpurple_nickname_parse(const char *nickname,
+			char **ret_nickname)
+{
+	silc_parse_userfqdn(nickname, ret_nickname, NULL);
+}
+
+static void
+silcpurple_login_connected(gpointer data, gint source, const gchar *error_message)
+{
+	PurpleConnection *gc = data;
+	SilcPurple sg;
+	SilcClient client;
+	SilcClientConnection conn;
+	PurpleAccount *account;
+	SilcClientConnectionParams params;
+	SilcUInt32 mask;
+	const char *dfile, *tmp;
+#ifdef SILC_ATTRIBUTE_USER_ICON
+	PurpleStoredImage *img;
+#endif
+#ifdef HAVE_SYS_UTSNAME_H
+	struct utsname u;
+#endif
+
+
+	g_return_if_fail(gc != NULL);
+
+	sg = gc->proto_data;
+
+	if (source < 0) {
+		purple_connection_error(gc, _("Connection failed"));
+		return;
+	}
+
+	client = sg->client;
+	account = sg->account;
+
+	/* Get session detachment data, if available */
+	memset(&params, 0, sizeof(params));
+	dfile = silcpurple_session_file(purple_account_get_username(sg->account));
+	params.detach_data = (unsigned char *)silc_file_readfile(dfile, &params.detach_data_len);
+	if (params.detach_data)
+		params.detach_data[params.detach_data_len] = 0;
+
+	/* Add connection to SILC client library */
+	conn = silc_client_add_connection(
+			  sg->client, &params,
+			  (char *)purple_account_get_string(account, "server",
+							  "silc.silcnet.org"),
+			  purple_account_get_int(account, "port", 706), sg);
+	if (!conn) {
+		purple_connection_error(gc, _("Cannot initialize SILC Client connection"));
+		gc->proto_data = NULL;
+		return;
+	}
+	sg->conn = conn;
+
+	/* Progress */
+	if (params.detach_data) {
+		purple_connection_update_progress(gc, _("Resuming session"), 2, 5);
+		sg->resuming = TRUE;
+	} else {
+		purple_connection_update_progress(gc, _("Performing key exchange"), 2, 5);
+	}
+
+	/* Perform SILC Key Exchange.  The "silc_connected" will be called
+	   eventually. */
+	silc_client_start_key_exchange(sg->client, sg->conn, source);
+
+	/* Set default attributes */
+	mask = SILC_ATTRIBUTE_MOOD_NORMAL;
+	silc_client_attribute_add(client, conn,
+				  SILC_ATTRIBUTE_STATUS_MOOD,
+				  SILC_32_TO_PTR(mask),
+				  sizeof(SilcUInt32));
+	mask = SILC_ATTRIBUTE_CONTACT_CHAT;
+	silc_client_attribute_add(client, conn,
+				  SILC_ATTRIBUTE_PREFERRED_CONTACT,
+				  SILC_32_TO_PTR(mask),
+				  sizeof(SilcUInt32));
+#ifdef HAVE_SYS_UTSNAME_H
+	if (!uname(&u)) {
+		SilcAttributeObjDevice dev;
+		memset(&dev, 0, sizeof(dev));
+		dev.type = SILC_ATTRIBUTE_DEVICE_COMPUTER;
+		dev.version = u.release;
+		dev.model = u.sysname;
+		silc_client_attribute_add(client, conn,
+					  SILC_ATTRIBUTE_DEVICE_INFO,
+					  (void *)&dev, sizeof(dev));
+	}
+#endif
+#ifdef _WIN32
+	tmp = _tzname[0];
+#else
+	tmp = tzname[0];
+#endif
+	silc_client_attribute_add(client, conn,
+				  SILC_ATTRIBUTE_TIMEZONE,
+				  (void *)tmp, strlen(tmp));
+
+#ifdef SILC_ATTRIBUTE_USER_ICON
+	/* Set our buddy icon */
+	img = purple_buddy_icons_find_account_icon(account);
+	silcpurple_buddy_set_icon(gc, img);
+	purple_imgstore_unref(img);
+#endif
+
+	silc_free(params.detach_data);
+}
+
+static void
+silcpurple_login(PurpleAccount *account)
+{
+	SilcPurple sg;
+	SilcClient client;
+	SilcClientParams params;
+	PurpleConnection *gc;
+	char pkd[256], prd[256];
+	const char *cipher, *hmac;
+	char *realname;
+	int i;
+
+	gc = account->gc;
+	if (!gc)
+		return;
+	gc->proto_data = NULL;
+
+	memset(&params, 0, sizeof(params));
+	strcat(params.nickname_format, "%n@%h%a");
+	params.nickname_parse = silcpurple_nickname_parse;
+	params.ignore_requested_attributes = FALSE;
+
+	/* Allocate SILC client */
+	client = silc_client_alloc(&ops, &params, gc, NULL);
+	if (!client) {
+		purple_connection_error(gc, _("Out of memory"));
+		return;
+	}
+
+	/* Get username, real name and local hostname for SILC library */
+	if (purple_account_get_username(account)) {
+		const char *u = purple_account_get_username(account);
+		char **up = g_strsplit(u, "@", 2);
+		client->username = strdup(up[0]);
+		g_strfreev(up);
+	} else {
+		client->username = silc_get_username();
+		purple_account_set_username(account, client->username);
+	}
+	realname = silc_get_real_name();
+	if (purple_account_get_user_info(account)) {
+		client->realname = strdup(purple_account_get_user_info(account));
+		free(realname);
+	} else if ((silc_get_real_name() != NULL) && (*realname != '\0')) {
+		client->realname = realname;
+		purple_account_set_user_info(account, client->realname);
+	} else {
+		free(realname);
+		client->realname = strdup(_("John Noname"));
+	}
+	client->hostname = silc_net_localhost();
+
+	purple_connection_set_display_name(gc, client->username);
+
+	/* Register requested cipher and HMAC */
+	cipher = purple_account_get_string(account, "cipher", SILC_DEFAULT_CIPHER);
+	for (i = 0; silc_default_ciphers[i].name; i++)
+		if (!strcmp(silc_default_ciphers[i].name, cipher)) {
+			silc_cipher_register(&(silc_default_ciphers[i]));
+			break;
+		}
+	hmac = purple_account_get_string(account, "hmac", SILC_DEFAULT_HMAC);
+	for (i = 0; silc_default_hmacs[i].name; i++)
+		if (!strcmp(silc_default_hmacs[i].name, hmac)) {
+			silc_hmac_register(&(silc_default_hmacs[i]));
+			break;
+		}
+
+	/* Init SILC client */
+	if (!silc_client_init(client)) {
+		gc->wants_to_die = TRUE;
+		purple_connection_error(gc, _("Cannot initialize SILC protocol"));
+		return;
+	}
+
+	/* Check the ~/.silc dir and create it, and new key pair if necessary. */
+	if (!silcpurple_check_silc_dir(gc)) {
+		gc->wants_to_die = TRUE;
+		purple_connection_error(gc, _("Cannot find/access ~/.silc directory"));
+		return;
+	}
+
+	/* Progress */
+	purple_connection_update_progress(gc, _("Connecting to SILC Server"), 1, 5);
+
+	/* Load SILC key pair */
+	g_snprintf(pkd, sizeof(pkd), "%s" G_DIR_SEPARATOR_S "public_key.pub", silcpurple_silcdir());
+	g_snprintf(prd, sizeof(prd), "%s" G_DIR_SEPARATOR_S "private_key.prv", silcpurple_silcdir());
+	if (!silc_load_key_pair((char *)purple_account_get_string(account, "public-key", pkd),
+							(char *)purple_account_get_string(account, "private-key", prd),
+				(gc->password == NULL) ? "" : gc->password, &client->pkcs,
+				&client->public_key, &client->private_key)) {
+		g_snprintf(pkd, sizeof(pkd), _("Could not load SILC key pair: %s"), strerror(errno));
+		purple_connection_error(gc, pkd);
+		return;
+	}
+
+	sg = silc_calloc(1, sizeof(*sg));
+	if (!sg)
+		return;
+	memset(sg, 0, sizeof(*sg));
+	sg->client = client;
+	sg->gc = gc;
+	sg->account = account;
+	gc->proto_data = sg;
+
+	/* Connect to the SILC server */
+	if (purple_proxy_connect(gc, account,
+			       purple_account_get_string(account, "server",
+						       "silc.silcnet.org"),
+			       purple_account_get_int(account, "port", 706),
+			       silcpurple_login_connected, gc) == NULL)
+	{
+		purple_connection_error(gc, _("Unable to create connection"));
+		return;
+	}
+
+	/* Schedule SILC using Glib's event loop */
+	sg->scheduler = purple_timeout_add(300, (GSourceFunc)silcpurple_scheduler, sg);
+}
+
+static int
+silcpurple_close_final(gpointer *context)
+{
+	SilcPurple sg = (SilcPurple)context;
+	silc_client_stop(sg->client);
+	silc_client_free(sg->client);
+#ifdef HAVE_SILCMIME_H
+	if (sg->mimeass)
+		silc_mime_assembler_free(sg->mimeass);
+#endif
+	silc_free(sg);
+	return 0;
+}
+
+static void
+silcpurple_close(PurpleConnection *gc)
+{
+	SilcPurple sg = gc->proto_data;
+
+	g_return_if_fail(sg != NULL);
+
+	/* Send QUIT */
+	silc_client_command_call(sg->client, sg->conn, NULL,
+				 "QUIT", "Download this: " PURPLE_WEBSITE, NULL);
+
+	if (sg->conn)
+		silc_client_close_connection(sg->client, sg->conn);
+
+	purple_timeout_remove(sg->scheduler);
+	purple_timeout_add(1, (GSourceFunc)silcpurple_close_final, sg);
+}
+
+
+/****************************** Protocol Actions *****************************/
+
+static void
+silcpurple_attrs_cancel(PurpleConnection *gc, PurpleRequestFields *fields)
+{
+	/* Nothing */
+}
+
+static void
+silcpurple_attrs_cb(PurpleConnection *gc, PurpleRequestFields *fields)
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	PurpleRequestField *f;
+	char *tmp;
+	SilcUInt32 tmp_len, mask;
+	SilcAttributeObjService service;
+	SilcAttributeObjDevice dev;
+	SilcVCardStruct vcard;
+	const char *val;
+
+	sg = gc->proto_data;
+	if (!sg)
+		return;
+
+	memset(&service, 0, sizeof(service));
+	memset(&dev, 0, sizeof(dev));
+	memset(&vcard, 0, sizeof(vcard));
+
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_USER_INFO, NULL);
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_SERVICE, NULL);
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_STATUS_MOOD, NULL);
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_STATUS_FREETEXT, NULL);
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_STATUS_MESSAGE, NULL);
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_PREFERRED_LANGUAGE, NULL);
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_PREFERRED_CONTACT, NULL);
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_TIMEZONE, NULL);
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_GEOLOCATION, NULL);
+	silc_client_attribute_del(client, conn,
+				  SILC_ATTRIBUTE_DEVICE_INFO, NULL);
+
+	/* Set mood */
+	mask = 0;
+	f = purple_request_fields_get_field(fields, "mood_normal");
+	if (f && purple_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_NORMAL;
+	f = purple_request_fields_get_field(fields, "mood_happy");
+	if (f && purple_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_HAPPY;
+	f = purple_request_fields_get_field(fields, "mood_sad");
+	if (f && purple_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_SAD;
+	f = purple_request_fields_get_field(fields, "mood_angry");
+	if (f && purple_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_ANGRY;
+	f = purple_request_fields_get_field(fields, "mood_jealous");
+	if (f && purple_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_JEALOUS;
+	f = purple_request_fields_get_field(fields, "mood_ashamed");
+	if (f && purple_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_ASHAMED;
+	f = purple_request_fields_get_field(fields, "mood_invincible");
+	if (f && purple_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_INVINCIBLE;
+	f = purple_request_fields_get_field(fields, "mood_inlove");
+	if (f && purple_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_INLOVE;
+	f = purple_request_fields_get_field(fields, "mood_sleepy");
+	if (f && purple_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_SLEEPY;
+	f = purple_request_fields_get_field(fields, "mood_bored");
+	if (f && purple_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_BORED;
+	f = purple_request_fields_get_field(fields, "mood_excited");
+	if (f && purple_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_EXCITED;
+	f = purple_request_fields_get_field(fields, "mood_anxious");
+	if (f && purple_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_MOOD_ANXIOUS;
+	silc_client_attribute_add(client, conn,
+				  SILC_ATTRIBUTE_STATUS_MOOD,
+				  SILC_32_TO_PTR(mask),
+				  sizeof(SilcUInt32));
+
+	/* Set preferred contact */
+	mask = 0;
+	f = purple_request_fields_get_field(fields, "contact_chat");
+	if (f && purple_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_CONTACT_CHAT;
+	f = purple_request_fields_get_field(fields, "contact_email");
+	if (f && purple_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_CONTACT_EMAIL;
+	f = purple_request_fields_get_field(fields, "contact_call");
+	if (f && purple_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_CONTACT_CALL;
+	f = purple_request_fields_get_field(fields, "contact_sms");
+	if (f && purple_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_CONTACT_SMS;
+	f = purple_request_fields_get_field(fields, "contact_mms");
+	if (f && purple_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_CONTACT_MMS;
+	f = purple_request_fields_get_field(fields, "contact_video");
+	if (f && purple_request_field_bool_get_value(f))
+		mask |= SILC_ATTRIBUTE_CONTACT_VIDEO;
+	if (mask)
+		silc_client_attribute_add(client, conn,
+					  SILC_ATTRIBUTE_PREFERRED_CONTACT,
+					  SILC_32_TO_PTR(mask),
+					  sizeof(SilcUInt32));
+
+	/* Set status text */
+	val = NULL;
+	f = purple_request_fields_get_field(fields, "status_text");
+	if (f)
+		val = purple_request_field_string_get_value(f);
+	if (val && *val)
+		silc_client_attribute_add(client, conn,
+					  SILC_ATTRIBUTE_STATUS_FREETEXT,
+					  (void *)val, strlen(val));
+
+	/* Set vcard */
+	val = NULL;
+	f = purple_request_fields_get_field(fields, "vcard");
+	if (f)
+		val = purple_request_field_string_get_value(f);
+	if (val && *val) {
+		purple_account_set_string(sg->account, "vcard", val);
+		tmp = silc_file_readfile(val, &tmp_len);
+		if (tmp) {
+			tmp[tmp_len] = 0;
+			if (silc_vcard_decode((unsigned char *)tmp, tmp_len, &vcard))
+				silc_client_attribute_add(client, conn,
+							  SILC_ATTRIBUTE_USER_INFO,
+							  (void *)&vcard,
+							  sizeof(vcard));
+		}
+		silc_vcard_free(&vcard);
+		silc_free(tmp);
+	} else {
+		purple_account_set_string(sg->account, "vcard", "");
+	}
+
+#ifdef HAVE_SYS_UTSNAME_H
+	/* Set device info */
+	f = purple_request_fields_get_field(fields, "device");
+	if (f && purple_request_field_bool_get_value(f)) {
+		struct utsname u;
+		if (!uname(&u)) {
+			dev.type = SILC_ATTRIBUTE_DEVICE_COMPUTER;
+			dev.version = u.release;
+			dev.model = u.sysname;
+			silc_client_attribute_add(client, conn,
+						  SILC_ATTRIBUTE_DEVICE_INFO,
+						  (void *)&dev, sizeof(dev));
+		}
+	}
+#endif
+
+	/* Set timezone */
+	val = NULL;
+	f = purple_request_fields_get_field(fields, "timezone");
+	if (f)
+		val = purple_request_field_string_get_value(f);
+	if (val && *val)
+		silc_client_attribute_add(client, conn,
+					  SILC_ATTRIBUTE_TIMEZONE,
+					  (void *)val, strlen(val));
+}
+
+static void
+silcpurple_attrs(PurplePluginAction *action)
+{
+	PurpleConnection *gc = (PurpleConnection *) action->context;
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *g;
+	PurpleRequestField *f;
+	SilcHashTable attrs;
+	SilcAttributePayload attr;
+	gboolean mnormal = TRUE, mhappy = FALSE, msad = FALSE,
+		mangry = FALSE, mjealous = FALSE, mashamed = FALSE,
+		minvincible = FALSE, minlove = FALSE, msleepy = FALSE,
+		mbored = FALSE, mexcited = FALSE, manxious = FALSE;
+	gboolean cemail = FALSE, ccall = FALSE, csms = FALSE,
+		cmms = FALSE, cchat = TRUE, cvideo = FALSE;
+	gboolean device = TRUE;
+	char status[1024];
+
+	sg = gc->proto_data;
+	if (!sg)
+		return;
+
+	memset(status, 0, sizeof(status));
+
+	attrs = silc_client_attributes_get(client, conn);
+	if (attrs) {
+		if (silc_hash_table_find(attrs,
+					 SILC_32_TO_PTR(SILC_ATTRIBUTE_STATUS_MOOD),
+					 NULL, (void *)&attr)) {
+			SilcUInt32 mood = 0;
+			silc_attribute_get_object(attr, &mood, sizeof(mood));
+			mnormal = !mood;
+			mhappy = (mood & SILC_ATTRIBUTE_MOOD_HAPPY);
+			msad = (mood & SILC_ATTRIBUTE_MOOD_SAD);
+			mangry = (mood & SILC_ATTRIBUTE_MOOD_ANGRY);
+			mjealous = (mood & SILC_ATTRIBUTE_MOOD_JEALOUS);
+			mashamed = (mood & SILC_ATTRIBUTE_MOOD_ASHAMED);
+			minvincible = (mood & SILC_ATTRIBUTE_MOOD_INVINCIBLE);
+			minlove = (mood & SILC_ATTRIBUTE_MOOD_INLOVE);
+			msleepy = (mood & SILC_ATTRIBUTE_MOOD_SLEEPY);
+			mbored = (mood & SILC_ATTRIBUTE_MOOD_BORED);
+			mexcited = (mood & SILC_ATTRIBUTE_MOOD_EXCITED);
+			manxious = (mood & SILC_ATTRIBUTE_MOOD_ANXIOUS);
+		}
+
+		if (silc_hash_table_find(attrs,
+					 SILC_32_TO_PTR(SILC_ATTRIBUTE_PREFERRED_CONTACT),
+					 NULL, (void *)&attr)) {
+			SilcUInt32 contact = 0;
+			silc_attribute_get_object(attr, &contact, sizeof(contact));
+			cemail = (contact & SILC_ATTRIBUTE_CONTACT_EMAIL);
+			ccall = (contact & SILC_ATTRIBUTE_CONTACT_CALL);
+			csms = (contact & SILC_ATTRIBUTE_CONTACT_SMS);
+			cmms = (contact & SILC_ATTRIBUTE_CONTACT_MMS);
+			cchat = (contact & SILC_ATTRIBUTE_CONTACT_CHAT);
+			cvideo = (contact & SILC_ATTRIBUTE_CONTACT_VIDEO);
+		}
+
+		if (silc_hash_table_find(attrs,
+					 SILC_32_TO_PTR(SILC_ATTRIBUTE_STATUS_FREETEXT),
+					 NULL, (void *)&attr))
+			silc_attribute_get_object(attr, &status, sizeof(status));
+
+		if (!silc_hash_table_find(attrs,
+					  SILC_32_TO_PTR(SILC_ATTRIBUTE_DEVICE_INFO),
+					  NULL, (void *)&attr))
+			device = FALSE;
+	}
+
+	fields = purple_request_fields_new();
+
+	g = purple_request_field_group_new(NULL);
+	f = purple_request_field_label_new("l3", _("Your Current Mood"));
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_bool_new("mood_normal", _("Normal"), mnormal);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_bool_new("mood_happy", _("Happy"), mhappy);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_bool_new("mood_sad", _("Sad"), msad);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_bool_new("mood_angry", _("Angry"), mangry);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_bool_new("mood_jealous", _("Jealous"), mjealous);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_bool_new("mood_ashamed", _("Ashamed"), mashamed);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_bool_new("mood_invincible", _("Invincible"), minvincible);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_bool_new("mood_inlove", _("In love"), minlove);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_bool_new("mood_sleepy", _("Sleepy"), msleepy);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_bool_new("mood_bored", _("Bored"), mbored);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_bool_new("mood_excited", _("Excited"), mexcited);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_bool_new("mood_anxious", _("Anxious"), manxious);
+	purple_request_field_group_add_field(g, f);
+
+	f = purple_request_field_label_new("l4", _("\nYour Preferred Contact Methods"));
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_bool_new("contact_chat", _("Chat"), cchat);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_bool_new("contact_email", _("E-mail"), cemail);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_bool_new("contact_call", _("Phone"), ccall);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_bool_new("contact_sms", _("SMS"), csms);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_bool_new("contact_mms", _("MMS"), cmms);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_bool_new("contact_video", _("Video conferencing"), cvideo);
+	purple_request_field_group_add_field(g, f);
+	purple_request_fields_add_group(fields, g);
+
+	g = purple_request_field_group_new(NULL);
+	f = purple_request_field_string_new("status_text", _("Your Current Status"),
+					  status[0] ? status : NULL, TRUE);
+	purple_request_field_group_add_field(g, f);
+	purple_request_fields_add_group(fields, g);
+
+	g = purple_request_field_group_new(NULL);
+#if 0
+	f = purple_request_field_label_new("l2", _("Online Services"));
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_bool_new("services",
+					_("Let others see what services you are using"),
+					TRUE);
+	purple_request_field_group_add_field(g, f);
+#endif
+#ifdef HAVE_SYS_UTSNAME_H
+	f = purple_request_field_bool_new("device",
+					_("Let others see what computer you are using"),
+					device);
+	purple_request_field_group_add_field(g, f);
+#endif
+	purple_request_fields_add_group(fields, g);
+
+	g = purple_request_field_group_new(NULL);
+	f = purple_request_field_string_new("vcard", _("Your VCard File"),
+					  purple_account_get_string(sg->account, "vcard", ""),
+					  FALSE);
+	purple_request_field_group_add_field(g, f);
+#ifdef _WIN32
+	f = purple_request_field_string_new("timezone", _("Timezone"), _tzname[0], FALSE);
+#else
+	f = purple_request_field_string_new("timezone", _("Timezone"), tzname[0], FALSE);
+#endif
+	purple_request_field_group_add_field(g, f);
+	purple_request_fields_add_group(fields, g);
+
+	purple_request_fields(gc, _("User Online Status Attributes"),
+			    _("User Online Status Attributes"),
+			    _("You can let other users see your online status information "
+			      "and your personal information. Please fill the information "
+			      "you would like other users to see about yourself."),
+			    fields,
+			    _("OK"), G_CALLBACK(silcpurple_attrs_cb),
+			    _("Cancel"), G_CALLBACK(silcpurple_attrs_cancel),
+				gc->account, NULL, NULL, gc);
+}
+
+static void
+silcpurple_detach(PurplePluginAction *action)
+{
+	PurpleConnection *gc = (PurpleConnection *) action->context;
+	SilcPurple sg;
+
+	if (!gc)
+		return;
+	sg = gc->proto_data;
+	if (!sg)
+		return;
+
+	/* Call DETACH */
+	silc_client_command_call(sg->client, sg->conn, "DETACH");
+	sg->detaching = TRUE;
+}
+
+static void
+silcpurple_view_motd(PurplePluginAction *action)
+{
+	PurpleConnection *gc = (PurpleConnection *) action->context;
+	SilcPurple sg;
+	char *tmp;
+
+	if (!gc)
+		return;
+	sg = gc->proto_data;
+	if (!sg)
+		return;
+
+	if (!sg->motd) {
+		purple_notify_error(
+		     gc, _("Message of the Day"), _("No Message of the Day available"),
+		     _("There is no Message of the Day associated with this connection"));
+		return;
+	}
+
+	tmp = g_markup_escape_text(sg->motd, -1);
+	purple_notify_formatted(gc, NULL, _("Message of the Day"), NULL,
+			      tmp, NULL, NULL);
+	g_free(tmp);
+}
+
+static void
+silcpurple_create_keypair_cancel(PurpleConnection *gc, PurpleRequestFields *fields)
+{
+	/* Nothing */
+}
+
+static void
+silcpurple_create_keypair_cb(PurpleConnection *gc, PurpleRequestFields *fields)
+{
+	SilcPurple sg = gc->proto_data;
+	PurpleRequestField *f;
+	const char *val, *pkfile = NULL, *prfile = NULL;
+	const char *pass1 = NULL, *pass2 = NULL, *un = NULL, *hn = NULL;
+	const char *rn = NULL, *e = NULL, *o = NULL, *c = NULL;
+	char *identifier;
+	int keylen = SILCPURPLE_DEF_PKCS_LEN;
+	SilcPublicKey public_key;
+
+	sg = gc->proto_data;
+	if (!sg)
+		return;
+
+	val = NULL;
+	f = purple_request_fields_get_field(fields, "pass1");
+	if (f)
+		val = purple_request_field_string_get_value(f);
+	if (val && *val)
+		pass1 = val;
+	else
+		pass1 = "";
+	val = NULL;
+	f = purple_request_fields_get_field(fields, "pass2");
+	if (f)
+		val = purple_request_field_string_get_value(f);
+	if (val && *val)
+		pass2 = val;
+	else
+		pass2 = "";
+
+	if (strcmp(pass1, pass2)) {
+		purple_notify_error(
+		     gc, _("Create New SILC Key Pair"), _("Passphrases do not match"), NULL);
+		return;
+	}
+
+	val = NULL;
+	f = purple_request_fields_get_field(fields, "key");
+	if (f)
+		val = purple_request_field_string_get_value(f);
+	if (val && *val)
+		keylen = atoi(val);
+	f = purple_request_fields_get_field(fields, "pkfile");
+	if (f)
+		pkfile = purple_request_field_string_get_value(f);
+	f = purple_request_fields_get_field(fields, "prfile");
+	if (f)
+		prfile = purple_request_field_string_get_value(f);
+
+	f = purple_request_fields_get_field(fields, "un");
+	if (f)
+		un = purple_request_field_string_get_value(f);
+	f = purple_request_fields_get_field(fields, "hn");
+	if (f)
+		hn = purple_request_field_string_get_value(f);
+	f = purple_request_fields_get_field(fields, "rn");
+	if (f)
+		rn = purple_request_field_string_get_value(f);
+	f = purple_request_fields_get_field(fields, "e");
+	if (f)
+		e = purple_request_field_string_get_value(f);
+	f = purple_request_fields_get_field(fields, "o");
+	if (f)
+		o = purple_request_field_string_get_value(f);
+	f = purple_request_fields_get_field(fields, "c");
+	if (f)
+		c = purple_request_field_string_get_value(f);
+
+	identifier = silc_pkcs_encode_identifier((char *)un, (char *)hn,
+						 (char *)rn, (char *)e, (char *)o, (char *)c);
+
+	/* Create the key pair */
+	if (!silc_create_key_pair(SILCPURPLE_DEF_PKCS, keylen, pkfile, prfile,
+				  identifier, pass1, NULL, &public_key, NULL,
+				  FALSE)) {
+		purple_notify_error(
+		     gc, _("Create New SILC Key Pair"), _("Key Pair Generation failed"), NULL);
+		return;
+	}
+
+	silcpurple_show_public_key(sg, NULL, public_key, NULL, NULL);
+
+	silc_pkcs_public_key_free(public_key);
+	silc_free(identifier);
+}
+
+static void
+silcpurple_create_keypair(PurplePluginAction *action)
+{
+	PurpleConnection *gc = (PurpleConnection *) action->context;
+	SilcPurple sg = gc->proto_data;
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *g;
+	PurpleRequestField *f;
+	const char *username, *realname;
+	char *hostname, **u;
+	char tmp[256], pkd[256], pkd2[256], prd[256], prd2[256];
+
+	username = purple_account_get_username(sg->account);
+	u = g_strsplit(username, "@", 2);
+	username = u[0];
+	realname = purple_account_get_user_info(sg->account);
+	hostname = silc_net_localhost();
+	g_snprintf(tmp, sizeof(tmp), "%s@%s", username, hostname);
+
+	g_snprintf(pkd2, sizeof(pkd2), "%s" G_DIR_SEPARATOR_S"public_key.pub", silcpurple_silcdir());
+	g_snprintf(prd2, sizeof(prd2), "%s" G_DIR_SEPARATOR_S"private_key.prv", silcpurple_silcdir());
+	g_snprintf(pkd, sizeof(pkd) - 1, "%s",
+		   purple_account_get_string(gc->account, "public-key", pkd2));
+	g_snprintf(prd, sizeof(prd) - 1, "%s",
+		   purple_account_get_string(gc->account, "private-key", prd2));
+
+	fields = purple_request_fields_new();
+
+	g = purple_request_field_group_new(NULL);
+	f = purple_request_field_string_new("key", _("Key length"), "2048", FALSE);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_string_new("pkfile", _("Public key file"), pkd, FALSE);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_string_new("prfile", _("Private key file"), prd, FALSE);
+	purple_request_field_group_add_field(g, f);
+	purple_request_fields_add_group(fields, g);
+
+	g = purple_request_field_group_new(NULL);
+	f = purple_request_field_string_new("un", _("Username"), username ? username : "", FALSE);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_string_new("hn", _("Hostname"), hostname ? hostname : "", FALSE);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_string_new("rn", _("Real name"), realname ? realname : "", FALSE);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_string_new("e", _("E-mail"), tmp, FALSE);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_string_new("o", _("Organization"), "", FALSE);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_string_new("c", _("Country"), "", FALSE);
+	purple_request_field_group_add_field(g, f);
+	purple_request_fields_add_group(fields, g);
+
+	g = purple_request_field_group_new(NULL);
+	f = purple_request_field_string_new("pass1", _("Passphrase"), "", FALSE);
+	purple_request_field_string_set_masked(f, TRUE);
+	purple_request_field_group_add_field(g, f);
+	f = purple_request_field_string_new("pass2", _("Passphrase (retype)"), "", FALSE);
+	purple_request_field_string_set_masked(f, TRUE);
+	purple_request_field_group_add_field(g, f);
+	purple_request_fields_add_group(fields, g);
+
+	purple_request_fields(gc, _("Create New SILC Key Pair"),
+			    _("Create New SILC Key Pair"), NULL, fields,
+			    _("Generate Key Pair"), G_CALLBACK(silcpurple_create_keypair_cb),
+			    _("Cancel"), G_CALLBACK(silcpurple_create_keypair_cancel),
+				gc->account, NULL, NULL, gc);
+
+	g_strfreev(u);
+	silc_free(hostname);
+}
+
+static void
+silcpurple_change_pass(PurplePluginAction *action)
+{
+	PurpleConnection *gc = (PurpleConnection *) action->context;
+	purple_account_request_change_password(purple_connection_get_account(gc));
+}
+
+static void
+silcpurple_change_passwd(PurpleConnection *gc, const char *old, const char *new)
+{
+        char prd[256];
+	g_snprintf(prd, sizeof(prd), "%s" G_DIR_SEPARATOR_S "private_key.pub", silcpurple_silcdir());
+	silc_change_private_key_passphrase(purple_account_get_string(gc->account,
+								   "private-key",
+								   prd), old, new);
+}
+
+static void
+silcpurple_show_set_info(PurplePluginAction *action)
+{
+	PurpleConnection *gc = (PurpleConnection *) action->context;
+	purple_account_request_change_user_info(purple_connection_get_account(gc));
+}
+
+static void
+silcpurple_set_info(PurpleConnection *gc, const char *text)
+{
+}
+
+static GList *
+silcpurple_actions(PurplePlugin *plugin, gpointer context)
+{
+	GList *list = NULL;
+	PurplePluginAction *act;
+
+	act = purple_plugin_action_new(_("Online Status"),
+			silcpurple_attrs);
+	list = g_list_append(list, act);
+
+	act = purple_plugin_action_new(_("Detach From Server"),
+			silcpurple_detach);
+	list = g_list_append(list, act);
+
+	act = purple_plugin_action_new(_("View Message of the Day"),
+			silcpurple_view_motd);
+	list = g_list_append(list, act);
+
+	act = purple_plugin_action_new(_("Create SILC Key Pair..."),
+			silcpurple_create_keypair);
+	list = g_list_append(list, act);
+
+	act = purple_plugin_action_new(_("Change Password..."),
+			silcpurple_change_pass);
+	list = g_list_append(list, act);
+
+	act = purple_plugin_action_new(_("Set User Info..."),
+			silcpurple_show_set_info);
+	list = g_list_append(list, act);
+
+	return list;
+}
+
+
+/******************************* IM Routines *********************************/
+
+typedef struct {
+	char *nick;
+	char *message;
+	SilcUInt32 message_len;
+	SilcMessageFlags flags;
+	PurpleMessageFlags gflags;
+} *SilcPurpleIM;
+
+static void
+silcpurple_send_im_resolved(SilcClient client,
+			  SilcClientConnection conn,
+			  SilcClientEntry *clients,
+			  SilcUInt32 clients_count,
+			  void *context)
+{
+	PurpleConnection *gc = client->application;
+	SilcPurple sg = gc->proto_data;
+	SilcPurpleIM im = context;
+	PurpleConversation *convo;
+	char tmp[256], *nickname = NULL;
+	SilcClientEntry client_entry;
+#ifdef HAVE_SILCMIME_H
+	SilcDList list;
+#endif
+
+	convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, im->nick,
+							sg->account);
+	if (!convo)
+		return;
+
+	if (!clients)
+		goto err;
+
+	if (clients_count > 1) {
+		silc_parse_userfqdn(im->nick, &nickname, NULL);
+
+		/* Find the correct one. The im->nick might be a formatted nick
+		   so this will find the correct one. */
+		clients = silc_client_get_clients_local(client, conn,
+							nickname, im->nick,
+							&clients_count);
+		if (!clients)
+			goto err;
+		client_entry = clients[0];
+		silc_free(clients);
+	} else {
+		client_entry = clients[0];
+	}
+
+#ifdef HAVE_SILCMIME_H
+	/* Check for images */
+	if (im->gflags & PURPLE_MESSAGE_IMAGES) {
+		list = silcpurple_image_message(im->message, (SilcUInt32 *)&im->flags);
+		if (list) {
+			/* Send one or more MIME message.  If more than one, they
+			   are MIME fragments due to over large message */
+			SilcBuffer buf;
+
+			silc_dlist_start(list);
+			while ((buf = silc_dlist_get(list)) != SILC_LIST_END)
+				silc_client_send_private_message(client, conn,
+								 client_entry, im->flags,
+								 buf->data, buf->len,
+								 TRUE);
+			silc_mime_partial_free(list);
+			purple_conv_im_write(PURPLE_CONV_IM(convo), conn->local_entry->nickname,
+				   im->message, 0, time(NULL));
+			goto out;
+		}
+	}
+#endif
+
+	/* Send the message */
+	silc_client_send_private_message(client, conn, client_entry, im->flags,
+					 (unsigned char *)im->message, im->message_len, TRUE);
+	purple_conv_im_write(PURPLE_CONV_IM(convo), conn->local_entry->nickname,
+			   im->message, 0, time(NULL));
+	goto out;
+
+ err:
+	g_snprintf(tmp, sizeof(tmp),
+		   _("User <I>%s</I> is not present in the network"), im->nick);
+	purple_conversation_write(convo, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
+
+ out:
+	g_free(im->nick);
+	g_free(im->message);
+	silc_free(im);
+	silc_free(nickname);
+}
+
+static int
+silcpurple_send_im(PurpleConnection *gc, const char *who, const char *message,
+		 PurpleMessageFlags flags)
+{
+	SilcPurple sg = gc->proto_data;
+	SilcClient client = sg->client;
+	SilcClientConnection conn = sg->conn;
+	SilcClientEntry *clients;
+	SilcUInt32 clients_count, mflags;
+	char *nickname, *msg, *tmp;
+	int ret = 0;
+	gboolean sign = purple_account_get_bool(sg->account, "sign-verify", FALSE);
+#ifdef HAVE_SILCMIME_H
+	SilcDList list;
+#endif
+
+	if (!who || !message)
+		return 0;
+
+	mflags = SILC_MESSAGE_FLAG_UTF8;
+
+	tmp = msg = purple_unescape_html(message);
+
+	if (!g_ascii_strncasecmp(msg, "/me ", 4)) {
+		msg += 4;
+		if (!*msg) {
+			g_free(tmp);
+			return 0;
+		}
+		mflags |= SILC_MESSAGE_FLAG_ACTION;
+	} else if (strlen(msg) > 1 && msg[0] == '/') {
+		if (!silc_client_command_call(client, conn, msg + 1))
+			purple_notify_error(gc, _("Call Command"), _("Cannot call command"),
+					_("Unknown command"));
+		g_free(tmp);
+		return 0;
+	}
+
+
+	if (!silc_parse_userfqdn(who, &nickname, NULL)) {
+		g_free(tmp);
+		return 0;
+	}
+
+	if (sign)
+		mflags |= SILC_MESSAGE_FLAG_SIGNED;
+
+	/* Find client entry */
+	clients = silc_client_get_clients_local(client, conn, nickname, who,
+						&clients_count);
+	if (!clients) {
+		/* Resolve unknown user */
+		SilcPurpleIM im = silc_calloc(1, sizeof(*im));
+		if (!im) {
+			g_free(tmp);
+			return 0;
+		}
+		im->nick = g_strdup(who);
+		im->message = g_strdup(message);
+		im->message_len = strlen(im->message);
+		im->flags = mflags;
+		im->gflags = flags;
+		silc_client_get_clients(client, conn, nickname, NULL,
+					silcpurple_send_im_resolved, im);
+		silc_free(nickname);
+		g_free(tmp);
+		return 0;
+	}
+
+#ifdef HAVE_SILCMIME_H
+	/* Check for images */
+	if (flags & PURPLE_MESSAGE_IMAGES) {
+		list = silcpurple_image_message(message, &mflags);
+		if (list) {
+			/* Send one or more MIME message.  If more than one, they
+			   are MIME fragments due to over large message */
+			SilcBuffer buf;
+
+			silc_dlist_start(list);
+			while ((buf = silc_dlist_get(list)) != SILC_LIST_END)
+				ret =
+			 	silc_client_send_private_message(client, conn,
+								 clients[0], mflags,
+								 buf->data, buf->len,
+								 TRUE);
+			silc_mime_partial_free(list);
+			g_free(tmp);
+			silc_free(nickname);
+			silc_free(clients);
+			return ret;
+		}
+	}
+#endif
+
+	/* Send private message directly */
+	ret = silc_client_send_private_message(client, conn, clients[0],
+					       mflags,
+					       (unsigned char *)msg,
+					       strlen(msg), TRUE);
+
+	g_free(tmp);
+	silc_free(nickname);
+	silc_free(clients);
+	return ret;
+}
+
+
+static GList *silcpurple_blist_node_menu(PurpleBlistNode *node) {
+	/* split this single menu building function back into the two
+	   original: one for buddies and one for chats */
+
+	if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
+		return silcpurple_chat_menu((PurpleChat *) node);
+	} else if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
+		return silcpurple_buddy_menu((PurpleBuddy *) node);
+	} else {
+		g_return_val_if_reached(NULL);
+	}
+}
+
+/********************************* Commands **********************************/
+
+static PurpleCmdRet silcpurple_cmd_chat_part(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	PurpleConnection *gc;
+	PurpleConversation *convo = conv;
+	int id = 0;
+
+	gc = purple_conversation_get_gc(conv);
+
+	if (gc == NULL)
+		return PURPLE_CMD_RET_FAILED;
+
+	if(args && args[0])
+		convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, args[0],
+									gc->account);
+
+	if (convo != NULL)
+		id = purple_conv_chat_get_id(PURPLE_CONV_CHAT(convo));
+
+	if (id == 0)
+		return PURPLE_CMD_RET_FAILED;
+
+	silcpurple_chat_leave(gc, id);
+
+	return PURPLE_CMD_RET_OK;
+
+}
+
+static PurpleCmdRet silcpurple_cmd_chat_topic(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	PurpleConnection *gc;
+	int id = 0;
+	char *buf, *tmp, *tmp2;
+	const char *topic;
+
+	gc = purple_conversation_get_gc(conv);
+	id = purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv));
+
+	if (gc == NULL || id == 0)
+		return PURPLE_CMD_RET_FAILED;
+
+	if (!args || !args[0]) {
+		topic = purple_conv_chat_get_topic (PURPLE_CONV_CHAT(conv));
+		if (topic) {
+			tmp = g_markup_escape_text(topic, -1);
+			tmp2 = purple_markup_linkify(tmp);
+			buf = g_strdup_printf(_("current topic is: %s"), tmp2);
+			g_free(tmp);
+			g_free(tmp2);
+		} else
+			buf = g_strdup(_("No topic is set"));
+		purple_conv_chat_write(PURPLE_CONV_CHAT(conv), gc->account->username, buf,
+							 PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NO_LOG, time(NULL));
+		g_free(buf);
+
+	}
+
+	if (args && args[0] && (strlen(args[0]) > 255)) {
+		*error = g_strdup(_("Topic too long"));
+		return PURPLE_CMD_RET_FAILED;
+	}
+
+	silcpurple_chat_set_topic(gc, id, args ? args[0] : NULL);
+
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet silcpurple_cmd_chat_join(PurpleConversation *conv,
+        const char *cmd, char **args, char **error, void *data)
+{
+	GHashTable *comp;
+
+	if(!args || !args[0])
+		return PURPLE_CMD_RET_FAILED;
+
+	comp = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
+
+	g_hash_table_replace(comp, "channel", args[0]);
+	if(args[1])
+		g_hash_table_replace(comp, "passphrase", args[1]);
+
+	silcpurple_chat_join(purple_conversation_get_gc(conv), comp);
+
+	g_hash_table_destroy(comp);
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet silcpurple_cmd_chat_list(PurpleConversation *conv,
+        const char *cmd, char **args, char **error, void *data)
+{
+	PurpleConnection *gc;
+	gc = purple_conversation_get_gc(conv);
+	purple_roomlist_show_with_account(purple_connection_get_account(gc));
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet silcpurple_cmd_whois(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	PurpleConnection *gc;
+
+	gc = purple_conversation_get_gc(conv);
+
+	if (gc == NULL)
+		return PURPLE_CMD_RET_FAILED;
+
+	silcpurple_get_info(gc, args[0]);
+
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet silcpurple_cmd_msg(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	int ret;
+	PurpleConnection *gc;
+
+	gc = purple_conversation_get_gc(conv);
+
+	if (gc == NULL)
+		return PURPLE_CMD_RET_FAILED;
+
+	ret = silcpurple_send_im(gc, args[0], args[1], PURPLE_MESSAGE_SEND);
+
+	if (ret)
+		return PURPLE_CMD_RET_OK;
+	else
+		return PURPLE_CMD_RET_FAILED;
+}
+
+static PurpleCmdRet silcpurple_cmd_query(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	int ret = 1;
+	PurpleConversation *convo;
+	PurpleConnection *gc;
+	PurpleAccount *account;
+
+	if (!args || !args[0]) {
+		*error = g_strdup(_("You must specify a nick"));
+		return PURPLE_CMD_RET_FAILED;
+	}
+
+	gc = purple_conversation_get_gc(conv);
+
+	if (gc == NULL)
+		return PURPLE_CMD_RET_FAILED;
+
+	account = purple_connection_get_account(gc);
+
+	convo = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, args[0]);
+
+	if (args[1]) {
+		ret = silcpurple_send_im(gc, args[0], args[1], PURPLE_MESSAGE_SEND);
+		purple_conv_im_write(PURPLE_CONV_IM(convo), purple_connection_get_display_name(gc),
+				args[1], PURPLE_MESSAGE_SEND, time(NULL));
+	}
+
+	if (ret)
+		return PURPLE_CMD_RET_OK;
+	else
+		return PURPLE_CMD_RET_FAILED;
+}
+
+static PurpleCmdRet silcpurple_cmd_motd(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	PurpleConnection *gc;
+	SilcPurple sg;
+	char *tmp;
+
+	gc = purple_conversation_get_gc(conv);
+
+	if (gc == NULL)
+		return PURPLE_CMD_RET_FAILED;
+
+	sg = gc->proto_data;
+
+	if (sg == NULL)
+		return PURPLE_CMD_RET_FAILED;
+
+	if (!sg->motd) {
+		*error = g_strdup(_("There is no Message of the Day associated with this connection"));
+		return PURPLE_CMD_RET_FAILED;
+	}
+
+	tmp = g_markup_escape_text(sg->motd, -1);
+	purple_notify_formatted(gc, NULL, _("Message of the Day"), NULL,
+			tmp, NULL, NULL);
+	g_free(tmp);
+
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet silcpurple_cmd_detach(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	gc = purple_conversation_get_gc(conv);
+
+	if (gc == NULL)
+		return PURPLE_CMD_RET_FAILED;
+
+	sg = gc->proto_data;
+
+	if (sg == NULL)
+		return PURPLE_CMD_RET_FAILED;
+
+	silc_client_command_call(sg->client, sg->conn, "DETACH");
+	sg->detaching = TRUE;
+
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet silcpurple_cmd_cmode(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	PurpleConnection *gc;
+	SilcPurple sg;
+	SilcChannelEntry channel;
+	char *silccmd, *silcargs, *msg, tmp[256];
+	const char *chname;
+
+	gc = purple_conversation_get_gc(conv);
+
+	if (gc == NULL || !args || gc->proto_data == NULL)
+		return PURPLE_CMD_RET_FAILED;
+
+	sg = gc->proto_data;
+
+	if (args[0])
+		chname = args[0];
+	else
+		chname = purple_conversation_get_name(conv);
+
+	if (!args[1]) {
+		channel = silc_client_get_channel(sg->client, sg->conn,
+										  (char *)chname);
+		if (!channel) {
+			*error = g_strdup_printf(_("channel %s not found"), chname);
+			return PURPLE_CMD_RET_FAILED;
+		}
+		if (channel->mode) {
+			silcpurple_get_chmode_string(channel->mode, tmp, sizeof(tmp));
+			msg = g_strdup_printf(_("channel modes for %s: %s"), chname, tmp);
+		} else {
+			msg = g_strdup_printf(_("no channel modes are set on %s"), chname);
+		}
+		purple_conv_chat_write(PURPLE_CONV_CHAT(conv), "",
+							 msg, PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NO_LOG, time(NULL));
+		g_free(msg);
+		return PURPLE_CMD_RET_OK;
+	}
+
+	silcargs = g_strjoinv(" ", args);
+	silccmd = g_strconcat(cmd, " ", args ? silcargs : NULL, NULL);
+	g_free(silcargs);
+	if (!silc_client_command_call(sg->client, sg->conn, silccmd)) {
+		g_free(silccmd);
+		*error = g_strdup_printf(_("Failed to set cmodes for %s"), args[0]);
+		return PURPLE_CMD_RET_FAILED;
+	}
+	g_free(silccmd);
+
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet silcpurple_cmd_generic(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	PurpleConnection *gc;
+	SilcPurple sg;
+	char *silccmd, *silcargs;
+
+	gc = purple_conversation_get_gc(conv);
+
+	if (gc == NULL)
+		return PURPLE_CMD_RET_FAILED;
+
+	sg = gc->proto_data;
+
+	if (sg == NULL)
+		return PURPLE_CMD_RET_FAILED;
+
+	silcargs = g_strjoinv(" ", args);
+	silccmd = g_strconcat(cmd, " ", args ? silcargs : NULL, NULL);
+	g_free(silcargs);
+	if (!silc_client_command_call(sg->client, sg->conn, silccmd)) {
+		g_free(silccmd);
+		*error = g_strdup_printf(_("Unknown command: %s, (may be a client bug)"), cmd);
+		return PURPLE_CMD_RET_FAILED;
+	}
+	g_free(silccmd);
+
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet silcpurple_cmd_quit(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	gc = purple_conversation_get_gc(conv);
+
+	if (gc == NULL)
+		return PURPLE_CMD_RET_FAILED;
+
+	sg = gc->proto_data;
+
+	if (sg == NULL)
+		return PURPLE_CMD_RET_FAILED;
+
+	silc_client_command_call(sg->client, sg->conn, NULL,
+				 "QUIT", (args && args[0]) ? args[0] : "Download this: " PURPLE_WEBSITE, NULL);
+
+	return PURPLE_CMD_RET_OK;
+}
+
+static PurpleCmdRet silcpurple_cmd_call(PurpleConversation *conv,
+		const char *cmd, char **args, char **error, void *data)
+{
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	gc = purple_conversation_get_gc(conv);
+
+	if (gc == NULL)
+		return PURPLE_CMD_RET_FAILED;
+
+	sg = gc->proto_data;
+
+	if (sg == NULL)
+		return PURPLE_CMD_RET_FAILED;
+
+	if (!silc_client_command_call(sg->client, sg->conn, args[0])) {
+		*error = g_strdup_printf(_("Unknown command: %s"), args[0]);
+		return PURPLE_CMD_RET_FAILED;
+	}
+
+	return PURPLE_CMD_RET_OK;
+}
+
+
+/************************** Plugin Initialization ****************************/
+
+static void
+silcpurple_register_commands(void)
+{
+	purple_cmd_register("part", "w", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT |
+			PURPLE_CMD_FLAG_PRPL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
+			"prpl-silc", silcpurple_cmd_chat_part, _("part [channel]:  Leave the chat"), NULL);
+	purple_cmd_register("leave", "w", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT |
+			PURPLE_CMD_FLAG_PRPL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
+			"prpl-silc", silcpurple_cmd_chat_part, _("leave [channel]:  Leave the chat"), NULL);
+	purple_cmd_register("topic", "s", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+			PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc",
+			silcpurple_cmd_chat_topic, _("topic [&lt;new topic&gt;]:  View or change the topic"), NULL);
+	purple_cmd_register("join", "ws", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT |
+			PURPLE_CMD_FLAG_PRPL_ONLY | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
+			"prpl-silc", silcpurple_cmd_chat_join,
+			_("join &lt;channel&gt; [&lt;password&gt;]:  Join a chat on this network"), NULL);
+	purple_cmd_register("list", "", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+			PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc",
+			silcpurple_cmd_chat_list, _("list:  List channels on this network"), NULL);
+	purple_cmd_register("whois", "w", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc",
+			silcpurple_cmd_whois, _("whois &lt;nick&gt;:  View nick's information"), NULL);
+	purple_cmd_register("msg", "ws", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc", silcpurple_cmd_msg,
+			_("msg &lt;nick&gt; &lt;message&gt;:  Send a private message to a user"), NULL);
+	purple_cmd_register("query", "ws", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+			PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcpurple_cmd_query,
+			_("query &lt;nick&gt; [&lt;message&gt;]:  Send a private message to a user"), NULL);
+	purple_cmd_register("motd", "", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+			PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcpurple_cmd_motd,
+			_("motd:  View the server's Message Of The Day"), NULL);
+	purple_cmd_register("detach", "", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc", silcpurple_cmd_detach,
+			_("detach:  Detach this session"), NULL);
+	purple_cmd_register("quit", "s", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+			PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcpurple_cmd_quit,
+			_("quit [message]:  Disconnect from the server, with an optional message"), NULL);
+	purple_cmd_register("call", "s", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc", silcpurple_cmd_call,
+			_("call &lt;command&gt;:  Call any silc client command"), NULL);
+	/* These below just get passed through for the silc client library to deal
+	 * with */
+	purple_cmd_register("kill", "ws", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+			PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcpurple_cmd_generic,
+			_("kill &lt;nick&gt; [-pubkey|&lt;reason&gt;]:  Kill nick"), NULL);
+	purple_cmd_register("nick", "w", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc", silcpurple_cmd_generic,
+			_("nick &lt;newnick&gt;:  Change your nickname"), NULL);
+	purple_cmd_register("whowas", "ww", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+			PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcpurple_cmd_generic,
+			_("whowas &lt;nick&gt;:  View nick's information"), NULL);
+	purple_cmd_register("cmode", "wws", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+			PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcpurple_cmd_cmode,
+			_("cmode &lt;channel&gt; [+|-&lt;modes&gt;] [arguments]:  Change or display channel modes"), NULL);
+	purple_cmd_register("cumode", "wws", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+			PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcpurple_cmd_generic,
+			_("cumode &lt;channel&gt; +|-&lt;modes&gt; &lt;nick&gt;:  Change nick's modes on channel"), NULL);
+	purple_cmd_register("umode", "w", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc", silcpurple_cmd_generic,
+			_("umode &lt;usermodes&gt;:  Set your modes in the network"), NULL);
+	purple_cmd_register("oper", "s", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc", silcpurple_cmd_generic,
+			_("oper &lt;nick&gt; [-pubkey]:  Get server operator privileges"), NULL);
+	purple_cmd_register("invite", "ws", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+			PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcpurple_cmd_generic,
+			_("invite &lt;channel&gt; [-|+]&lt;nick&gt;:  invite nick or add/remove from channel invite list"), NULL);
+	purple_cmd_register("kick", "wws", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+			PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcpurple_cmd_generic,
+			_("kick &lt;channel&gt; &lt;nick&gt; [comment]:  Kick client from channel"), NULL);
+	purple_cmd_register("info", "w", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+			PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcpurple_cmd_generic,
+			_("info [server]:  View server administrative details"), NULL);
+	purple_cmd_register("ban", "ww", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+			PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcpurple_cmd_generic,
+			_("ban [&lt;channel&gt; +|-&lt;nick&gt;]:  Ban client from channel"), NULL);
+	purple_cmd_register("getkey", "w", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc", silcpurple_cmd_generic,
+			_("getkey &lt;nick|server&gt;:  Retrieve client's or server's public key"), NULL);
+	purple_cmd_register("stats", "", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc", silcpurple_cmd_generic,
+			_("stats:  View server and network statistics"), NULL);
+	purple_cmd_register("ping", "", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc", silcpurple_cmd_generic,
+			_("ping:  Send PING to the connected server"), NULL);
+#if 0 /* Purple doesn't handle these yet */
+	purple_cmd_register("users", "w", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
+			"prpl-silc", silcpurple_cmd_users,
+			_("users &lt;channel&gt;:  List users in channel"));
+	purple_cmd_register("names", "ww", PURPLE_CMD_P_PRPL,
+			PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY |
+			PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, "prpl-silc", silcpurple_cmd_names,
+			_("names [-count|-ops|-halfops|-voices|-normal] &lt;channel(s)&gt;:  List specific users in channel(s)"));
+#endif
+}
+
+static PurpleWhiteboardPrplOps silcpurple_wb_ops =
+{
+	silcpurple_wb_start,
+	silcpurple_wb_end,
+	silcpurple_wb_get_dimensions,
+	silcpurple_wb_set_dimensions,
+	silcpurple_wb_get_brush,
+	silcpurple_wb_set_brush,
+	silcpurple_wb_send,
+	silcpurple_wb_clear,
+
+	/* padding */
+	NULL,
+	NULL,
+	NULL,
+	NULL
+};
+
+static PurplePluginProtocolInfo prpl_info =
+{
+#ifdef HAVE_SILCMIME_H
+	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME |
+	OPT_PROTO_PASSWORD_OPTIONAL | OPT_PROTO_IM_IMAGE |
+	OPT_PROTO_SLASH_COMMANDS_NATIVE,
+#else
+	OPT_PROTO_CHAT_TOPIC | OPT_PROTO_UNIQUE_CHATNAME |
+	OPT_PROTO_PASSWORD_OPTIONAL |
+	OPT_PROTO_SLASH_COMMANDS_NATIVE,
+#endif
+	NULL,						/* user_splits */
+	NULL,						/* protocol_options */
+#ifdef SILC_ATTRIBUTE_USER_ICON
+	{"jpeg,gif,png,bmp", 0, 0, 96, 96, 0, PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */
+#else
+	NO_BUDDY_ICONS,
+#endif
+	silcpurple_list_icon,			/* list_icon */
+	NULL,				/* list_emblems */
+	silcpurple_status_text,		/* status_text */
+	silcpurple_tooltip_text,		/* tooltip_text */
+	silcpurple_away_states,		/* away_states */
+	silcpurple_blist_node_menu,	/* blist_node_menu */
+	silcpurple_chat_info,			/* chat_info */
+	silcpurple_chat_info_defaults,/* chat_info_defaults */
+	silcpurple_login,				/* login */
+	silcpurple_close,				/* close */
+	silcpurple_send_im,			/* send_im */
+	silcpurple_set_info,			/* set_info */
+	NULL,						/* send_typing */
+	silcpurple_get_info,			/* get_info */
+	silcpurple_set_status,		/* set_status */
+	silcpurple_idle_set,			/* set_idle */
+	silcpurple_change_passwd,		/* change_passwd */
+	silcpurple_add_buddy,			/* add_buddy */
+	NULL,						/* add_buddies */
+	silcpurple_remove_buddy,		/* remove_buddy */
+	NULL,						/* remove_buddies */
+	NULL,						/* add_permit */
+	NULL,						/* add_deny */
+	NULL,						/* rem_permit */
+	NULL,						/* rem_deny */
+	NULL,						/* set_permit_deny */
+	silcpurple_chat_join,			/* join_chat */
+	NULL,						/* reject_chat */
+	silcpurple_get_chat_name,		/* get_chat_name */
+	silcpurple_chat_invite,		/* chat_invite */
+	silcpurple_chat_leave,		/* chat_leave */
+	NULL,						/* chat_whisper */
+	silcpurple_chat_send,			/* chat_send */
+	silcpurple_keepalive,			/* keepalive */
+	NULL,						/* register_user */
+	NULL,						/* get_cb_info */
+	NULL,						/* get_cb_away */
+	NULL,						/* alias_buddy */
+	NULL,						/* group_buddy */
+	NULL,						/* rename_group */
+	NULL,						/* buddy_free */
+	NULL,						/* convo_closed */
+	NULL,						/* normalize */
+#ifdef SILC_ATTRIBUTE_USER_ICON
+	silcpurple_buddy_set_icon,			/* set_buddy_icon */
+#else
+	NULL,
+#endif
+	NULL,						/* remove_group */
+	NULL,						/* get_cb_real_name */
+	silcpurple_chat_set_topic,	/* set_chat_topic */
+	NULL,						/* find_blist_chat */
+	silcpurple_roomlist_get_list,	/* roomlist_get_list */
+	silcpurple_roomlist_cancel,	/* roomlist_cancel */
+	NULL,						/* roomlist_expand_category */
+	NULL,						/* can_receive_file */
+	silcpurple_ftp_send_file,		/* send_file */
+	silcpurple_ftp_new_xfer,		/* new_xfer */
+	NULL,						/* offline_message */
+	&silcpurple_wb_ops,			/* whiteboard_prpl_ops */
+	NULL,                       /* send_raw */
+	NULL,                       /* roomlist_room_serialize */
+
+	/* padding */
+	NULL,
+	NULL,
+	NULL,
+	NULL
+};
+
+static PurplePluginInfo info =
+{
+	PURPLE_PLUGIN_MAGIC,
+	PURPLE_MAJOR_VERSION,
+	PURPLE_MINOR_VERSION,
+	PURPLE_PLUGIN_PROTOCOL,                             /**< type           */
+	NULL,                                             /**< ui_requirement */
+	0,                                                /**< flags          */
+	NULL,                                             /**< dependencies   */
+	PURPLE_PRIORITY_DEFAULT,                            /**< priority       */
+
+	"prpl-silc",                                      /**< id             */
+	"SILC",                                           /**< name           */
+	"1.0",                                            /**< version        */
+	/**  summary        */
+	N_("SILC Protocol Plugin"),
+	/**  description    */
+	N_("Secure Internet Live Conferencing (SILC) Protocol"),
+	"Pekka Riikonen",                                 /**< author         */
+	"http://silcnet.org/",                            /**< homepage       */
+
+	NULL,                                             /**< load           */
+	NULL,                                             /**< unload         */
+	NULL,                                             /**< destroy        */
+
+	NULL,                                             /**< ui_info        */
+	&prpl_info,                                       /**< extra_info     */
+	NULL,                                             /**< prefs_info     */
+	silcpurple_actions,
+
+	/* padding */
+	NULL,
+	NULL,
+	NULL,
+	NULL
+};
+
+static void
+init_plugin(PurplePlugin *plugin)
+{
+	PurpleAccountOption *option;
+	PurpleAccountUserSplit *split;
+	char tmp[256];
+	int i;
+	PurpleKeyValuePair *kvp;
+	GList *list = NULL;
+
+	silc_plugin = plugin;
+
+	split = purple_account_user_split_new(_("Network"), "silcnet.org", '@');
+	prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
+
+	/* Account options */
+	option = purple_account_option_string_new(_("Connect server"),
+						"server",
+						"silc.silcnet.org");
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+	option = purple_account_option_int_new(_("Port"), "port", 706);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+	g_snprintf(tmp, sizeof(tmp), "%s" G_DIR_SEPARATOR_S "public_key.pub", silcpurple_silcdir());
+	option = purple_account_option_string_new(_("Public Key file"),
+						"public-key", tmp);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+	g_snprintf(tmp, sizeof(tmp), "%s" G_DIR_SEPARATOR_S "private_key.prv", silcpurple_silcdir());
+	option = purple_account_option_string_new(_("Private Key file"),
+						"private-key", tmp);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	for (i = 0; silc_default_ciphers[i].name; i++) {
+		kvp = g_new0(PurpleKeyValuePair, 1);
+		kvp->key = g_strdup(silc_default_ciphers[i].name);
+		kvp->value = g_strdup(silc_default_ciphers[i].name);
+		list = g_list_append(list, kvp);
+	}
+	option = purple_account_option_list_new(_("Cipher"), "cipher", list);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	list = NULL;
+	for (i = 0; silc_default_hmacs[i].name; i++) {
+		kvp = g_new0(PurpleKeyValuePair, 1);
+		kvp->key = g_strdup(silc_default_hmacs[i].name);
+		kvp->value = g_strdup(silc_default_hmacs[i].name);
+		list = g_list_append(list, kvp);
+	}
+	option = purple_account_option_list_new(_("HMAC"), "hmac", list);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	option = purple_account_option_bool_new(_("Public key authentication"),
+					      "pubkey-auth", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+	option = purple_account_option_bool_new(_("Block IMs without Key Exchange"),
+					      "block-ims", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+	option = purple_account_option_bool_new(_("Block messages to whiteboard"),
+					      "block-wb", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+	option = purple_account_option_bool_new(_("Automatically open whiteboard"),
+					      "open-wb", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+	option = purple_account_option_bool_new(_("Digitally sign and verify all messages"),
+					      "sign-verify", FALSE);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+
+	purple_prefs_remove("/plugins/prpl/silc");
+
+	silcpurple_register_commands();
+
+#ifdef _WIN32
+	silc_net_win32_init();
+#endif
+}
+
+PURPLE_INIT_PLUGIN(silc, init_plugin, info);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/silc10/silcpurple.h	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,173 @@
+/*
+
+  silcpurple.h
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  Copyright (C) 2004 Pekka Riikonen
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; version 2 of the License.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+*/
+
+#ifndef SILCPURPLE_H
+#define SILCPURPLE_H
+
+/* Purple includes */
+#include "internal.h"
+#include "account.h"
+#include "accountopt.h"
+#include "cmds.h"
+#include "conversation.h"
+#include "debug.h"
+#include "ft.h"
+#include "notify.h"
+#include "prpl.h"
+#include "request.h"
+#include "roomlist.h"
+#include "server.h"
+#include "util.h"
+
+/* Default public and private key file names */
+#define SILCPURPLE_PUBLIC_KEY_NAME "public_key.pub"
+#define SILCPURPLE_PRIVATE_KEY_NAME "private_key.prv"
+
+/* Default settings for creating key pair */
+#define SILCPURPLE_DEF_PKCS "rsa"
+#define SILCPURPLE_DEF_PKCS_LEN 2048
+
+#define SILCPURPLE_PRVGRP 0x001fffff
+
+/* Status IDs */
+#define SILCPURPLE_STATUS_ID_OFFLINE	"offline"
+#define SILCPURPLE_STATUS_ID_AVAILABLE "available"
+#define SILCPURPLE_STATUS_ID_HYPER	"hyper"
+#define SILCPURPLE_STATUS_ID_AWAY		"away"
+#define SILCPURPLE_STATUS_ID_BUSY		"busy"
+#define SILCPURPLE_STATUS_ID_INDISPOSED "indisposed"
+#define SILCPURPLE_STATUS_ID_PAGE		"page"
+
+typedef struct {
+	unsigned long id;
+	const char *channel;
+	unsigned long chid;
+	const char *parentch;
+	SilcChannelPrivateKey key;
+} *SilcPurplePrvgrp;
+
+/* The SILC Purple plugin context */
+typedef struct SilcPurpleStruct {
+	SilcClient client;
+	SilcClientConnection conn;
+
+	guint scheduler;
+	PurpleConnection *gc;
+	PurpleAccount *account;
+	unsigned long channel_ids;
+	GList *grps;
+
+	char *motd;
+	PurpleRoomlist *roomlist;
+#ifdef HAVE_SILCMIME_H
+	SilcMimeAssembler mimeass;
+#endif
+	unsigned int detaching            : 1;
+	unsigned int resuming             : 1;
+	unsigned int roomlist_canceled    : 1;
+	unsigned int chpk                 : 1;
+} *SilcPurple;
+
+
+gboolean silcpurple_check_silc_dir(PurpleConnection *gc);
+void silcpurple_chat_join_done(SilcClient client,
+			     SilcClientConnection conn,
+			     SilcClientEntry *clients,
+			     SilcUInt32 clients_count,
+			     void *context);
+const char *silcpurple_silcdir(void);
+const char *silcpurple_session_file(const char *account);
+void silcpurple_verify_public_key(SilcClient client, SilcClientConnection conn,
+				const char *name, SilcSocketType conn_type,
+				unsigned char *pk, SilcUInt32 pk_len,
+				SilcSKEPKType pk_type,
+				SilcVerifyPublicKey completion, void *context);
+GList *silcpurple_buddy_menu(PurpleBuddy *buddy);
+void silcpurple_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group);
+void silcpurple_send_buddylist(PurpleConnection *gc);
+void silcpurple_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group);
+void silcpurple_buddy_keyagr_request(SilcClient client,
+				   SilcClientConnection conn,
+				   SilcClientEntry client_entry,
+				   const char *hostname, SilcUInt16 port);
+void silcpurple_idle_set(PurpleConnection *gc, int idle);
+void silcpurple_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full);
+char *silcpurple_status_text(PurpleBuddy *b);
+gboolean silcpurple_ip_is_private(const char *ip);
+void silcpurple_ftp_send_file(PurpleConnection *gc, const char *name, const char *file);
+PurpleXfer *silcpurple_ftp_new_xfer(PurpleConnection *gc, const char *name);
+void silcpurple_ftp_request(SilcClient client, SilcClientConnection conn,
+			  SilcClientEntry client_entry, SilcUInt32 session_id,
+			  const char *hostname, SilcUInt16 port);
+void silcpurple_show_public_key(SilcPurple sg,
+			      const char *name, SilcPublicKey public_key,
+			      GCallback callback, void *context);
+void silcpurple_get_info(PurpleConnection *gc, const char *who);
+SilcAttributePayload
+silcpurple_get_attr(SilcDList attrs, SilcAttribute attribute);
+void silcpurple_get_umode_string(SilcUInt32 mode, char *buf,
+			       SilcUInt32 buf_size);
+void silcpurple_get_chmode_string(SilcUInt32 mode, char *buf,
+				SilcUInt32 buf_size);
+void silcpurple_get_chumode_string(SilcUInt32 mode, char *buf,
+				 SilcUInt32 buf_size);
+GList *silcpurple_chat_info(PurpleConnection *gc);
+GHashTable *silcpurple_chat_info_defaults(PurpleConnection *gc, const char *chat_name);
+GList *silcpurple_chat_menu(PurpleChat *);
+void silcpurple_chat_join(PurpleConnection *gc, GHashTable *data);
+char *silcpurple_get_chat_name(GHashTable *data);
+void silcpurple_chat_invite(PurpleConnection *gc, int id, const char *msg,
+			  const char *name);
+void silcpurple_chat_leave(PurpleConnection *gc, int id);
+int silcpurple_chat_send(PurpleConnection *gc, int id, const char *msg, PurpleMessageFlags flags);
+void silcpurple_chat_set_topic(PurpleConnection *gc, int id, const char *topic);
+PurpleRoomlist *silcpurple_roomlist_get_list(PurpleConnection *gc);
+void silcpurple_roomlist_cancel(PurpleRoomlist *list);
+void silcpurple_chat_chauth_show(SilcPurple sg, SilcChannelEntry channel,
+			       SilcBuffer channel_pubkeys);
+void silcpurple_parse_attrs(SilcDList attrs, char **moodstr, char **statusstr,
+					 char **contactstr, char **langstr, char **devicestr,
+					 char **tzstr, char **geostr);
+#ifdef SILC_ATTRIBUTE_USER_ICON
+void silcpurple_buddy_set_icon(PurpleConnection *gc, PurpleStoredImage *img);
+#endif
+#ifdef HAVE_SILCMIME_H
+char *silcpurple_file2mime(const char *filename);
+SilcDList silcpurple_image_message(const char *msg, SilcUInt32 *mflags);
+#endif
+
+#ifdef _WIN32
+typedef int uid_t;
+
+struct passwd {
+	char	*pw_name;	/* user name */
+	char	*pw_passwd;	/* user password */
+	int		pw_uid;		/* user id */
+	int		pw_gid;		/* group id */
+	char	*pw_gecos;	/* real name */
+	char	*pw_dir;	/* home directory */
+	char	*pw_shell;	/* shell program */
+};
+
+struct passwd *getpwuid(int uid);
+int getuid(void);
+int geteuid(void);
+#endif
+
+#endif /* SILCPURPLE_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/silc10/util.c	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,771 @@
+/*
+
+  silcpurple_util.c
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  Copyright (C) 2004 - 2005 Pekka Riikonen
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; version 2 of the License.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+*/
+
+#include "silcincludes.h"
+#include "silcclient.h"
+#include "silcpurple.h"
+#include "imgstore.h"
+
+/**************************** Utility Routines *******************************/
+
+static char str[256], str2[256];
+
+const char *silcpurple_silcdir(void)
+{
+	const char *hd = purple_home_dir();
+	memset(str, 0, sizeof(str));
+	g_snprintf(str, sizeof(str) - 1, "%s" G_DIR_SEPARATOR_S ".silc", hd ? hd : "/tmp");
+	return (const char *)str;
+}
+
+const char *silcpurple_session_file(const char *account)
+{
+	memset(str2, 0, sizeof(str2));
+	g_snprintf(str2, sizeof(str2) - 1, "%s" G_DIR_SEPARATOR_S "%s_session",
+		   silcpurple_silcdir(), account);
+	return (const char *)str2;
+}
+
+gboolean silcpurple_ip_is_private(const char *ip)
+{
+	if (silc_net_is_ip4(ip)) {
+		if (!strncmp(ip, "10.", 3)) {
+			return TRUE;
+		} else if (!strncmp(ip, "172.", 4) && strlen(ip) > 6) {
+			char tmp[3];
+			int s;
+			memset(tmp, 0, sizeof(tmp));
+			strncpy(tmp, ip + 4, 2);
+			s = atoi(tmp);
+			if (s >= 16 && s <= 31)
+				return TRUE;
+		} else if (!strncmp(ip, "192.168.", 8)) {
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+/* This checks stats for various SILC files and directories. First it
+   checks if ~/.silc directory exist and is owned by the correct user. If
+   it doesn't exist, it will create the directory. After that it checks if
+   user's Public and Private key files exists and creates them if needed. */
+
+gboolean silcpurple_check_silc_dir(PurpleConnection *gc)
+{
+	char filename[256], file_public_key[256], file_private_key[256];
+	char servfilename[256], clientfilename[256], friendsfilename[256];
+	char pkd[256], prd[256];
+	struct stat st;
+	struct passwd *pw;
+	int fd;
+
+	pw = getpwuid(getuid());
+	if (!pw) {
+		purple_debug_error("silc", "silc: %s\n", strerror(errno));
+		return FALSE;
+	}
+
+	g_snprintf(filename, sizeof(filename) - 1, "%s", silcpurple_silcdir());
+	g_snprintf(servfilename, sizeof(servfilename) - 1, "%s" G_DIR_SEPARATOR_S "serverkeys",
+		   silcpurple_silcdir());
+	g_snprintf(clientfilename, sizeof(clientfilename) - 1, "%s" G_DIR_SEPARATOR_S "clientkeys",
+		   silcpurple_silcdir());
+	g_snprintf(friendsfilename, sizeof(friendsfilename) - 1, "%s" G_DIR_SEPARATOR_S "friends",
+		   silcpurple_silcdir());
+
+	/*
+	 * Check ~/.silc directory
+	 */
+	if ((g_stat(filename, &st)) == -1) {
+		/* If dir doesn't exist */
+		if (errno == ENOENT) {
+			if (pw->pw_uid == geteuid()) {
+				if ((g_mkdir(filename, 0755)) == -1) {
+					purple_debug_error("silc", "Couldn't create '%s' directory\n", filename);
+					return FALSE;
+				}
+			} else {
+				purple_debug_error("silc", "Couldn't create '%s' directory due to a wrong uid!\n",
+					filename);
+				return FALSE;
+			}
+		} else {
+			purple_debug_error("silc", "Couldn't stat '%s' directory, error: %s\n", filename, strerror(errno));
+			return FALSE;
+		}
+	} else {
+#ifndef _WIN32
+		/* Check the owner of the dir */
+		if (st.st_uid != 0 && st.st_uid != pw->pw_uid) {
+			purple_debug_error("silc", "You don't seem to own '%s' directory\n",
+				filename);
+			return FALSE;
+		}
+#endif
+	}
+
+	/*
+	 * Check ~./silc/serverkeys directory
+	 */
+	if ((g_stat(servfilename, &st)) == -1) {
+		/* If dir doesn't exist */
+		if (errno == ENOENT) {
+			if (pw->pw_uid == geteuid()) {
+				if ((g_mkdir(servfilename, 0755)) == -1) {
+					purple_debug_error("silc", "Couldn't create '%s' directory\n", servfilename);
+					return FALSE;
+				}
+			} else {
+				purple_debug_error("silc", "Couldn't create '%s' directory due to a wrong uid!\n",
+					servfilename);
+				return FALSE;
+			}
+		} else {
+			purple_debug_error("silc", "Couldn't stat '%s' directory, error: %s\n",
+							 servfilename, strerror(errno));
+			return FALSE;
+		}
+	}
+
+	/*
+	 * Check ~./silc/clientkeys directory
+	 */
+	if ((g_stat(clientfilename, &st)) == -1) {
+		/* If dir doesn't exist */
+		if (errno == ENOENT) {
+			if (pw->pw_uid == geteuid()) {
+				if ((g_mkdir(clientfilename, 0755)) == -1) {
+					purple_debug_error("silc", "Couldn't create '%s' directory\n", clientfilename);
+					return FALSE;
+				}
+			} else {
+				purple_debug_error("silc", "Couldn't create '%s' directory due to a wrong uid!\n",
+					clientfilename);
+				return FALSE;
+			}
+		} else {
+			purple_debug_error("silc", "Couldn't stat '%s' directory, error: %s\n",
+							 clientfilename, strerror(errno));
+			return FALSE;
+		}
+	}
+
+	/*
+	 * Check ~./silc/friends directory
+	 */
+	if ((g_stat(friendsfilename, &st)) == -1) {
+		/* If dir doesn't exist */
+		if (errno == ENOENT) {
+			if (pw->pw_uid == geteuid()) {
+				if ((g_mkdir(friendsfilename, 0755)) == -1) {
+					purple_debug_error("silc", "Couldn't create '%s' directory\n", friendsfilename);
+					return FALSE;
+				}
+			} else {
+				purple_debug_error("silc", "Couldn't create '%s' directory due to a wrong uid!\n",
+					friendsfilename);
+				return FALSE;
+			}
+		} else {
+			purple_debug_error("silc", "Couldn't stat '%s' directory, error: %s\n",
+							 friendsfilename, strerror(errno));
+			return FALSE;
+		}
+	}
+
+	/*
+	 * Check Public and Private keys
+	 */
+	g_snprintf(pkd, sizeof(pkd), "%s" G_DIR_SEPARATOR_S "public_key.pub", silcpurple_silcdir());
+	g_snprintf(prd, sizeof(prd), "%s" G_DIR_SEPARATOR_S "private_key.prv", silcpurple_silcdir());
+	g_snprintf(file_public_key, sizeof(file_public_key) - 1, "%s",
+		   purple_account_get_string(gc->account, "public-key", pkd));
+	g_snprintf(file_private_key, sizeof(file_public_key) - 1, "%s",
+		   purple_account_get_string(gc->account, "private-key", prd));
+
+	if ((g_stat(file_public_key, &st)) == -1) {
+		/* If file doesn't exist */
+		if (errno == ENOENT) {
+			purple_connection_update_progress(gc, _("Creating SILC key pair..."), 1, 5);
+			if (!silc_create_key_pair(SILCPURPLE_DEF_PKCS,
+					     SILCPURPLE_DEF_PKCS_LEN,
+					     file_public_key, file_private_key, NULL,
+					     (gc->password == NULL) ? "" : gc->password,
+						 NULL, NULL, NULL, FALSE)) {
+				purple_debug_error("silc", "Couldn't create key pair\n");
+				return FALSE;
+			}
+
+			if ((g_stat(file_public_key, &st)) == -1) {
+				purple_debug_error("silc", "Couldn't stat '%s' public key, error: %s\n",
+					file_public_key, strerror(errno));
+				return FALSE;
+			}
+		} else {
+			purple_debug_error("silc", "Couldn't stat '%s' public key, error: %s\n",
+							 file_public_key, strerror(errno));
+			return FALSE;
+		}
+	}
+
+#ifndef _WIN32
+	/* Check the owner of the public key */
+	if (st.st_uid != 0 && st.st_uid != pw->pw_uid) {
+		purple_debug_error("silc", "You don't seem to own your public key!?\n");
+		return FALSE;
+	}
+#endif
+
+	if ((fd = g_open(file_private_key, O_RDONLY, 0)) != -1) {
+		if ((fstat(fd, &st)) == -1) {
+			purple_debug_error("silc", "Couldn't stat '%s' private key, error: %s\n",
+							 file_private_key, strerror(errno));
+			close(fd);
+			return FALSE;
+		}
+	} else if ((g_stat(file_private_key, &st)) == -1) {
+		/* If file doesn't exist */
+		if (errno == ENOENT) {
+			purple_connection_update_progress(gc, _("Creating SILC key pair..."), 1, 5);
+			if (!silc_create_key_pair(SILCPURPLE_DEF_PKCS,
+					     SILCPURPLE_DEF_PKCS_LEN,
+					     file_public_key, file_private_key, NULL,
+					     (gc->password == NULL) ? "" : gc->password,
+						 NULL, NULL, NULL, FALSE)) {
+				purple_debug_error("silc", "Couldn't create key pair\n");
+				return FALSE;
+			}
+
+			if ((fd = g_open(file_private_key, O_RDONLY, 0)) != -1) {
+				if ((fstat(fd, &st)) == -1) {
+					purple_debug_error("silc", "Couldn't stat '%s' private key, error: %s\n",
+							 file_private_key, strerror(errno));
+					close(fd);
+					return FALSE;
+				}
+			}
+			/* This shouldn't really happen because silc_create_key_pair()
+			 * will set the permissions */
+			else if ((g_stat(file_private_key, &st)) == -1) {
+				purple_debug_error("silc", "Couldn't stat '%s' private key, error: %s\n",
+					file_private_key, strerror(errno));
+				return FALSE;
+			}
+		} else {
+			purple_debug_error("silc", "Couldn't stat '%s' private key, error: %s\n",
+							 file_private_key, strerror(errno));
+			return FALSE;
+		}
+	}
+
+#ifndef _WIN32
+	/* Check the owner of the private key */
+	if (st.st_uid != 0 && st.st_uid != pw->pw_uid) {
+		purple_debug_error("silc", "You don't seem to own your private key!?\n");
+		if (fd != -1)
+			close(fd);
+		return FALSE;
+	}
+
+	/* Check the permissions for the private key */
+	if ((st.st_mode & 0777) != 0600) {
+		purple_debug_warning("silc", "Wrong permissions in your private key file `%s'!\n"
+			"Trying to change them ...\n", file_private_key);
+		if ((fd == -1) || (fchmod(fd, S_IRUSR | S_IWUSR)) == -1) {
+			purple_debug_error("silc",
+				"Failed to change permissions for private key file!\n"
+				"Permissions for your private key file must be 0600.\n");
+			if (fd != -1)
+				close(fd);
+			return FALSE;
+		}
+		purple_debug_warning("silc", "Done.\n\n");
+	}
+#endif
+
+	if (fd != -1)
+		close(fd);
+
+	return TRUE;
+}
+
+#ifdef _WIN32
+struct passwd *getpwuid(uid_t uid) {
+	struct passwd *pwd = calloc(1, sizeof(struct passwd));
+	return pwd;
+}
+
+uid_t getuid() {
+	return 0;
+}
+
+uid_t geteuid() {
+	return 0;
+}
+#endif
+
+void silcpurple_show_public_key(SilcPurple sg,
+			      const char *name, SilcPublicKey public_key,
+			      GCallback callback, void *context)
+{
+	SilcPublicKeyIdentifier ident;
+	SilcPKCS pkcs;
+	char *fingerprint, *babbleprint;
+	unsigned char *pk;
+	SilcUInt32 pk_len, key_len = 0;
+	GString *s;
+	char *buf;
+
+	ident = silc_pkcs_decode_identifier(public_key->identifier);
+	if (!ident)
+		return;
+
+	pk = silc_pkcs_public_key_encode(public_key, &pk_len);
+	fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
+	babbleprint = silc_hash_babbleprint(NULL, pk, pk_len);
+
+	if (silc_pkcs_alloc((unsigned char *)public_key->name, &pkcs)) {
+		key_len = silc_pkcs_public_key_set(pkcs, public_key);
+		silc_pkcs_free(pkcs);
+	}
+
+	s = g_string_new("");
+	if (ident->realname)
+		/* Hint for translators: Please check the tabulator width here and in
+		   the next strings (short strings: 2 tabs, longer strings 1 tab,
+		   sum: 3 tabs or 24 characters) */
+		g_string_append_printf(s, _("Real Name: \t%s\n"), ident->realname);
+	if (ident->username)
+		g_string_append_printf(s, _("User Name: \t%s\n"), ident->username);
+	if (ident->email)
+		g_string_append_printf(s, _("E-Mail: \t\t%s\n"), ident->email);
+	if (ident->host)
+		g_string_append_printf(s, _("Host Name: \t%s\n"), ident->host);
+	if (ident->org)
+		g_string_append_printf(s, _("Organization: \t%s\n"), ident->org);
+	if (ident->country)
+		g_string_append_printf(s, _("Country: \t%s\n"), ident->country);
+	g_string_append_printf(s, _("Algorithm: \t%s\n"), public_key->name);
+	g_string_append_printf(s, _("Key Length: \t%d bits\n"), (int)key_len);
+	g_string_append_printf(s, "\n");
+	g_string_append_printf(s, _("Public Key Fingerprint:\n%s\n\n"), fingerprint);
+	g_string_append_printf(s, _("Public Key Babbleprint:\n%s"), babbleprint);
+
+	buf = g_string_free(s, FALSE);
+
+	purple_request_action(sg->gc, _("Public Key Information"),
+			    _("Public Key Information"),
+			    buf, 0, purple_connection_get_account(sg->gc),
+				NULL, NULL, context, 1, _("Close"), callback);
+
+	g_free(buf);
+	silc_free(fingerprint);
+	silc_free(babbleprint);
+	silc_free(pk);
+	silc_pkcs_free_identifier(ident);
+}
+
+SilcAttributePayload
+silcpurple_get_attr(SilcDList attrs, SilcAttribute attribute)
+{
+	SilcAttributePayload attr = NULL;
+
+	if (!attrs)
+		return NULL;
+
+	silc_dlist_start(attrs);
+	while ((attr = silc_dlist_get(attrs)) != SILC_LIST_END)
+		if (attribute == silc_attribute_get_attribute(attr))
+			break;
+
+	return attr;
+}
+
+void silcpurple_get_umode_string(SilcUInt32 mode, char *buf,
+			       SilcUInt32 buf_size)
+{
+	memset(buf, 0, buf_size);
+	if ((mode & SILC_UMODE_SERVER_OPERATOR) ||
+	    (mode & SILC_UMODE_ROUTER_OPERATOR)) {
+		strcat(buf, (mode & SILC_UMODE_SERVER_OPERATOR) ?
+		       "[server operator] " :
+		       (mode & SILC_UMODE_ROUTER_OPERATOR) ?
+		       "[SILC operator] " : "[unknown mode] ");
+	}
+	if (mode & SILC_UMODE_GONE)
+		strcat(buf, "[away] ");
+	if (mode & SILC_UMODE_INDISPOSED)
+		strcat(buf, "[indisposed] ");
+	if (mode & SILC_UMODE_BUSY)
+		strcat(buf, "[busy] ");
+	if (mode & SILC_UMODE_PAGE)
+		strcat(buf, "[wake me up] ");
+	if (mode & SILC_UMODE_HYPER)
+		strcat(buf, "[hyperactive] ");
+	if (mode & SILC_UMODE_ROBOT)
+		strcat(buf, "[robot] ");
+	if (mode & SILC_UMODE_ANONYMOUS)
+		strcat(buf, "[anonymous] ");
+	if (mode & SILC_UMODE_BLOCK_PRIVMSG)
+		strcat(buf, "[blocks private messages] ");
+	if (mode & SILC_UMODE_DETACHED)
+		strcat(buf, "[detached] ");
+	if (mode & SILC_UMODE_REJECT_WATCHING)
+		strcat(buf, "[rejects watching] ");
+	if (mode & SILC_UMODE_BLOCK_INVITE)
+		strcat(buf, "[blocks invites] ");
+}
+
+void silcpurple_get_chmode_string(SilcUInt32 mode, char *buf,
+				SilcUInt32 buf_size)
+{
+	memset(buf, 0, buf_size);
+	if (mode & SILC_CHANNEL_MODE_FOUNDER_AUTH)
+		strcat(buf, "[permanent] ");
+	if (mode & SILC_CHANNEL_MODE_PRIVATE)
+		strcat(buf, "[private] ");
+	if (mode & SILC_CHANNEL_MODE_SECRET)
+		strcat(buf, "[secret] ");
+	if (mode & SILC_CHANNEL_MODE_PRIVKEY)
+		strcat(buf, "[private key] ");
+	if (mode & SILC_CHANNEL_MODE_INVITE)
+		strcat(buf, "[invite only] ");
+	if (mode & SILC_CHANNEL_MODE_TOPIC)
+		strcat(buf, "[topic restricted] ");
+	if (mode & SILC_CHANNEL_MODE_ULIMIT)
+		strcat(buf, "[user count limit] ");
+	if (mode & SILC_CHANNEL_MODE_PASSPHRASE)
+		strcat(buf, "[passphrase auth] ");
+	if (mode & SILC_CHANNEL_MODE_CHANNEL_AUTH)
+		strcat(buf, "[public key auth] ");
+	if (mode & SILC_CHANNEL_MODE_SILENCE_USERS)
+		strcat(buf, "[users silenced] ");
+	if (mode & SILC_CHANNEL_MODE_SILENCE_OPERS)
+		strcat(buf, "[operators silenced] ");
+}
+
+void silcpurple_get_chumode_string(SilcUInt32 mode, char *buf,
+				 SilcUInt32 buf_size)
+{
+	memset(buf, 0, buf_size);
+	if (mode & SILC_CHANNEL_UMODE_CHANFO)
+		strcat(buf, "[founder] ");
+	if (mode & SILC_CHANNEL_UMODE_CHANOP)
+		strcat(buf, "[operator] ");
+	if (mode & SILC_CHANNEL_UMODE_BLOCK_MESSAGES)
+		strcat(buf, "[blocks messages] ");
+	if (mode & SILC_CHANNEL_UMODE_BLOCK_MESSAGES_USERS)
+		strcat(buf, "[blocks user messages] ");
+	if (mode & SILC_CHANNEL_UMODE_BLOCK_MESSAGES_ROBOTS)
+		strcat(buf, "[blocks robot messages] ");
+	if (mode & SILC_CHANNEL_UMODE_QUIET)
+		strcat(buf, "[quieted] ");
+}
+
+void
+silcpurple_parse_attrs(SilcDList attrs, char **moodstr, char **statusstr,
+					 char **contactstr, char **langstr, char **devicestr,
+					 char **tzstr, char **geostr)
+{
+	SilcAttributePayload attr;
+	SilcAttributeMood mood = 0;
+	SilcAttributeContact contact;
+	SilcAttributeObjDevice device;
+	SilcAttributeObjGeo geo;
+
+	char tmp[1024];
+	GString *s;
+
+	*moodstr = NULL;
+	*statusstr = NULL;
+	*contactstr = NULL;
+	*langstr = NULL;
+	*devicestr = NULL;
+	*tzstr = NULL;
+	*geostr = NULL;
+
+	if (!attrs)
+		return;
+
+	s = g_string_new("");
+	attr = silcpurple_get_attr(attrs, SILC_ATTRIBUTE_STATUS_MOOD);
+	if (attr && silc_attribute_get_object(attr, &mood, sizeof(mood))) {
+		if (mood & SILC_ATTRIBUTE_MOOD_HAPPY)
+			g_string_append_printf(s, "[%s] ", _("Happy"));
+		if (mood & SILC_ATTRIBUTE_MOOD_SAD)
+			g_string_append_printf(s, "[%s] ", _("Sad"));
+		if (mood & SILC_ATTRIBUTE_MOOD_ANGRY)
+			g_string_append_printf(s, "[%s] ", _("Angry"));
+		if (mood & SILC_ATTRIBUTE_MOOD_JEALOUS)
+			g_string_append_printf(s, "[%s] ", _("Jealous"));
+		if (mood & SILC_ATTRIBUTE_MOOD_ASHAMED)
+			g_string_append_printf(s, "[%s] ", _("Ashamed"));
+		if (mood & SILC_ATTRIBUTE_MOOD_INVINCIBLE)
+			g_string_append_printf(s, "[%s] ", _("Invincible"));
+		if (mood & SILC_ATTRIBUTE_MOOD_INLOVE)
+			g_string_append_printf(s, "[%s] ", _("In Love"));
+		if (mood & SILC_ATTRIBUTE_MOOD_SLEEPY)
+			g_string_append_printf(s, "[%s] ", _("Sleepy"));
+		if (mood & SILC_ATTRIBUTE_MOOD_BORED)
+			g_string_append_printf(s, "[%s] ", _("Bored"));
+		if (mood & SILC_ATTRIBUTE_MOOD_EXCITED)
+			g_string_append_printf(s, "[%s] ", _("Excited"));
+		if (mood & SILC_ATTRIBUTE_MOOD_ANXIOUS)
+			g_string_append_printf(s, "[%s] ", _("Anxious"));
+	}
+	if (strlen(s->str)) {
+		*moodstr = s->str;
+		g_string_free(s, FALSE);
+	} else
+		g_string_free(s, TRUE);
+
+	attr = silcpurple_get_attr(attrs, SILC_ATTRIBUTE_STATUS_FREETEXT);
+	memset(tmp, 0, sizeof(tmp));
+	if (attr && silc_attribute_get_object(attr, tmp, sizeof(tmp)))
+		*statusstr = g_strdup(tmp);
+
+	s = g_string_new("");
+	attr = silcpurple_get_attr(attrs, SILC_ATTRIBUTE_PREFERRED_CONTACT);
+	if (attr && silc_attribute_get_object(attr, &contact, sizeof(contact))) {
+		if (contact & SILC_ATTRIBUTE_CONTACT_CHAT)
+			g_string_append_printf(s, "[%s] ", _("Chat"));
+		if (contact & SILC_ATTRIBUTE_CONTACT_EMAIL)
+			g_string_append_printf(s, "[%s] ", _("E-Mail"));
+		if (contact & SILC_ATTRIBUTE_CONTACT_CALL)
+			g_string_append_printf(s, "[%s] ", _("Phone"));
+		if (contact & SILC_ATTRIBUTE_CONTACT_PAGE)
+			g_string_append_printf(s, "[%s] ", _("Paging"));
+		if (contact & SILC_ATTRIBUTE_CONTACT_SMS)
+			g_string_append_printf(s, "[%s] ", _("SMS"));
+		if (contact & SILC_ATTRIBUTE_CONTACT_MMS)
+			g_string_append_printf(s, "[%s] ", _("MMS"));
+		if (contact & SILC_ATTRIBUTE_CONTACT_VIDEO)
+			g_string_append_printf(s, "[%s] ", _("Video Conferencing"));
+	}
+	if (strlen(s->str)) {
+		*contactstr = s->str;
+		g_string_free(s, FALSE);
+	} else
+		g_string_free(s, TRUE);
+
+	attr = silcpurple_get_attr(attrs, SILC_ATTRIBUTE_PREFERRED_LANGUAGE);
+	memset(tmp, 0, sizeof(tmp));
+	if (attr && silc_attribute_get_object(attr, tmp, sizeof(tmp)))
+		*langstr = g_strdup(tmp);
+
+	s = g_string_new("");
+	attr = silcpurple_get_attr(attrs, SILC_ATTRIBUTE_DEVICE_INFO);
+	memset(&device, 0, sizeof(device));
+	if (attr && silc_attribute_get_object(attr, &device, sizeof(device))) {
+		if (device.type == SILC_ATTRIBUTE_DEVICE_COMPUTER)
+			g_string_append_printf(s, "%s: ", _("Computer"));
+		if (device.type == SILC_ATTRIBUTE_DEVICE_MOBILE_PHONE)
+			g_string_append_printf(s, "%s: ", _("Mobile Phone"));
+		if (device.type == SILC_ATTRIBUTE_DEVICE_PDA)
+			g_string_append_printf(s, "%s: ", _("PDA"));
+		if (device.type == SILC_ATTRIBUTE_DEVICE_TERMINAL)
+			g_string_append_printf(s, "%s: ", _("Terminal"));
+		g_string_append_printf(s, "%s %s %s %s",
+				device.manufacturer ? device.manufacturer : "",
+				device.version ? device.version : "",
+				device.model ? device.model : "",
+				device.language ? device.language : "");
+	}
+	if (strlen(s->str)) {
+		*devicestr = s->str;
+		g_string_free(s, FALSE);
+	} else
+		g_string_free(s, TRUE);
+
+	attr = silcpurple_get_attr(attrs, SILC_ATTRIBUTE_TIMEZONE);
+	memset(tmp, 0, sizeof(tmp));
+	if (attr && silc_attribute_get_object(attr, tmp, sizeof(tmp)))
+		*tzstr = g_strdup(tmp);
+
+	attr = silcpurple_get_attr(attrs, SILC_ATTRIBUTE_GEOLOCATION);
+	memset(&geo, 0, sizeof(geo));
+	if (attr && silc_attribute_get_object(attr, &geo, sizeof(geo)))
+		*geostr = g_strdup_printf("%s %s %s (%s)",
+				geo.longitude ? geo.longitude : "",
+				geo.latitude ? geo.latitude : "",
+				geo.altitude ? geo.altitude : "",
+				geo.accuracy ? geo.accuracy : "");
+}
+
+#ifdef HAVE_SILCMIME_H
+/* Returns MIME type of filetype */
+
+char *silcpurple_file2mime(const char *filename)
+{
+	const char *ct;
+
+	ct = strrchr(filename, '.');
+	if (!ct)
+		return NULL;
+	else if (!g_ascii_strcasecmp(".png", ct))
+		return strdup("image/png");
+	else if (!g_ascii_strcasecmp(".jpg", ct))
+		return strdup("image/jpeg");
+	else if (!g_ascii_strcasecmp(".jpeg", ct))
+		return strdup("image/jpeg");
+	else if (!g_ascii_strcasecmp(".gif", ct))
+		return strdup("image/gif");
+	else if (!g_ascii_strcasecmp(".tiff", ct))
+		return strdup("image/tiff");
+	
+	return NULL;
+}
+
+/* Checks if message has images, and assembles MIME message if it has. 
+   If only one image is present, creates simple MIME image message.  If 
+   there are multiple images and/or text with images multipart MIME 
+   message is created. */
+
+SilcDList silcpurple_image_message(const char *msg, SilcUInt32 *mflags)
+{
+	SilcMime mime = NULL, p;
+	SilcDList list, parts = NULL;
+	const char *start, *end, *last;
+	GData *attribs;
+	char *type;
+	gboolean images = FALSE;
+
+	last = msg;
+	while (last && *last && purple_markup_find_tag("img", last, &start,
+						     &end, &attribs)) {
+		PurpleStoredImage *image = NULL;
+		const char *id;
+
+		/* Check if there is text before image */
+		if (start - last) {
+			char *text, *tmp;
+			p = silc_mime_alloc();
+
+			/* Add content type */
+			silc_mime_add_field(p, "Content-Type",
+					    "text/plain; charset=utf-8");
+
+			tmp = g_strndup(last, start - last);
+			text = purple_unescape_html(tmp);
+			g_free(tmp);
+			/* Add text */
+			silc_mime_add_data(p, text, strlen(text));
+			g_free(text);
+
+			if (!parts)
+				parts = silc_dlist_init();
+			silc_dlist_add(parts, p);
+		}
+
+		id = g_datalist_get_data(&attribs, "id");
+		if (id && (image = purple_imgstore_find_by_id(atoi(id)))) {
+			unsigned long imglen = purple_imgstore_get_size(image);
+			gconstpointer img = purple_imgstore_get_data(image);
+
+			p = silc_mime_alloc();
+
+			/* Add content type */
+			type = silcpurple_file2mime(purple_imgstore_get_filename(image));
+			if (!type) {
+				g_datalist_clear(&attribs);
+				last = end + 1;
+				continue;
+			}
+			silc_mime_add_field(p, "Content-Type", type);
+			silc_free(type);
+
+			/* Add content transfer encoding */
+			silc_mime_add_field(p, "Content-Transfer-Encoding", "binary");
+
+			/* Add image data */
+			silc_mime_add_data(p, img, imglen);
+
+			if (!parts)
+				parts = silc_dlist_init();
+			silc_dlist_add(parts, p);
+			images = TRUE;
+		}
+
+		g_datalist_clear(&attribs);
+
+		/* Continue after tag */
+		last = end + 1;
+	}
+
+	/* Check for text after the image(s) */
+	if (images && last && *last) {
+		char *tmp = purple_unescape_html(last);
+		p = silc_mime_alloc();
+
+		/* Add content type */
+		silc_mime_add_field(p, "Content-Type",
+				    "text/plain; charset=utf-8");
+
+		/* Add text */
+		silc_mime_add_data(p, tmp, strlen(tmp));
+		g_free(tmp);
+
+		if (!parts)
+			parts = silc_dlist_init();
+		silc_dlist_add(parts, p);
+	}
+
+	/* If there weren't any images, don't return anything. */
+	if (!images) {
+		if (parts)
+			silc_dlist_uninit(parts);
+		return NULL;
+	}
+
+	if (silc_dlist_count(parts) > 1) {
+		/* Multipart MIME message */
+		char b[32];
+		mime = silc_mime_alloc();
+		silc_mime_add_field(mime, "MIME-Version", "1.0");
+		g_snprintf(b, sizeof(b), "b%4X%4X",
+			   (unsigned int)time(NULL),
+			   silc_dlist_count(parts)); 
+		silc_mime_set_multipart(mime, "mixed", b);
+		silc_dlist_start(parts);
+		while ((p = silc_dlist_get(parts)) != SILC_LIST_END)
+			silc_mime_add_multipart(mime, p);
+	} else {
+		/* Simple MIME message */
+		silc_dlist_start(parts);
+		mime = silc_dlist_get(parts);
+		silc_mime_add_field(mime, "MIME-Version", "1.0");
+	}
+
+	*mflags &= ~SILC_MESSAGE_FLAG_UTF8;
+	*mflags |= SILC_MESSAGE_FLAG_DATA;
+
+	/* Encode message. Fragment if it is too large */
+	list = silc_mime_encode_partial(mime, 0xfc00);
+
+	silc_dlist_uninit(parts);
+
+	/* Added multiparts gets freed here */
+	silc_mime_free(mime);
+
+	return list;
+}
+
+#endif /* HAVE_SILCMIME_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/silc10/wb.c	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,517 @@
+/*
+
+  wb.c
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  Copyright (C) 2005 Pekka Riikonen
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; version 2 of the License.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+*/
+
+#include "silcincludes.h"
+#include "silcclient.h"
+#include "silcpurple.h"
+#include "wb.h"
+
+/*
+  SILC Whiteboard packet:
+
+  1 byte	command
+  2 bytes	width
+  2 bytes	height
+  4 bytes	brush color
+  2 bytes	brush size
+  n bytes	data 
+
+  Data:
+
+  4 bytes	x
+  4 bytes	y
+
+  Commands:
+
+  0x01		draw
+  0x02		clear
+
+  MIME:
+
+  MIME-Version: 1.0
+  Content-Type: application/x-wb
+  Content-Transfer-Encoding: binary
+
+*/
+
+#define SILCPURPLE_WB_MIME "MIME-Version: 1.0\r\nContent-Type: application/x-wb\r\nContent-Transfer-Encoding: binary\r\n\r\n"
+#define SILCPURPLE_WB_HEADER strlen(SILCPURPLE_WB_MIME) + 11
+
+#define SILCPURPLE_WB_WIDTH 500
+#define SILCPURPLE_WB_HEIGHT 400
+#define SILCPURPLE_WB_WIDTH_MAX 1024
+#define SILCPURPLE_WB_HEIGHT_MAX 1024
+
+/* Commands */
+typedef enum {
+	SILCPURPLE_WB_DRAW 	= 0x01,
+	SILCPURPLE_WB_CLEAR	= 0x02,
+} SilcPurpleWbCommand;
+
+/* Brush size */
+typedef enum {
+	SILCPURPLE_WB_BRUSH_SMALL = 2,
+	SILCPURPLE_WB_BRUSH_MEDIUM = 5,
+	SILCPURPLE_WB_BRUSH_LARGE = 10,
+} SilcPurpleWbBrushSize;
+
+/* Brush color (XXX Purple should provide default colors) */
+typedef enum {
+	SILCPURPLE_WB_COLOR_BLACK		= 0,
+	SILCPURPLE_WB_COLOR_RED		= 13369344,
+	SILCPURPLE_WB_COLOR_GREEN		= 52224,
+	SILCPURPLE_WB_COLOR_BLUE		= 204,
+	SILCPURPLE_WB_COLOR_YELLOW 	= 15658496,
+	SILCPURPLE_WB_COLOR_ORANGE	= 16737792,
+	SILCPURPLE_WB_COLOR_CYAN		= 52428,
+	SILCPURPLE_WB_COLOR_VIOLET	= 5381277,
+	SILCPURPLE_WB_COLOR_PURPLE	= 13369548,
+	SILCPURPLE_WB_COLOR_TAN		= 12093547,
+	SILCPURPLE_WB_COLOR_BROWN		= 5256485,
+	SILCPURPLE_WB_COLOR_GREY		= 11184810,
+	SILCPURPLE_WB_COLOR_WHITE		= 16777215,
+} SilcPurpleWbColor;
+
+typedef struct {
+	int type;		/* 0 = buddy, 1 = channel */
+	union {
+		SilcClientEntry client;
+		SilcChannelEntry channel;
+	} u;
+	int width;
+	int height;
+	int brush_size;
+	int brush_color;
+} *SilcPurpleWb;
+
+/* Initialize whiteboard */
+
+PurpleWhiteboard *silcpurple_wb_init(SilcPurple sg, SilcClientEntry client_entry)
+{
+        SilcClientConnection conn;
+	PurpleWhiteboard *wb;
+	SilcPurpleWb wbs;
+
+	conn = sg->conn;
+	wb = purple_whiteboard_get_session(sg->account, client_entry->nickname);
+	if (!wb)
+		wb = purple_whiteboard_create(sg->account, client_entry->nickname, 0);
+	if (!wb)
+		return NULL;
+
+	if (!wb->proto_data) {
+		wbs = silc_calloc(1, sizeof(*wbs));
+		if (!wbs)
+			return NULL;
+		wbs->type = 0;
+		wbs->u.client = client_entry;
+		wbs->width = SILCPURPLE_WB_WIDTH;
+		wbs->height = SILCPURPLE_WB_HEIGHT;
+		wbs->brush_size = SILCPURPLE_WB_BRUSH_SMALL;
+		wbs->brush_color = SILCPURPLE_WB_COLOR_BLACK;
+		wb->proto_data = wbs;
+
+		/* Start the whiteboard */
+		purple_whiteboard_start(wb);
+		purple_whiteboard_clear(wb);
+	}
+
+	return wb;
+}
+
+PurpleWhiteboard *silcpurple_wb_init_ch(SilcPurple sg, SilcChannelEntry channel)
+{
+	PurpleWhiteboard *wb;
+	SilcPurpleWb wbs;
+
+	wb = purple_whiteboard_get_session(sg->account, channel->channel_name);
+	if (!wb)
+		wb = purple_whiteboard_create(sg->account, channel->channel_name, 0);
+	if (!wb)
+		return NULL;
+
+	if (!wb->proto_data) {
+		wbs = silc_calloc(1, sizeof(*wbs));
+		if (!wbs)
+			return NULL;
+		wbs->type = 1;
+		wbs->u.channel = channel;
+		wbs->width = SILCPURPLE_WB_WIDTH;
+		wbs->height = SILCPURPLE_WB_HEIGHT;
+		wbs->brush_size = SILCPURPLE_WB_BRUSH_SMALL;
+		wbs->brush_color = SILCPURPLE_WB_COLOR_BLACK;
+		wb->proto_data = wbs;
+
+		/* Start the whiteboard */
+		purple_whiteboard_start(wb);
+		purple_whiteboard_clear(wb);
+	}
+
+	return wb;
+}
+
+static void
+silcpurple_wb_parse(SilcPurpleWb wbs, PurpleWhiteboard *wb,
+		  unsigned char *message, SilcUInt32 message_len)
+{
+	SilcUInt8 command;
+	SilcUInt16 width, height, brush_size;
+	SilcUInt32 brush_color, x, y, dx, dy;
+	SilcBufferStruct buf;
+	int ret;
+
+	/* Parse the packet */
+	silc_buffer_set(&buf, message, message_len);
+	ret = silc_buffer_unformat(&buf,
+				   SILC_STR_UI_CHAR(&command),
+				   SILC_STR_UI_SHORT(&width),
+				   SILC_STR_UI_SHORT(&height),
+				   SILC_STR_UI_INT(&brush_color),
+				   SILC_STR_UI_SHORT(&brush_size),
+				   SILC_STR_END);
+	if (ret < 0)
+		return;
+	silc_buffer_pull(&buf, ret);
+
+	/* Update whiteboard if its dimensions changed */
+	if (width != wbs->width || height != wbs->height)
+		silcpurple_wb_set_dimensions(wb, height, width);
+
+	if (command == SILCPURPLE_WB_DRAW) {
+		/* Parse data and draw it */
+		ret = silc_buffer_unformat(&buf,
+					   SILC_STR_UI_INT(&dx),
+					   SILC_STR_UI_INT(&dy),
+					   SILC_STR_END);
+		if (ret < 0)
+			return;
+		silc_buffer_pull(&buf, 8);
+		x = dx;
+		y = dy;
+		while (buf.len > 0) {
+			ret = silc_buffer_unformat(&buf,
+						   SILC_STR_UI_INT(&dx),
+						   SILC_STR_UI_INT(&dy),
+						   SILC_STR_END);
+			if (ret < 0)
+				return;
+			silc_buffer_pull(&buf, 8);
+
+			purple_whiteboard_draw_line(wb, x, y, x + dx, y + dy,
+						  brush_color, brush_size);
+			x += dx;
+			y += dy;
+		}
+	}
+
+	if (command == SILCPURPLE_WB_CLEAR)
+		purple_whiteboard_clear(wb);
+}
+
+typedef struct {
+  unsigned char *message;
+  SilcUInt32 message_len;
+  SilcPurple sg;
+  SilcClientEntry sender;
+  SilcChannelEntry channel;
+} *SilcPurpleWbRequest;
+
+static void
+silcpurple_wb_request_cb(SilcPurpleWbRequest req, gint id)
+{
+	PurpleWhiteboard *wb;
+
+        if (id != 1)
+                goto out;
+
+	if (!req->channel)
+		wb = silcpurple_wb_init(req->sg, req->sender);
+	else
+		wb = silcpurple_wb_init_ch(req->sg, req->channel);
+
+	silcpurple_wb_parse(wb->proto_data, wb, req->message, req->message_len);
+
+  out:
+	silc_free(req->message);
+	silc_free(req);
+}
+
+static void
+silcpurple_wb_request(SilcClient client, const unsigned char *message, 
+		    SilcUInt32 message_len, SilcClientEntry sender, 
+		    SilcChannelEntry channel)
+{
+	char tmp[128];
+	SilcPurpleWbRequest req;
+	PurpleConnection *gc;
+	SilcPurple sg;
+
+	gc = client->application;
+	sg = gc->proto_data;
+
+	/* Open whiteboard automatically if requested */
+	if (purple_account_get_bool(sg->account, "open-wb", FALSE)) {
+		PurpleWhiteboard *wb;
+
+		if (!channel)
+			wb = silcpurple_wb_init(sg, sender);
+		else
+			wb = silcpurple_wb_init_ch(sg, channel);
+
+		silcpurple_wb_parse(wb->proto_data, wb, (unsigned char *)message,
+				  message_len);
+		return;
+	}
+
+	if (!channel) {
+		g_snprintf(tmp, sizeof(tmp),
+			_("%s sent message to whiteboard. Would you like "
+			  "to open the whiteboard?"), sender->nickname);
+	} else {
+		g_snprintf(tmp, sizeof(tmp),
+			_("%s sent message to whiteboard on %s channel. "
+			  "Would you like to open the whiteboard?"),
+			sender->nickname, channel->channel_name);
+	}
+
+	req = silc_calloc(1, sizeof(*req));
+	if (!req)
+		return;
+	req->message = silc_memdup(message, message_len);
+	req->message_len = message_len;
+	req->sender = sender;
+	req->channel = channel;
+	req->sg = sg;
+
+	purple_request_action(gc, _("Whiteboard"), tmp, NULL, 1,
+				sg->account, sender->nickname, NULL, req, 2,
+			    _("Yes"), G_CALLBACK(silcpurple_wb_request_cb),
+			    _("No"), G_CALLBACK(silcpurple_wb_request_cb));
+}
+
+/* Process incoming whiteboard message */
+
+void silcpurple_wb_receive(SilcClient client, SilcClientConnection conn,
+			 SilcClientEntry sender, SilcMessagePayload payload,
+			 SilcMessageFlags flags, const unsigned char *message,
+			 SilcUInt32 message_len)
+{
+	SilcPurple sg;
+        PurpleConnection *gc;
+	PurpleWhiteboard *wb;
+	SilcPurpleWb wbs;
+
+	gc = client->application;
+        sg = gc->proto_data;
+
+	wb = purple_whiteboard_get_session(sg->account, sender->nickname);
+	if (!wb) {
+		/* Ask user if they want to open the whiteboard */
+		silcpurple_wb_request(client, message, message_len,
+				    sender, NULL);
+		return;
+	}
+
+	wbs = wb->proto_data;
+	silcpurple_wb_parse(wbs, wb, (unsigned char *)message, message_len);
+}
+
+/* Process incoming whiteboard message on channel */
+
+void silcpurple_wb_receive_ch(SilcClient client, SilcClientConnection conn,
+			    SilcClientEntry sender, SilcChannelEntry channel,
+			    SilcMessagePayload payload,
+			    SilcMessageFlags flags,
+			    const unsigned char *message,
+			    SilcUInt32 message_len)
+{
+	SilcPurple sg;
+        PurpleConnection *gc;
+	PurpleWhiteboard *wb;
+	SilcPurpleWb wbs;
+
+	gc = client->application;
+        sg = gc->proto_data;
+
+	wb = purple_whiteboard_get_session(sg->account, channel->channel_name);
+	if (!wb) {
+		/* Ask user if they want to open the whiteboard */
+		silcpurple_wb_request(client, message, message_len,
+				    sender, channel);
+		return;
+	}
+
+	wbs = wb->proto_data;
+	silcpurple_wb_parse(wbs, wb, (unsigned char *)message, message_len);
+}
+
+/* Send whiteboard message */
+
+void silcpurple_wb_send(PurpleWhiteboard *wb, GList *draw_list)
+{
+	SilcPurpleWb wbs = wb->proto_data;
+	SilcBuffer packet;
+	GList *list;
+	int len;
+        PurpleConnection *gc;
+        SilcPurple sg;
+
+	g_return_if_fail(draw_list);
+	gc = purple_account_get_connection(wb->account);
+	g_return_if_fail(gc);
+ 	sg = gc->proto_data;
+	g_return_if_fail(sg);
+
+	len = SILCPURPLE_WB_HEADER;
+	for (list = draw_list; list; list = list->next)
+		len += 4;
+
+	packet = silc_buffer_alloc_size(len);
+	if (!packet)
+		return;
+
+	/* Assmeble packet */
+	silc_buffer_format(packet,
+			   SILC_STR_UI32_STRING(SILCPURPLE_WB_MIME),
+			   SILC_STR_UI_CHAR(SILCPURPLE_WB_DRAW),
+			   SILC_STR_UI_SHORT(wbs->width),
+			   SILC_STR_UI_SHORT(wbs->height),
+			   SILC_STR_UI_INT(wbs->brush_color),
+			   SILC_STR_UI_SHORT(wbs->brush_size),
+			   SILC_STR_END);
+	silc_buffer_pull(packet, SILCPURPLE_WB_HEADER);
+	for (list = draw_list; list; list = list->next) {
+		silc_buffer_format(packet,
+				   SILC_STR_UI_INT(GPOINTER_TO_INT(list->data)),
+				   SILC_STR_END);
+		silc_buffer_pull(packet, 4);
+	}
+
+	/* Send the message */
+	if (wbs->type == 0) {
+		/* Private message */
+		silc_client_send_private_message(sg->client, sg->conn, 
+						 wbs->u.client, 
+						 SILC_MESSAGE_FLAG_DATA,
+						 packet->head, len, TRUE);
+	} else if (wbs->type == 1) {
+		/* Channel message. Channel private keys are not supported. */
+		silc_client_send_channel_message(sg->client, sg->conn,
+						 wbs->u.channel, NULL,
+						 SILC_MESSAGE_FLAG_DATA,
+						 packet->head, len, TRUE);
+	}
+
+	silc_buffer_free(packet);
+}
+
+/* Purple Whiteboard operations */
+
+void silcpurple_wb_start(PurpleWhiteboard *wb)
+{
+	/* Nothing here.  Everything is in initialization */
+}
+
+void silcpurple_wb_end(PurpleWhiteboard *wb)
+{
+	silc_free(wb->proto_data);
+	wb->proto_data = NULL;
+}
+
+void silcpurple_wb_get_dimensions(const PurpleWhiteboard *wb, int *width, int *height)
+{
+	SilcPurpleWb wbs = wb->proto_data;
+	*width = wbs->width;
+	*height = wbs->height;
+}
+
+void silcpurple_wb_set_dimensions(PurpleWhiteboard *wb, int width, int height)
+{
+	SilcPurpleWb wbs = wb->proto_data;
+	wbs->width = width > SILCPURPLE_WB_WIDTH_MAX ? SILCPURPLE_WB_WIDTH_MAX :
+			width;
+	wbs->height = height > SILCPURPLE_WB_HEIGHT_MAX ? SILCPURPLE_WB_HEIGHT_MAX :
+			height;
+
+	/* Update whiteboard */
+	purple_whiteboard_set_dimensions(wb, wbs->width, wbs->height);
+}
+
+void silcpurple_wb_get_brush(const PurpleWhiteboard *wb, int *size, int *color)
+{
+	SilcPurpleWb wbs = wb->proto_data;
+	*size = wbs->brush_size;
+	*color = wbs->brush_color;
+}
+
+void silcpurple_wb_set_brush(PurpleWhiteboard *wb, int size, int color)
+{
+	SilcPurpleWb wbs = wb->proto_data;
+	wbs->brush_size = size;
+	wbs->brush_color = color;
+
+	/* Update whiteboard */
+	purple_whiteboard_set_brush(wb, size, color);
+}
+
+void silcpurple_wb_clear(PurpleWhiteboard *wb)
+{
+	SilcPurpleWb wbs = wb->proto_data;
+	SilcBuffer packet;
+	int len;
+        PurpleConnection *gc;
+        SilcPurple sg;
+
+	gc = purple_account_get_connection(wb->account);
+	g_return_if_fail(gc);
+ 	sg = gc->proto_data;
+	g_return_if_fail(sg);
+
+	len = SILCPURPLE_WB_HEADER;
+	packet = silc_buffer_alloc_size(len);
+	if (!packet)
+		return;
+
+	/* Assmeble packet */
+	silc_buffer_format(packet,
+			   SILC_STR_UI32_STRING(SILCPURPLE_WB_MIME),
+			   SILC_STR_UI_CHAR(SILCPURPLE_WB_CLEAR),
+			   SILC_STR_UI_SHORT(wbs->width),
+			   SILC_STR_UI_SHORT(wbs->height),
+			   SILC_STR_UI_INT(wbs->brush_color),
+			   SILC_STR_UI_SHORT(wbs->brush_size),
+			   SILC_STR_END);
+
+	/* Send the message */
+	if (wbs->type == 0) {
+		/* Private message */
+		silc_client_send_private_message(sg->client, sg->conn, 
+						 wbs->u.client, 
+						 SILC_MESSAGE_FLAG_DATA,
+						 packet->head, len, TRUE);
+	} else if (wbs->type == 1) {
+		/* Channel message */
+		silc_client_send_channel_message(sg->client, sg->conn,
+						 wbs->u.channel, NULL,
+						 SILC_MESSAGE_FLAG_DATA,
+						 packet->head, len, TRUE);
+	}
+
+	silc_buffer_free(packet);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/silc10/wb.h	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,49 @@
+/*
+
+  silcpurple.h
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  Copyright (C) 2005 Pekka Riikonen
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; version 2 of the License.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+*/
+
+#ifndef SILCPURPLE_WB_H
+#define SILCPURPLE_WB_H
+
+#include "silcpurple.h"
+#include "whiteboard.h"
+
+PurpleWhiteboard *
+silcpurple_wb_init(SilcPurple sg, SilcClientEntry client_entry);
+PurpleWhiteboard *
+silcpurple_wb_init_ch(SilcPurple sg, SilcChannelEntry channel);
+void silcpurple_wb_receive(SilcClient client, SilcClientConnection conn,
+			 SilcClientEntry sender, SilcMessagePayload payload,
+			 SilcMessageFlags flags, const unsigned char *message,
+			 SilcUInt32 message_len);
+void silcpurple_wb_receive_ch(SilcClient client, SilcClientConnection conn,
+			    SilcClientEntry sender, SilcChannelEntry channel,
+			    SilcMessagePayload payload,
+			    SilcMessageFlags flags,
+			    const unsigned char *message,
+			    SilcUInt32 message_len);
+void silcpurple_wb_start(PurpleWhiteboard *wb);
+void silcpurple_wb_end(PurpleWhiteboard *wb);
+void silcpurple_wb_get_dimensions(const PurpleWhiteboard *wb, int *width, int *height);
+void silcpurple_wb_set_dimensions(PurpleWhiteboard *wb, int width, int height);
+void silcpurple_wb_get_brush(const PurpleWhiteboard *wb, int *size, int *color);
+void silcpurple_wb_set_brush(PurpleWhiteboard *wb, int size, int color);
+void silcpurple_wb_send(PurpleWhiteboard *wb, GList *draw_list);
+void silcpurple_wb_clear(PurpleWhiteboard *wb);
+
+#endif /* SILCPURPLE_WB_H */
--- a/libpurple/protocols/simple/simple.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/simple/simple.c	Tue Jun 12 21:21:37 2007 +0000
@@ -1243,7 +1243,7 @@
 					foundxpidf = TRUE;
 				if(tmp2) {
 					*tmp2 = ',';
-					tmp = tmp2;
+					tmp = tmp2 + 1;
 					while(*tmp == ' ') tmp++;
 				} else
 					tmp = 0;
--- a/libpurple/protocols/toc/PROTOCOL	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/toc/PROTOCOL	Tue Jun 12 21:21:37 2007 +0000
@@ -14,10 +14,10 @@
 #   along with this program; if not, write to the Free Software
 #   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
-# Note from Jim Duchek, gaim maintainer -- this may not be the latest
-# version of this document, I provide it as a service.  Download a copy
-# of TiK (http://www.aim.aol.com/tik/) for the latest version of this
-# doc.
+# Note from Jim Duchek, former libpurple maintainer -- this may not be
+# the latest version of this document, I provide it as a service.
+# Download a copy of TiK (http://www.aim.aol.com/tik/) for the latest
+# version of this doc.
 
 # Note from Eric Warmenhoven, random guy -- this appears to be the last
 # published version of the protocol, and AOL has stopped hosting the TiK
--- a/libpurple/protocols/yahoo/yahoo.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Tue Jun 12 21:21:37 2007 +0000
@@ -3861,7 +3861,7 @@
 		if (acct && !purple_account_is_connected(acct))
 			acct = NULL;
 	} else { /* Otherwise find an active account for the protocol */
-		GList *l = purple_accounts_get_all();
+		const GList *l = purple_accounts_get_all();
 		while (l) {
 			if (!strcmp(prpl, purple_account_get_protocol_id(l->data))
 					&& purple_account_is_connected(l->data)) {
--- a/libpurple/protocols/yahoo/yahoochat.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoochat.c	Tue Jun 12 21:21:37 2007 +0000
@@ -633,10 +633,10 @@
  * I think conference names are always ascii.
  */
 
-void yahoo_conf_leave(struct yahoo_data *yd, const char *room, const char *dn, GList *who)
+void yahoo_conf_leave(struct yahoo_data *yd, const char *room, const char *dn, const GList *who)
 {
 	struct yahoo_packet *pkt;
-	GList *w;
+	const GList *w;
 
 	purple_debug_misc("yahoo", "leaving conference %s\n", room);
 	
@@ -653,11 +653,11 @@
 }
 
 static int yahoo_conf_send(PurpleConnection *gc, const char *dn, const char *room,
-							GList *members, const char *what)
+							const GList *members, const char *what)
 {
 	struct yahoo_data *yd = gc->proto_data;
 	struct yahoo_packet *pkt;
-	GList *who;
+	const GList *who;
 	char *msg, *msg2;
 	int utf8 = 1;
 
@@ -714,7 +714,7 @@
 {
 	struct yahoo_data *yd = gc->proto_data;
 	struct yahoo_packet *pkt;
-	GList *members;
+	const GList *members;
 	char *msg2 = NULL;
 
 	if (msg)
--- a/libpurple/protocols/yahoo/yahoochat.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoochat.h	Tue Jun 12 21:21:37 2007 +0000
@@ -50,7 +50,7 @@
 char *yahoo_get_chat_name(GHashTable *data);
 void yahoo_c_invite(PurpleConnection *gc, int id, const char *msg, const char *name);
 
-void yahoo_conf_leave(struct yahoo_data *yd, const char *room, const char *dn, GList *who);
+void yahoo_conf_leave(struct yahoo_data *yd, const char *room, const char *dn, const GList *who);
 
 void yahoo_chat_goto(PurpleConnection *gc, const char *name);
 
--- a/libpurple/prpl.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/prpl.h	Tue Jun 12 21:21:37 2007 +0000
@@ -158,6 +158,12 @@
 	 */
 	OPT_PROTO_REGISTER_NOSCREENNAME = 0x00000200,
 
+	/**
+	 * Indicates that slash commands are native to this protocol.
+	 * Used as a hint that unknown commands should not be sent as messages.
+	 */
+	OPT_PROTO_SLASH_COMMANDS_NATIVE = 0x00000400,
+
 } PurpleProtocolOptions;
 
 /**
--- a/libpurple/purple-remote	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/purple-remote	Tue Jun 12 21:21:37 2007 +0000
@@ -157,6 +157,12 @@
 
         return None
 
+    elif command == "getstatus":
+        current = purple.PurpleSavedstatusGetCurrent()
+        status_type = purple.PurpleSavedstatusGetType(current)
+        status_id = purple.PurplePrimitiveGetIdFromType(status_type)
+        return status_id
+
     elif command == "getinfo":
         account = findaccount(accountname, protocol)
         connection = cpurple.PurpleAccountGetConnection(account)
--- a/libpurple/savedstatuses.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/savedstatuses.c	Tue Jun 12 21:21:37 2007 +0000
@@ -357,7 +357,7 @@
 schedule_save(void)
 {
 	if (save_timer == 0)
-		save_timer = purple_timeout_add(5000, save_cb, NULL);
+		save_timer = purple_timeout_add_seconds(5, save_cb, NULL);
 }
 
 
--- a/libpurple/server.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/server.c	Tue Jun 12 21:21:37 2007 +0000
@@ -92,7 +92,7 @@
 
 	/* because we're modifying or creating a lar, schedule the
 	 * function to expire them as the pref dictates */
-	purple_timeout_add((SECS_BEFORE_RESENDING_AUTORESPONSE + 1) * 1000, expire_last_auto_responses, NULL);
+	purple_timeout_add_seconds((SECS_BEFORE_RESENDING_AUTORESPONSE + 1), expire_last_auto_responses, NULL);
 
 	tmp = last_auto_responses;
 
@@ -233,8 +233,9 @@
 			char *tmp = g_strdup_printf(_("%s is now known as %s.\n"),
 										who, alias);
 
-			purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM,
-									time(NULL));
+			purple_conversation_write(conv, NULL, tmp,
+					PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NO_LINKIFY,
+					time(NULL));
 
 			g_free(tmp);
 		}
--- a/libpurple/util.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/util.c	Tue Jun 12 21:21:37 2007 +0000
@@ -22,6 +22,7 @@
  */
 #include "internal.h"
 
+#include "cipher.h"
 #include "conversation.h"
 #include "core.h"
 #include "debug.h"
@@ -156,6 +157,31 @@
 	return data;
 }
 
+gchar *
+purple_base16_encode_chunked(const guchar *data, gsize len)
+{
+	int i;
+	gchar *ascii = NULL;
+
+	g_return_val_if_fail(data != NULL, NULL);
+	g_return_val_if_fail(len > 0,   NULL);
+
+	/* For each byte of input, we need 2 bytes for the hex representation
+	 * and 1 for the colon.
+	 * The final colon will be replaced by a terminating NULL
+	 */
+	ascii = g_malloc(len * 3 + 1);
+
+	for (i = 0; i < len; i++)
+		g_snprintf(&ascii[i * 3], 4, "%02hhx:", data[i]);
+
+	/* Replace the final colon with NULL */
+	ascii[len * 3 - 1] = 0;
+
+	return ascii;
+}
+
+
 /**************************************************************************
  * Base64 Functions
  **************************************************************************/
@@ -987,7 +1013,6 @@
 	g_return_val_if_fail(    needle != NULL, FALSE);
 	g_return_val_if_fail(   *needle != '\0', FALSE);
 	g_return_val_if_fail(  haystack != NULL, FALSE);
-	g_return_val_if_fail( *haystack != '\0', FALSE);
 	g_return_val_if_fail(     start != NULL, FALSE);
 	g_return_val_if_fail(       end != NULL, FALSE);
 	g_return_val_if_fail(attributes != NULL, FALSE);
@@ -1397,6 +1422,40 @@
 						plain = g_string_append_c(plain, '\n');
 					continue;
 				}
+				if(!g_ascii_strncasecmp(c, "<img", 4) && (*(c+4) == '>' || *(c+4) == ' ')) {
+					const char *p = c;
+					GString *src = NULL;
+					struct purple_parse_tag *pt;
+					while(*p && *p != '>') {
+						if(!g_ascii_strncasecmp(p, "src=", strlen("src="))) {
+							const char *q = p + strlen("src=");
+							src = g_string_new("");
+							if(*q == '\'' || *q == '\"')
+								q++;
+							while(*q && *q != '\"' && *q != '\'' && *q != ' ') {
+								src = g_string_append_c(src, *q);
+								q++;
+							}
+							p = q;
+						}
+						p++;
+					}
+					if ((c = strchr(c, '>')) != NULL)
+						c++;
+					else
+						c = p;
+					pt = g_new0(struct purple_parse_tag, 1);
+					pt->src_tag = "img";
+					pt->dest_tag = "img";
+					tags = g_list_prepend(tags, pt);
+					if(xhtml && src && src->len)
+						g_string_append_printf(xhtml, "<img src='%s' alt=''>", g_strstrip(src->str));
+					else
+						pt->ignore = TRUE;
+					if (src)
+						g_string_free(src, TRUE);
+					continue;
+				}
 				if(!g_ascii_strncasecmp(c, "<b>", 3) || !g_ascii_strncasecmp(c, "<bold>", strlen("<bold>"))) {
 					struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
 					pt->src_tag = *(c+2) == '>' ? "b" : "bold";
@@ -1538,10 +1597,7 @@
 					pt->dest_tag = "span";
 					tags = g_list_prepend(tags, pt);
 					if(style->len)
-					{
-						if(xhtml)
-							g_string_append_printf(xhtml, "<span style='%s'>", g_strstrip(style->str));
-					}
+						g_string_append_printf(xhtml, "<span style='%s'>", g_strstrip(style->str));
 					else
 						pt->ignore = TRUE;
 					g_string_free(style, TRUE);
@@ -2654,19 +2710,49 @@
 
 	if (len >= 4)
 	{
-		if (!strncmp((char *)data, "BM", 2))
-			return "bmp";
-		else if (!strncmp((char *)data, "GIF8", 4))
+		if (!strncmp((char *)data, "GIF8", 4))
 			return "gif";
-		else if (!strncmp((char *)data, "\xff\xd8\xff\xe0", 4))
+		else if (!strncmp((char *)data, "\xff\xd8\xff", 3)) /* 4th may be e0 through ef */
 			return "jpg";
 		else if (!strncmp((char *)data, "\x89PNG", 4))
 			return "png";
+		else if (!strncmp((char *)data, "MM", 2) ||
+				 !strncmp((char *)data, "II", 2))
+			return "tif";
+		else if (!strncmp((char *)data, "BM", 2))
+			return "bmp";
 	}
 
 	return "icon";
 }
 
+char *
+purple_util_get_image_filename(gconstpointer image_data, size_t image_len)
+{
+	PurpleCipherContext *context;
+	gchar digest[41];
+
+	context = purple_cipher_context_new_by_name("sha1", NULL);
+	if (context == NULL)
+	{
+		purple_debug_error("util", "Could not find sha1 cipher\n");
+		g_return_val_if_reached(NULL);
+	}
+
+	/* Hash the image data */
+	purple_cipher_context_append(context, image_data, image_len);
+	if (!purple_cipher_context_digest_to_str(context, sizeof(digest), digest, NULL))
+	{
+		purple_debug_error("util", "Failed to get SHA-1 digest.\n");
+		g_return_val_if_reached(NULL);
+	}
+	purple_cipher_context_destroy(context);
+
+	/* Return the filename */
+	return g_strdup_printf("%s.%s", digest,
+	                       purple_util_get_image_extension(image_data, image_len));
+}
+
 gboolean
 purple_program_is_valid(const char *program)
 {
--- a/libpurple/util.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/util.h	Tue Jun 12 21:21:37 2007 +0000
@@ -32,6 +32,7 @@
 
 #include "account.h"
 #include "xmlnode.h"
+#include "notify.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -118,6 +119,21 @@
  */
 guchar *purple_base16_decode(const char *str, gsize *ret_len);
 
+/**
+ * Converts a chunk of binary data to a chunked base-16 representation
+ * (handy for key fingerprints)
+ *
+ * Example output: 01:23:45:67:89:AB:CD:EF
+ *
+ * @param data The data to convert.
+ * @param len  The length of the data.
+ *
+ * @return The base-16 string in the ASCII chunked encoding.  Must be
+ *         g_free'd when no longer needed.
+ */
+gchar *purple_base16_encode_chunked(const guchar *data, gsize len);
+
+
 /*@}*/
 
 /**************************************************************************/
@@ -608,6 +624,12 @@
 const char *
 purple_util_get_image_extension(gconstpointer data, size_t len);
 
+/**
+ * Returns a SHA-1 hash string of the data passed in with the correct file
+ * extention appended.
+ */
+char *purple_util_get_image_filename(gconstpointer image_data, size_t image_len);
+
 /*@}*/
 
 
--- a/libpurple/win32/global.mak	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/win32/global.mak	Tue Jun 12 21:21:37 2007 +0000
@@ -14,7 +14,7 @@
 GTKSPELL_TOP ?= $(WIN32_DEV_TOP)/gtkspell-2.0.6
 GTK_TOP ?= $(WIN32_DEV_TOP)/gtk_2_0
 GTK_BIN ?= $(GTK_TOP)/bin
-HOWL_TOP ?= $(WIN32_DEV_TOP)/howl-1.0.0
+BONJOUR_TOP ?= $(WIN32_DEV_TOP)/Bonjour_SDK
 LIBXML2_TOP ?= $(WIN32_DEV_TOP)/libxml2
 MEANWHILE_TOP ?= $(WIN32_DEV_TOP)/meanwhile-1.0.2
 NSPR_TOP ?= $(WIN32_DEV_TOP)/nspr-4.6.4
--- a/libpurple/win32/libc_interface.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/win32/libc_interface.c	Tue Jun 12 21:21:37 2007 +0000
@@ -1,6 +1,6 @@
 /*
  * purple
- * 
+ *
  * Copyright (C) 2002-2003, Herman Bloggs <hermanator12002@yahoo.com>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -74,7 +74,7 @@
 	int ret;
 
 	ret = connect( socket, addr, length );
-	
+
 	if( ret == SOCKET_ERROR ) {
 		errno = WSAGetLastError();
 		if( errno == WSAEWOULDBLOCK )
@@ -129,6 +129,8 @@
 	if ((ret = sendto(socket, buf, len, flags, to, tolen)
 			) == SOCKET_ERROR) {
 		errno = WSAGetLastError();
+		if(errno == WSAEWOULDBLOCK || errno == WSAEINPROGRESS)
+			errno = EAGAIN;
 		return -1;
 	}
 	return ret;
@@ -302,7 +304,7 @@
 	if(wpurple_is_socket(fd)) {
 		if((ret = recv(fd, buf, size, 0)) == SOCKET_ERROR) {
 			errno = WSAGetLastError();
-			if(errno == WSAEWOULDBLOCK)
+			if(errno == WSAEWOULDBLOCK || errno == WSAEINPROGRESS)
 				errno = EAGAIN;
 			return -1;
 		}
@@ -330,7 +332,7 @@
 
 	if (ret == SOCKET_ERROR) {
 		errno = WSAGetLastError();
-		if(errno == WSAEWOULDBLOCK)
+		if(errno == WSAEWOULDBLOCK || errno == WSAEINPROGRESS)
 			errno = EAGAIN;
 		return -1;
 	}
@@ -350,7 +352,7 @@
 
 	if((ret = recv(fd, buf, len, flags)) == SOCKET_ERROR) {
 			errno = WSAGetLastError();
-			if(errno == WSAEWOULDBLOCK)
+			if(errno == WSAEWOULDBLOCK || errno == WSAEINPROGRESS)
 				errno = EAGAIN;
 			return -1;
 	} else {
@@ -392,7 +394,7 @@
 		z->tz_minuteswest = _timezone/60;
 		z->tz_dsttime = _daylight;
 	}
-	
+
 	if (p != 0) {
 		_ftime(&timebuffer);
 	   	p->tv_sec = timebuffer.time;			/* seconds since 1-1-1970 */
@@ -1044,7 +1046,7 @@
  * Returns: zero if the pathname refers to an existing file system
  * object that has all the tested permissions, or -1 otherwise or on
  * error.
- * 
+ *
  * Since: 2.8
  */
 int
@@ -1056,7 +1058,7 @@
       wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL);
       int retval;
       int save_errno;
-      
+
       if (wfilename == NULL)
 	{
 	  errno = EINVAL;
@@ -1072,7 +1074,7 @@
       return retval;
     }
   else
-    {    
+    {
       gchar *cp_filename = g_locale_from_utf8 (filename, -1, NULL, NULL, NULL);
       int retval;
       int save_errno;
--- a/libpurple/xmlnode.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/xmlnode.c	Tue Jun 12 21:21:37 2007 +0000
@@ -333,8 +333,9 @@
 	for(c = node->child; c; c = c->next) {
 		if(c->type == XMLNODE_TYPE_DATA) {
 			if(!str)
-				str = g_string_new("");
-			str = g_string_append_len(str, c->data, c->data_sz);
+				str = g_string_new_len(c->data, c->data_sz);
+			else
+				str = g_string_append_len(str, c->data, c->data_sz);
 		}
 	}
 
@@ -344,6 +345,18 @@
 	return g_string_free(str, FALSE);
 }
 
+char *
+xmlnode_get_data_unescaped(xmlnode *node)
+{
+	char *escaped = xmlnode_get_data(node);
+
+	char *unescaped = escaped ? purple_unescape_html(escaped) : NULL;
+
+	g_free(escaped);
+
+	return unescaped;
+}
+
 static char *
 xmlnode_to_str_helper(xmlnode *node, int *len, gboolean formatting, int depth)
 {
--- a/libpurple/xmlnode.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/libpurple/xmlnode.h	Tue Jun 12 21:21:37 2007 +0000
@@ -124,14 +124,24 @@
 void xmlnode_insert_data(xmlnode *node, const char *data, gssize size);
 
 /**
- * Gets data from a node.
+ * Gets (escaped) data from a node.
  *
  * @param node The node to get data from.
  *
- * @return The data from the node.  You must g_free
+ * @return The data from the node.  This data is in raw escaped format.
+ *         You must g_free this string when finished using it.
+ */
+char *xmlnode_get_data(xmlnode *node);
+
+/**
+ * Gets unescaped data from a node.
+ *
+ * @param node The node to get data from.
+ *
+ * @return The data from the node, in unescaped form.   You must g_free
  *         this string when finished using it.
  */
-char *xmlnode_get_data(xmlnode *node);
+char *xmlnode_get_data_unescaped(xmlnode *node);
 
 /**
  * Sets an attribute for a node.
--- a/pidgin.spec.in	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin.spec.in	Tue Jun 12 21:21:37 2007 +0000
@@ -83,6 +83,7 @@
 Summary:    Development headers, documentation, and libraries for Pidgin
 Group:      Applications/Internet
 Requires:   pidgin = %{version}, libpurple-devel = %{version}
+Requires:   gtk2-devel
 Requires:   pkgconfig
 Obsoletes:  gaim-devel
 Provides:   gaim-devel
@@ -102,6 +103,12 @@
 Group:      Applications/Internet
 Requires:   libpurple = %{version}
 Requires:   pkgconfig
+%if "%{_vendor}" == "suse"
+# For SuSE:
+%{?_with_dbus:Requires: dbus-1-devel >= 0.35}
+%else
+%{?_with_dbus:Requires: dbus-devel >= 0.35}
+%endif
 
 %if 0%{?_with_howl:1} || 0%{?_with_avahi:1}
 %package -n libpurple-bonjour
@@ -134,6 +141,7 @@
 Summary:    Headers etc. for finch stuffs
 Group:      Applications/Internet
 Requires:   finch = %{version}, libpurple-devel = %{version}
+Requires:   ncurses-devel
 Requires:   pkgconfig
 %endif
 
@@ -450,6 +458,10 @@
 %endif
 
 %changelog
+* Tue Jun 5 2007 Stu Tomlinson <stu@nosnilmot.com>
+- Add missing Requires for gtk2-devel, dbus-devel & ncurses-devel to
+  appropriate -devel subpackages
+
 * Sun May 27 2007 Stu Tomlinson <stu@nosnilmot.com>
 - add cyrus-sasl-plain & cyrus-sasl-md5 to Requires
 
--- a/pidgin/Makefile.am	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/Makefile.am	Tue Jun 12 21:21:37 2007 +0000
@@ -109,6 +109,8 @@
 	gtksession.c \
 	gtksound.c \
 	gtksourceiter.c \
+	gtksourceundomanager.c \
+	gtksourceview-marshal.c \
 	gtkstatusbox.c \
 	gtkthemes.c \
 	gtkutils.c \
@@ -156,6 +158,8 @@
 	gtksession.h \
 	gtksound.h \
 	gtksourceiter.h \
+	gtksourceundomanager.h \
+	gtksourceview-marshal.h \
 	gtkstatusbox.h \
 	pidginstock.h \
 	gtkthemes.h \
--- a/pidgin/Makefile.mingw	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/Makefile.mingw	Tue Jun 12 21:21:37 2007 +0000
@@ -85,6 +85,7 @@
 			gtkscrollbook.c \
 			gtksound.c \
 			gtksourceiter.c \
+			gtksourceundomanager.c \
 			gtkstatusbox.c \
 			gtkthemes.c \
 			gtkutils.c \
--- a/pidgin/gtkaccount.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkaccount.c	Tue Jun 12 21:21:37 2007 +0000
@@ -461,7 +461,7 @@
 		PurpleAccountUserSplit *split = l->data;
 		char *buf;
 
-		buf = g_strdup_printf("%s:", purple_account_user_split_get_text(split));
+		buf = g_strdup_printf("_%s:", purple_account_user_split_get_text(split));
 
 		entry = gtk_entry_new();
 
@@ -480,11 +480,15 @@
 
 		GtkWidget *entry = l->data;
 		PurpleAccountUserSplit *split = l2->data;
-		const char *value = NULL, *protocol = NULL;
+		const char *value = NULL;
 		char *c;
 
 		if (dialog->account != NULL) {
-			c = strrchr(username,
+			if(purple_account_user_split_get_reverse(split))
+				c = strrchr(username,
+						purple_account_user_split_get_separator(split));
+			else
+				c = strchr(username,
 						purple_account_user_split_get_separator(split));
 
 			if (c != NULL) {
@@ -500,9 +504,8 @@
 		/* Google Talk default domain hackery! */
 		menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(dialog->protocol_menu));
 		item = gtk_menu_get_active(GTK_MENU(menu));
-		protocol = g_object_get_data(G_OBJECT(item), "protocol");
-		if (value == NULL && protocol != NULL && !strcmp(protocol, "prpl-fake") &&
-				!strcmp(purple_account_user_split_get_text(split), _("Domain")))
+		if (value == NULL && g_object_get_data(G_OBJECT(item), "fake") && 
+			!strcmp(purple_account_user_split_get_text(split), _("Domain")))
 			value = "gmail.com";
 
 		if (value != NULL)
@@ -525,7 +528,7 @@
 
 	/* Alias */
 	dialog->alias_entry = gtk_entry_new();
-	add_pref_box(dialog, vbox, _("Local _alias:"), dialog->alias_entry);
+	add_pref_box(dialog, vbox, _("_Local alias:"), dialog->alias_entry);
 
 	/* Remember Password */
 	dialog->remember_pass_check =
@@ -702,8 +705,8 @@
 	PurpleKeyValuePair *kvp;
 	GList *l;
 	char buf[1024];
-	char *title;
-	const char *str_value, *protocol;
+	char *title, *tmp;
+	const char *str_value;
 	gboolean bool_value;
 
 	if (dialog->protocol_frame != NULL) {
@@ -759,8 +762,9 @@
 						purple_account_option_get_default_bool(option));
 				}
 
-				check = gtk_check_button_new_with_label(
-					purple_account_option_get_text(option));
+				tmp = g_strconcat("_", purple_account_option_get_text(option), NULL);
+				check = gtk_check_button_new_with_mnemonic(tmp);
+				g_free(tmp);
 
 				gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check),
 											 bool_value);
@@ -792,7 +796,7 @@
 				entry = gtk_entry_new();
 				gtk_entry_set_text(GTK_ENTRY(entry), buf);
 
-				title = g_strdup_printf("%s:",
+				title = g_strdup_printf("_%s:",
 						purple_account_option_get_text(option));
 
 				add_pref_box(dialog, vbox, title, entry);
@@ -829,15 +833,14 @@
 				/* Google Talk default domain hackery! */
 				menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(dialog->protocol_menu));
 				item = gtk_menu_get_active(GTK_MENU(menu));
-				protocol = g_object_get_data(G_OBJECT(item), "protocol");
-				if (str_value == NULL && protocol != NULL && !strcmp(protocol, "prpl-fake") &&
+				if (str_value == NULL && g_object_get_data(G_OBJECT(item), "fake") &&
 					!strcmp(_("Connect server"),  purple_account_option_get_text(option)))
 					str_value = "talk.google.com";
-		
+
 				if (str_value != NULL)
 					gtk_entry_set_text(GTK_ENTRY(entry), str_value);
 
-				title = g_strdup_printf("%s:",
+				title = g_strdup_printf("_%s:",
 						purple_account_option_get_text(option));
 
 				add_pref_box(dialog, vbox, title, entry);
@@ -898,7 +901,7 @@
 				gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo),
 						renderer, "text", 0, NULL);
 
-				title = g_strdup_printf("%s:",
+				title = g_strdup_printf("_%s:",
 						purple_account_option_get_text(option));
 
 				add_pref_box(dialog, vbox, title, combo);
@@ -1024,7 +1027,7 @@
 	if (dialog->proxy_frame != NULL)
 		gtk_widget_destroy(dialog->proxy_frame);
 
-	frame = pidgin_make_frame(parent, _("Pro_xy Options"));
+	frame = pidgin_make_frame(parent, _("Proxy Options"));
 	dialog->proxy_frame = gtk_widget_get_parent(gtk_widget_get_parent(frame));
 
 	gtk_box_reorder_child(GTK_BOX(parent), dialog->proxy_frame, 1);
@@ -1467,18 +1470,8 @@
 	if ((dialog->plugin = purple_find_prpl(dialog->protocol_id)) != NULL)
 		dialog->prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(dialog->plugin);
 
-
-	dialog->window = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-	gtk_window_set_role(GTK_WINDOW(win), "account");
-
-	if (type == PIDGIN_ADD_ACCOUNT_DIALOG)
-		gtk_window_set_title(GTK_WINDOW(win), _("Add Account"));
-	else
-		gtk_window_set_title(GTK_WINDOW(win), _("Modify Account"));
-
-	gtk_window_set_resizable(GTK_WINDOW(win), FALSE);
-
-	gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER);
+	dialog->window = win = pidgin_create_window((type == PIDGIN_ADD_ACCOUNT_DIALOG) ? _("Add Account") : _("Modify Account"),
+		PIDGIN_HIG_BORDER, "account", FALSE);
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
 					 G_CALLBACK(account_win_destroy_cb), dialog);
@@ -1593,7 +1586,7 @@
 
 	account = purple_connection_get_account(gc);
 	model = GTK_TREE_MODEL(accounts_window->model);
-	index = g_list_index(purple_accounts_get_all(), account);
+	index = g_list_index((GList *)purple_accounts_get_all(), account);
 
 	if (gtk_tree_model_iter_nth_child(model, &iter, NULL, index))
 	{
@@ -1796,13 +1789,13 @@
 				case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
 					move_account_after(dialog->model, &dialog->drag_iter,
 									   &iter);
-					dest_index = g_list_index(purple_accounts_get_all(),
+					dest_index = g_list_index((GList *)purple_accounts_get_all(),
 											  account) + 1;
 					break;
 
 				case GTK_TREE_VIEW_DROP_BEFORE:
 				case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
-					dest_index = g_list_index(purple_accounts_get_all(),
+					dest_index = g_list_index((GList *)purple_accounts_get_all(),
 											  account);
 
 					move_account_before(dialog->model, &dialog->drag_iter,
@@ -2101,7 +2094,7 @@
 static gboolean
 populate_accounts_list(AccountsWindow *dialog)
 {
-	GList *l;
+	const GList *l;
 	gboolean ret = FALSE;
 	GdkPixbuf *global_buddyicon = NULL;
 	const char *path;
@@ -2305,7 +2298,7 @@
 global_buddyicon_changed(const char *name, PurplePrefType type,
 			gconstpointer value, gpointer window)
 {
-	GList *list;
+	const GList *list;
 	for (list = purple_accounts_get_all(); list; list = list->next) {
 		account_modified_cb(list->data, window);
 	}
@@ -2322,7 +2315,6 @@
 	GtkWidget *button;
 	int width, height;
 
-
 	if (accounts_window != NULL) {
 		gtk_window_present(GTK_WINDOW(accounts_window->window));
 		return;
@@ -2333,11 +2325,8 @@
 	width  = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/accounts/dialog/width");
 	height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/accounts/dialog/height");
 
-	dialog->window = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+	dialog->window = win = pidgin_create_window(_("Accounts"), PIDGIN_HIG_BORDER, "accounts", TRUE);
 	gtk_window_set_default_size(GTK_WINDOW(win), width, height);
-	gtk_window_set_role(GTK_WINDOW(win), "accounts");
-	gtk_window_set_title(GTK_WINDOW(win), _("Accounts"));
-	gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER);
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
 					 G_CALLBACK(accedit_win_destroy_cb), accounts_window);
@@ -2440,7 +2429,7 @@
 {
 	PurpleConnection *gc = purple_account_get_connection(data->account);
 
-	if (g_list_find(purple_connections_get_all(), gc))
+	if (g_list_find((GList *)purple_connections_get_all(), gc))
 	{
 		purple_blist_request_add_buddy(data->account, data->username,
 									 NULL, data->alias);
--- a/pidgin/gtkblist.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkblist.c	Tue Jun 12 21:21:37 2007 +0000
@@ -266,20 +266,13 @@
 	purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/width",  event->width);
 	purple_prefs_set_int(PIDGIN_PREFS_ROOT "/blist/height", event->height);
 
-	gtk_widget_set_size_request(gtkblist->headline_label,
-				    purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width")-25,-1);
 	/* continue to handle event normally */
 	return FALSE;
 }
 
 static void gtk_blist_menu_info_cb(GtkWidget *w, PurpleBuddy *b)
 {
-	PurpleNotifyUserInfo *info = purple_notify_user_info_new();
-	purple_notify_user_info_add_pair(info, _("Information"), _("Retrieving..."));
-	purple_notify_userinfo(b->account->gc, purple_buddy_get_name(b), info, NULL, NULL);
-	purple_notify_user_info_destroy(info);
-
-	serv_get_info(b->account->gc, b->name);
+	pidgin_retrieve_user_info(b->account->gc, purple_buddy_get_name(b));
 }
 
 static void gtk_blist_menu_im_cb(GtkWidget *w, PurpleBuddy *b)
@@ -688,7 +681,7 @@
 gboolean
 pidgin_blist_joinchat_is_showable()
 {
-	GList *c;
+	const GList *c;
 	PurpleConnection *gc;
 
 	for (c = purple_connections_get_all(); c != NULL; c = c->next) {
@@ -1142,7 +1135,8 @@
 }
 
 static gboolean
-gtk_blist_key_press_cb(GtkWidget *tv, GdkEventKey *event, gpointer data) {
+gtk_blist_key_press_cb(GtkWidget *tv, GdkEventKey *event, gpointer data)
+{
 	PurpleBlistNode *node;
 	GValue val;
 	GtkTreeIter iter;
@@ -1169,7 +1163,7 @@
 			return FALSE;
 		}
 		if(buddy)
-			serv_get_info(buddy->account->gc, buddy->name);
+			pidgin_retrieve_user_info(buddy->account->gc, buddy->name);
 	} else if (event->keyval == GDK_F2) {
 		gtk_blist_menu_alias_cb(tv, node);
 	}
@@ -1423,7 +1417,7 @@
 			prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
 
 		if (prpl && prpl_info->get_info)
-			serv_get_info(b->account->gc, b->name);
+			pidgin_retrieve_user_info(b->account->gc, b->name);
 		handled = TRUE;
 	}
 
@@ -1542,7 +1536,7 @@
 add_buddies_from_vcard(const char *prpl_id, PurpleGroup *group, GList *list,
 					   const char *alias)
 {
-	GList *l;
+	const GList *l;
 	PurpleAccount *account = NULL;
 	PurpleConnection *gc;
 
@@ -2895,7 +2889,7 @@
 		prpl = purple_find_prpl(purple_account_get_protocol_id(chat->account));
 		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
 
-		if (g_list_length(purple_connections_get_all()) > 1)
+		if (g_list_length((GList *)purple_connections_get_all()) > 1)
 		{
 			tmp = g_markup_escape_text(chat->account->username, -1);
 			g_string_append_printf(str, _("\n<b>Account:</b> %s"), tmp);
@@ -2960,7 +2954,7 @@
 		user_info = purple_notify_user_info_new();
 
 		/* Account */
-		if (full && g_list_length(purple_connections_get_all()) > 1)
+		if (full && g_list_length((GList *)purple_connections_get_all()) > 1)
 		{
 			tmp = g_markup_escape_text(purple_account_get_username(
 									   purple_buddy_get_account(b)), -1);
@@ -3837,15 +3831,17 @@
 static gboolean
 gtk_blist_window_key_press_cb(GtkWidget *w, GdkEventKey *event, PidginBuddyList *gtkblist)
 {
-	GtkWidget *imhtml;
+	GtkWidget *widget;
 
 	if (!gtkblist)
 		return FALSE;
 
-	imhtml = gtk_window_get_focus(GTK_WINDOW(gtkblist->window));
-
-	if (GTK_IS_IMHTML(imhtml) && gtk_bindings_activate(GTK_OBJECT(imhtml), event->keyval, event->state))
-		return TRUE;
+	widget = gtk_window_get_focus(GTK_WINDOW(gtkblist->window));
+
+	if (GTK_IS_IMHTML(widget) || GTK_IS_ENTRY(widget)) {
+		if (gtk_bindings_activate(GTK_OBJECT(widget), event->keyval, event->state))
+			return TRUE;
+	}
 	return FALSE;
 }
 
@@ -4208,7 +4204,7 @@
 				{"application/x-im-contact", 0, DRAG_BUDDY},
 				{"text/x-vcard", 0, DRAG_VCARD }};
 	if (gtkblist && gtkblist->window) {
-		purple_blist_set_visible(purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/list_visible"));
+		purple_blist_set_visible(TRUE);
 		return;
 	}
 
@@ -4217,9 +4213,7 @@
 	gtkblist->empty_avatar = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 32, 32);
 	gdk_pixbuf_fill(gtkblist->empty_avatar, 0x00000000);
 
-	gtkblist->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-	gtk_window_set_role(GTK_WINDOW(gtkblist->window), "buddy_list");
-	gtk_window_set_title(GTK_WINDOW(gtkblist->window), _("Buddy List"));
+	gtkblist->window = pidgin_create_window(_("Buddy List"), 0, "buddy_list", TRUE);
 	g_signal_connect(G_OBJECT(gtkblist->window), "focus-in-event",
 			 G_CALLBACK(blist_focus_cb), gtkblist);
 	GTK_WINDOW(gtkblist->window)->allow_shrink = TRUE;
@@ -5695,7 +5689,7 @@
 {
 	PidginAddChatData *data;
 	PidginBuddyList *gtkblist;
-	GList *l;
+	const GList *l;
 	PurpleConnection *gc;
 	GtkWidget *label;
 	GtkWidget *rowbox;
@@ -6462,7 +6456,8 @@
 {
 	GtkWidget *menuitem = NULL, *submenu = NULL;
 	GtkAccelGroup *accel_group = NULL;
-	GList *l = NULL, *accounts = NULL;
+	GList *l = NULL;
+	const GList *accounts;
 	gboolean disabled_accounts = FALSE;
 
 	if (accountmenu == NULL)
--- a/pidgin/gtkconv.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkconv.c	Tue Jun 12 21:21:37 2007 +0000
@@ -271,65 +271,7 @@
 default_formatize(PidginConversation *c)
 {
 	PurpleConversation *conv = c->active_conv;
-
-	if (conv->features & PURPLE_CONNECTION_HTML)
-	{
-		char color[8];
-		GdkColor fg_color, bg_color;
-
-		if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_bold") != GTK_IMHTML(c->entry)->edit.bold)
-			gtk_imhtml_toggle_bold(GTK_IMHTML(c->entry));
-
-		if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_italic") != GTK_IMHTML(c->entry)->edit.italic)
-			gtk_imhtml_toggle_italic(GTK_IMHTML(c->entry));
-
-		if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_underline") != GTK_IMHTML(c->entry)->edit.underline)
-			gtk_imhtml_toggle_underline(GTK_IMHTML(c->entry));
-
-		gtk_imhtml_toggle_fontface(GTK_IMHTML(c->entry),
-			purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/font_face"));
-
-		if (!(conv->features & PURPLE_CONNECTION_NO_FONTSIZE))
-		{
-			int size = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/font_size");
-
-			/* 3 is the default. */
-			if (size != 3)
-				gtk_imhtml_font_set_size(GTK_IMHTML(c->entry), size);
-		}
-
-		if(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor"), "") != 0)
-		{
-			gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor"),
-							&fg_color);
-			g_snprintf(color, sizeof(color), "#%02x%02x%02x",
-									fg_color.red   / 256,
-									fg_color.green / 256,
-									fg_color.blue  / 256);
-		} else
-			strcpy(color, "");
-
-		gtk_imhtml_toggle_forecolor(GTK_IMHTML(c->entry), color);
-
-		if(!(conv->features & PURPLE_CONNECTION_NO_BGCOLOR) &&
-		   strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor"), "") != 0)
-		{
-			gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor"),
-							&bg_color);
-			g_snprintf(color, sizeof(color), "#%02x%02x%02x",
-									bg_color.red   / 256,
-									bg_color.green / 256,
-									bg_color.blue  / 256);
-		} else
-			strcpy(color, "");
-
-		gtk_imhtml_toggle_background(GTK_IMHTML(c->entry), color);
-
-		if (conv->features & PURPLE_CONNECTION_FORMATTING_WBFO)
-			gtk_imhtml_set_whole_buffer_formatting_only(GTK_IMHTML(c->entry), TRUE);
-		else
-			gtk_imhtml_set_whole_buffer_formatting_only(GTK_IMHTML(c->entry), FALSE);
-	}
+	gtk_imhtml_setup_entry(GTK_IMHTML(c->entry), conv->features);
 }
 
 static void
@@ -477,6 +419,7 @@
 	char *cmd;
 	const char *prefix;
 	GtkTextIter start;
+	gboolean retval = FALSE;
 
 	gtkconv = PIDGIN_CONVERSATION(conv);
 	prefix = pidgin_get_cmd_prefix();
@@ -500,24 +443,50 @@
 		gtk_text_buffer_get_end_iter(GTK_IMHTML(gtkconv->entry)->text_buffer, &end);
 		markup = gtk_imhtml_get_markup_range(GTK_IMHTML(gtkconv->entry), &start, &end);
 		status = purple_cmd_do_command(conv, cmdline, markup, &error);
-		g_free(cmd);
 		g_free(markup);
 
 		switch (status) {
 			case PURPLE_CMD_STATUS_OK:
-				return TRUE;
+				retval = TRUE;
+				break;
 			case PURPLE_CMD_STATUS_NOT_FOUND:
-				return FALSE;
+				{
+					PurplePluginProtocolInfo *prpl_info = NULL;
+					PurpleConnection *gc;
+
+					if ((gc = purple_conversation_get_gc(conv)))
+						prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
+
+					if ((prpl_info != NULL) && (prpl_info->options & OPT_PROTO_SLASH_COMMANDS_NATIVE)) {
+						char *firstspace;
+						char *slash;
+
+						firstspace = strchr(cmdline, ' ');
+						if (firstspace != NULL) {
+							slash = strrchr(firstspace, '/');
+						} else {
+							slash = strchr(cmdline, '/');
+						}
+
+						if (slash == NULL) {
+							purple_conversation_write(conv, "", _("Unknown command."), PURPLE_MESSAGE_NO_LOG, time(NULL));
+							retval = TRUE;
+						}
+					}
+					break;
+				}
 			case PURPLE_CMD_STATUS_WRONG_ARGS:
 				purple_conversation_write(conv, "", _("Syntax Error:  You typed the wrong number of arguments "
 								    "to that command."),
 						PURPLE_MESSAGE_NO_LOG, time(NULL));
-				return TRUE;
+				retval = TRUE;
+				break;
 			case PURPLE_CMD_STATUS_FAILED:
 				purple_conversation_write(conv, "", error ? error : _("Your command failed for an unknown reason."),
 						PURPLE_MESSAGE_NO_LOG, time(NULL));
 				g_free(error);
-				return TRUE;
+				retval = TRUE;
+				break;
 			case PURPLE_CMD_STATUS_WRONG_TYPE:
 				if(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM)
 					purple_conversation_write(conv, "", _("That command only works in chats, not IMs."),
@@ -525,16 +494,18 @@
 				else
 					purple_conversation_write(conv, "", _("That command only works in IMs, not chats."),
 							PURPLE_MESSAGE_NO_LOG, time(NULL));
-				return TRUE;
+				retval = TRUE;
+				break;
 			case PURPLE_CMD_STATUS_WRONG_PRPL:
 				purple_conversation_write(conv, "", _("That command doesn't work on this protocol."),
 						PURPLE_MESSAGE_NO_LOG, time(NULL));
-				return TRUE;
+				retval = TRUE;
+				break;
 		}
 	}
 
 	g_free(cmd);
-	return FALSE;
+	return retval;
 }
 
 static void
@@ -666,7 +637,7 @@
 				purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)), who);
 		}
 		else
-			prpl_info->get_info(gc, who);
+			pidgin_retrieve_user_info(gc, who);
 	}
 }
 
@@ -677,14 +648,8 @@
 	PurpleConversation *conv = gtkconv->active_conv;
 
 	if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) {
-		PurpleNotifyUserInfo *info = purple_notify_user_info_new();
-		purple_notify_user_info_add_pair(info, _("Information"), _("Retrieving..."));
-		purple_notify_userinfo(conv->account->gc, purple_conversation_get_name(conv), info, NULL, NULL);
-		purple_notify_user_info_destroy(info);
-
-		serv_get_info(purple_conversation_get_gc(conv),
+		pidgin_retrieve_user_info(purple_conversation_get_gc(conv),
 					  purple_conversation_get_name(conv));
-
 		gtk_widget_grab_focus(gtkconv->entry);
 	} else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
 		/* Get info of the person currently selected in the GtkTreeView */
@@ -1247,6 +1212,37 @@
 }
 
 static void
+menu_insert_link_cb(gpointer data, guint action, GtkWidget *widget)
+{
+	PidginWindow *win = data;
+	PidginConversation *gtkconv;
+	GtkIMHtmlToolbar *toolbar;
+
+	gtkconv = pidgin_conv_window_get_active_gtkconv(win);
+	toolbar = GTK_IMHTMLTOOLBAR(gtkconv->toolbar);
+
+	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar->link),
+			!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar->link)));
+}
+
+static void
+menu_insert_image_cb(gpointer data, guint action, GtkWidget *widget)
+{
+	PidginWindow *win = data;
+	PurpleConversation *conv;
+	PidginConversation *gtkconv;
+	GtkIMHtmlToolbar *toolbar;
+
+	gtkconv = pidgin_conv_window_get_active_gtkconv(win);
+	conv    = gtkconv->active_conv;
+	toolbar = GTK_IMHTMLTOOLBAR(gtkconv->toolbar);
+
+	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar->image),
+			!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toolbar->image)));
+}
+
+
+static void
 menu_alias_cb(gpointer data, guint action, GtkWidget *widget)
 {
 	PidginWindow *win = data;
@@ -2228,34 +2224,18 @@
 static GList *get_prpl_icon_list(PurpleAccount *account)
 {
 	GList *l = NULL;
-	GdkPixbuf *pixbuf;
-	PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(account)));
-	const char *prpl = prpl_info->list_icon(account, NULL);
-	char *filename, *path;
-	l = g_hash_table_lookup(prpl_lists, prpl);
+	PurplePlugin *prpl = purple_find_prpl(purple_account_get_protocol_id(account));
+	PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
+	const char *prplname = prpl_info->list_icon(account, NULL);
+	l = g_hash_table_lookup(prpl_lists, prplname);
 	if (l)
 		return l;
-	filename = g_strdup_printf("%s.png", prpl);
-
-	path = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", "16", filename, NULL);
-	pixbuf = gdk_pixbuf_new_from_file(path, NULL);
-	if (pixbuf)
-		l = g_list_append(l, pixbuf);
-	g_free(path);
-
-	path = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", "22", filename, NULL);
-        pixbuf = gdk_pixbuf_new_from_file(path, NULL);
-        if (pixbuf)
-                l = g_list_append(l, pixbuf);
-        g_free(path);
-
-	path = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", "48", filename, NULL);
-        pixbuf = gdk_pixbuf_new_from_file(path, NULL);
-        if (pixbuf)
-                l = g_list_append(l, pixbuf);
-        g_free(path);
-
-	g_hash_table_insert(prpl_lists, g_strdup(prpl), l);
+
+	l = g_list_prepend(l, pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_LARGE));
+	l = g_list_prepend(l, pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_MEDIUM));
+	l = g_list_prepend(l, pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL));
+
+	g_hash_table_insert(prpl_lists, g_strdup(prplname), l);
 	return l;
 }
 
@@ -2696,7 +2676,7 @@
 										gboolean hidden_only,
 										guint max_count)
 {
-	GList *l;
+	const GList *l;
 	GList *r = NULL;
 	guint c = 0;
 
@@ -2818,6 +2798,14 @@
 
 	{ "/Conversation/sep3", NULL, NULL, 0, "<Separator>", NULL },
 
+	{ N_("/Conversation/Insert Lin_k..."), NULL, menu_insert_link_cb, 0,
+		"<StockItem>", PIDGIN_STOCK_TOOLBAR_INSERT_LINK },
+	{ N_("/Conversation/Insert Imag_e..."), NULL, menu_insert_image_cb, 0,
+		"<StockItem>", PIDGIN_STOCK_TOOLBAR_INSERT_IMAGE },
+
+	{ "/Conversation/sep4", NULL, NULL, 0, "<Separator>", NULL },
+
+
 	{ N_("/Conversation/_Close"), NULL, menu_close_conv_cb, 0,
 			"<StockItem>", GTK_STOCK_CLOSE },
 
@@ -2897,14 +2885,25 @@
 	GList *list;
 	PidginConversation *gtkconv;
 	PurpleConversation *conv;
-	PurpleBuddy *buddy;
+	PurpleBlistNode *node = NULL;
+	PurpleChat *chat = NULL;
+	PurpleBuddy *buddy = NULL;
 
 	gtkconv = pidgin_conv_window_get_active_gtkconv(win);
 	conv = gtkconv->active_conv;
-	buddy = purple_find_buddy(conv->account, conv->name);
 
 	menu = gtk_item_factory_get_widget(win->menu.item_factory, N_("/Conversation/More"));
 
+	if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT)
+		chat = purple_blist_find_chat(conv->account, conv->name);
+	else
+		buddy = purple_find_buddy(conv->account, conv->name);
+
+	if (chat)
+		node = (PurpleBlistNode *)chat;
+	else if (buddy)
+		node = (PurpleBlistNode *)buddy;
+
 	/* Remove the previous entries */
 	for (list = gtk_container_get_children(GTK_CONTAINER(menu)); list; )
 	{
@@ -2914,12 +2913,11 @@
 	}
 
 	/* Now add the stuff */
-	if (buddy)
+	if (node)
 	{
 		if (purple_account_is_connected(conv->account))
-			pidgin_append_blist_node_proto_menu(menu, conv->account->gc,
-												  (PurpleBlistNode *)buddy);
-		pidgin_append_blist_node_extended_menu(menu, (PurpleBlistNode *)buddy);
+			pidgin_append_blist_node_proto_menu(menu, conv->account->gc, node);
+		pidgin_append_blist_node_extended_menu(menu, node);
 	}
 
 	if ((list = gtk_container_get_children(GTK_CONTAINER(menu))) == NULL)
@@ -2932,10 +2930,65 @@
 	gtk_widget_show_all(menu);
 }
 
+static void
+remove_from_list(GtkWidget *widget, PidginWindow *win)
+{
+	GList *list = g_object_get_data(G_OBJECT(win->window), "plugin-actions");
+	list = g_list_remove(list, widget);
+	g_object_set_data(G_OBJECT(win->window), "plugin-actions", list);
+}
+
+static void
+regenerate_plugins_items(PidginWindow *win)
+{
+	GList *action_items;
+	GtkWidget *menu;
+	GList *list;
+	PidginConversation *gtkconv;
+	PurpleConversation *conv;
+	GtkWidget *item;
+
+	if (win->window == NULL || win == hidden_convwin)
+		return;
+
+	gtkconv = pidgin_conv_window_get_active_gtkconv(win);
+	if (gtkconv == NULL)
+		return;
+
+	conv = gtkconv->active_conv;
+	action_items = g_object_get_data(G_OBJECT(win->window), "plugin-actions");
+
+	/* Remove the old menuitems */
+	while (action_items) {
+		g_signal_handlers_disconnect_by_func(G_OBJECT(action_items->data),
+					G_CALLBACK(remove_from_list), win);
+		gtk_widget_destroy(action_items->data);
+		action_items = g_list_delete_link(action_items, action_items);
+	}
+
+	menu = gtk_item_factory_get_widget(win->menu.item_factory, N_("/Options"));
+
+	list = purple_conversation_get_extended_menu(conv);
+	if (list) {
+		action_items = g_list_prepend(NULL, (item = pidgin_separator(menu)));
+		g_signal_connect(G_OBJECT(item), "destroy", G_CALLBACK(remove_from_list), win);
+	}
+
+	for(; list; list = g_list_delete_link(list, list)) {
+		PurpleMenuAction *act = (PurpleMenuAction *) list->data;
+		item = pidgin_append_menu_action(menu, act, conv);
+		action_items = g_list_prepend(action_items, item);
+		gtk_widget_show_all(item);
+		g_signal_connect(G_OBJECT(item), "destroy", G_CALLBACK(remove_from_list), win);
+	}
+	g_object_set_data(G_OBJECT(win->window), "plugin-actions", action_items);
+}
+
 static void menubar_activated(GtkWidget *item, gpointer data)
 {
 	PidginWindow *win = data;
 	regenerate_options_items(win);
+	regenerate_plugins_items(win);
 
 	/* The following are to make sure the 'More' submenu is not regenerated every time
 	 * the focus shifts from 'Conversations' to some other menu and back. */
@@ -3032,6 +3085,18 @@
 		gtk_item_factory_get_widget(win->menu.item_factory,
 		                            N_("/Conversation/Remove..."));
 
+	/* --- */
+
+	win->menu.insert_link =
+		gtk_item_factory_get_widget(win->menu.item_factory,
+				N_("/Conversation/Insert Link..."));
+
+	win->menu.insert_image =
+		gtk_item_factory_get_widget(win->menu.item_factory,
+				N_("/Conversation/Insert Image..."));
+
+	/* --- */
+
 	win->menu.logging =
 		gtk_item_factory_get_widget(win->menu.item_factory,
 		                            N_("/Options/Enable Logging"));
@@ -3737,7 +3802,7 @@
 		g_list_free(list);
 	} else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
 		PurpleConvChat *chat = PURPLE_CONV_CHAT(conv);
-		GList *l = purple_conv_chat_get_users(chat);
+		const GList *l = purple_conv_chat_get_users(chat);
 		GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(PIDGIN_CONVERSATION(conv)->u.chat->list));
 		GtkTreeIter iter;
 		int f;
@@ -4135,45 +4200,17 @@
 	}
 }
 
-static GtkWidget *
-setup_chat_pane(PidginConversation *gtkconv)
-{
-	PurplePluginProtocolInfo *prpl_info;
+static void
+setup_chat_topic(PidginConversation *gtkconv, GtkWidget *vbox)
+{
 	PurpleConversation *conv = gtkconv->active_conv;
-	PidginChatPane *gtkchat;
-	PurpleConnection *gc;
-	GtkWidget *vpaned, *hpaned;
-	GtkWidget *vbox, *hbox, *frame;
-	GtkWidget *imhtml_sw;
-	GtkPolicyType imhtml_sw_hscroll;
-	GtkWidget *lbox;
-	GtkWidget *label;
-	GtkWidget *list;
-	GtkWidget *sw;
-	GtkListStore *ls;
-	GtkCellRenderer *rend;
-	GtkTreeViewColumn *col;
-	void *blist_handle = purple_blist_get_handle();
-	GList *focus_chain = NULL;
-	int ul_width;
-
-	gtkchat = gtkconv->u.chat;
-	gc      = purple_conversation_get_gc(conv);
-	g_return_val_if_fail(gc != NULL, NULL);
-	g_return_val_if_fail(gc->prpl != NULL, NULL);
-	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
-
-	/* Setup the outer pane. */
-	vpaned = gtk_vpaned_new();
-	gtk_widget_show(vpaned);
-
-	/* Setup the top part of the pane. */
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
-	gtk_paned_pack1(GTK_PANED(vpaned), vbox, TRUE, TRUE);
-	gtk_widget_show(vbox);
-
+	PurpleConnection *gc = purple_conversation_get_gc(conv);
+	PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
 	if (prpl_info->options & OPT_PROTO_CHAT_TOPIC)
 	{
+		GtkWidget *hbox, *label;
+		PidginChatPane *gtkchat = gtkconv->u.chat;
+		
 		hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
 		gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
 		gtk_widget_show(hbox);
@@ -4194,35 +4231,19 @@
 		gtk_box_pack_start(GTK_BOX(hbox), gtkchat->topic_text, TRUE, TRUE, 0);
 		gtk_widget_show(gtkchat->topic_text);
 	}
-
-	/* Setup the horizontal pane. */
-	hpaned = gtk_hpaned_new();
-	gtk_box_pack_start(GTK_BOX(vbox), hpaned, TRUE, TRUE, 0);
-	gtk_widget_show(hpaned);
-
-	/* Setup gtkihmtml. */
-	frame = pidgin_create_imhtml(FALSE, &gtkconv->imhtml, NULL, &imhtml_sw);
-	gtk_widget_set_name(gtkconv->imhtml, "pidgin_conv_imhtml");
-	gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml), TRUE);
-	gtk_paned_pack1(GTK_PANED(hpaned), frame, TRUE, TRUE);
-	gtk_widget_show(frame);
-	gtk_scrolled_window_get_policy(GTK_SCROLLED_WINDOW(imhtml_sw),
-	                               &imhtml_sw_hscroll, NULL);
-	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(imhtml_sw),
-	                               imhtml_sw_hscroll, GTK_POLICY_ALWAYS);
-
-	gtk_widget_set_size_request(gtkconv->imhtml,
-			purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/default_width"),
-			purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/default_height"));
-	g_signal_connect(G_OBJECT(gtkconv->imhtml), "size-allocate",
-					 G_CALLBACK(size_allocate_cb), gtkconv);
-
-	g_signal_connect_after(G_OBJECT(gtkconv->imhtml), "button_press_event",
-						   G_CALLBACK(entry_stop_rclick_cb), NULL);
-	g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_press_event",
-						   G_CALLBACK(refocus_entry_cb), gtkconv);
-	g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_release_event",
-						   G_CALLBACK(refocus_entry_cb), gtkconv);
+}
+
+static void
+setup_chat_userlist(PidginConversation *gtkconv, GtkWidget *hpaned)
+{
+	PidginChatPane *gtkchat = gtkconv->u.chat;
+	GtkWidget *lbox, *sw, *list;
+	GtkListStore *ls;
+	GtkCellRenderer *rend;
+	GtkTreeViewColumn *col;
+	int ul_width;
+	void *blist_handle = purple_blist_get_handle();
+	PurpleConversation *conv = gtkconv->active_conv;
 
 	/* Build the right pane. */
 	lbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
@@ -4270,9 +4291,7 @@
 			 G_CALLBACK(gtkconv_chat_popup_menu_cb), gtkconv);
 	g_signal_connect(G_OBJECT(lbox), "size-allocate", G_CALLBACK(lbox_size_allocate_cb), gtkconv);
 
-
 	rend = gtk_cell_renderer_text_new();
-
 	g_object_set(rend,
 				 "foreground-set", TRUE,
 				 "weight-set", TRUE,
@@ -4303,10 +4322,72 @@
 	gtkchat->list = list;
 
 	gtk_container_add(GTK_CONTAINER(sw), list);
+}
+
+static GtkWidget *
+setup_common_pane(PidginConversation *gtkconv)
+{
+	GtkWidget *paned, *vbox, *frame, *imhtml_sw;
+	PurpleConversation *conv = gtkconv->active_conv;
+	gboolean chat = (conv->type == PURPLE_CONV_TYPE_CHAT);
+	GtkPolicyType imhtml_sw_hscroll;
+
+	paned = gtk_vpaned_new();
+	gtk_widget_show(paned);
+
+	/* Setup the top part of the pane */
+	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+	gtk_paned_pack1(GTK_PANED(paned), vbox, TRUE, TRUE);
+	gtk_widget_show(vbox);
+
+	/* Setup the gtkimhtml widget */
+	frame = pidgin_create_imhtml(FALSE, &gtkconv->imhtml, NULL, &imhtml_sw);
+	if (chat) {
+		GtkWidget *hpaned;
+
+		/* Add the topic */
+		setup_chat_topic(gtkconv, vbox);
+
+		/* Add the gtkimhtml frame */
+		hpaned = gtk_hpaned_new();
+		gtk_box_pack_start(GTK_BOX(vbox), hpaned, TRUE, TRUE, 0);
+		gtk_widget_show(hpaned);
+		gtk_paned_pack1(GTK_PANED(hpaned), frame, TRUE, TRUE);
+
+		/* Now add the userlist */
+		setup_chat_userlist(gtkconv, hpaned);
+	} else {
+		gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
+	}
+	gtk_widget_show(frame);
+
+	gtk_widget_set_name(gtkconv->imhtml, "pidgin_conv_imhtml");
+	gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml),TRUE);
+
+	gtk_scrolled_window_get_policy(GTK_SCROLLED_WINDOW(imhtml_sw),
+	                               &imhtml_sw_hscroll, NULL);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(imhtml_sw),
+	                               imhtml_sw_hscroll, GTK_POLICY_ALWAYS);
+
+	gtk_widget_set_size_request(gtkconv->imhtml,
+			chat ? purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/default_width") :
+			purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/default_width"),
+			chat ? purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/default_height") :
+			purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/default_height"));
+
+	g_signal_connect(G_OBJECT(gtkconv->imhtml), "size-allocate",
+	                 G_CALLBACK(size_allocate_cb), gtkconv);
+
+	g_signal_connect_after(G_OBJECT(gtkconv->imhtml), "button_press_event",
+	                       G_CALLBACK(entry_stop_rclick_cb), NULL);
+	g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_press_event",
+	                 G_CALLBACK(refocus_entry_cb), gtkconv);
+	g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_release_event",
+	                 G_CALLBACK(refocus_entry_cb), gtkconv);
 
 	/* Setup the bottom half of the conversation window */
 	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
-	gtk_paned_pack2(GTK_PANED(vpaned), vbox, FALSE, TRUE);
+	gtk_paned_pack2(GTK_PANED(paned), vbox, FALSE, TRUE);
 	gtk_widget_show(vbox);
 
 	gtkconv->lower_hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
@@ -4322,20 +4403,15 @@
 	gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
 	gtk_widget_show(frame);
 
-	g_signal_connect(G_OBJECT(gtkconv->entry), "populate-popup",
-					 G_CALLBACK(entry_popup_menu_cb), gtkconv);
-
 	gtk_widget_set_name(gtkconv->entry, "pidgin_conv_entry");
 	gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->entry),
-								 purple_account_get_protocol_name(conv->account));
+			purple_account_get_protocol_name(conv->account));
 	gtk_widget_set_size_request(gtkconv->lower_hbox, -1,
-			purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/entry_height"));
-	gtkconv->entry_buffer =
-		gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->entry));
-	g_object_set_data(G_OBJECT(gtkconv->entry_buffer), "user_data", gtkconv);
-	g_signal_connect_swapped(G_OBJECT(gtkconv->entry_buffer), "changed",
-                                 G_CALLBACK(resize_imhtml_cb), gtkconv);
-
+			chat ? purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/chat/entry_height") :
+			purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/entry_height"));
+
+	g_signal_connect(G_OBJECT(gtkconv->entry), "populate-popup",
+	                 G_CALLBACK(entry_popup_menu_cb), gtkconv);
 	g_signal_connect(G_OBJECT(gtkconv->entry), "key_press_event",
 	                 G_CALLBACK(entry_key_press_cb), gtkconv);
 	g_signal_connect_after(G_OBJECT(gtkconv->entry), "message_send",
@@ -4345,129 +4421,28 @@
 	g_signal_connect(G_OBJECT(gtkconv->lower_hbox), "size-allocate",
 	                 G_CALLBACK(size_allocate_cb), gtkconv);
 
-	default_formatize(gtkconv);
-
-	/*
-	 * Focus for chat windows should be as follows:
-	 * Tab title -> chat topic -> conversation scrollback -> user list ->
-	 *   user list buttons -> entry -> buttons at bottom
-	 */
-	focus_chain = g_list_prepend(focus_chain, gtkconv->entry);
-	gtk_container_set_focus_chain(GTK_CONTAINER(vbox), focus_chain);
-
-	return vpaned;
-}
-
-static GtkWidget *
-setup_im_pane(PidginConversation *gtkconv)
-{
-	PurpleConversation *conv = gtkconv->active_conv;
-	GtkWidget *frame;
-	GtkWidget *imhtml_sw;
-	GtkPolicyType imhtml_sw_hscroll;
-	GtkWidget *paned;
-	GtkWidget *vbox;
-	GtkWidget *vbox2;
-	GList *focus_chain = NULL;
-
-	/* Setup the outer pane */
-	paned = gtk_vpaned_new();
-	gtk_widget_show(paned);
-
-	/* Setup the top part of the pane */
-	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
-	gtk_paned_pack1(GTK_PANED(paned), vbox, TRUE, TRUE);
-	gtk_widget_show(vbox);
-
-	/* Setup the gtkimhtml widget */
-	frame = pidgin_create_imhtml(FALSE, &gtkconv->imhtml, NULL, &imhtml_sw);
-	gtk_widget_set_name(gtkconv->imhtml, "pidgin_conv_imhtml");
-	gtk_imhtml_show_comments(GTK_IMHTML(gtkconv->imhtml),TRUE);
-	gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
-	gtk_widget_show(frame);
-	gtk_scrolled_window_get_policy(GTK_SCROLLED_WINDOW(imhtml_sw),
-	                               &imhtml_sw_hscroll, NULL);
-	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(imhtml_sw),
-	                               imhtml_sw_hscroll, GTK_POLICY_ALWAYS);
-
-	gtk_widget_set_size_request(gtkconv->imhtml,
-			purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/default_width"),
-			purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/default_height"));
-	g_signal_connect(G_OBJECT(gtkconv->imhtml), "size-allocate",
-	                 G_CALLBACK(size_allocate_cb), gtkconv);
-
-	g_signal_connect_after(G_OBJECT(gtkconv->imhtml), "button_press_event",
-	                       G_CALLBACK(entry_stop_rclick_cb), NULL);
-	g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_press_event",
-	                 G_CALLBACK(refocus_entry_cb), gtkconv);
-	g_signal_connect(G_OBJECT(gtkconv->imhtml), "key_release_event",
-	                 G_CALLBACK(refocus_entry_cb), gtkconv);
-
-	/* Setup the bottom half of the conversation window */
-	vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
-	gtk_paned_pack2(GTK_PANED(paned), vbox2, FALSE, TRUE);
-	gtk_widget_show(vbox2);
-
-	gtkconv->lower_hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
-	gtk_box_pack_start(GTK_BOX(vbox2), gtkconv->lower_hbox, TRUE, TRUE, 0);
-	gtk_widget_show(gtkconv->lower_hbox);
-
-	vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
-	gtk_box_pack_end(GTK_BOX(gtkconv->lower_hbox), vbox2, TRUE, TRUE, 0);
-	gtk_widget_show(vbox2);
-
-	/* Setup the toolbar, entry widget and all signals */
-	frame = pidgin_create_imhtml(TRUE, &gtkconv->entry, &gtkconv->toolbar, NULL);
-	gtk_box_pack_start(GTK_BOX(vbox2), frame, TRUE, TRUE, 0);
-	gtk_widget_show(frame);
-
-	g_signal_connect(G_OBJECT(gtkconv->entry), "populate-popup",
-					 G_CALLBACK(entry_popup_menu_cb), gtkconv);
-
-	gtk_widget_set_name(gtkconv->entry, "pidgin_conv_entry");
-	gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->entry),
-								 purple_account_get_protocol_name(conv->account));
-	gtk_widget_set_size_request(gtkconv->lower_hbox, -1,
-			purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/im/entry_height"));
 	gtkconv->entry_buffer =
 		gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->entry));
 	g_object_set_data(G_OBJECT(gtkconv->entry_buffer), "user_data", gtkconv);
 
-	g_signal_connect(G_OBJECT(gtkconv->entry), "key_press_event",
-	                 G_CALLBACK(entry_key_press_cb), gtkconv);
-	g_signal_connect_after(G_OBJECT(gtkconv->entry), "message_send",
-	                       G_CALLBACK(send_cb), gtkconv);
-	g_signal_connect_after(G_OBJECT(gtkconv->entry), "button_press_event",
-	                       G_CALLBACK(entry_stop_rclick_cb), NULL);
-	g_signal_connect(G_OBJECT(gtkconv->lower_hbox), "size-allocate",
-	                 G_CALLBACK(size_allocate_cb), gtkconv);
-
-	g_signal_connect(G_OBJECT(gtkconv->entry_buffer), "insert_text",
-	                 G_CALLBACK(insert_text_cb), gtkconv);
-	g_signal_connect(G_OBJECT(gtkconv->entry_buffer), "delete_range",
-	                 G_CALLBACK(delete_text_cb), gtkconv);
+	if (!chat) {
+		/* For sending typing notifications for IMs */
+		g_signal_connect(G_OBJECT(gtkconv->entry_buffer), "insert_text",
+						 G_CALLBACK(insert_text_cb), gtkconv);
+		g_signal_connect(G_OBJECT(gtkconv->entry_buffer), "delete_range",
+						 G_CALLBACK(delete_text_cb), gtkconv);
+		gtkconv->u.im->typing_timer = 0;
+		gtkconv->u.im->animate = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/im/animate_buddy_icons");
+		gtkconv->u.im->show_icon = TRUE;
+	}
+
 	g_signal_connect_swapped(G_OBJECT(gtkconv->entry_buffer), "changed",
 				 G_CALLBACK(resize_imhtml_cb), gtkconv);
 
-	/* had to move this after the imtoolbar is attached so that the
-	 * signals get fired to toggle the buttons on the toolbar as well.
-	 */
 	default_formatize(gtkconv);
-
 	g_signal_connect_after(G_OBJECT(gtkconv->entry), "format_function_clear",
-						   G_CALLBACK(clear_formatting_cb), gtkconv);
-
-	gtkconv->u.im->animate = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/im/animate_buddy_icons");
-	gtkconv->u.im->show_icon = TRUE;
-
-	/*
-	 * Focus for IM windows should be as follows:
-	 * Tab title -> conversation scrollback -> entry
-	 */
-	focus_chain = g_list_prepend(focus_chain, gtkconv->entry);
-	gtk_container_set_focus_chain(GTK_CONTAINER(vbox2), focus_chain);
-
-	gtkconv->u.im->typing_timer = 0;
+	                       G_CALLBACK(clear_formatting_cb), gtkconv);
+
 	return paned;
 }
 
@@ -4663,12 +4638,10 @@
 
 	if (conv_type == PURPLE_CONV_TYPE_IM) {
 		gtkconv->u.im = g_malloc0(sizeof(PidginImPane));
-
-		pane = setup_im_pane(gtkconv);
 	} else if (conv_type == PURPLE_CONV_TYPE_CHAT) {
 		gtkconv->u.chat = g_malloc0(sizeof(PidginChatPane));
-		pane = setup_chat_pane(gtkconv);
-	}
+	}
+	pane = setup_common_pane(gtkconv);
 
 	gtk_imhtml_set_format_functions(GTK_IMHTML(gtkconv->imhtml),
 			gtk_imhtml_get_format_functions(GTK_IMHTML(gtkconv->imhtml)) | GTK_IMHTML_IMAGE);
@@ -5066,7 +5039,11 @@
 	g_return_if_fail(gc != NULL);
 
 	/* Make sure URLs are clickable */
-	displaying = purple_markup_linkify(message);
+	if(flags & PURPLE_MESSAGE_NO_LINKIFY)
+		displaying = g_strdup(message);
+	else
+		displaying = purple_markup_linkify(message);
+
 	plugin_return = GPOINTER_TO_INT(purple_signal_emit_return_1(
 							pidgin_conversations_get_handle(), (type == PURPLE_CONV_TYPE_IM ?
 							"displaying-im-msg" : "displaying-chat-msg"),
@@ -5419,7 +5396,7 @@
 	gtkconv = PIDGIN_CONVERSATION(conv);
 	gtkchat = gtkconv->u.chat;
 
-	num_users = g_list_length(purple_conv_chat_get_users(chat));
+	num_users = g_list_length((GList *)purple_conv_chat_get_users(chat));
 
 	g_snprintf(tmp, sizeof(tmp),
 			   ngettext("%d person in room", "%d people in room",
@@ -5513,7 +5490,7 @@
 	gtkconv = PIDGIN_CONVERSATION(conv);
 	gtkchat = gtkconv->u.chat;
 
-	num_users = g_list_length(purple_conv_chat_get_users(chat));
+	num_users = g_list_length((GList *)purple_conv_chat_get_users(chat));
 
 	for (l = users; l != NULL; l = l->next) {
 		model = gtk_tree_view_get_model(GTK_TREE_VIEW(gtkchat->list));
@@ -5897,6 +5874,8 @@
 			gtk_widget_hide(win->menu.add);
 		}
 
+		gtk_widget_show(win->menu.insert_link);
+		gtk_widget_show(win->menu.insert_image);
 		gtk_widget_show(win->menu.show_icon);
 	} else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
 		/* Show stuff that applies to Chats, hide stuff that applies to IMs */
@@ -5922,6 +5901,8 @@
 			gtk_widget_show(win->menu.remove);
 		}
 
+		gtk_widget_show(win->menu.insert_link);
+		gtk_widget_show(win->menu.insert_image);
 	}
 
 	/*
@@ -5963,6 +5944,8 @@
 		gtk_widget_set_sensitive(win->menu.add_pounce, TRUE);
 		gtk_widget_set_sensitive(win->menu.get_info, (prpl_info->get_info != NULL));
 		gtk_widget_set_sensitive(win->menu.invite, (prpl_info->chat_invite != NULL));
+		gtk_widget_set_sensitive(win->menu.insert_link, (conv->features & PURPLE_CONNECTION_HTML));
+		gtk_widget_set_sensitive(win->menu.insert_image, (prpl_info->options & OPT_PROTO_IM_IMAGE) && !(conv->features & PURPLE_CONNECTION_NO_IMAGES));
 
 		if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM)
 		{
@@ -5997,6 +5980,8 @@
 		gtk_widget_set_sensitive(win->menu.alias, FALSE);
 		gtk_widget_set_sensitive(win->menu.add, FALSE);
 		gtk_widget_set_sensitive(win->menu.remove, FALSE);
+		gtk_widget_set_sensitive(win->menu.insert_link, TRUE);
+		gtk_widget_set_sensitive(win->menu.insert_image, FALSE);
 	}
 
 	/*
@@ -6502,7 +6487,7 @@
 close_on_tabs_pref_cb(const char *name, PurplePrefType type,
 					  gconstpointer value, gpointer data)
 {
-	GList *l;
+	const GList *l;
 	PurpleConversation *conv;
 	PidginConversation *gtkconv;
 
@@ -6526,7 +6511,7 @@
 				   gconstpointer value, gpointer data)
 {
 #ifdef USE_GTKSPELL
-	GList *cl;
+	const GList *cl;
 	PurpleConversation *conv;
 	PidginConversation *gtkconv;
 	GtkSpell *spell;
@@ -6573,7 +6558,7 @@
 show_timestamps_pref_cb(const char *name, PurplePrefType type,
 						gconstpointer value, gpointer data)
 {
-	GList *l;
+	const GList *l;
 	PurpleConversation *conv;
 	PidginConversation *gtkconv;
 	PidginWindow *win;
@@ -6601,7 +6586,7 @@
 show_formatting_toolbar_pref_cb(const char *name, PurplePrefType type,
 								gconstpointer value, gpointer data)
 {
-	GList *l;
+	const GList *l;
 	PurpleConversation *conv;
 	PidginConversation *gtkconv;
 	PidginWindow *win;
@@ -6631,7 +6616,7 @@
 animate_buddy_icons_pref_cb(const char *name, PurplePrefType type,
 							gconstpointer value, gpointer data)
 {
-	GList *l;
+	const GList *l;
 	PurpleConversation *conv;
 	PidginConversation *gtkconv;
 	PidginWindow *win;
@@ -6658,7 +6643,7 @@
 show_buddy_icons_pref_cb(const char *name, PurplePrefType type,
 						 gconstpointer value, gpointer data)
 {
-	GList *l;
+	const GList *l;
 
 	for (l = purple_get_conversations(); l != NULL; l = l->next) {
 		PurpleConversation *conv = l->data;
@@ -6784,7 +6769,7 @@
 static void
 account_signed_off_cb(PurpleConnection *gc, gpointer event)
 {
-	GList *iter;
+	const GList *iter;
 
 	for (iter = purple_get_conversations(); iter; iter = iter->next)
 	{
@@ -7973,6 +7958,7 @@
 
 	generate_send_to_items(win);
 	regenerate_options_items(win);
+	regenerate_plugins_items(win);
 
 	pidgin_conv_switch_active_conversation(conv);
 
@@ -8043,6 +8029,12 @@
 	prpl_lists = g_hash_table_new(g_str_hash, g_str_equal);
 }
 
+static void
+plugin_changed_cb(PurplePlugin *p, gpointer data)
+{
+	regenerate_plugins_items(data);
+}
+
 PidginWindow *
 pidgin_conv_window_new()
 {
@@ -8056,10 +8048,7 @@
 	window_list = g_list_append(window_list, win);
 
 	/* Create the window. */
-	win->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-	gtk_window_set_role(GTK_WINDOW(win->window), "conversation");
-	gtk_window_set_resizable(GTK_WINDOW(win->window), TRUE);
-	gtk_container_set_border_width(GTK_CONTAINER(win->window), 0);
+	win->window = pidgin_create_window(NULL, 0, "conversation", TRUE);
 	GTK_WINDOW(win->window)->allow_shrink = TRUE;
 
 	if (available_list == NULL) {
@@ -8117,6 +8106,13 @@
 
 	gtk_widget_show(testidea);
 
+	/* Update the plugin actions when plugins are (un)loaded */
+	purple_signal_connect(purple_plugins_get_handle(), "plugin-load",
+			win, PURPLE_CALLBACK(plugin_changed_cb), win);
+	purple_signal_connect(purple_plugins_get_handle(), "plugin-unload",
+			win, PURPLE_CALLBACK(plugin_changed_cb), win);
+
+
 #ifdef _WIN32
 	g_signal_connect(G_OBJECT(win->window), "show",
 	                 G_CALLBACK(winpidgin_ensure_onscreen), win->window);
@@ -8156,6 +8152,7 @@
 	g_object_unref(G_OBJECT(win->menu.item_factory));
 
 	purple_notify_close_with_handle(win);
+	purple_signals_disconnect_by_handle(win);
 
 	g_free(win);
 }
--- a/pidgin/gtkdialogs.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkdialogs.c	Tue Jun 12 21:21:37 2007 +0000
@@ -823,7 +823,7 @@
 		found = pidgin_dialogs_ee(username);
 
 	if (!found && username != NULL && *username != '\0' && account != NULL)
-		serv_get_info(purple_account_get_connection(account), username);
+		pidgin_retrieve_user_info(purple_account_get_connection(account), username);
 
 	g_free(username);
 }
--- a/pidgin/gtkdialogs.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkdialogs.h	Tue Jun 12 21:21:37 2007 +0000
@@ -36,10 +36,15 @@
 void pidgin_dialogs_im_with_user(PurpleAccount *, const char *);
 void pidgin_dialogs_info(void);
 void pidgin_dialogs_log(void);
+
+/**
+ * @deprecated This function is no longer used and will be removed in
+ *             Pidgin 3.0.0 unless there is sufficient demand to keep it.
+ */
 void pidgin_dialogs_alias_contact(PurpleContact *);
+
 void pidgin_dialogs_alias_buddy(PurpleBuddy *);
 void pidgin_dialogs_alias_chat(PurpleChat *);
-
 void pidgin_dialogs_remove_buddy(PurpleBuddy *);
 void pidgin_dialogs_remove_group(PurpleGroup *);
 void pidgin_dialogs_remove_chat(PurpleChat *);
--- a/pidgin/gtkdocklet-x11.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkdocklet-x11.c	Tue Jun 12 21:21:37 2007 +0000
@@ -281,7 +281,7 @@
 	 * The x11 docklet tracks whether it successfully embedded in a pref and
 	 * allows for a longer timeout period if it successfully embedded the last
 	 * time it was run. This should hopefully solve problems with the buddy
-	 * list not properly starting hidden when gaim is started on login.
+	 * list not properly starting hidden when Pidgin is started on login.
 	 */
 	if(!recreate) {
 		pidgin_docklet_embedded();
--- a/pidgin/gtkdocklet.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkdocklet.c	Tue Jun 12 21:21:37 2007 +0000
@@ -109,7 +109,8 @@
 static gboolean
 docklet_update_status()
 {
-	GList *convs, *l;
+	GList *convs;
+	const GList *l;
 	int count;
 	PurpleSavedStatus *saved_status;
 	PurpleStatusPrimitive newstatus = PURPLE_STATUS_OFFLINE;
@@ -213,8 +214,7 @@
 static gboolean
 online_account_supports_chat()
 {
-	GList *c = NULL;
-	c = purple_connections_get_all();
+	const GList *c = purple_connections_get_all();
 
 	while(c != NULL) {
 		PurpleConnection *gc = c->data;
--- a/pidgin/gtkeventloop.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkeventloop.c	Tue Jun 12 21:21:37 2007 +0000
@@ -120,7 +120,11 @@
 	pidgin_input_add,
 	g_source_remove,
 	NULL, /* input_get_error */
+#if GLIB_CHECK_VERSION(2,14,0)
+	g_timeout_add_seconds,
+#else
 	NULL,
+#endif
 	NULL,
 	NULL,
 	NULL
--- a/pidgin/gtkft.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkft.c	Tue Jun 12 21:21:37 2007 +0000
@@ -758,10 +758,7 @@
 		purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/filetransfer/clear_finished");
 
 	/* Create the window. */
-	dialog->window = window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-	gtk_window_set_role(GTK_WINDOW(window), "file transfer");
-	gtk_window_set_title(GTK_WINDOW(window), _("File Transfers"));
-	gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER);
+	dialog->window = window = pidgin_create_window(_("File Transfers"), PIDGIN_HIG_BORDER, "file transfer", TRUE);
 
 	g_signal_connect(G_OBJECT(window), "delete_event",
 					 G_CALLBACK(delete_win_cb), dialog);
--- a/pidgin/gtkimhtml.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkimhtml.c	Tue Jun 12 21:21:37 2007 +0000
@@ -27,10 +27,15 @@
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
+
+#include "pidgin.h"
+
 #include "debug.h"
 #include "util.h"
 #include "gtkimhtml.h"
 #include "gtksourceiter.h"
+#include "gtksourceundomanager.h"
+#include "gtksourceview-marshal.h"
 #include <gtk/gtk.h>
 #include <glib/gerror.h>
 #include <gdk/gdkkeysyms.h>
@@ -136,6 +141,8 @@
 	CLEAR_FORMAT,
 	UPDATE_FORMAT,
 	MESSAGE_SEND,
+	UNDO,
+	REDO,
 	LAST_SIGNAL
 };
 static guint signals [LAST_SIGNAL] = { 0 };
@@ -1150,6 +1157,23 @@
 	return FALSE;
 }
 
+static void
+gtk_imhtml_undo(GtkIMHtml *imhtml) {
+	g_return_if_fail(GTK_IS_IMHTML(imhtml));
+	g_return_if_fail(imhtml->editable);
+	
+	gtk_source_undo_manager_undo(imhtml->undo_manager);
+}
+
+static void
+gtk_imhtml_redo(GtkIMHtml *imhtml) {
+	g_return_if_fail(GTK_IS_IMHTML(imhtml));
+	g_return_if_fail(imhtml->editable);
+	
+	gtk_source_undo_manager_redo(imhtml->undo_manager);
+
+}
+
 static gboolean imhtml_message_send(GtkIMHtml *imhtml)
 {
 	return FALSE;
@@ -1228,6 +1252,7 @@
 	g_queue_free(imhtml->animations);
 	g_free(imhtml->protocol_name);
 	g_free(imhtml->search_string);
+	g_object_unref(imhtml->undo_manager);
 	G_OBJECT_CLASS(parent_class)->finalize (object);
 	if (clipboard_selection)
 		gtk_clipboard_set_with_owner(clipboard_selection,
@@ -1297,10 +1322,32 @@
 					     NULL,
 					     0, g_cclosure_marshal_VOID__VOID,
 					     G_TYPE_NONE, 0);
+        signals [UNDO] = g_signal_new ("undo",
+                        		      G_TYPE_FROM_CLASS (klass),
+		                              G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                		              G_STRUCT_OFFSET (GtkIMHtmlClass, undo),
+		                              NULL,
+		                              NULL,
+                		              gtksourceview_marshal_VOID__VOID,
+		                              G_TYPE_NONE,
+		                              0);
+        signals [REDO] = g_signal_new ("redo",
+                        		      G_TYPE_FROM_CLASS (klass),
+		                              G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		                              G_STRUCT_OFFSET (GtkIMHtmlClass, redo),
+		                              NULL,
+		                              NULL,
+		                              gtksourceview_marshal_VOID__VOID,
+		                              G_TYPE_NONE,
+		                              0);
+
+
 
 	klass->toggle_format = imhtml_toggle_format;
 	klass->message_send = imhtml_message_send;
 	klass->clear_format = imhtml_clear_formatting;
+	klass->undo = gtk_imhtml_undo;
+	klass->redo = gtk_imhtml_redo;
 
 	gobject_class->finalize = gtk_imhtml_finalize;
 	widget_class->drag_motion = gtk_text_view_drag_motion;
@@ -1325,12 +1372,17 @@
 	gtk_binding_entry_add_signal (binding_set, GDK_r, GDK_CONTROL_MASK, "format_function_clear", 0);
 	gtk_binding_entry_add_signal (binding_set, GDK_KP_Enter, 0, "message_send", 0);
 	gtk_binding_entry_add_signal (binding_set, GDK_Return, 0, "message_send", 0);
+        gtk_binding_entry_add_signal (binding_set, GDK_z, GDK_CONTROL_MASK, "undo", 0);
+        gtk_binding_entry_add_signal (binding_set, GDK_z, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "redo", 0);
+        gtk_binding_entry_add_signal (binding_set, GDK_F14, 0, "undo", 0);
+
 }
 
 static void gtk_imhtml_init (GtkIMHtml *imhtml)
 {
 	GtkTextIter iter;
 	imhtml->text_buffer = gtk_text_buffer_new(NULL);
+	imhtml->undo_manager = gtk_source_undo_manager_new(imhtml->text_buffer);
 	gtk_text_buffer_get_end_iter (imhtml->text_buffer, &iter);
 	gtk_text_view_set_buffer(GTK_TEXT_VIEW(imhtml), imhtml->text_buffer);
 	gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(imhtml), GTK_WRAP_WORD_CHAR);
@@ -3113,14 +3165,13 @@
 GtkIMHtmlScalable *gtk_imhtml_image_new(GdkPixbuf *img, const gchar *filename, int id)
 {
 	GtkIMHtmlImage *im_image = g_malloc(sizeof(GtkIMHtmlImage));
-	GtkImage *image = GTK_IMAGE(gtk_image_new_from_pixbuf(img));
 
 	GTK_IMHTML_SCALABLE(im_image)->scale = gtk_imhtml_image_scale;
 	GTK_IMHTML_SCALABLE(im_image)->add_to = gtk_imhtml_image_add_to;
 	GTK_IMHTML_SCALABLE(im_image)->free = gtk_imhtml_image_free;
 
 	im_image->pixbuf = img;
-	im_image->image = image;
+	im_image->image = GTK_IMAGE(gtk_image_new_from_pixbuf(im_image->pixbuf));
 	im_image->width = gdk_pixbuf_get_width(img);
 	im_image->height = gdk_pixbuf_get_height(img);
 	im_image->mark = NULL;
@@ -3132,6 +3183,76 @@
 	return GTK_IMHTML_SCALABLE(im_image);
 }
 
+static gboolean
+animate_image_cb(gpointer data)
+{
+	GtkIMHtmlImage *im_image;
+	int width, height;
+	int delay;
+
+	im_image = data;
+
+	/* Update the pointer to this GdkPixbuf frame of the animation */
+	g_object_unref(G_OBJECT(im_image->pixbuf));
+	gdk_pixbuf_animation_iter_advance(GTK_IMHTML_ANIMATION(im_image)->iter, NULL);
+	im_image->pixbuf = gdk_pixbuf_animation_iter_get_pixbuf(GTK_IMHTML_ANIMATION(im_image)->iter);
+	g_object_ref(G_OBJECT(im_image->pixbuf));
+
+	/* Update the displayed GtkImage */
+	width = gdk_pixbuf_get_width(gtk_image_get_pixbuf(im_image->image));
+	height = gdk_pixbuf_get_height(gtk_image_get_pixbuf(im_image->image));
+	if (width > 0 && height > 0)
+	{
+		/* Need to scale the new frame to the same size as the old frame */
+		GdkPixbuf *tmp;
+		tmp = gdk_pixbuf_scale_simple(im_image->pixbuf, width, height, GDK_INTERP_BILINEAR);
+		gtk_image_set_from_pixbuf(im_image->image, tmp);
+		g_object_unref(G_OBJECT(tmp));
+	} else {
+		/* Display at full-size */
+		gtk_image_set_from_pixbuf(im_image->image, im_image->pixbuf);
+	}
+
+	delay = MIN(gdk_pixbuf_animation_iter_get_delay_time(GTK_IMHTML_ANIMATION(im_image)->iter), 100);
+	GTK_IMHTML_ANIMATION(im_image)->timer = g_timeout_add(delay, animate_image_cb, im_image);
+
+	return FALSE;
+}
+
+GtkIMHtmlScalable *gtk_imhtml_animation_new(GdkPixbufAnimation *anim, const gchar *filename, int id)
+{
+	GtkIMHtmlImage *im_image = g_malloc(sizeof(GtkIMHtmlAnimation));
+
+	GTK_IMHTML_SCALABLE(im_image)->scale = gtk_imhtml_image_scale;
+	GTK_IMHTML_SCALABLE(im_image)->add_to = gtk_imhtml_image_add_to;
+	GTK_IMHTML_SCALABLE(im_image)->free = gtk_imhtml_animation_free;
+
+	GTK_IMHTML_ANIMATION(im_image)->anim = anim;
+	if (gdk_pixbuf_animation_is_static_image(anim)) {
+		GTK_IMHTML_ANIMATION(im_image)->iter = NULL;
+		im_image->pixbuf = gdk_pixbuf_animation_get_static_image(anim);
+		GTK_IMHTML_ANIMATION(im_image)->timer = 0;
+	} else {
+		int delay;
+		GTK_IMHTML_ANIMATION(im_image)->iter = gdk_pixbuf_animation_get_iter(anim, NULL);
+		im_image->pixbuf = gdk_pixbuf_animation_iter_get_pixbuf(GTK_IMHTML_ANIMATION(im_image)->iter);
+		delay = MIN(gdk_pixbuf_animation_iter_get_delay_time(GTK_IMHTML_ANIMATION(im_image)->iter), 100);
+		GTK_IMHTML_ANIMATION(im_image)->timer = g_timeout_add(delay, animate_image_cb, im_image);
+	}
+	im_image->image = GTK_IMAGE(gtk_image_new_from_pixbuf(im_image->pixbuf));
+	im_image->width = gdk_pixbuf_animation_get_width(anim);
+	im_image->height = gdk_pixbuf_animation_get_height(anim);
+	im_image->mark = NULL;
+	im_image->filename = g_strdup(filename);
+	im_image->id = id;
+	im_image->filesel = NULL;
+
+	g_object_ref(anim);
+	g_object_ref(im_image->pixbuf);
+
+	return GTK_IMHTML_SCALABLE(im_image);
+}
+
 void gtk_imhtml_image_scale(GtkIMHtmlScalable *scale, int width, int height)
 {
 	GtkIMHtmlImage *im_image = (GtkIMHtmlImage *)scale;
@@ -3390,7 +3511,6 @@
 static gboolean gtk_imhtml_smiley_clicked(GtkWidget *w, GdkEvent *event, GtkIMHtmlSmiley *smiley)
 {
 	GdkPixbufAnimation *anim = NULL;
-	GdkPixbuf *pix = NULL;
 	GtkIMHtmlScalable *image = NULL;
 	gboolean ret;
 	
@@ -3401,11 +3521,9 @@
 	if (!anim)
 		return FALSE;
 
-	pix = gdk_pixbuf_animation_get_static_image(anim);
-	image = gtk_imhtml_image_new(pix, smiley->smile, 0);
+	image = gtk_imhtml_animation_new(anim, smiley->smile, 0);
 	ret = gtk_imhtml_image_clicked(w, event, (GtkIMHtmlImage*)image);
 	g_object_set_data_full(G_OBJECT(w), "image-data", image, (GDestroyNotify)gtk_imhtml_image_free);
-	g_object_unref(G_OBJECT(pix));
 	return ret;
 }
 
@@ -3420,6 +3538,19 @@
 	g_free(scale);
 }
 
+void gtk_imhtml_animation_free(GtkIMHtmlScalable *scale)
+{
+	GtkIMHtmlAnimation *animation = (GtkIMHtmlAnimation *)scale;
+
+	if (animation->timer > 0)
+		g_source_remove(animation->timer);
+	if (animation->iter != NULL)
+		g_object_unref(animation->iter);
+	g_object_unref(animation->anim);
+
+	gtk_imhtml_image_free(scale);
+}
+
 void gtk_imhtml_image_add_to(GtkIMHtmlScalable *scale, GtkIMHtml *imhtml, GtkTextIter *iter)
 {
 	GtkIMHtmlImage *image = (GtkIMHtmlImage *)scale;
@@ -4500,7 +4631,7 @@
 
 void gtk_imhtml_insert_image_at_iter(GtkIMHtml *imhtml, int id, GtkTextIter *iter)
 {
-	GdkPixbuf *pixbuf = NULL;
+	GdkPixbufAnimation *anim = NULL;
 	const char *filename = NULL;
 	gpointer image;
 	GdkRectangle rect;
@@ -4527,28 +4658,33 @@
 			GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
 			gdk_pixbuf_loader_write(loader, data, len, NULL);
 			gdk_pixbuf_loader_close(loader, NULL);
-			pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
-			if (pixbuf)
-				g_object_ref(G_OBJECT(pixbuf));
+			anim = gdk_pixbuf_loader_get_animation(loader);
+			if (anim)
+				g_object_ref(G_OBJECT(anim));
 			g_object_unref(G_OBJECT(loader));
 		}
 
 	}
 
-	if (pixbuf) {
+	if (anim) {
 		struct im_image_data *t = g_new(struct im_image_data, 1);
 		filename = imhtml->funcs->image_get_filename(image);
 		imhtml->funcs->image_ref(id);
 		t->id = id;
 		t->mark = gtk_text_buffer_create_mark(imhtml->text_buffer, NULL, iter, TRUE);
 		imhtml->im_images = g_slist_prepend(imhtml->im_images, t);
+		scalable = gtk_imhtml_animation_new(anim, filename, id);
+		g_object_unref(G_OBJECT(anim));
 	} else {
+		GdkPixbuf *pixbuf;
 		pixbuf = gtk_widget_render_icon(GTK_WIDGET(imhtml), GTK_STOCK_MISSING_IMAGE,
 						GTK_ICON_SIZE_BUTTON, "gtkimhtml-missing-image");
+		scalable = gtk_imhtml_image_new(pixbuf, filename, id);
+		g_object_unref(G_OBJECT(pixbuf));
 	}
 
 	sd = g_new(struct scalable_data, 1);
-	sd->scalable = scalable = gtk_imhtml_image_new(pixbuf, filename, id);
+	sd->scalable = scalable;
 	sd->mark = gtk_text_buffer_create_mark(imhtml->text_buffer, NULL, iter, TRUE);
 	gtk_text_view_get_visible_rect(GTK_TEXT_VIEW(imhtml), &rect);
 	scalable->add_to(scalable, imhtml, iter);
@@ -4556,8 +4692,6 @@
 		gtk_text_view_get_right_margin(GTK_TEXT_VIEW(imhtml));
 	scalable->scale(scalable, rect.width - minus, rect.height);
 	imhtml->scalables = g_list_append(imhtml->scalables, sd);
-
-	g_object_unref(G_OBJECT(pixbuf));
 }
 
 static const gchar *tag_to_html_start(GtkTextTag *tag)
@@ -4883,3 +5017,70 @@
 	g_return_if_fail(imhtml != NULL);
 	imhtml->funcs = f;
 }
+
+void gtk_imhtml_setup_entry(GtkIMHtml *imhtml, PurpleConnectionFlags flags)
+{
+	if (flags & PURPLE_CONNECTION_HTML) {
+		char color[8];
+		GdkColor fg_color, bg_color;
+
+		gtk_imhtml_set_format_functions(imhtml, GTK_IMHTML_ALL);
+		if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_bold") != imhtml->edit.bold)
+			gtk_imhtml_toggle_bold(imhtml);
+
+		if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_italic") != imhtml->edit.italic)
+			gtk_imhtml_toggle_italic(imhtml);
+
+		if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_underline") != imhtml->edit.underline)
+			gtk_imhtml_toggle_underline(imhtml);
+
+		gtk_imhtml_toggle_fontface(imhtml,
+			purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/font_face"));
+
+		if (!(flags & PURPLE_CONNECTION_NO_FONTSIZE))
+		{
+			int size = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/font_size");
+
+			/* 3 is the default. */
+			if (size != 3)
+				gtk_imhtml_font_set_size(imhtml, size);
+		}
+
+		if(strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor"), "") != 0)
+		{
+			gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor"),
+							&fg_color);
+			g_snprintf(color, sizeof(color), "#%02x%02x%02x",
+									fg_color.red   / 256,
+									fg_color.green / 256,
+									fg_color.blue  / 256);
+		} else
+			strcpy(color, "");
+
+		gtk_imhtml_toggle_forecolor(imhtml, color);
+
+		if(!(flags & PURPLE_CONNECTION_NO_BGCOLOR) &&
+		   strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor"), "") != 0)
+		{
+			gdk_color_parse(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor"),
+							&bg_color);
+			g_snprintf(color, sizeof(color), "#%02x%02x%02x",
+									bg_color.red   / 256,
+									bg_color.green / 256,
+									bg_color.blue  / 256);
+		} else
+			strcpy(color, "");
+
+		gtk_imhtml_toggle_background(imhtml, color);
+
+		if (flags & PURPLE_CONNECTION_FORMATTING_WBFO)
+			gtk_imhtml_set_whole_buffer_formatting_only(imhtml, TRUE);
+		else
+			gtk_imhtml_set_whole_buffer_formatting_only(imhtml, FALSE);
+	} else {
+		imhtml_clear_formatting(imhtml);
+		gtk_imhtml_set_format_functions(imhtml, 0);
+	}
+}
+
+
--- a/pidgin/gtkimhtml.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkimhtml.h	Tue Jun 12 21:21:37 2007 +0000
@@ -27,6 +27,9 @@
 #include <gtk/gtktextview.h>
 #include <gtk/gtktooltips.h>
 #include <gtk/gtkimage.h>
+#include "gtksourceundomanager.h"
+
+#include "connection.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -43,6 +46,7 @@
 #define GTK_IS_IMHTML(obj)         (GTK_CHECK_TYPE ((obj), GTK_TYPE_IMHTML))
 #define GTK_IS_IMHTML_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), GTK_TYPE_IMHTML))
 #define GTK_IMHTML_SCALABLE(obj)   ((GtkIMHtmlScalable *)obj)
+#define GTK_IMHTML_ANIMATION(obj)   ((GtkIMHtmlAnimation *)obj)
 
 typedef struct _GtkIMHtml			GtkIMHtml;
 typedef struct _GtkIMHtmlClass		GtkIMHtmlClass;
@@ -51,6 +55,7 @@
 typedef struct _GtkIMHtmlSmiley		GtkIMHtmlSmiley;
 typedef struct _GtkIMHtmlScalable	GtkIMHtmlScalable;
 typedef struct _GtkIMHtmlImage		GtkIMHtmlImage;
+typedef struct _GtkIMHtmlAnimation	GtkIMHtmlAnimation;
 typedef struct _GtkIMHtmlHr			GtkIMHtmlHr;
 typedef struct _GtkIMHtmlFuncs		GtkIMHtmlFuncs;
 
@@ -126,6 +131,7 @@
 
 	GSList *im_images;
 	GtkIMHtmlFuncs *funcs;
+	GtkSourceUndoManager *undo_manager;
 };
 
 struct _GtkIMHtmlClass {
@@ -137,6 +143,8 @@
 	void (*clear_format)(GtkIMHtml *);
 	void (*update_format)(GtkIMHtml *);
 	gboolean (*message_send)(GtkIMHtml *);
+	void (*undo)(GtkIMHtml *);
+	void (*redo)(GtkIMHtml *);
 };
 
 struct _GtkIMHtmlFontDetail {
@@ -175,8 +183,8 @@
 
 struct _GtkIMHtmlImage {
 	GtkIMHtmlScalable scalable;
-	GtkImage *image;
-	GdkPixbuf *pixbuf;
+	GtkImage *image; /**< Contains the scaled version of this pixbuf. */
+	GdkPixbuf *pixbuf; /**< The original pixbuf, before any scaling. */
 	GtkTextMark *mark;
 	gchar *filename;
 	int width;
@@ -185,6 +193,13 @@
 	GtkWidget *filesel;
 };
 
+struct _GtkIMHtmlAnimation {
+	GtkIMHtmlImage imhtmlimage;
+	GdkPixbufAnimation *anim; /**< The original animation, before any scaling. */
+	GdkPixbufAnimationIter *iter;
+	guint timer;
+};
+
 struct _GtkIMHtmlHr {
 	GtkIMHtmlScalable scalable;
 	GtkWidget *sep;
@@ -405,7 +420,7 @@
 GtkIMHtmlScalable *gtk_imhtml_scalable_new(void);
 
 /**
- * Creates and returns an new GTK+ IM/HTML scalable object with an image.
+ * Creates and returns a new GTK+ IM/HTML scalable object with an image.
  *
  * @param img      A GdkPixbuf of the image to add.
  * @param filename The filename to associate with the image.
@@ -416,19 +431,47 @@
 GtkIMHtmlScalable *gtk_imhtml_image_new(GdkPixbuf *img, const gchar *filename, int id);
 
 /**
+ * Creates and returns a new GTK+ IM/HTML scalable object with an
+ * animated image.
+ *
+ * @param img      A GdkPixbufAnimation of the image to add.
+ * @param filename The filename to associate with the image.
+ * @param id       The id to associate with the image.
+ *
+ * @return A new IM/HTML Scalable object with an image.
+ */
+/*
+ * TODO: All this animation code could be combined much better with
+ *       the image code.  It couldn't be done when it was written
+ *       because it requires breaking backward compatibility.  It
+ *       would be good to do it for 3.0.0.
+ */
+GtkIMHtmlScalable *gtk_imhtml_animation_new(GdkPixbufAnimation *img, const gchar *filename, int id);
+
+/**
  * Destroys and frees a GTK+ IM/HTML scalable image.
  *
  * @param scale The GTK+ IM/HTML scalable.
  */
+/* TODO: Is there any reason this isn't private? */
 void gtk_imhtml_image_free(GtkIMHtmlScalable *scale);
 
 /**
+ * Destroys and frees a GTK+ IM/HTML scalable animation.
+ *
+ * @param scale The GTK+ IM/HTML scalable.
+ */
+/* TODO: Is there any reason this isn't private? */
+void gtk_imhtml_animation_free(GtkIMHtmlScalable *scale);
+
+/**
  * Rescales a GTK+ IM/HTML scalable image to a given size.
  *
  * @param scale  The GTK+ IM/HTML scalable.
  * @param width  The new width.
  * @param height The new height.
  */
+/* TODO: Is there any reason this isn't private? */
 void gtk_imhtml_image_scale(GtkIMHtmlScalable *scale, int width, int height);
 
 /**
@@ -438,6 +481,7 @@
  * @param imhtml The GTK+ IM/HTML.
  * @param iter   The GtkTextIter at which to add the scalable.
  */
+/* TODO: Is there any reason this isn't private? */
 void gtk_imhtml_image_add_to(GtkIMHtmlScalable *scale, GtkIMHtml *imhtml, GtkTextIter *iter);
 
 /**
@@ -786,6 +830,14 @@
  */
 char *gtk_imhtml_get_text(GtkIMHtml *imhtml, GtkTextIter *start, GtkTextIter *stop);
 
+/**
+ * Setup formatting for an imhtml depending on the flags specified.
+ *
+ * @param imhtml  The GTK+ IM/HTML.
+ * @param flags   The connection flag which describes the allowed types of formatting.
+ */
+void gtk_imhtml_setup_entry(GtkIMHtml *imhtml, PurpleConnectionFlags flags);
+
 /*@}*/
 
 #ifdef __cplusplus
--- a/pidgin/gtkimhtmltoolbar.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Tue Jun 12 21:21:37 2007 +0000
@@ -792,7 +792,8 @@
 	g_object_unref(object);
 }
 
-static void update_buttons(GtkIMHtmlToolbar *toolbar) {
+static void update_buttons(GtkIMHtmlToolbar *toolbar)
+{
 	gboolean bold, italic, underline;
 	char *tmp;
 	char *tmp2;
@@ -862,51 +863,53 @@
                     int               *x,
                     int               *y,
                     gboolean          *push_in,
-                    gpointer data)
+                    gpointer          data)
 {
-  GtkRequisition menu_req;
-  GtkTextDirection direction;
-  GdkRectangle monitor;
-  gint monitor_num;
-  GdkScreen *screen;
-  GtkWidget *widget = data;
+	GtkRequisition menu_req;
+	GtkTextDirection direction;
+	GdkRectangle monitor;
+	gint monitor_num;
+	GdkScreen *screen;
+	GtkWidget *widget = GTK_WIDGET(data);
 
-  gtk_widget_size_request (GTK_WIDGET (widget), &menu_req);
+	gtk_widget_size_request (GTK_WIDGET (menu), &menu_req);
 
-  direction = gtk_widget_get_direction (widget);
+	direction = gtk_widget_get_direction (widget);
 
-  screen = gtk_widget_get_screen (GTK_WIDGET (menu));
-  monitor_num = gdk_screen_get_monitor_at_window (screen, widget->window);
-  if (monitor_num < 0)
-    monitor_num = 0;
-  gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
+	screen = gtk_widget_get_screen (GTK_WIDGET (menu));
+	monitor_num = gdk_screen_get_monitor_at_window (screen, widget->window);
+	if (monitor_num < 0)
+		monitor_num = 0;
+	gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
 
-  gdk_window_get_origin (widget->window, x, y);
-  *x += widget->allocation.x;
-    *y += widget->allocation.y;
+	gdk_window_get_origin (widget->window, x, y);
+	*x += widget->allocation.x;
+	*y += widget->allocation.y;
 
-      if (direction == GTK_TEXT_DIR_LTR)
-	*x += MAX (widget->allocation.width - menu_req.width, 0);
-      else if (menu_req.width > widget->allocation.width)
-        *x -= menu_req.width - widget->allocation.width;
+	if (direction == GTK_TEXT_DIR_LTR)
+		*x += MAX (widget->allocation.width - menu_req.width, 0);
+	else if (menu_req.width > widget->allocation.width)
+		*x -= menu_req.width - widget->allocation.width;
 
-      if ((*y + widget->allocation.height + menu_req.height) <= monitor.y + monitor.height)
-	*y += widget->allocation.height;
-      else if ((*y - menu_req.height) >= monitor.y)
-	*y -= menu_req.height;
-      else if (monitor.y + monitor.height - (*y + widget->allocation.height) > *y)
-	*y += widget->allocation.height;
-      else
-	*y -= menu_req.height;
-  *push_in = FALSE;
+	if ((*y + widget->allocation.height + menu_req.height) <= monitor.y + monitor.height)
+		*y += widget->allocation.height;
+	else if ((*y - menu_req.height) >= monitor.y)
+		*y -= menu_req.height;
+	else if (monitor.y + monitor.height - (*y + widget->allocation.height) > *y)
+		*y += widget->allocation.height;
+	else
+		*y -= menu_req.height;
+	*push_in = FALSE;
 }
 
-static void pidgin_menu_clicked(GtkWidget *button, 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());
 }
 
-static void pidgin_menu_deactivate(GtkWidget *menu, GtkToggleButton *button) {
+static void pidgin_menu_deactivate(GtkWidget *menu, GtkToggleButton *button)
+{
 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), FALSE);
 }
 
@@ -1026,8 +1029,20 @@
 	g_signal_connect(G_OBJECT(button), "clicked",
 			 G_CALLBACK(insert_smiley_cb), toolbar);
 	toolbar->smiley = button;
+}
 
+static void
+button_sensitiveness_changed(GtkWidget *button, gpointer dontcare, GtkWidget *item)
+{
+	gtk_widget_set_sensitive(item, GTK_WIDGET_IS_SENSITIVE(button));
+}
 
+static void
+update_menuitem(GtkToggleButton *button, GtkCheckMenuItem *item)
+{
+	g_signal_handlers_block_by_func(G_OBJECT(item), G_CALLBACK(gtk_button_clicked), button);
+	gtk_check_menu_item_set_active(item, gtk_toggle_button_get_active(button));
+	g_signal_handlers_unblock_by_func(G_OBJECT(item), G_CALLBACK(gtk_button_clicked), button);
 }
 
 static void gtk_imhtmltoolbar_init (GtkIMHtmlToolbar *toolbar)
@@ -1042,6 +1057,25 @@
 	GtkWidget *insert_menu;
 	GtkWidget *button;
 	GtkWidget *sep;
+	int i;
+	struct {
+		const char *label;
+		GtkWidget **button;
+	} buttons[] = {
+		{_("_Bold"), &toolbar->bold},
+		{_("_Italic"), &toolbar->italic},
+		{_("_Underline"), &toolbar->underline},
+		{_("_Larger"), &toolbar->larger_size},
+#if 0
+		{_("_Normal"), &toolbar->normal_size},
+#endif
+		{_("_Smaller"), &toolbar->smaller_size},
+		{_("_Font face"), &toolbar->font},
+		{_("_Foreground color"), &toolbar->fgcolor},
+		{_("_Background color"), &toolbar->bgcolor},
+		{NULL, NULL}
+	};
+
 
 	toolbar->imhtml = NULL;
 	toolbar->font_dialog = NULL;
@@ -1057,7 +1091,7 @@
 
 	gtk_imhtmltoolbar_create_old_buttons(toolbar);
 
-	/* Bold */
+	/* Fonts */
 	font_button = gtk_toggle_button_new();
 	gtk_button_set_relief(GTK_BUTTON(font_button), GTK_RELIEF_NONE);
 	bbox = gtk_hbox_new(FALSE, 3);
@@ -1071,44 +1105,21 @@
 
 	font_menu = gtk_menu_new();
 
-	button = gtk_check_menu_item_new_with_mnemonic(_("_Bold"));
-	g_signal_connect_swapped(G_OBJECT(button), "activate", G_CALLBACK(gtk_button_clicked), toolbar->bold);
-	gtk_menu_shell_append(GTK_MENU_SHELL(font_menu), button);
-
-	button = gtk_check_menu_item_new_with_mnemonic(_("_Italic"));
-	g_signal_connect_swapped(G_OBJECT(button), "activate", G_CALLBACK(gtk_button_clicked), toolbar->italic);
-	gtk_menu_shell_append(GTK_MENU_SHELL(font_menu), button);
-
-	button = gtk_check_menu_item_new_with_mnemonic(_("_Underline"));
-	g_signal_connect_swapped(G_OBJECT(button), "activate", G_CALLBACK(gtk_button_clicked), toolbar->underline);
-	gtk_menu_shell_append(GTK_MENU_SHELL(font_menu), button);
-
-	button = gtk_menu_item_new_with_mnemonic(_("_Larger"));
-	g_signal_connect_swapped(G_OBJECT(button), "activate", G_CALLBACK(gtk_button_clicked), toolbar->larger_size);
-	gtk_menu_shell_append(GTK_MENU_SHELL(font_menu), button);
-
-	button = gtk_menu_item_new_with_mnemonic(_("_Normal"));
-	g_signal_connect_swapped(G_OBJECT(button), "activate", G_CALLBACK(gtk_button_clicked), toolbar->normal_size);
-	gtk_menu_shell_append(GTK_MENU_SHELL(font_menu), button);
-
-	button = gtk_menu_item_new_with_mnemonic(_("_Smaller"));
-	g_signal_connect_swapped(G_OBJECT(button), "activate", G_CALLBACK(gtk_button_clicked), toolbar->smaller_size);
-	gtk_menu_shell_append(GTK_MENU_SHELL(font_menu), button);
-
-	button = gtk_menu_item_new_with_mnemonic(_("_Font face"));
-	g_signal_connect_swapped(G_OBJECT(button), "activate", G_CALLBACK(gtk_button_clicked), toolbar->font);
-	gtk_menu_shell_append(GTK_MENU_SHELL(font_menu), button);
-
-	button = gtk_menu_item_new_with_mnemonic(_("_Foreground color"));
-	g_signal_connect_swapped(G_OBJECT(button), "activate", G_CALLBACK(gtk_button_clicked), toolbar->fgcolor);
-	gtk_menu_shell_append(GTK_MENU_SHELL(font_menu), button);
-
-	button = gtk_menu_item_new_with_mnemonic(_("_Background color"));
-	g_signal_connect_swapped(G_OBJECT(button), "activate", G_CALLBACK(gtk_button_clicked), toolbar->bgcolor);
-	gtk_menu_shell_append(GTK_MENU_SHELL(font_menu), button);
-
+	
+	for (i = 0; buttons[i].label; i++) {
+		GtkWidget *old = *buttons[i].button;
+		GtkWidget *menuitem = gtk_check_menu_item_new_with_mnemonic(buttons[i].label);
+		g_signal_connect_swapped(G_OBJECT(menuitem), "activate",
+				G_CALLBACK(gtk_button_clicked), old);
+		g_signal_connect_after(G_OBJECT(old), "toggled",
+				G_CALLBACK(update_menuitem), menuitem);
+		gtk_menu_shell_append(GTK_MENU_SHELL(font_menu), menuitem);
+		g_signal_connect(G_OBJECT(old), "notify::sensitive",
+				G_CALLBACK(button_sensitiveness_changed), menuitem);
+	}
+  
 	g_signal_connect(G_OBJECT(font_button), "clicked", G_CALLBACK(pidgin_menu_clicked), font_menu);
-	g_signal_connect(G_OBJECT(font_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), font_button);	
+	g_signal_connect(G_OBJECT(font_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), font_button);
 
 	/* Sep */
 	sep = gtk_vseparator_new();
@@ -1135,7 +1146,7 @@
 	gtk_box_pack_start(GTK_BOX(hbox), sep, FALSE, FALSE, 0);
 	gtk_widget_show_all(sep);
 
-	/* Insert Link */
+	/* Insert */
 	insert_button = gtk_toggle_button_new();
 	gtk_button_set_relief(GTK_BUTTON(insert_button), GTK_RELIEF_NONE);
 	bbox = gtk_hbox_new(FALSE, 3);
--- a/pidgin/gtklog.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtklog.c	Tue Jun 12 21:21:37 2007 +0000
@@ -757,7 +757,7 @@
 
 void pidgin_syslog_show()
 {
-	GList *accounts = NULL;
+	const GList *accounts;
 	GList *logs = NULL;
 
 	if (syslog_viewer != NULL) {
--- a/pidgin/gtkmain.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkmain.c	Tue Jun 12 21:21:37 2007 +0000
@@ -127,7 +127,7 @@
 		}
 		g_strfreev(names);
 	} else { /* no name given, use the first account */
-		GList *accounts;
+		const GList *accounts;
 
 		accounts = purple_accounts_get_all();
 		if (accounts != NULL)
@@ -441,7 +441,7 @@
 	char *opt_session_arg = NULL;
 	int dologin_ret = -1;
 	char *search_path;
-	GList *accounts;
+	const GList *accounts;
 #ifdef HAVE_SIGNAL_H
 	int sig_indx;	/* for setting up signal catching */
 	sigset_t sigset;
@@ -456,6 +456,7 @@
 	gboolean gui_check;
 	gboolean debug_enabled;
 	gboolean migration_failed = FALSE;
+	GList *active_accounts;
 
 	struct option long_options[] = {
 		{"config",   required_argument, NULL, 'c'},
@@ -733,6 +734,15 @@
 		abort();
 	}
 
+	if (!purple_core_ensure_single_instance()) {
+		purple_core_quit();
+#ifdef HAVE_SIGNAL_H
+		g_free(segfault_message);
+#endif
+		return 0;
+	}
+		
+
 	/* TODO: Move blist loading into purple_blist_init() */
 	purple_set_blist(purple_blist_new());
 	purple_blist_load();
@@ -819,13 +829,13 @@
 		purple_accounts_restore_current_statuses();
 	}
 
-	if ((accounts = purple_accounts_get_all_active()) == NULL)
+	if ((active_accounts = purple_accounts_get_all_active()) == NULL)
 	{
 		pidgin_accounts_window_show();
 	}
 	else
 	{
-		g_list_free(accounts);
+		g_list_free(active_accounts);
 	}
 
 #ifdef HAVE_STARTUP_NOTIFICATION
--- a/pidgin/gtknotify.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtknotify.c	Tue Jun 12 21:21:37 2007 +0000
@@ -577,10 +577,8 @@
 	char label_text[2048];
 	char *linked_text, *primary_esc, *secondary_esc;
 
-	window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-	gtk_window_set_title(GTK_WINDOW(window), title);
+	window = pidgin_create_window(title, PIDGIN_HIG_BORDER, NULL, TRUE);
 	gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
-	gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER);
 
 	g_signal_connect(G_OBJECT(window), "delete_event",
 					 G_CALLBACK(formatted_close_cb), NULL);
@@ -712,10 +710,8 @@
 	data->results = results;
 
 	/* Create the window */
-	window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-	gtk_window_set_title(GTK_WINDOW(window), (title ? title :_("Search Results")));
+	window = pidgin_create_window(title ? title :_("Search Results"), PIDGIN_HIG_BORDER, NULL, TRUE);
 	gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
-	gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER);
 
 	g_signal_connect_swapped(G_OBJECT(window), "delete_event",
 							 G_CALLBACK(searchresults_close_cb), data);
--- a/pidgin/gtkpounce.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkpounce.c	Tue Jun 12 21:21:37 2007 +0000
@@ -38,6 +38,7 @@
 
 #include "gtkblist.h"
 #include "gtkdialogs.h"
+#include "gtkimhtml.h"
 #include "gtkpounce.h"
 #include "pidginstock.h"
 #include "gtkutils.h"
@@ -241,7 +242,8 @@
 save_pounce_cb(GtkWidget *w, PidginPounceDialog *dialog)
 {
 	const char *name;
-	const char *message, *command, *sound, *reason;
+	const char *command, *sound, *reason;
+	char *message;
 	PurplePounceEvent events   = PURPLE_POUNCE_NONE;
 	PurplePounceOption options = PURPLE_POUNCE_OPTION_NONE;
 
@@ -290,13 +292,16 @@
 		events |= PURPLE_POUNCE_MESSAGE_RECEIVED;
 
 	/* Data fields */
-	message = gtk_entry_get_text(GTK_ENTRY(dialog->send_msg_entry));
+	message = gtk_imhtml_get_markup(GTK_IMHTML(dialog->send_msg_entry));
 	command = gtk_entry_get_text(GTK_ENTRY(dialog->exec_cmd_entry));
 	sound   = gtk_entry_get_text(GTK_ENTRY(dialog->play_sound_entry));
 	reason  = gtk_entry_get_text(GTK_ENTRY(dialog->popup_entry));
 
 	if (*reason == '\0') reason = NULL;
-	if (*message == '\0') message = NULL;
+	if (*message == '\0') {
+		g_free(message);
+		message = NULL;
+	}
 	if (*command == '\0') command = NULL;
 	if (*sound   == '\0') sound   = NULL;
 
@@ -349,6 +354,7 @@
 		gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(dialog->save_pounce)));
 
 	update_pounces();
+	g_free(message);
 
 	delete_win_cb(NULL, NULL, dialog);
 }
@@ -446,6 +452,14 @@
 	{"application/x-im-contact", 0, 1}
 };
 
+static void
+reset_send_msg_entry(PidginPounceDialog *dialog, GtkWidget *dontcare)
+{
+	PurpleAccount *account = pidgin_account_option_menu_get_selected(dialog->account_menu);
+	gtk_imhtml_setup_entry(GTK_IMHTML(dialog->send_msg_entry),
+			(account && account->gc) ? account->gc->flags : PURPLE_CONNECTION_HTML);
+}
+
 void
 pidgin_pounce_editor_show(PurpleAccount *account, const char *name,
 							PurplePounce *cur_pounce)
@@ -462,6 +476,7 @@
 	GtkSizeGroup *sg;
 	GPtrArray *sound_widgets;
 	GPtrArray *exec_widgets;
+	GtkWidget *send_msg_imhtml;
 
 	g_return_if_fail((cur_pounce != NULL) ||
 	                 (account != NULL) ||
@@ -481,7 +496,7 @@
 	}
 	else
 	{
-		GList *connections = purple_connections_get_all();
+		const GList *connections = purple_connections_get_all();
 		PurpleConnection *gc;
 
 		if (connections != NULL)
@@ -498,15 +513,9 @@
 	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
 
 	/* Create the window. */
-	dialog->window = window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+	dialog->window = window = pidgin_create_window((cur_pounce == NULL ? _("New Buddy Pounce") : _("Edit Buddy Pounce")),
+		PIDGIN_HIG_BORDER, "buddy_pounce", FALSE) ;
 	gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
-	gtk_window_set_role(GTK_WINDOW(window), "buddy_pounce");
-	gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
-	gtk_window_set_title(GTK_WINDOW(window),
-						 (cur_pounce == NULL
-						  ? _("New Buddy Pounce") : _("Edit Buddy Pounce")));
-
-	gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER);
 
 	g_signal_connect(G_OBJECT(window), "delete_event",
 					 G_CALLBACK(delete_win_cb), dialog);
@@ -653,7 +662,8 @@
 	dialog->play_sound
 		= gtk_check_button_new_with_mnemonic(_("P_lay a sound"));
 
-	dialog->send_msg_entry    = gtk_entry_new();
+	send_msg_imhtml = pidgin_create_imhtml(TRUE, &dialog->send_msg_entry, NULL, NULL);
+	reset_send_msg_entry(dialog, NULL);
 	dialog->exec_cmd_entry    = gtk_entry_new();
 	dialog->popup_entry       = gtk_entry_new();
 	dialog->exec_cmd_browse   = gtk_button_new_with_mnemonic(_("Brows_e..."));
@@ -661,7 +671,7 @@
 	dialog->play_sound_browse = gtk_button_new_with_mnemonic(_("Br_owse..."));
 	dialog->play_sound_test   = gtk_button_new_with_mnemonic(_("Pre_view"));
 
-	gtk_widget_set_sensitive(dialog->send_msg_entry,    FALSE);
+	gtk_widget_set_sensitive(send_msg_imhtml,           FALSE);
 	gtk_widget_set_sensitive(dialog->exec_cmd_entry,    FALSE);
 	gtk_widget_set_sensitive(dialog->popup_entry,       FALSE);
 	gtk_widget_set_sensitive(dialog->exec_cmd_browse,   FALSE);
@@ -673,8 +683,6 @@
 	gtk_size_group_add_widget(sg, dialog->open_win);
 	gtk_size_group_add_widget(sg, dialog->popup);
 	gtk_size_group_add_widget(sg, dialog->popup_entry);
-	gtk_size_group_add_widget(sg, dialog->send_msg);
-	gtk_size_group_add_widget(sg, dialog->send_msg_entry);
 	gtk_size_group_add_widget(sg, dialog->exec_cmd);
 	gtk_size_group_add_widget(sg, dialog->exec_cmd_entry);
 	gtk_size_group_add_widget(sg, dialog->exec_cmd_browse);
@@ -689,23 +697,23 @@
 					 GTK_FILL, 0, 0, 0);
 	gtk_table_attach(GTK_TABLE(table), dialog->popup_entry,      1, 4, 1, 2,
 					 GTK_FILL, 0, 0, 0);
-	gtk_table_attach(GTK_TABLE(table), dialog->send_msg,         0, 1, 2, 3,
+	gtk_table_attach(GTK_TABLE(table), dialog->send_msg,         0, 4, 2, 3,
 					 GTK_FILL, 0, 0, 0);
-	gtk_table_attach(GTK_TABLE(table), dialog->send_msg_entry,   1, 4, 2, 3,
+	gtk_table_attach(GTK_TABLE(table), send_msg_imhtml,          0, 4, 3, 4,
 					 GTK_FILL, 0, 0, 0);
-	gtk_table_attach(GTK_TABLE(table), dialog->exec_cmd,         0, 1, 3, 4,
+	gtk_table_attach(GTK_TABLE(table), dialog->exec_cmd,         0, 1, 4, 5,
 					 GTK_FILL, 0, 0, 0);
-	gtk_table_attach(GTK_TABLE(table), dialog->exec_cmd_entry,   1, 2, 3, 4,
+	gtk_table_attach(GTK_TABLE(table), dialog->exec_cmd_entry,   1, 2, 4, 5,
 					 GTK_FILL, 0, 0, 0);
-	gtk_table_attach(GTK_TABLE(table), dialog->exec_cmd_browse,   2, 3, 3, 4,
+	gtk_table_attach(GTK_TABLE(table), dialog->exec_cmd_browse,  2, 3, 4, 5,
 					 GTK_FILL | GTK_EXPAND, 0, 0, 0);
-	gtk_table_attach(GTK_TABLE(table), dialog->play_sound,       0, 1, 4, 5,
+	gtk_table_attach(GTK_TABLE(table), dialog->play_sound,       0, 1, 5, 6,
 					 GTK_FILL, 0, 0, 0);
-	gtk_table_attach(GTK_TABLE(table), dialog->play_sound_entry, 1, 2, 4, 5,
+	gtk_table_attach(GTK_TABLE(table), dialog->play_sound_entry, 1, 2, 5, 6,
 					 GTK_FILL, 0, 0, 0);
-	gtk_table_attach(GTK_TABLE(table), dialog->play_sound_browse, 2, 3, 4, 5,
+	gtk_table_attach(GTK_TABLE(table), dialog->play_sound_browse,2, 3, 5, 6,
 					 GTK_FILL | GTK_EXPAND, 0, 0, 0);
-	gtk_table_attach(GTK_TABLE(table), dialog->play_sound_test, 3, 4, 4, 5,
+	gtk_table_attach(GTK_TABLE(table), dialog->play_sound_test,  3, 4, 5, 6,
 					 GTK_FILL | GTK_EXPAND, 0, 0, 0);
 
 	gtk_table_set_row_spacings(GTK_TABLE(table), PIDGIN_HIG_BOX_SPACE / 2);
@@ -714,7 +722,7 @@
 	gtk_widget_show(dialog->popup);
 	gtk_widget_show(dialog->popup_entry);
 	gtk_widget_show(dialog->send_msg);
-	gtk_widget_show(dialog->send_msg_entry);
+	gtk_widget_show(send_msg_imhtml);
 	gtk_widget_show(dialog->exec_cmd);
 	gtk_widget_show(dialog->exec_cmd_entry);
 	gtk_widget_show(dialog->exec_cmd_browse);
@@ -729,7 +737,7 @@
 
 	g_signal_connect(G_OBJECT(dialog->send_msg), "clicked",
 					 G_CALLBACK(pidgin_toggle_sensitive),
-					 dialog->send_msg_entry);
+					 send_msg_imhtml);
 
 	g_signal_connect(G_OBJECT(dialog->popup), "clicked",
 					 G_CALLBACK(pidgin_toggle_sensitive),
@@ -765,7 +773,12 @@
 	g_object_set_data_full(G_OBJECT(dialog->window), "sound-widgets",
 				sound_widgets, (GDestroyNotify)g_ptr_array_free);
 
-	g_signal_connect(G_OBJECT(dialog->send_msg_entry), "activate",
+	g_signal_connect_swapped(G_OBJECT(dialog->send_msg_entry), "format_function_clear",
+			G_CALLBACK(reset_send_msg_entry), dialog);
+	g_signal_connect_swapped(G_OBJECT(dialog->account_menu), "changed",
+			G_CALLBACK(reset_send_msg_entry), dialog);
+
+	g_signal_connect(G_OBJECT(dialog->send_msg_entry), "message_send",
 					 G_CALLBACK(save_pounce_cb), dialog);
 	g_signal_connect(G_OBJECT(dialog->popup_entry), "activate",
 					 G_CALLBACK(save_pounce_cb), dialog);
@@ -783,7 +796,7 @@
 	gtk_widget_show(table);
 
 	dialog->on_away =
-		gtk_check_button_new_with_mnemonic(_("P_ounce only when my status is not available"));
+		gtk_check_button_new_with_mnemonic(_("P_ounce only when my status is not Available"));
 	gtk_table_attach(GTK_TABLE(table), dialog->on_away, 0, 1, 0, 1,
 					 GTK_FILL, 0, 0, 0);
 
@@ -892,7 +905,7 @@
 													  "send-message",
 													  "message")) != NULL)
 		{
-			gtk_entry_set_text(GTK_ENTRY(dialog->send_msg_entry), value);
+			gtk_imhtml_append_text(GTK_IMHTML(dialog->send_msg_entry), value, 0);
 		}
 
 		if ((value = purple_pounce_action_get_attribute(cur_pounce,
@@ -1323,11 +1336,8 @@
 	width  = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/pounces/dialog/width");
 	height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/pounces/dialog/height");
 
-	dialog->window = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+	dialog->window = win = pidgin_create_window(_("Buddy Pounces"), PIDGIN_HIG_BORDER, "pounces", TRUE);
 	gtk_window_set_default_size(GTK_WINDOW(win), width, height);
-	gtk_window_set_role(GTK_WINDOW(win), "pounces");
-	gtk_window_set_title(GTK_WINDOW(win), _("Buddy Pounces"));
-	gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER);
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
 					 G_CALLBACK(pounces_manager_destroy_cb), dialog);
--- a/pidgin/gtkprefs.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkprefs.c	Tue Jun 12 21:21:37 2007 +0000
@@ -979,17 +979,7 @@
 
 	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0);
 
-	if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_bold"))
-		gtk_imhtml_toggle_bold(GTK_IMHTML(imhtml));
-	if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_italic"))
-		gtk_imhtml_toggle_italic(GTK_IMHTML(imhtml));
-	if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/send_underline"))
-		gtk_imhtml_toggle_underline(GTK_IMHTML(imhtml));
-
-	gtk_imhtml_font_set_size(GTK_IMHTML(imhtml), purple_prefs_get_int(PIDGIN_PREFS_ROOT "/conversations/font_size"));
-	gtk_imhtml_toggle_forecolor(GTK_IMHTML(imhtml), purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/fgcolor"));
-	gtk_imhtml_toggle_background(GTK_IMHTML(imhtml), purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/bgcolor"));
-	gtk_imhtml_toggle_fontface(GTK_IMHTML(imhtml), purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/font_face"));
+	gtk_imhtml_setup_entry(GTK_IMHTML(imhtml), PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO);
 
 	g_signal_connect_after(G_OBJECT(imhtml), "format_function_toggle",
 					 G_CALLBACK(formatting_toggle_cb), toolbar);
@@ -1987,11 +1977,7 @@
 	/* Back to instant-apply! I win!  BU-HAHAHA! */
 
 	/* Create the window */
-	prefs = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-	gtk_window_set_role(GTK_WINDOW(prefs), "preferences");
-	gtk_window_set_title(GTK_WINDOW(prefs), _("Preferences"));
-	gtk_window_set_resizable (GTK_WINDOW(prefs), FALSE);
-	gtk_container_set_border_width(GTK_CONTAINER(prefs), PIDGIN_HIG_BORDER);
+	prefs = pidgin_create_window(_("Preferences"), PIDGIN_HIG_BORDER, "preferences", FALSE);
 	g_signal_connect(G_OBJECT(prefs), "destroy",
 					 G_CALLBACK(delete_prefs), NULL);
 
--- a/pidgin/gtkprivacy.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkprivacy.c	Tue Jun 12 21:21:37 2007 +0000
@@ -366,11 +366,7 @@
 
 	dialog = g_new0(PidginPrivacyDialog, 1);
 
-	dialog->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-	gtk_window_set_resizable(GTK_WINDOW(dialog->win), FALSE);
-	gtk_window_set_role(GTK_WINDOW(dialog->win), "privacy");
-	gtk_window_set_title(GTK_WINDOW(dialog->win), _("Privacy"));
-	gtk_container_set_border_width(GTK_CONTAINER(dialog->win), PIDGIN_HIG_BORDER);
+	dialog->win = pidgin_create_window(_("Privacy"), PIDGIN_HIG_BORDER, "privacy", FALSE);
 
 	g_signal_connect(G_OBJECT(dialog->win), "delete_event",
 					 G_CALLBACK(destroy_cb), dialog);
--- a/pidgin/gtkrequest.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkrequest.c	Tue Jun 12 21:21:37 2007 +0000
@@ -323,7 +323,8 @@
 	/* Setup the dialog */
 	gtk_container_set_border_width(GTK_CONTAINER(dialog), PIDGIN_HIG_BORDER/2);
 	gtk_container_set_border_width(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BORDER/2);
-	gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
+	if (!multiline)
+		gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
 	gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
 	gtk_dialog_set_default_response(GTK_DIALOG(dialog), 0);
 	gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(dialog)->vbox), PIDGIN_HIG_BORDER);
@@ -341,7 +342,7 @@
 	/* Vertical box */
 	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER);
 
-	gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
 
 	/* Descriptive label */
 	primary_esc = (primary != NULL) ? g_markup_escape_text(primary, -1) : NULL;
@@ -359,7 +360,7 @@
 	gtk_label_set_markup(GTK_LABEL(label), label_text);
 	gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
-	gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 0);
+	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
 
 	g_free(label_text);
 
@@ -1069,16 +1070,12 @@
 	data->cbs[0] = ok_cb;
 	data->cbs[1] = cancel_cb;
 
-	data->dialog = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-
-	if (title != NULL)
-		gtk_window_set_title(GTK_WINDOW(win), title);
+	
 #ifdef _WIN32
-		gtk_window_set_title(GTK_WINDOW(win), PIDGIN_ALERT_TITLE);
-#endif
-
-	gtk_window_set_role(GTK_WINDOW(win), "multifield");
-	gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER);
+	data->dialog = win = pidgin_create_window(PIDGIN_ALERT_TITLE, PIDGIN_HIG_BORDER, "multifield", TRUE) ;
+#else /* !_WIN32 */
+	data->dialog = win = pidgin_create_window(title, PIDGIN_HIG_BORDER, "multifield", TRUE) ;
+#endif /* _WIN32 */
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
 					 G_CALLBACK(destroy_multifield_cb), data);
--- a/pidgin/gtkroomlist.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkroomlist.c	Tue Jun 12 21:21:37 2007 +0000
@@ -343,7 +343,7 @@
 gboolean
 pidgin_roomlist_is_showable()
 {
-	GList *c;
+	const GList *c;
 	PurpleConnection *gc;
 
 	for (c = purple_connections_get_all(); c != NULL; c = c->next) {
@@ -371,11 +371,7 @@
 	dialog->account = account;
 
 	/* Create the window. */
-	dialog->window = window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-	gtk_window_set_role(GTK_WINDOW(window), "room list");
-	gtk_window_set_title(GTK_WINDOW(window), _("Room List"));
-
-	gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER);
+	dialog->window = window = pidgin_create_window(_("Room List"), PIDGIN_HIG_BORDER, "room list", TRUE);
 
 	g_signal_connect(G_OBJECT(window), "delete_event",
 					 G_CALLBACK(delete_win_cb), dialog);
--- a/pidgin/gtksavedstatuses.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtksavedstatuses.c	Tue Jun 12 21:21:37 2007 +0000
@@ -551,11 +551,8 @@
 	width  = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/status/dialog/width");
 	height = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/status/dialog/height");
 
-	dialog->window = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+	dialog->window = win = pidgin_create_window(_("Saved Statuses"), PIDGIN_HIG_BORDER, "statuses", TRUE);
 	gtk_window_set_default_size(GTK_WINDOW(win), width, height);
-	gtk_window_set_role(GTK_WINDOW(win), "statuses");
-	gtk_window_set_title(GTK_WINDOW(win), _("Saved Statuses"));
-	gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER);
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
 					 G_CALLBACK(status_window_destroy_cb), dialog);
@@ -1015,7 +1012,7 @@
 static void
 status_editor_populate_list(StatusEditor *dialog, PurpleSavedStatus *saved_status)
 {
-	GList *iter;
+	const GList *iter;
 	PurpleSavedStatusSub *substatus;
 
 	gtk_list_store_clear(dialog->model);
@@ -1085,11 +1082,7 @@
 	if (edit)
 		dialog->original_title = g_strdup(purple_savedstatus_get_title(saved_status));
 
-	dialog->window = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-	gtk_window_set_role(GTK_WINDOW(win), "status");
-	gtk_window_set_title(GTK_WINDOW(win), _("Status"));
-	gtk_window_set_resizable(GTK_WINDOW(win), FALSE);
-	gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER);
+	dialog->window = win = pidgin_create_window (_("Status"), PIDGIN_HIG_BORDER, "status", FALSE) ;
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
 					 G_CALLBACK(status_editor_destroy_cb), dialog);
@@ -1137,7 +1130,7 @@
 
 	/* Status message */
 	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
-	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(vbox), hbox, TRUE, TRUE, 0);
 
 	label = gtk_label_new_with_mnemonic(_("_Message:"));
 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
@@ -1423,13 +1416,9 @@
 	dialog->status_editor = status_editor;
 	dialog->account = account;
 
-	dialog->window = win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-	gtk_window_set_role(GTK_WINDOW(win), "substatus");
 	tmp = g_strdup_printf(_("Status for %s"), purple_account_get_username(account));
-	gtk_window_set_title(GTK_WINDOW(win), tmp);
+	dialog->window = win = pidgin_create_window(tmp, PIDGIN_HIG_BORDER, "substatus", FALSE) ;
 	g_free(tmp);
-	gtk_window_set_resizable(GTK_WINDOW(win), FALSE);
-	gtk_container_set_border_width(GTK_CONTAINER(win), PIDGIN_HIG_BORDER);
 
 	g_signal_connect(G_OBJECT(win), "delete_event",
 					 G_CALLBACK(substatus_editor_destroy_cb), dialog);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtksourceundomanager.c	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,1123 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gtksourceundomanager.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi 
+ * Copyright (C) 2002-2005  Paolo Maggi 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, 
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "gtksourceundomanager.h"
+#include "gtksourceview-marshal.h"
+
+
+#define DEFAULT_MAX_UNDO_LEVELS		25
+
+
+typedef struct _GtkSourceUndoAction  			GtkSourceUndoAction;
+typedef struct _GtkSourceUndoInsertAction		GtkSourceUndoInsertAction;
+typedef struct _GtkSourceUndoDeleteAction		GtkSourceUndoDeleteAction;
+
+typedef enum {
+	GTK_SOURCE_UNDO_ACTION_INSERT,
+	GTK_SOURCE_UNDO_ACTION_DELETE
+} GtkSourceUndoActionType;
+
+/* 
+ * We use offsets instead of GtkTextIters because the last ones
+ * require to much memory in this context without giving us any advantage.
+ */ 
+
+struct _GtkSourceUndoInsertAction
+{
+	gint   pos; 
+	gchar *text;
+	gint   length;
+	gint   chars;
+};
+
+struct _GtkSourceUndoDeleteAction
+{
+	gint   start;
+	gint   end;
+	gchar *text;
+	gboolean forward;
+};
+
+struct _GtkSourceUndoAction
+{
+	GtkSourceUndoActionType action_type;
+	
+	union {
+		GtkSourceUndoInsertAction  insert;
+		GtkSourceUndoDeleteAction  delete;
+	} action;
+
+	gint order_in_group;
+
+	/* It is TRUE whether the action can be merged with the following action. */
+	guint mergeable : 1;
+
+	/* It is TRUE whether the action is marked as "modified".
+	 * An action is marked as "modified" if it changed the 
+	 * state of the buffer from "not modified" to "modified". Only the first
+	 * action of a group can be marked as modified.
+	 * There can be a single action marked as "modified" in the actions list.
+	 */
+	guint modified  : 1;
+};
+
+/* INVALID is a pointer to an invalid action */
+#define INVALID ((void *) "IA")
+
+struct _GtkSourceUndoManagerPrivate
+{
+	GtkTextBuffer	*document;
+	
+	GList*		 actions;
+	gint 		 next_redo;	
+
+	gint 		 actions_in_current_group;
+	
+	gint		 running_not_undoable_actions;
+
+	gint		 num_of_groups;
+
+	gint		 max_undo_levels;
+	
+	guint	 	 can_undo : 1;
+	guint		 can_redo : 1;
+	
+	/* It is TRUE whether, while undoing an action of the current group (with order_in_group > 1),
+	 * the state of the buffer changed from "not modified" to "modified".
+	 */
+	guint	 	 modified_undoing_group : 1;	
+
+	/* Pointer to the action (in the action list) marked as "modified".
+	 * It is NULL when no action is marked as "modified". 
+	 * It is INVALID when the action marked as "modified" has been removed 
+	 * from the action list (freeing the list or resizing it) */
+	GtkSourceUndoAction *modified_action;
+};
+
+enum {
+	CAN_UNDO,
+	CAN_REDO,
+	LAST_SIGNAL
+};
+
+static void gtk_source_undo_manager_class_init 			(GtkSourceUndoManagerClass 	*klass);
+static void gtk_source_undo_manager_init 			(GtkSourceUndoManager 		*um);
+static void gtk_source_undo_manager_finalize 			(GObject 			*object);
+
+static void gtk_source_undo_manager_insert_text_handler 	(GtkTextBuffer 			*buffer, 
+							 	 GtkTextIter 			*pos,
+		                             		 	 const 	gchar 			*text, 
+							 	 gint 				 length, 
+							 	 GtkSourceUndoManager 		*um);
+static void gtk_source_undo_manager_delete_range_handler 	(GtkTextBuffer 			*buffer, 
+							 	 GtkTextIter 			*start,
+                        		      		 	 GtkTextIter 			*end,
+							 	 GtkSourceUndoManager 		*um);
+static void gtk_source_undo_manager_begin_user_action_handler 	(GtkTextBuffer 			*buffer, 
+								 GtkSourceUndoManager 		*um);
+static void gtk_source_undo_manager_modified_changed_handler	(GtkTextBuffer                  *buffer,
+								 GtkSourceUndoManager           *um);
+
+static void gtk_source_undo_manager_free_action_list 		(GtkSourceUndoManager 		*um);
+
+static void gtk_source_undo_manager_add_action 			(GtkSourceUndoManager 		*um, 
+		                                         	 const GtkSourceUndoAction 	*undo_action);
+static void gtk_source_undo_manager_free_first_n_actions 	(GtkSourceUndoManager 		*um, 
+								 gint 				 n);
+static void gtk_source_undo_manager_check_list_size 		(GtkSourceUndoManager 		*um);
+
+static gboolean gtk_source_undo_manager_merge_action 		(GtkSourceUndoManager 		*um, 
+		                                        	 const GtkSourceUndoAction 	*undo_action);
+
+static GObjectClass 	*parent_class 				= NULL;
+static guint 		undo_manager_signals [LAST_SIGNAL] 	= { 0 };
+
+GType
+gtk_source_undo_manager_get_type (void)
+{
+	static GType undo_manager_type = 0;
+
+  	if (undo_manager_type == 0)
+    	{
+      		static const GTypeInfo our_info =
+      		{
+        		sizeof (GtkSourceUndoManagerClass),
+        		NULL,		/* base_init */
+        		NULL,		/* base_finalize */
+        		(GClassInitFunc) gtk_source_undo_manager_class_init,
+        		NULL,           /* class_finalize */
+        		NULL,           /* class_data */
+        		sizeof (GtkSourceUndoManager),
+        		0,              /* n_preallocs */
+        		(GInstanceInitFunc) gtk_source_undo_manager_init,
+        		NULL		/* value_table */
+      		};
+
+      		undo_manager_type = g_type_register_static (G_TYPE_OBJECT,
+                					    "GtkSourceUndoManager",
+							    &our_info,
+							    0);
+    	}
+
+	return undo_manager_type;
+}
+
+static void
+gtk_source_undo_manager_class_init (GtkSourceUndoManagerClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  	parent_class = g_type_class_peek_parent (klass);
+
+  	object_class->finalize = gtk_source_undo_manager_finalize;
+
+        klass->can_undo 	= NULL;
+	klass->can_redo 	= NULL;
+	
+	undo_manager_signals[CAN_UNDO] =
+   		g_signal_new ("can_undo",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (GtkSourceUndoManagerClass, can_undo),
+			      NULL, NULL,
+			      gtksourceview_marshal_VOID__BOOLEAN,
+			      G_TYPE_NONE,
+			      1,
+			      G_TYPE_BOOLEAN);
+
+	undo_manager_signals[CAN_REDO] =
+   		g_signal_new ("can_redo",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (GtkSourceUndoManagerClass, can_redo),
+			      NULL, NULL,
+			      gtksourceview_marshal_VOID__BOOLEAN,
+			      G_TYPE_NONE,
+			      1,
+			      G_TYPE_BOOLEAN);
+}
+
+static void
+gtk_source_undo_manager_init (GtkSourceUndoManager *um)
+{
+	um->priv = g_new0 (GtkSourceUndoManagerPrivate, 1);
+
+	um->priv->actions = NULL;
+	um->priv->next_redo = 0;
+
+	um->priv->can_undo = FALSE;
+	um->priv->can_redo = FALSE;
+
+	um->priv->running_not_undoable_actions = 0;
+
+	um->priv->num_of_groups = 0;
+
+	um->priv->max_undo_levels = DEFAULT_MAX_UNDO_LEVELS;
+
+	um->priv->modified_action = NULL;
+
+	um->priv->modified_undoing_group = FALSE;
+}
+
+static void
+gtk_source_undo_manager_finalize (GObject *object)
+{
+	GtkSourceUndoManager *um;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (object));
+	
+   	um = GTK_SOURCE_UNDO_MANAGER (object);
+
+	g_return_if_fail (um->priv != NULL);
+
+	if (um->priv->actions != NULL)
+	{
+		gtk_source_undo_manager_free_action_list (um);
+	}
+
+	g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document),
+			  G_CALLBACK (gtk_source_undo_manager_delete_range_handler), 
+			  um);
+
+	g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document),
+			  G_CALLBACK (gtk_source_undo_manager_insert_text_handler), 
+			  um);
+	
+	g_signal_handlers_disconnect_by_func (G_OBJECT (um->priv->document),
+			  G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler), 
+			  um);
+
+	g_free (um->priv);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+GtkSourceUndoManager*
+gtk_source_undo_manager_new (GtkTextBuffer* buffer)
+{
+ 	GtkSourceUndoManager *um;
+
+	um = GTK_SOURCE_UNDO_MANAGER (g_object_new (GTK_SOURCE_TYPE_UNDO_MANAGER, NULL));
+
+	g_return_val_if_fail (um->priv != NULL, NULL);
+  	um->priv->document = buffer;
+
+	g_signal_connect (G_OBJECT (buffer), "insert_text",
+			  G_CALLBACK (gtk_source_undo_manager_insert_text_handler), 
+			  um);
+
+	g_signal_connect (G_OBJECT (buffer), "delete_range",
+			  G_CALLBACK (gtk_source_undo_manager_delete_range_handler), 
+			  um);
+
+	g_signal_connect (G_OBJECT (buffer), "begin_user_action",
+			  G_CALLBACK (gtk_source_undo_manager_begin_user_action_handler), 
+			  um);
+
+	g_signal_connect (G_OBJECT (buffer), "modified_changed",
+			  G_CALLBACK (gtk_source_undo_manager_modified_changed_handler),
+			  um);
+	return um;
+}
+
+void 
+gtk_source_undo_manager_begin_not_undoable_action (GtkSourceUndoManager *um)
+{
+	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
+	g_return_if_fail (um->priv != NULL);
+
+	++um->priv->running_not_undoable_actions;
+}
+
+static void 
+gtk_source_undo_manager_end_not_undoable_action_internal (GtkSourceUndoManager *um)
+{
+	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
+	g_return_if_fail (um->priv != NULL);
+
+	g_return_if_fail (um->priv->running_not_undoable_actions > 0);
+	
+	--um->priv->running_not_undoable_actions;
+}
+
+void 
+gtk_source_undo_manager_end_not_undoable_action (GtkSourceUndoManager *um)
+{
+	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
+	g_return_if_fail (um->priv != NULL);
+
+	gtk_source_undo_manager_end_not_undoable_action_internal (um);
+
+	if (um->priv->running_not_undoable_actions == 0)
+	{	
+		gtk_source_undo_manager_free_action_list (um);
+	
+		um->priv->next_redo = -1;	
+
+		if (um->priv->can_undo)
+		{
+			um->priv->can_undo = FALSE;
+			g_signal_emit (G_OBJECT (um), 
+				       undo_manager_signals [CAN_UNDO], 
+				       0, 
+				       FALSE);
+		}
+
+		if (um->priv->can_redo)
+		{
+			um->priv->can_redo = FALSE;
+			g_signal_emit (G_OBJECT (um), 
+				       undo_manager_signals [CAN_REDO], 
+				       0, 
+				       FALSE);
+		}
+	}
+}
+
+gboolean
+gtk_source_undo_manager_can_undo (const GtkSourceUndoManager *um)
+{
+	g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE);
+	g_return_val_if_fail (um->priv != NULL, FALSE);
+
+	return um->priv->can_undo;
+}
+
+gboolean 
+gtk_source_undo_manager_can_redo (const GtkSourceUndoManager *um)
+{
+	g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE);
+	g_return_val_if_fail (um->priv != NULL, FALSE);
+
+	return um->priv->can_redo;
+}
+
+static void
+set_cursor (GtkTextBuffer *buffer, gint cursor)
+{
+	GtkTextIter iter;
+	
+	/* Place the cursor at the requested position */
+	gtk_text_buffer_get_iter_at_offset (buffer, &iter, cursor);
+	gtk_text_buffer_place_cursor (buffer, &iter);
+}
+
+static void 
+insert_text (GtkTextBuffer *buffer, gint pos, const gchar *text, gint len)
+{
+	GtkTextIter iter;
+	
+	gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos);
+	gtk_text_buffer_insert (buffer, &iter, text, len);
+}
+
+static void 
+delete_text (GtkTextBuffer *buffer, gint start, gint end)
+{
+	GtkTextIter start_iter;
+	GtkTextIter end_iter;
+
+	gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
+
+	if (end < 0)
+		gtk_text_buffer_get_end_iter (buffer, &end_iter);
+	else
+		gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
+
+	gtk_text_buffer_delete (buffer, &start_iter, &end_iter);
+}
+
+static gchar*
+get_chars (GtkTextBuffer *buffer, gint start, gint end)
+{
+	GtkTextIter start_iter;
+	GtkTextIter end_iter;
+
+	gtk_text_buffer_get_iter_at_offset (buffer, &start_iter, start);
+
+	if (end < 0)
+		gtk_text_buffer_get_end_iter (buffer, &end_iter);
+	else
+		gtk_text_buffer_get_iter_at_offset (buffer, &end_iter, end);
+
+	return gtk_text_buffer_get_slice (buffer, &start_iter, &end_iter, TRUE);
+}
+
+void 
+gtk_source_undo_manager_undo (GtkSourceUndoManager *um)
+{
+	GtkSourceUndoAction *undo_action;
+	gboolean modified = FALSE;
+
+	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
+	g_return_if_fail (um->priv != NULL);
+	g_return_if_fail (um->priv->can_undo);
+	
+	um->priv->modified_undoing_group = FALSE;
+
+	gtk_source_undo_manager_begin_not_undoable_action (um);
+
+	do
+	{	
+		undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo + 1);
+		g_return_if_fail (undo_action != NULL);
+
+		/* undo_action->modified can be TRUE only if undo_action->order_in_group <= 1 */
+		g_return_if_fail ((undo_action->order_in_group <= 1) ||
+				  ((undo_action->order_in_group > 1) && !undo_action->modified));
+
+		if (undo_action->order_in_group <= 1)
+		{
+			/* Set modified to TRUE only if the buffer did not change its state from
+			 * "not modified" to "modified" undoing an action (with order_in_group > 1) 
+			 * in current group. */
+			modified = (undo_action->modified && !um->priv->modified_undoing_group);
+		}
+
+		switch (undo_action->action_type)
+		{
+			case GTK_SOURCE_UNDO_ACTION_DELETE:
+				insert_text (
+					um->priv->document, 
+					undo_action->action.delete.start, 
+					undo_action->action.delete.text,
+					strlen (undo_action->action.delete.text));
+
+				if (undo_action->action.delete.forward)
+					set_cursor (
+						um->priv->document, 
+						undo_action->action.delete.start);
+				else
+					set_cursor (
+						um->priv->document, 
+						undo_action->action.delete.end);
+
+				break;
+				
+			case GTK_SOURCE_UNDO_ACTION_INSERT:
+				delete_text (
+					um->priv->document, 
+					undo_action->action.insert.pos, 
+					undo_action->action.insert.pos + 
+						undo_action->action.insert.chars); 
+
+				set_cursor (
+					um->priv->document, 
+					undo_action->action.insert.pos);
+				break;
+
+			default:
+				/* Unknown action type. */
+				g_return_if_reached ();
+		}
+
+		++um->priv->next_redo;
+
+	} while (undo_action->order_in_group > 1);
+
+	if (modified)
+	{
+		--um->priv->next_redo;
+		gtk_text_buffer_set_modified (um->priv->document, FALSE);
+		++um->priv->next_redo;
+	}
+
+	gtk_source_undo_manager_end_not_undoable_action_internal (um);
+	
+	um->priv->modified_undoing_group = FALSE;
+
+	if (!um->priv->can_redo)
+	{
+		um->priv->can_redo = TRUE;
+		g_signal_emit (G_OBJECT (um), 
+			       undo_manager_signals [CAN_REDO], 
+			       0, 
+			       TRUE);
+	}
+
+	if (um->priv->next_redo >= (gint)(g_list_length (um->priv->actions) - 1))
+	{
+		um->priv->can_undo = FALSE;
+		g_signal_emit (G_OBJECT (um), 
+			       undo_manager_signals [CAN_UNDO], 
+			       0, 
+			       FALSE);
+	}
+}
+
+void 
+gtk_source_undo_manager_redo (GtkSourceUndoManager *um)
+{
+	GtkSourceUndoAction *undo_action;
+	gboolean modified = FALSE;
+
+	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
+	g_return_if_fail (um->priv != NULL);
+	g_return_if_fail (um->priv->can_redo);
+	
+	undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo);
+	g_return_if_fail (undo_action != NULL);
+
+	gtk_source_undo_manager_begin_not_undoable_action (um);
+
+	do
+	{
+		if (undo_action->modified)
+		{
+			g_return_if_fail (undo_action->order_in_group <= 1);
+			modified = TRUE;
+		}
+
+		--um->priv->next_redo;
+	
+		switch (undo_action->action_type)
+		{
+			case GTK_SOURCE_UNDO_ACTION_DELETE:
+				delete_text (
+					um->priv->document, 
+					undo_action->action.delete.start, 
+					undo_action->action.delete.end); 
+
+				set_cursor (
+					um->priv->document,
+					undo_action->action.delete.start);
+
+				break;
+				
+			case GTK_SOURCE_UNDO_ACTION_INSERT:
+				set_cursor (
+					um->priv->document,
+					undo_action->action.insert.pos);
+
+				insert_text (
+					um->priv->document, 
+					undo_action->action.insert.pos, 
+					undo_action->action.insert.text,
+					undo_action->action.insert.length);
+
+				break;
+
+			default:
+				/* Unknown action type */
+				++um->priv->next_redo;
+				g_return_if_reached ();
+		}
+
+		if (um->priv->next_redo < 0)
+			undo_action = NULL;
+		else
+			undo_action = g_list_nth_data (um->priv->actions, um->priv->next_redo);
+			
+	} while ((undo_action != NULL) && (undo_action->order_in_group > 1));
+
+	if (modified)
+	{
+		++um->priv->next_redo;
+		gtk_text_buffer_set_modified (um->priv->document, FALSE);
+		--um->priv->next_redo;
+	}
+
+	gtk_source_undo_manager_end_not_undoable_action_internal (um);
+
+	if (um->priv->next_redo < 0)
+	{
+		um->priv->can_redo = FALSE;
+		g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE);
+	}
+
+	if (!um->priv->can_undo)
+	{
+		um->priv->can_undo = TRUE;
+		g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, TRUE);
+	}
+}
+
+static void
+gtk_source_undo_action_free (GtkSourceUndoAction *action)
+{
+	if (action == NULL)
+		return;
+
+	if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
+		g_free (action->action.insert.text);
+	else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
+		g_free (action->action.delete.text);
+	else
+		g_return_if_reached ();
+
+	g_free (action);
+}
+
+static void 
+gtk_source_undo_manager_free_action_list (GtkSourceUndoManager *um)
+{
+	GList *l;
+
+	l = um->priv->actions;
+
+	while (l != NULL)
+	{
+		GtkSourceUndoAction *action = l->data;
+
+		if (action->order_in_group == 1)
+			--um->priv->num_of_groups;
+
+		if (action->modified)
+			um->priv->modified_action = INVALID;
+
+		gtk_source_undo_action_free (action);
+
+		l = g_list_next (l);
+	}
+
+	g_list_free (um->priv->actions);
+	um->priv->actions = NULL;	
+}
+
+static void 
+gtk_source_undo_manager_insert_text_handler (GtkTextBuffer 		*buffer, 
+					     GtkTextIter 		*pos,
+		                             const gchar 		*text, 
+					     gint 			 length, 
+					     GtkSourceUndoManager 	*um)
+{
+	GtkSourceUndoAction undo_action;
+	
+	if (um->priv->running_not_undoable_actions > 0)
+		return;
+
+	g_return_if_fail (strlen (text) >= (guint)length);
+	
+	undo_action.action_type = GTK_SOURCE_UNDO_ACTION_INSERT;
+
+	undo_action.action.insert.pos    = gtk_text_iter_get_offset (pos);
+	undo_action.action.insert.text   = (gchar*) text;
+	undo_action.action.insert.length = length;
+	undo_action.action.insert.chars  = g_utf8_strlen (text, length);
+
+	if ((undo_action.action.insert.chars > 1) || (g_utf8_get_char (text) == '\n'))
+
+	       	undo_action.mergeable = FALSE;
+	else
+		undo_action.mergeable = TRUE;
+
+	undo_action.modified = FALSE;
+
+	gtk_source_undo_manager_add_action (um, &undo_action);
+}
+
+static void 
+gtk_source_undo_manager_delete_range_handler (GtkTextBuffer 		*buffer, 
+					      GtkTextIter 		*start,
+                        		      GtkTextIter 		*end, 
+					      GtkSourceUndoManager 	*um)
+{
+	GtkSourceUndoAction undo_action;
+	GtkTextIter insert_iter;
+	
+	if (um->priv->running_not_undoable_actions > 0)
+		return;
+
+	undo_action.action_type = GTK_SOURCE_UNDO_ACTION_DELETE;
+
+	gtk_text_iter_order (start, end);
+
+	undo_action.action.delete.start  = gtk_text_iter_get_offset (start);
+	undo_action.action.delete.end    = gtk_text_iter_get_offset (end);
+
+	undo_action.action.delete.text   = get_chars (
+						buffer,
+						undo_action.action.delete.start,
+						undo_action.action.delete.end);
+
+	/* figure out if the user used the Delete or the Backspace key */
+	gtk_text_buffer_get_iter_at_mark (buffer, &insert_iter,
+					  gtk_text_buffer_get_insert (buffer));
+	if (gtk_text_iter_get_offset (&insert_iter) <= undo_action.action.delete.start)
+		undo_action.action.delete.forward = TRUE;
+	else
+		undo_action.action.delete.forward = FALSE;
+
+	if (((undo_action.action.delete.end - undo_action.action.delete.start) > 1) ||
+	     (g_utf8_get_char (undo_action.action.delete.text  ) == '\n'))
+	       	undo_action.mergeable = FALSE;
+	else
+		undo_action.mergeable = TRUE;
+
+	undo_action.modified = FALSE;
+	
+	gtk_source_undo_manager_add_action (um, &undo_action);
+
+	g_free (undo_action.action.delete.text);
+
+}
+
+static void 
+gtk_source_undo_manager_begin_user_action_handler (GtkTextBuffer *buffer, GtkSourceUndoManager *um)
+{
+	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
+	g_return_if_fail (um->priv != NULL);
+
+	if (um->priv->running_not_undoable_actions > 0)
+		return;
+
+	um->priv->actions_in_current_group = 0;
+}
+
+static void
+gtk_source_undo_manager_add_action (GtkSourceUndoManager 	*um, 
+				    const GtkSourceUndoAction 	*undo_action)
+{
+	GtkSourceUndoAction* action;
+	
+	if (um->priv->next_redo >= 0)
+	{
+		gtk_source_undo_manager_free_first_n_actions (um, um->priv->next_redo + 1);
+	}
+
+	um->priv->next_redo = -1;
+
+	if (!gtk_source_undo_manager_merge_action (um, undo_action))
+	{
+		action = g_new (GtkSourceUndoAction, 1);
+		*action = *undo_action;
+
+		if (action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
+			action->action.insert.text = g_strdup (undo_action->action.insert.text);
+		else if (action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
+			action->action.delete.text = g_strdup (undo_action->action.delete.text); 
+		else
+		{
+			g_free (action);
+			g_return_if_reached ();
+		}
+		
+		++um->priv->actions_in_current_group;
+		action->order_in_group = um->priv->actions_in_current_group;
+
+		if (action->order_in_group == 1)
+			++um->priv->num_of_groups;
+	
+		um->priv->actions = g_list_prepend (um->priv->actions, action);
+	}
+	
+	gtk_source_undo_manager_check_list_size (um);
+
+	if (!um->priv->can_undo)
+	{
+		um->priv->can_undo = TRUE;
+		g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, TRUE);
+	}
+
+	if (um->priv->can_redo)
+	{
+		um->priv->can_redo = FALSE;
+		g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE);
+	}
+}
+
+static void 
+gtk_source_undo_manager_free_first_n_actions (GtkSourceUndoManager	*um, 
+					      gint 			 n)
+{
+	gint i;
+
+	if (um->priv->actions == NULL)
+		return;
+
+	for (i = 0; i < n; i++)
+	{
+		GtkSourceUndoAction *action = g_list_first (um->priv->actions)->data;
+
+		if (action->order_in_group == 1)
+			--um->priv->num_of_groups;
+
+		if (action->modified)
+			um->priv->modified_action = INVALID;
+
+		gtk_source_undo_action_free (action);
+
+		um->priv->actions = g_list_delete_link (um->priv->actions,
+							um->priv->actions);
+
+		if (um->priv->actions == NULL) 
+			return;
+	}
+}
+
+static void 
+gtk_source_undo_manager_check_list_size (GtkSourceUndoManager *um)
+{
+	gint undo_levels;
+	
+	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
+	g_return_if_fail (um->priv != NULL);
+	
+	undo_levels = gtk_source_undo_manager_get_max_undo_levels (um);
+	
+	if (undo_levels < 1)
+		return;
+
+	if (um->priv->num_of_groups > undo_levels)
+	{
+		GtkSourceUndoAction *undo_action;
+		GList *last;
+		
+		last = g_list_last (um->priv->actions);
+		undo_action = (GtkSourceUndoAction*) last->data;
+			
+		do
+		{
+			GList *tmp;
+			
+			if (undo_action->order_in_group == 1)
+				--um->priv->num_of_groups;
+
+			if (undo_action->modified)
+				um->priv->modified_action = INVALID;
+
+			gtk_source_undo_action_free (undo_action);
+
+			tmp = g_list_previous (last);
+			um->priv->actions = g_list_delete_link (um->priv->actions, last);
+			last = tmp;
+			g_return_if_fail (last != NULL); 
+
+			undo_action = (GtkSourceUndoAction*) last->data;
+
+		} while ((undo_action->order_in_group > 1) || 
+			 (um->priv->num_of_groups > undo_levels));
+	}	
+}
+
+/**
+ * gtk_source_undo_manager_merge_action:
+ * @um: a #GtkSourceUndoManager. 
+ * @undo_action: a #GtkSourceUndoAction.
+ * 
+ * This function tries to merge the undo action at the top of
+ * the stack with a new undo action. So when we undo for example
+ * typing, we can undo the whole word and not each letter by itself.
+ * 
+ * Return Value: %TRUE is merge was sucessful, %FALSE otherwise.²
+ **/
+static gboolean 
+gtk_source_undo_manager_merge_action (GtkSourceUndoManager 	*um, 
+				      const GtkSourceUndoAction *undo_action)
+{
+	GtkSourceUndoAction *last_action;
+	
+	g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), FALSE);
+	g_return_val_if_fail (um->priv != NULL, FALSE);
+
+	if (um->priv->actions == NULL)
+		return FALSE;
+
+	last_action = (GtkSourceUndoAction*) g_list_nth_data (um->priv->actions, 0);
+
+	if (!last_action->mergeable)
+		return FALSE;
+
+	if ((!undo_action->mergeable) ||
+	    (undo_action->action_type != last_action->action_type))
+	{
+		last_action->mergeable = FALSE;
+		return FALSE;
+	}
+
+	if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_DELETE)
+	{				
+		if ((last_action->action.delete.forward != undo_action->action.delete.forward) ||
+		    ((last_action->action.delete.start != undo_action->action.delete.start) &&
+		     (last_action->action.delete.start != undo_action->action.delete.end)))
+		{
+			last_action->mergeable = FALSE;
+			return FALSE;
+		}
+		
+		if (last_action->action.delete.start == undo_action->action.delete.start)
+		{
+			gchar *str;
+			
+#define L  (last_action->action.delete.end - last_action->action.delete.start - 1)
+#define g_utf8_get_char_at(p,i) g_utf8_get_char(g_utf8_offset_to_pointer((p),(i)))
+		
+			/* Deleted with the delete key */
+			if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') &&
+			    (g_utf8_get_char (undo_action->action.delete.text) != '\t') &&
+                            ((g_utf8_get_char_at (last_action->action.delete.text, L) == ' ') ||
+			     (g_utf8_get_char_at (last_action->action.delete.text, L)  == '\t')))
+			{
+				last_action->mergeable = FALSE;
+				return FALSE;
+			}
+			
+			str = g_strdup_printf ("%s%s", last_action->action.delete.text, 
+				undo_action->action.delete.text);
+			
+			g_free (last_action->action.delete.text);
+			last_action->action.delete.end += (undo_action->action.delete.end - 
+							   undo_action->action.delete.start);
+			last_action->action.delete.text = str;
+		}
+		else
+		{
+			gchar *str;
+			
+			/* Deleted with the backspace key */
+			if ((g_utf8_get_char (undo_action->action.delete.text) != ' ') &&
+			    (g_utf8_get_char (undo_action->action.delete.text) != '\t') &&
+                            ((g_utf8_get_char (last_action->action.delete.text) == ' ') ||
+			     (g_utf8_get_char (last_action->action.delete.text) == '\t')))
+			{
+				last_action->mergeable = FALSE;
+				return FALSE;
+			}
+
+			str = g_strdup_printf ("%s%s", undo_action->action.delete.text, 
+				last_action->action.delete.text);
+			
+			g_free (last_action->action.delete.text);
+			last_action->action.delete.start = undo_action->action.delete.start;
+			last_action->action.delete.text = str;
+		}
+	}
+	else if (undo_action->action_type == GTK_SOURCE_UNDO_ACTION_INSERT)
+	{
+		gchar* str;
+		
+#define I (last_action->action.insert.chars - 1)
+		
+		if ((undo_action->action.insert.pos != 
+		     	(last_action->action.insert.pos + last_action->action.insert.chars)) ||
+		    ((g_utf8_get_char (undo_action->action.insert.text) != ' ') &&
+		      (g_utf8_get_char (undo_action->action.insert.text) != '\t') &&
+		     ((g_utf8_get_char_at (last_action->action.insert.text, I) == ' ') ||
+		      (g_utf8_get_char_at (last_action->action.insert.text, I) == '\t')))
+		   )
+		{
+			last_action->mergeable = FALSE;
+			return FALSE;
+		}
+
+		str = g_strdup_printf ("%s%s", last_action->action.insert.text, 
+				undo_action->action.insert.text);
+		
+		g_free (last_action->action.insert.text);
+		last_action->action.insert.length += undo_action->action.insert.length;
+		last_action->action.insert.text = str;
+		last_action->action.insert.chars += undo_action->action.insert.chars;
+
+	}
+	else
+		/* Unknown action inside undo merge encountered */
+		g_return_val_if_reached (TRUE);
+		
+	return TRUE;
+}
+
+gint
+gtk_source_undo_manager_get_max_undo_levels (GtkSourceUndoManager *um)
+{
+	g_return_val_if_fail (um != NULL, 0);
+	g_return_val_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um), 0);
+
+	return um->priv->max_undo_levels;
+}
+
+void
+gtk_source_undo_manager_set_max_undo_levels (GtkSourceUndoManager	*um,
+				  	     gint			 max_undo_levels)
+{
+	gint old_levels;
+	
+	g_return_if_fail (um != NULL);
+	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
+
+	old_levels = um->priv->max_undo_levels;
+	um->priv->max_undo_levels = max_undo_levels;
+
+	if (max_undo_levels < 1)
+		return;
+		
+	if (old_levels > max_undo_levels)
+	{
+		/* strip redo actions first */
+		while (um->priv->next_redo >= 0 && (um->priv->num_of_groups > max_undo_levels))
+		{
+			gtk_source_undo_manager_free_first_n_actions (um, 1);
+			um->priv->next_redo--;
+		}
+		
+		/* now remove undo actions if necessary */
+		gtk_source_undo_manager_check_list_size (um);
+
+		/* emit "can_undo" and/or "can_redo" if appropiate */
+		if (um->priv->next_redo < 0 && um->priv->can_redo)
+		{
+			um->priv->can_redo = FALSE;
+			g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_REDO], 0, FALSE);
+		}
+
+		if (um->priv->can_undo &&
+		    um->priv->next_redo >= (gint)(g_list_length (um->priv->actions) - 1))
+		{
+			um->priv->can_undo = FALSE;
+			g_signal_emit (G_OBJECT (um), undo_manager_signals [CAN_UNDO], 0, FALSE);
+		}
+	}
+}
+
+static void
+gtk_source_undo_manager_modified_changed_handler (GtkTextBuffer        *buffer,
+                                            	  GtkSourceUndoManager *um)
+{
+	GtkSourceUndoAction *action;
+	GList *list;
+	
+	g_return_if_fail (GTK_SOURCE_IS_UNDO_MANAGER (um));
+	g_return_if_fail (um->priv != NULL);
+
+	if (um->priv->actions == NULL)
+		return;
+
+	list = g_list_nth (um->priv->actions, um->priv->next_redo + 1);
+
+	if (list != NULL)
+		action = (GtkSourceUndoAction*) list->data;
+	else
+		action = NULL;
+
+	if (gtk_text_buffer_get_modified (buffer) == FALSE)
+	{	
+		if (action != NULL)
+			action->mergeable = FALSE;
+
+		if (um->priv->modified_action != NULL)
+		{
+			if (um->priv->modified_action != INVALID)
+				um->priv->modified_action->modified = FALSE;
+
+			um->priv->modified_action = NULL;
+		}
+
+		return;
+	}
+
+	if (action == NULL)
+	{
+		g_return_if_fail (um->priv->running_not_undoable_actions > 0);
+
+		return;
+	}
+	
+	/* gtk_text_buffer_get_modified (buffer) == TRUE */
+
+	g_return_if_fail (um->priv->modified_action == NULL);
+
+	if (action->order_in_group > 1)
+		um->priv->modified_undoing_group  = TRUE;
+
+	while (action->order_in_group > 1)
+	{
+		list = g_list_next (list);
+		g_return_if_fail (list != NULL);
+
+		action = (GtkSourceUndoAction*) list->data;
+		g_return_if_fail (action != NULL);
+	}
+
+	action->modified = TRUE;
+	um->priv->modified_action = action;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtksourceundomanager.h	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,83 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gtksourceundomanager.h
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
+ * Copyright (C) 2000, 2001 Chema Celorio, Paolo Maggi 
+ * Copyright (C) 2002, 2003 Paolo Maggi 
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, 
+ * Boston, MA 02111-1307, USA. * *
+ */
+ 
+#ifndef __GTK_SOURCE_UNDO_MANAGER_H__
+#define __GTK_SOURCE_UNDO_MANAGER_H__
+
+#include <gtk/gtktextbuffer.h>
+
+#define GTK_SOURCE_TYPE_UNDO_MANAGER             	(gtk_source_undo_manager_get_type ())
+#define GTK_SOURCE_UNDO_MANAGER(obj)			(GTK_CHECK_CAST ((obj), GTK_SOURCE_TYPE_UNDO_MANAGER, GtkSourceUndoManager))
+#define GTK_SOURCE_UNDO_MANAGER_CLASS(klass)		(GTK_CHECK_CLASS_CAST ((klass), GTK_SOURCE_TYPE_UNDO_MANAGER, GtkSourceUndoManagerClass))
+#define GTK_SOURCE_IS_UNDO_MANAGER(obj)			(GTK_CHECK_TYPE ((obj), GTK_SOURCE_TYPE_UNDO_MANAGER))
+#define GTK_SOURCE_IS_UNDO_MANAGER_CLASS(klass)  	(GTK_CHECK_CLASS_TYPE ((klass), GTK_SOURCE_TYPE_UNDO_MANAGER))
+#define GTK_SOURCE_UNDO_MANAGER_GET_CLASS(obj)  	(GTK_CHECK_GET_CLASS ((obj), GTK_SOURCE_TYPE_UNDO_MANAGER, GtkSourceUndoManagerClass))
+
+
+typedef struct _GtkSourceUndoManager        	GtkSourceUndoManager;
+typedef struct _GtkSourceUndoManagerClass 	GtkSourceUndoManagerClass;
+
+typedef struct _GtkSourceUndoManagerPrivate 	GtkSourceUndoManagerPrivate;
+
+struct _GtkSourceUndoManager
+{
+	GObject base;
+	
+	GtkSourceUndoManagerPrivate *priv;
+};
+
+struct _GtkSourceUndoManagerClass
+{
+	GObjectClass parent_class;
+
+	/* Signals */
+	void (*can_undo) (GtkSourceUndoManager *um, gboolean can_undo);
+    	void (*can_redo) (GtkSourceUndoManager *um, gboolean can_redo);
+};
+
+GType        		gtk_source_undo_manager_get_type	(void) G_GNUC_CONST;
+
+GtkSourceUndoManager* 	gtk_source_undo_manager_new 		(GtkTextBuffer 		*buffer);
+
+gboolean		gtk_source_undo_manager_can_undo	(const GtkSourceUndoManager *um);
+gboolean		gtk_source_undo_manager_can_redo 	(const GtkSourceUndoManager *um);
+
+void			gtk_source_undo_manager_undo 		(GtkSourceUndoManager 	*um);
+void			gtk_source_undo_manager_redo 		(GtkSourceUndoManager 	*um);
+
+void			gtk_source_undo_manager_begin_not_undoable_action 
+								(GtkSourceUndoManager	*um);
+void			gtk_source_undo_manager_end_not_undoable_action 
+								(GtkSourceUndoManager	*um);
+
+gint			gtk_source_undo_manager_get_max_undo_levels 
+								(GtkSourceUndoManager 	*um);
+void			gtk_source_undo_manager_set_max_undo_levels 
+								(GtkSourceUndoManager 	*um,
+				  	     			 gint		 	 undo_levels);
+
+#endif /* __GTK_SOURCE_UNDO_MANAGER_H__ */
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtksourceview-marshal.c	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,95 @@
+#include "gtksourceview-marshal.h"
+
+#include	<glib-object.h>
+
+
+#ifdef G_ENABLE_DEBUG
+#define g_marshal_value_peek_boolean(v)  g_value_get_boolean (v)
+#define g_marshal_value_peek_char(v)     g_value_get_char (v)
+#define g_marshal_value_peek_uchar(v)    g_value_get_uchar (v)
+#define g_marshal_value_peek_int(v)      g_value_get_int (v)
+#define g_marshal_value_peek_uint(v)     g_value_get_uint (v)
+#define g_marshal_value_peek_long(v)     g_value_get_long (v)
+#define g_marshal_value_peek_ulong(v)    g_value_get_ulong (v)
+#define g_marshal_value_peek_int64(v)    g_value_get_int64 (v)
+#define g_marshal_value_peek_uint64(v)   g_value_get_uint64 (v)
+#define g_marshal_value_peek_enum(v)     g_value_get_enum (v)
+#define g_marshal_value_peek_flags(v)    g_value_get_flags (v)
+#define g_marshal_value_peek_float(v)    g_value_get_float (v)
+#define g_marshal_value_peek_double(v)   g_value_get_double (v)
+#define g_marshal_value_peek_string(v)   (char*) g_value_get_string (v)
+#define g_marshal_value_peek_param(v)    g_value_get_param (v)
+#define g_marshal_value_peek_boxed(v)    g_value_get_boxed (v)
+#define g_marshal_value_peek_pointer(v)  g_value_get_pointer (v)
+#define g_marshal_value_peek_object(v)   g_value_get_object (v)
+#else /* !G_ENABLE_DEBUG */
+/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API.
+ *          Do not access GValues directly in your code. Instead, use the
+ *          g_value_get_*() functions
+ */
+#define g_marshal_value_peek_boolean(v)  (v)->data[0].v_int
+#define g_marshal_value_peek_char(v)     (v)->data[0].v_int
+#define g_marshal_value_peek_uchar(v)    (v)->data[0].v_uint
+#define g_marshal_value_peek_int(v)      (v)->data[0].v_int
+#define g_marshal_value_peek_uint(v)     (v)->data[0].v_uint
+#define g_marshal_value_peek_long(v)     (v)->data[0].v_long
+#define g_marshal_value_peek_ulong(v)    (v)->data[0].v_ulong
+#define g_marshal_value_peek_int64(v)    (v)->data[0].v_int64
+#define g_marshal_value_peek_uint64(v)   (v)->data[0].v_uint64
+#define g_marshal_value_peek_enum(v)     (v)->data[0].v_long
+#define g_marshal_value_peek_flags(v)    (v)->data[0].v_ulong
+#define g_marshal_value_peek_float(v)    (v)->data[0].v_float
+#define g_marshal_value_peek_double(v)   (v)->data[0].v_double
+#define g_marshal_value_peek_string(v)   (v)->data[0].v_pointer
+#define g_marshal_value_peek_param(v)    (v)->data[0].v_pointer
+#define g_marshal_value_peek_boxed(v)    (v)->data[0].v_pointer
+#define g_marshal_value_peek_pointer(v)  (v)->data[0].v_pointer
+#define g_marshal_value_peek_object(v)   (v)->data[0].v_pointer
+#endif /* !G_ENABLE_DEBUG */
+
+
+/* VOID:VOID (gtksourceview-marshal.list:1) */
+
+/* VOID:BOOLEAN (gtksourceview-marshal.list:2) */
+
+/* VOID:BOXED (gtksourceview-marshal.list:3) */
+
+/* VOID:BOXED,BOXED (gtksourceview-marshal.list:4) */
+void
+gtksourceview_marshal_VOID__BOXED_BOXED (GClosure     *closure,
+                                         GValue       *return_value,
+                                         guint         n_param_values,
+                                         const GValue *param_values,
+                                         gpointer      invocation_hint,
+                                         gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__BOXED_BOXED) (gpointer     data1,
+                                                  gpointer     arg_1,
+                                                  gpointer     arg_2,
+                                                  gpointer     data2);
+  register GMarshalFunc_VOID__BOXED_BOXED callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__BOXED_BOXED) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_boxed (param_values + 1),
+            g_marshal_value_peek_boxed (param_values + 2),
+            data2);
+}
+
+/* VOID:STRING (gtksourceview-marshal.list:5) */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/gtksourceview-marshal.h	Tue Jun 12 21:21:37 2007 +0000
@@ -0,0 +1,32 @@
+
+#ifndef __gtksourceview_marshal_MARSHAL_H__
+#define __gtksourceview_marshal_MARSHAL_H__
+
+#include	<glib-object.h>
+
+G_BEGIN_DECLS
+
+/* VOID:VOID (gtksourceview-marshal.list:1) */
+#define gtksourceview_marshal_VOID__VOID	g_cclosure_marshal_VOID__VOID
+
+/* VOID:BOOLEAN (gtksourceview-marshal.list:2) */
+#define gtksourceview_marshal_VOID__BOOLEAN	g_cclosure_marshal_VOID__BOOLEAN
+
+/* VOID:BOXED (gtksourceview-marshal.list:3) */
+#define gtksourceview_marshal_VOID__BOXED	g_cclosure_marshal_VOID__BOXED
+
+/* VOID:BOXED,BOXED (gtksourceview-marshal.list:4) */
+extern void gtksourceview_marshal_VOID__BOXED_BOXED (GClosure     *closure,
+                                                     GValue       *return_value,
+                                                     guint         n_param_values,
+                                                     const GValue *param_values,
+                                                     gpointer      invocation_hint,
+                                                     gpointer      marshal_data);
+
+/* VOID:STRING (gtksourceview-marshal.list:5) */
+#define gtksourceview_marshal_VOID__STRING	g_cclosure_marshal_VOID__STRING
+
+G_END_DECLS
+
+#endif /* __gtksourceview_marshal_MARSHAL_H__ */
+
--- a/pidgin/gtkstatusbox.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkstatusbox.c	Tue Jun 12 21:21:37 2007 +0000
@@ -1434,7 +1434,7 @@
 			}
 		}
 	} else {
-		GList *accounts;
+		const GList *accounts;
 		for (accounts = purple_accounts_get_all(); accounts != NULL; accounts = accounts->next) {
 			PurpleAccount *account = accounts->data;
 			PurplePlugin *plug = purple_find_prpl(purple_account_get_protocol_id(account));
--- a/pidgin/gtkthemes.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkthemes.c	Tue Jun 12 21:21:37 2007 +0000
@@ -237,7 +237,7 @@
 	}
 
 	if (load) {
-		GList *cnv;
+		const GList *cnv;
 
 		if (current_smiley_theme)
 			pidgin_themes_destroy_smiley_theme(current_smiley_theme);
--- a/pidgin/gtkutils.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkutils.c	Tue Jun 12 21:21:37 2007 +0000
@@ -130,6 +130,22 @@
 }
 
 GtkWidget *
+pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable)
+{
+	GtkWindow *wnd = NULL;
+
+	wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
+	if (title)
+		gtk_window_set_title(wnd, title);
+	gtk_container_set_border_width(GTK_CONTAINER(wnd), border_width);
+	if (role)
+		gtk_window_set_role(wnd, role);
+	gtk_window_set_resizable(wnd, resizable);
+
+	return GTK_WIDGET(wnd);
+}
+
+GtkWidget *
 pidgin_create_imhtml(gboolean editable, GtkWidget **imhtml_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret)
 {
 	GtkWidget *frame;
@@ -245,13 +261,14 @@
 		gtk_widget_show(to_toggle);
 }
 
-void pidgin_separator(GtkWidget *menu)
+GtkWidget *pidgin_separator(GtkWidget *menu)
 {
 	GtkWidget *menuitem;
 
 	menuitem = gtk_separator_menu_item_new();
 	gtk_widget_show(menuitem);
 	gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+	return menuitem;
 }
 
 GtkWidget *pidgin_new_item(GtkWidget *menu, const char *str)
@@ -462,7 +479,7 @@
 }
 
 static GtkWidget *
-aop_menu_item_new(GtkSizeGroup *sg, GdkPixbuf *pixbuf, const char *lbl, gpointer per_item_data)
+aop_menu_item_new(GtkSizeGroup *sg, GdkPixbuf *pixbuf, const char *lbl, gpointer per_item_data, const char *data)
 {
 	GtkWidget *item;
 	GtkWidget *hbox;
@@ -495,6 +512,7 @@
 	gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
 	gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
 
+	g_object_set_data(G_OBJECT (item), data, per_item_data);
 	g_object_set_data(G_OBJECT (item), "aop_per_item_data", per_item_data);
 
 	pidgin_set_accessible_label(item, label);
@@ -502,6 +520,39 @@
 	return item;
 }
 
+static GdkPixbuf *
+pidgin_create_prpl_icon_from_prpl(PurplePlugin *prpl, PidginPrplIconSize size, PurpleAccount *account)
+{
+	PurplePluginProtocolInfo *prpl_info;
+	const char *protoname = NULL;
+	char buf[MAXPATHLEN];
+	char *filename = NULL;
+	GdkPixbuf *pixbuf;
+
+	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
+	if (prpl_info->list_icon == NULL)
+		return NULL;
+
+	protoname = prpl_info->list_icon(account, NULL);
+	if (protoname == NULL)
+		return NULL;
+
+	/*
+	 * Status icons will be themeable too, and then it will look up
+	 * protoname from the theme
+	 */
+	g_snprintf(buf, sizeof(buf), "%s.png", protoname);
+
+	filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols",
+				    size == PIDGIN_PRPL_ICON_SMALL ? "16" :
+				    size == PIDGIN_PRPL_ICON_MEDIUM ? "22" : "48",
+				    buf, NULL);
+	pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
+	g_free(filename);
+
+	return pixbuf;
+}
+
 static GtkWidget *
 aop_option_menu_new(AopMenu *aop_menu, GCallback cb, gpointer user_data)
 {
@@ -552,25 +603,6 @@
 	}
 }
 
-static GdkPixbuf *
-get_prpl_pixbuf(PurplePluginProtocolInfo *prpl_info)
-{
-	const char *proto_name;
-	GdkPixbuf *pixbuf = NULL;
-	char *filename;
-	char buf[256];
-
-	proto_name = prpl_info->list_icon(NULL, NULL);
-	g_return_val_if_fail(proto_name != NULL, NULL);
-
-	g_snprintf(buf, sizeof(buf), "%s.png", proto_name);
-	filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", "16", buf, NULL);
-	pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
-	g_free(filename);
-
-	return pixbuf;
-}
-
 static AopMenu *
 create_protocols_menu(const char *default_proto_id)
 {
@@ -602,11 +634,14 @@
 		if (gtalk_name && strcmp(gtalk_name, plugin->info->name) < 0) {
 			char *filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols",
 			                                  "16", "google-talk.png", NULL);
+			GtkWidget *item;
+
 			pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
 			g_free(filename);
 
 			gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
-				aop_menu_item_new(sg, pixbuf, gtalk_name, "prpl-jabber"));
+				item = aop_menu_item_new(sg, pixbuf, gtalk_name, "prpl-jabber", "protocol"));
+			g_object_set_data(G_OBJECT(item), "fake", GINT_TO_POINTER(1));
 
 			if (pixbuf)
 				g_object_unref(pixbuf);
@@ -615,10 +650,10 @@
 			i++;
 		}
 
-		pixbuf = get_prpl_pixbuf(prpl_info);
+		pixbuf = pidgin_create_prpl_icon_from_prpl(plugin, PIDGIN_PRPL_ICON_SMALL, NULL);
 
 		gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
-			aop_menu_item_new(sg, pixbuf, plugin->info->name, plugin->info->id));
+			aop_menu_item_new(sg, pixbuf, plugin->info->name, plugin->info->id, "protocol"));
 
 		if (pixbuf)
 			g_object_unref(pixbuf);
@@ -652,8 +687,8 @@
 	AopMenu *aop_menu = NULL;
 	PurpleAccount *account;
 	GdkPixbuf *pixbuf = NULL;
-	GList *list;
-	GList *p;
+	const GList *list;
+	const GList *p;
 	GtkSizeGroup *sg;
 	int i;
 	char buf[256];
@@ -670,7 +705,6 @@
 	sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
 
 	for (p = list, i = 0; p != NULL; p = p->next, i++) {
-		PurplePluginProtocolInfo *prpl_info = NULL;
 		PurplePlugin *plugin;
 
 		if (show_all)
@@ -688,18 +722,12 @@
 
 		plugin = purple_find_prpl(purple_account_get_protocol_id(account));
 
-		if (plugin)
-			prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin);
-
-		/* Load the image. */
-		if (prpl_info) {
-			pixbuf = get_prpl_pixbuf(prpl_info);
-
-			if (pixbuf) {
-				if (purple_account_is_disconnected(account) && show_all &&
-						purple_connections_get_all())
-					gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
-			}
+		pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
+
+		if (pixbuf) {
+			if (purple_account_is_disconnected(account) && show_all &&
+					purple_connections_get_all())
+				gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE);
 		}
 
 		if (purple_account_get_alias(account)) {
@@ -714,7 +742,7 @@
 		}
 
 		gtk_menu_shell_append(GTK_MENU_SHELL(aop_menu->menu),
-			aop_menu_item_new(sg, pixbuf, buf, account));
+			aop_menu_item_new(sg, pixbuf, buf, account, "account"));
 
 		if (pixbuf)
 			g_object_unref(pixbuf);
@@ -883,6 +911,15 @@
 	g_free(filename);
 }
 
+void pidgin_retrieve_user_info(PurpleConnection *conn, const char *name)
+{
+	PurpleNotifyUserInfo *info = purple_notify_user_info_new();
+	purple_notify_user_info_add_pair(info, _("Information"), _("Retrieving..."));
+	purple_notify_userinfo(conn, name, info, NULL, NULL);
+	purple_notify_user_info_destroy(info);
+	serv_get_info(conn, name);
+}
+
 gboolean
 pidgin_parse_x_im_contact(const char *msg, gboolean all_accounts,
 							PurpleAccount **ret_account, char **ret_protocol,
@@ -958,9 +995,9 @@
 		/* Check for a compatible account. */
 		if (ret_account != NULL)
 		{
-			GList *list;
+			const GList *list;
 			PurpleAccount *account = NULL;
-			GList *l;
+			const GList *l;
 			const char *protoname;
 
 			if (all_accounts)
@@ -1583,40 +1620,13 @@
 pidgin_create_prpl_icon(PurpleAccount *account, PidginPrplIconSize size)
 {
 	PurplePlugin *prpl;
-	PurplePluginProtocolInfo *prpl_info;
-	const char *protoname = NULL;
-	char buf[256]; /* TODO: We should use a define for max file length */
-	char *filename = NULL;
-	GdkPixbuf *pixbuf;
 
 	g_return_val_if_fail(account != NULL, NULL);
 
 	prpl = purple_find_prpl(purple_account_get_protocol_id(account));
 	if (prpl == NULL)
 		return NULL;
-
-	prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
-	if (prpl_info->list_icon == NULL)
-		return NULL;
-
-	protoname = prpl_info->list_icon(account, NULL);
-	if (protoname == NULL)
-		return NULL;
-
-	/*
-	 * Status icons will be themeable too, and then it will look up
-	 * protoname from the theme
-	 */
-	g_snprintf(buf, sizeof(buf), "%s.png", protoname);
-
-	filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols",
-				    size == PIDGIN_PRPL_ICON_SMALL ? "16" :
-				    size == PIDGIN_PRPL_ICON_MEDIUM ? "22" : "48",
-				    buf, NULL);
-	pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
-	g_free(filename);
-
-	return pixbuf;
+	return pidgin_create_prpl_icon_from_prpl(prpl, size, account);
 }
 
 static void
@@ -1632,62 +1642,63 @@
 		callback(object, data);
 }
 
-void
+GtkWidget *
 pidgin_append_menu_action(GtkWidget *menu, PurpleMenuAction *act,
                             gpointer object)
 {
+	GtkWidget *menuitem;
+
 	if (act == NULL) {
-		pidgin_separator(menu);
-	} else {
-		GtkWidget *menuitem;
-
-		if (act->children == NULL) {
-			menuitem = gtk_menu_item_new_with_mnemonic(act->label);
-
-			if (act->callback != NULL) {
-				g_object_set_data(G_OBJECT(menuitem),
-				                  "purplecallback",
-				                  act->callback);
-				g_object_set_data(G_OBJECT(menuitem),
-				                  "purplecallbackdata",
-				                  act->data);
-				g_signal_connect(G_OBJECT(menuitem), "activate",
-				                 G_CALLBACK(menu_action_cb),
-				                 object);
-			} else {
-				gtk_widget_set_sensitive(menuitem, FALSE);
-			}
-
-			gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+		return pidgin_separator(menu);
+	}
+
+	if (act->children == NULL) {
+		menuitem = gtk_menu_item_new_with_mnemonic(act->label);
+
+		if (act->callback != NULL) {
+			g_object_set_data(G_OBJECT(menuitem),
+							  "purplecallback",
+							  act->callback);
+			g_object_set_data(G_OBJECT(menuitem),
+							  "purplecallbackdata",
+							  act->data);
+			g_signal_connect(G_OBJECT(menuitem), "activate",
+							 G_CALLBACK(menu_action_cb),
+							 object);
 		} else {
-			GList *l = NULL;
-			GtkWidget *submenu = NULL;
-			GtkAccelGroup *group;
-
-			menuitem = gtk_menu_item_new_with_mnemonic(act->label);
-			gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
-
-			submenu = gtk_menu_new();
-			gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
-
-			group = gtk_menu_get_accel_group(GTK_MENU(menu));
-			if (group) {
-				char *path = g_strdup_printf("%s/%s", GTK_MENU_ITEM(menuitem)->accel_path, act->label);
-				gtk_menu_set_accel_path(GTK_MENU(submenu), path);
-				g_free(path);
-				gtk_menu_set_accel_group(GTK_MENU(submenu), group);
-			}
-
-			for (l = act->children; l; l = l->next) {
-				PurpleMenuAction *act = (PurpleMenuAction *)l->data;
-
-				pidgin_append_menu_action(submenu, act, object);
-			}
-			g_list_free(act->children);
-			act->children = NULL;
+			gtk_widget_set_sensitive(menuitem, FALSE);
 		}
-		purple_menu_action_free(act);
+
+		gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+	} else {
+		GList *l = NULL;
+		GtkWidget *submenu = NULL;
+		GtkAccelGroup *group;
+
+		menuitem = gtk_menu_item_new_with_mnemonic(act->label);
+		gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
+
+		submenu = gtk_menu_new();
+		gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
+
+		group = gtk_menu_get_accel_group(GTK_MENU(menu));
+		if (group) {
+			char *path = g_strdup_printf("%s/%s", GTK_MENU_ITEM(menuitem)->accel_path, act->label);
+			gtk_menu_set_accel_path(GTK_MENU(submenu), path);
+			g_free(path);
+			gtk_menu_set_accel_group(GTK_MENU(submenu), group);
+		}
+
+		for (l = act->children; l; l = l->next) {
+			PurpleMenuAction *act = (PurpleMenuAction *)l->data;
+
+			pidgin_append_menu_action(submenu, act, object);
+		}
+		g_list_free(act->children);
+		act->children = NULL;
 	}
+	purple_menu_action_free(act);
+	return menuitem;
 }
 
 #if GTK_CHECK_VERSION(2,3,0)
@@ -3034,20 +3045,20 @@
 
         row = pixels;
         for (i = 3; i < rowstride; i+=4) {
-                if (row[i] != 0xff)
+                if (row[i] < 0xfe)
                         return FALSE;
         }
 
         for (i = 1; i < height - 1; i++) {
                 row = pixels + (i*rowstride);
-                if (row[3] != 0xff || row[rowstride-1] != 0xff) {
+                if (row[3] < 0xfe || row[rowstride-1] < 0xfe) {
                         return FALSE;
-                }
+            }
         }
 
         row = pixels + ((height-1) * rowstride);
         for (i = 3; i < rowstride; i+=4) {
-                if (row[i] != 0xff)
+                if (row[i] < 0xfe)
                         return FALSE;
         }
 
--- a/pidgin/gtkutils.h	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkutils.h	Tue Jun 12 21:21:37 2007 +0000
@@ -93,6 +93,18 @@
 GtkWidget *pidgin_create_imhtml(gboolean editable, GtkWidget **imhtml_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret);
 
 /**
+ * Creates a new window
+ *
+ * @param title        The window title, or @c NULL
+ * @param border_width The window's desired border width
+ * @param role         A string indicating what the window is responsible for doing, or @c NULL
+ * @param resizable    Whether the window should be resizable (@c TRUE) or not (@c FALSE)
+ *
+ * @since 2.1.0
+ */
+GtkWidget *pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable);
+
+/**
  * Toggles the sensitivity of a widget.
  *
  * @param widget    @c NULL. Used for signal handlers.
@@ -130,8 +142,10 @@
  * Adds a separator to a menu.
  *
  * @param menu The menu to add a separator to.
+ *
+ * @return The separator.
  */
-void pidgin_separator(GtkWidget *menu);
+GtkWidget *pidgin_separator(GtkWidget *menu);
 
 /**
  * Creates a menu item.
@@ -307,6 +321,14 @@
 void pidgin_load_accels(void);
 
 /**
+ * Get information about a user. Show immediate feedback.
+ *
+ * @param conn   The connection to get information from.
+ * @param name   The user to get information about.
+ */
+void pidgin_retrieve_user_info(PurpleConnection *conn, const char *name);
+
+/**
  * Parses an application/x-im-contact MIME message and returns the
  * data inside.
  *
@@ -404,8 +426,10 @@
  * @param menu    The menu to append to.
  * @param act     The PurpleMenuAction to append.
  * @param gobject The object to be passed to the action callback.
+ *
+ * @return   The menuitem added.
  */
-void pidgin_append_menu_action(GtkWidget *menu, PurpleMenuAction *act,
+GtkWidget *pidgin_append_menu_action(GtkWidget *menu, PurpleMenuAction *act,
                                  gpointer gobject);
 
 /**
--- a/pidgin/gtkwhiteboard.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/gtkwhiteboard.c	Tue Jun 12 21:21:37 2007 +0000
@@ -28,6 +28,7 @@
 #include "debug.h"
 
 #include "gtkwhiteboard.h"
+#include "gtkutils.h"
 
 /******************************************************************************
  * Prototypes
@@ -143,21 +144,14 @@
 		gtkwb->brush_color = 0xff0000;
 	}
 
-	window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-	gtkwb->window = window;
-	gtk_widget_set_name(window, wb->who);
-
 	/* Try and set window title as the name of the buddy, else just use their
 	 * username
 	 */
 	buddy = purple_find_buddy(wb->account, wb->who);
 
-	if (buddy != NULL)
-		gtk_window_set_title((GtkWindow*)(window), purple_buddy_get_contact_alias(buddy));
-	else
-		gtk_window_set_title((GtkWindow*)(window), wb->who);
-
-	gtk_window_set_resizable((GtkWindow*)(window), FALSE);
+	window = pidgin_create_window(buddy != NULL ? purple_buddy_get_contact_alias(buddy) : wb->who, 0, NULL, FALSE);
+	gtkwb->window = window;
+	gtk_widget_set_name(window, wb->who);
 
 	g_signal_connect(G_OBJECT(window), "delete_event",
 					 G_CALLBACK(whiteboard_close_cb), gtkwb);
--- a/pidgin/plugins/cap/cap.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/plugins/cap/cap.c	Tue Jun 12 21:21:37 2007 +0000
@@ -918,7 +918,8 @@
 
 static PidginPluginUiInfo ui_info = {
 	get_config_frame,
-	0 /* page_num (reserved) */
+	0 /* page_num (reserved) */,
+	NULL,NULL,NULL,NULL
 };
 
 static PurplePluginInfo info = {
@@ -944,7 +945,8 @@
 	&ui_info,										/**< ui_info		*/
 	NULL,											/**< extra_info	 */
 	NULL,											/**< prefs_info		*/
-	NULL
+	NULL,
+	NULL,NULL,NULL,NULL
 };
 
 static GtkWidget * get_config_frame(PurplePlugin *plugin) {
--- a/pidgin/plugins/gestures/gestures.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/plugins/gestures/gestures.c	Tue Jun 12 21:21:37 2007 +0000
@@ -176,7 +176,7 @@
 plugin_load(PurplePlugin *plugin)
 {
 	PurpleConversation *conv;
-	GList *l;
+	const GList *l;
 
 	for (l = purple_get_conversations(); l != NULL; l = l->next) {
 		conv = (PurpleConversation *)l->data;
@@ -199,7 +199,7 @@
 {
 	PurpleConversation *conv;
 	PidginConversation *gtkconv;
-	GList *l;
+	const GList *l;
 
 	for (l = purple_get_conversations(); l != NULL; l = l->next) {
 		conv = (PurpleConversation *)l->data;
--- a/pidgin/plugins/gevolution/add_buddy_dialog.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/plugins/gevolution/add_buddy_dialog.c	Tue Jun 12 21:21:37 2007 +0000
@@ -162,7 +162,7 @@
 		GList *list, const char *id)
 {
 	PurpleAccount *account = NULL;
-	GList *l;
+	const GList *l;
 	GtkTreeIter iter;
 	GdkPixbuf *pixbuf;
 
@@ -442,10 +442,7 @@
 	if (username != NULL)
 		dialog->username = g_strdup(username);
 
-	dialog->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-	gtk_window_set_role(GTK_WINDOW(dialog->win), "add_buddy");
-	gtk_window_set_title(GTK_WINDOW(dialog->win), _("Add Buddy"));
-	gtk_container_set_border_width(GTK_CONTAINER(dialog->win), 12);
+	dialog->win = pidgin_create_window(_("Add Buddy"), PIDGIN_HIG_BORDER, "add_buddy", TRUE);
 	gtk_widget_set_size_request(dialog->win, -1, 400);
 
 	g_signal_connect(G_OBJECT(dialog->win), "delete_event",
--- a/pidgin/plugins/gevolution/assoc-buddy.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/plugins/gevolution/assoc-buddy.c	Tue Jun 12 21:21:37 2007 +0000
@@ -329,9 +329,7 @@
 
 	dialog->buddy = buddy;
 
-	dialog->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-	gtk_window_set_role(GTK_WINDOW(dialog->win), "assoc_buddy");
-	gtk_container_set_border_width(GTK_CONTAINER(dialog->win), 12);
+	dialog->win = pidgin_create_window(NULL, PIDGIN_HIG_BORDER, "assoc_buddy", TRUE);
 
 	g_signal_connect(G_OBJECT(dialog->win), "delete_event",
 					 G_CALLBACK(delete_win_cb), dialog);
--- a/pidgin/plugins/gevolution/gevolution.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/plugins/gevolution/gevolution.c	Tue Jun 12 21:21:37 2007 +0000
@@ -38,7 +38,6 @@
 
 #include <libedata-book/Evolution-DataServer-Addressbook.h>
 
-#include <libebook/e-book-listener.h>
 #include <libedata-book/e-data-book-factory.h>
 #include <bonobo/bonobo-main.h>
 
@@ -70,7 +69,8 @@
 						const char *prpl_id, EContactField field)
 {
 	GList *ims = e_contact_get(contact, field);
-	GList *l, *l2;
+	const GList *l;
+	const GList *l2;
 
 	if (ims == NULL)
 		return;
@@ -401,7 +401,7 @@
 	GtkCellRenderer *renderer;
 	GdkPixbuf *pixbuf;
 	GtkListStore *model;
-	GList *l;
+	const GList *l;
 
 	/* Outside container */
 	ret = gtk_vbox_new(FALSE, 18);
--- a/pidgin/plugins/gevolution/new_person_dialog.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/plugins/gevolution/new_person_dialog.c	Tue Jun 12 21:21:37 2007 +0000
@@ -246,11 +246,7 @@
 	dialog->book = book;
 	g_object_ref(book);
 
-	dialog->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-	gtk_window_set_role(GTK_WINDOW(dialog->win), "new_person");
-	gtk_window_set_title(GTK_WINDOW(dialog->win), _("New Person"));	
-	gtk_window_set_resizable(GTK_WINDOW(dialog->win), FALSE);
-	gtk_container_set_border_width(GTK_CONTAINER(dialog->win), 12);
+	dialog->win = pidgin_create_window(_("New Person"), PIDGIN_HIG_BORDER, "new_person", FALSE);
 
 	g_signal_connect(G_OBJECT(dialog->win), "delete_event",
 					 G_CALLBACK(delete_win_cb), dialog);
--- a/pidgin/plugins/notify.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/plugins/notify.c	Tue Jun 12 21:21:37 2007 +0000
@@ -624,7 +624,7 @@
 static void
 apply_method()
 {
-	GList *convs;
+	const GList *convs;
 	PidginWindow *purplewin = NULL;
 
 	for (convs = purple_get_conversations(); convs != NULL;
@@ -644,7 +644,7 @@
 static void
 apply_notify()
 {
-	GList *convs = purple_get_conversations();
+	const GList *convs = purple_get_conversations();
 
 	while (convs) {
 		PurpleConversation *conv = (PurpleConversation *)convs->data;
@@ -818,7 +818,7 @@
 static gboolean
 plugin_load(PurplePlugin *plugin)
 {
-	GList *convs = purple_get_conversations();
+	const GList *convs = purple_get_conversations();
 	void *conv_handle = purple_conversations_get_handle();
 	void *gtk_conv_handle = pidgin_conversations_get_handle();
 
@@ -860,7 +860,7 @@
 static gboolean
 plugin_unload(PurplePlugin *plugin)
 {
-	GList *convs = purple_get_conversations();
+	const GList *convs = purple_get_conversations();
 
 	while (convs) {
 		PurpleConversation *conv = (PurpleConversation *)convs->data;
--- a/pidgin/plugins/spellchk.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/plugins/spellchk.c	Tue Jun 12 21:21:37 2007 +0000
@@ -2118,7 +2118,7 @@
 plugin_load(PurplePlugin *plugin)
 {
 	void *conv_handle = purple_conversations_get_handle();
-	GList *convs;
+	const GList *convs;
 
 	load_conf();
 
@@ -2137,7 +2137,7 @@
 static gboolean
 plugin_unload(PurplePlugin *plugin)
 {
-	GList *convs;
+	const GList *convs;
 
 	/* Detach from existing conversations */
 	for (convs = purple_get_conversations(); convs != NULL; convs = convs->next)
--- a/pidgin/plugins/ticker/ticker.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/plugins/ticker/ticker.c	Tue Jun 12 21:21:37 2007 +0000
@@ -36,6 +36,7 @@
 
 #include "gtkblist.h"
 #include "gtkplugin.h"
+#include "gtkutils.h"
 
 #include "gtkticker.h"
 
@@ -70,12 +71,10 @@
 		return;
 	}
 
-	tickerwindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+	tickerwindow = pidgin_create_window(_("Buddy Ticker"), 0, "ticker", TRUE);
 	gtk_window_set_default_size(GTK_WINDOW(tickerwindow), 500, -1);
 	g_signal_connect(G_OBJECT(tickerwindow), "delete_event",
 			G_CALLBACK (buddy_ticker_destroy_window), NULL);
-	gtk_window_set_title (GTK_WINDOW(tickerwindow), _("Buddy Ticker"));
-	gtk_window_set_role (GTK_WINDOW(tickerwindow), "ticker");
 
 	ticker = gtk_ticker_new();
 	gtk_ticker_set_spacing(GTK_TICKER(ticker), 20);
--- a/pidgin/plugins/timestamp.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/plugins/timestamp.c	Tue Jun 12 21:21:37 2007 +0000
@@ -77,7 +77,7 @@
 	time_t now = time(NULL) / interval * interval;
 	time_t then;
 
-	if (!g_list_find(purple_get_conversations(), conv))
+	if (!g_list_find((GList *)purple_get_conversations(), conv))
 		return FALSE;
 
 	then = GPOINTER_TO_INT(purple_conversation_get_data(
@@ -98,7 +98,7 @@
 	PidginConversation *gtk_conv = PIDGIN_CONVERSATION(conv);
 	GtkTextBuffer *buffer;
 
-	if (!g_list_find(purple_get_conversations(), conv))
+	if (!g_list_find((GList *)purple_get_conversations(), conv))
 		return;
 
 	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtk_conv->imhtml));
--- a/pidgin/plugins/xmppconsole.c	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/plugins/xmppconsole.c	Tue Jun 12 21:21:37 2007 +0000
@@ -29,6 +29,7 @@
 #if !GTK_CHECK_VERSION(2,4,0)
 #include "pidgincombobox.h"
 #endif
+#include "gtkutils.h"
 
 typedef struct {
 	PurpleConnection *gc;
@@ -730,7 +731,7 @@
 	GtkWidget *label;
 	GtkTextBuffer *buffer;
 	GtkWidget *toolbar;
-	GList *connections;
+	const GList *connections;
 #if GTK_CHECK_VERSION(2,4,0)
 	GtkToolItem *button;
 #endif
@@ -742,10 +743,8 @@
 	
 	console = g_new0(XmppConsole, 1);
 
-	console->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
-	gtk_window_set_title(GTK_WINDOW(console->window), _("XMPP Console"));
+	console->window = pidgin_create_window(_("XMPP Console"), PIDGIN_HIG_BORDER, NULL, TRUE);
 	g_signal_connect(G_OBJECT(console->window), "destroy", G_CALLBACK(console_destroy), NULL);
-	gtk_container_set_border_width(GTK_CONTAINER(console->window), 12);
 	gtk_window_set_default_size(GTK_WINDOW(console->window), 580, 400);
 	gtk_container_add(GTK_CONTAINER(console->window), vbox);
 
--- a/pidgin/win32/nsis/pidgin-installer.nsi	Tue Jun 12 13:54:04 2007 +0000
+++ b/pidgin/win32/nsis/pidgin-installer.nsi	Tue Jun 12 21:21:37 2007 +0000
@@ -690,6 +690,7 @@
     Delete "$INSTDIR\plugins\iconaway.dll"
     Delete "$INSTDIR\plugins\idle.dll"
     Delete "$INSTDIR\plugins\libaim.dll"
+    Delete "$INSTDIR\plugins\libbonjour.dll"
     Delete "$INSTDIR\plugins\libgg.dll"
     Delete "$INSTDIR\plugins\libicq.dll"
     Delete "$INSTDIR\plugins\libirc.dll"
@@ -1067,6 +1068,7 @@
 
   have_gtk:
     ; GTK+ is already installed; check version.
+	; Change this to not even run the GTK installer if this version is already installed.
     ${VersionCompare} ${GTK_INSTALL_VERSION} $0 $3
     IntCmp $3 1 +1 good_version good_version
     ${VersionCompare} ${GTK_MIN_VERSION} $0 $3
--- a/po/POTFILES.in	Tue Jun 12 13:54:04 2007 +0000
+++ b/po/POTFILES.in	Tue Jun 12 21:21:37 2007 +0000
@@ -132,6 +132,14 @@
 libpurple/protocols/silc/silc.c
 libpurple/protocols/silc/util.c
 libpurple/protocols/silc/wb.c
+libpurple/protocols/silc10/buddy.c
+libpurple/protocols/silc10/chat.c
+libpurple/protocols/silc10/ft.c
+libpurple/protocols/silc10/ops.c
+libpurple/protocols/silc10/pk.c
+libpurple/protocols/silc10/silc.c
+libpurple/protocols/silc10/util.c
+libpurple/protocols/silc10/wb.c
 libpurple/protocols/simple/simple.c
 libpurple/protocols/toc/toc.c
 libpurple/protocols/yahoo/yahoo.c
--- a/valgrind-suppressions	Tue Jun 12 13:54:04 2007 +0000
+++ b/valgrind-suppressions	Tue Jun 12 21:21:37 2007 +0000
@@ -147,8 +147,8 @@
    fun:PR_Init
    fun:rsa_nss_init
    fun:GE_plugin_load
-   fun:gaim_plugin_load
-   fun:gaim_plugins_load_saved
+   fun:purple_plugin_load
+   fun:purple_plugins_load_saved
    fun:main
 }