Mercurial > pidgin.yaz
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(¶ms, 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, + ¶ms, 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(¶ms, 0, sizeof(params)); + params.timeout_secs = 60; + silc_client_perform_key_agreement(a->client, a->conn, + client_entry, ¶ms, + 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(¶ms, 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, + ¶ms, 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(¶ms, 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, ¶ms, + 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(¶ms, 0, sizeof(params)); - dfile = silcpurple_session_file(purple_account_get_username(sg->account)); - params.detach_data = (unsigned char *)silc_file_readfile(dfile, ¶ms.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, ¶ms, - (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(¶ms, 0, sizeof(params)); + dfile = silcpurple_session_file(purple_account_get_username(sg->account)); + params.detach_data = (unsigned char *)silc_file_readfile(dfile, ¶ms.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, ¶ms, 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(¶ms, 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, ¶ms, 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(¶ms, 0, sizeof(params)); + dfile = silcpurple_session_file(purple_account_get_username(sg->account)); + params.detach_data = (unsigned char *)silc_file_readfile(dfile, ¶ms.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, ¶ms, + (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(¶ms, 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, ¶ms, 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 [<new topic>]: 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 <channel> [<password>]: 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 <nick>: 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 <nick> <message>: 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 <nick> [<message>]: 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 <command>: 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 <nick> [-pubkey|<reason>]: 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 <newnick>: 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 <nick>: 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 <channel> [+|-<modes>] [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 <channel> +|-<modes> <nick>: 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 <usermodes>: 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 <nick> [-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 <channel> [-|+]<nick>: 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 <channel> <nick> [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 [<channel> +|-<nick>]: 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 <nick|server>: 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 <channel>: 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] <channel(s)>: 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, >kconv->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, >kconv->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, >kconv->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, >kconv->entry, >kconv->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 }