Wed, 27 Jun 2007 21:43:18 +0000
propagate from branch 'im.pidgin.pidgin' (head b80fbc607f57c3c6e3fb76ed964d0544ae4afa2e)
to branch 'org.maemo.garage.pidgin.smiley-install' (head 73c2353f2cfa43257c7b70853421d973a1394c1a)
| libpurple/protocols/bonjour/dns_sd.c | file | annotate | diff | comparison | revisions | |
| libpurple/protocols/bonjour/dns_sd.h | file | annotate | diff | comparison | revisions | |
| libpurple/protocols/bonjour/mdns_howl.c | file | annotate | diff | comparison | revisions | |
| libpurple/protocols/bonjour/mdns_howl.h | file | annotate | diff | comparison | revisions | |
| libpurple/protocols/qq/group_misc.c | file | annotate | diff | comparison | revisions | |
| libpurple/protocols/qq/group_misc.h | file | annotate | diff | comparison | revisions | |
| pidgin/gtkprefs.c | file | annotate | diff | comparison | revisions | |
| pidgin/gtkthemes.c | file | annotate | diff | comparison | revisions |
--- a/COPYRIGHT Sun Jun 03 09:40:38 2007 +0000 +++ b/COPYRIGHT Wed Jun 27 21:43:18 2007 +0000 @@ -8,6 +8,7 @@ Dave Ahlswede Manuel Amador Matt Amato +Elliott Sales de Andrade Geoffrey Antos Daniel Atallah Paul Aurich @@ -51,6 +52,7 @@ Jeremy Brooks Jonathan Brossard Philip Brown +Norbert Buchmuller Sean Burke Thomas Butter Trevor Caira @@ -88,6 +90,7 @@ Jeramey Crawford Michael Culbertson Steven Danna +Chris Davies Martijn Dekker Vinicius Depizzol Philip Derrin @@ -144,6 +147,7 @@ Charlie Gordon Ryan C. Gordon Miah Gregory +David Grohmann Christian Hammond Erick Hamness Fred Hampton @@ -224,6 +228,7 @@ Lalo Martins John Matthews Simo Mattila +Michal Matyska Ryan McCabe Peter McCurdy Kurt McKee @@ -264,12 +269,14 @@ Ted Percival Eduardo Pérez Matt Perry +Nathan Peterson Celso Pinto Joao Luís Marques Pinto Aleksander Piotrowski Julien Pivotto Ari Pollak Robey Pointer +Eric Polino Stephen Pope Nathan Poznick Jory A. Pratt @@ -349,6 +356,7 @@ Cestonaro Thilo Will Thompson Douglas Thrift (douglaswth) +Mark Tiefenbruck Andrew Tinney Jeffery To Warren Togami
--- a/ChangeLog Sun Jun 03 09:40:38 2007 +0000 +++ b/ChangeLog Wed Jun 27 21:43:18 2007 +0000 @@ -1,14 +1,49 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul -version 2.0.2 (??/??/????): +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 (06/14/2007): Pidgin: * Added a custom conversation font option to preferences + * Fixed smiley ordering in the insert smiley popup to be more intuitive + * Conversation->More menu items work for Chats as well as Buddies, + including those not on your buddy list + * newline plugin should work better with conversation colors plugin now + * Get Info on users now provides immediate feedback that something is + happening + * Aliasing a buddy will not be interrupted by other buddy list activity + * Using the -l option to log in to a specific account works better libpurple: * Moving an ICQ buddy from one group to another no longer re-requests authorization from that person (Rene Hausleitner) * Added nullprpl, an example protocol plugin (Ryan Barrett) * Fixed SOCKS5 bug which caused Jabber file receiving to fail + * 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) + * Fix for some QQ authentication problems + * Fix for building on FreeBSD + * Prevent "Logged in:" times for AIM buddies being ridiculously high + * Updates and fixes to Bonjour support + * Improve ICQ encoding support for some non-latin languages Finch: * Auto account reconnecting
--- a/ChangeLog.API Sun Jun 03 09:40:38 2007 +0000 +++ b/ChangeLog.API Wed Jun 27 21:43:18 2007 +0000 @@ -1,5 +1,104 @@ 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. + * 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_image_data_calculate_filename + * 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 + * purple_pounces_get_all_for_ui + + Changed: + * The documentation of the following functions now properly + declares that the returned value must not be modified or + freed, which was always the case: + * 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 + * The following functions now return a GList* instead of a + const GList*, as const is not very useful with GLists. The + returned value still must not be modified or freed: + * purple_account_get_status_types + * purple_mime_document_get_fields + * purple_mime_document_get_parts + * purple_mime_part_get_fields + * purple_request_fields_get_required + * purple_request_field_list_get_selected + * purple_request_field_list_get_items + * purple_status_type_get_attrs + * purple_presence_get_statuses + * purple_request_field_list_set_selected now takes a GList* + instead of a const GList* for items, as const is not very + useful with GLists. The passed list is still not modified + or freed. + * purple_presence_add_list now takes a GList* instead of a + const GList* for source_list, as const is not very useful with + GLists. The passed list is still not modified or freed. + + Pidgin: + Added: + * gtk_imhtml_setup_entry + * pidgin_create_window + * pidgin_retrieve_user_info and pidgin_retrieve_user_info_in_chat, + shows immediate feedback when getting information about a user. + * gtk_imhtml_animation_new + Can be used for inserting an animated image into an IMHTML. + * pidgin_menu_position_func_helper + * pidgin_blist_get_name_markup, returns the buddy list markup + text for a given buddy. + + Changed: + * pidgin_append_menu_action returns the menuitem added to the menu. + * pidgin_separator returns the separator added to the menu. + * PidginConversation has struct members to handle the new info + pane: + * infopane + * infopane_hbox + * infopane_model + * infopane_iter + + Finch: + Added: + * finch_retrieve_user_info + + Changed: + * gnt_tree_get_rows() now returns a GList* instead of a const + GList*, as const is not very useful with GLists. The + returned value still must not be modified or freed. + +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 Sun Jun 03 09:40:38 2007 +0000 +++ b/ChangeLog.win32 Wed Jun 27 21:43:18 2007 +0000 @@ -1,4 +1,7 @@ -version 2.0.1 (??/??/????): +version 2.0.2 (6/14/2007): + * 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/NEWS Sun Jun 03 09:40:38 2007 +0000 +++ b/NEWS Wed Jun 27 21:43:18 2007 +0000 @@ -1,5 +1,18 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +2.0.2 (6/14/2007): + Sean: Another big maintenance release. Again, about 100 tickets were + resolved in this release, and they keep coming in. Lots of bug fixes, + some minor icon adjustements, hopefully we addressed some ICQ + internationalization issues, and support for Bonjour on Windows! + Our next release will be 2.1.0, and will come with some great new + features. + + Stu: I think we're gradually getting the hang of this 3 week thing + again. This release includes yet more bug fixes. I'd also like to + specifically thank Pekka Riikonen for the patch to enable using SILC + Toolkit 1.1 with Pidgin/libpurple that is included in this release. + 2.0.1 (5/24/2007): Sean: 2.0.1! Three weeks later, as scheduled! It is so nice to have regular, frequent, releases again! This is a bugfix release; We have
--- a/config.h.mingw Sun Jun 03 09:40:38 2007 +0000 +++ b/config.h.mingw Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/configure.ac Wed Jun 27 21:43:18 2007 +0000 @@ -43,19 +43,19 @@ # # 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]) m4_define([purple_display_version], purple_version[]m4_ifdef([purple_version_suffix],[purple_version_suffix])) -m4_define([gnt_lt_current], [0]) -m4_define([gnt_major_version], [1]) +m4_define([gnt_lt_current], [1]) +m4_define([gnt_major_version], [2]) m4_define([gnt_minor_version], [0]) -m4_define([gnt_micro_version], [2]) +m4_define([gnt_micro_version], [0]) m4_define([gnt_version_suffix], [devel]) m4_define([gnt_version], [gnt_major_version.gnt_minor_version.gnt_micro_version]) @@ -142,7 +142,7 @@ dnl If we don't have msgfmt, then po/ is going to fail -- ensure that dnl AM_GLIB_GNU_GETTEXT found it. -if test x$MSGFMT = xno -o x$GMSGFMT = x +if test x$MSGFMT = xno then AC_ERROR([ @@ -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") @@ -2056,6 +2104,8 @@ pidgin/pixmaps/tray/16/scalable/Makefile pidgin/pixmaps/tray/22/Makefile pidgin/pixmaps/tray/22/scalable/Makefile + pidgin/pixmaps/tray/32/Makefile + pidgin/pixmaps/tray/48/Makefile pidgin/plugins/Makefile pidgin/plugins/cap/Makefile pidgin/plugins/gestures/Makefile @@ -2090,6 +2140,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 Sun Jun 03 09:40:38 2007 +0000 +++ b/doc/conversation-signals.dox Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/doc/finch.1.in Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/doc/funniest_home_convos.txt Wed Jun 27 21:43:18 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/doc/gtkrc-2.0 Sun Jun 03 09:40:38 2007 +0000 +++ b/doc/gtkrc-2.0 Wed Jun 27 21:43:18 2007 +0000 @@ -39,16 +39,17 @@ } widget_class "*" style "my-style-name" -# This style sets the expander size on a treeview to 0, which has the result +# This sets the expander size on a treeview to 0, which has the result # of reducing the indent for items in the tree. This can be applied to the -# Pidgin buddy list to reduce it's width. -style "my-narrow-tree" -{ - GtkTreeView::expander_size = 0 -} - +# Pidgin buddy list to reduce the width of group and contact rows. +# With the recent (~2.0.0) buddy list changes this isn't particularly +# important anymore. +#style "my-narrow-tree" +#{ +# GtkTreeView::expander_size = 0 +#} # And apply the style to the buddy list: -widget "*pidgin_blist_treeview" style "my-narrow-tree" +#widget "*pidgin_blist_treeview" style "my-narrow-tree" # In Pidgin, you can set custom keybindings in your theme. # Here is an example to follow:
--- a/finch/finch.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/finch.c Wed Jun 27 21:43:18 2007 +0000 @@ -19,6 +19,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "finch.h" + #include "account.h" #include "conversation.h" #include "core.h" @@ -37,7 +39,6 @@ #include "whiteboard.h" #include "gntdebug.h" -#include "finch.h" #include "gntprefs.h" #include "gntui.h" #include "gntidle.h" @@ -156,11 +157,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 Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/gntaccount.c Wed Jun 27 21:43:18 2007 +0000 @@ -31,6 +31,9 @@ #include <gntlabel.h> #include <gntline.h> #include <gnttree.h> +#include <gntwindow.h> + +#include "finch.h" #include <account.h> #include <accountopt.h> @@ -40,7 +43,7 @@ #include <request.h> #include "gntaccount.h" -#include "finch.h" +#include "gntblist.h" #include <string.h> @@ -280,7 +283,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'; @@ -743,12 +750,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() @@ -865,25 +878,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 +925,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 Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/gntblist.c Wed Jun 27 21:43:18 2007 +0000 @@ -22,6 +22,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "finch.h" + #include <account.h> #include <blist.h> #include <notify.h> @@ -33,7 +35,6 @@ #include <util.h> #include "debug.h" -#include "finch.h" #include "gntbox.h" #include "gntcombobox.h" #include "gntentry.h" @@ -104,6 +105,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 +191,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 +219,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 +641,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 +840,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 +2318,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 Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/gntblist.h Wed Jun 27 21:43:18 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/gntconn.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/gntconn.c Wed Jun 27 21:43:18 2007 +0000 @@ -22,14 +22,15 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "finch.h" + #include "account.h" #include "core.h" -#include "connection.c" +#include "connection.h" #include "debug.h" #include "request.h" #include "gntconn.h" -#include "finch.h" #define INITIAL_RECON_DELAY_MIN 8000 #define INITIAL_RECON_DELAY_MAX 60000
--- a/finch/gntconv.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/gntconv.c Wed Jun 27 21:43:18 2007 +0000 @@ -24,12 +24,13 @@ */ #include <string.h> +#include "finch.h" + #include <cmds.h> #include <idle.h> #include <prefs.h> #include <util.h> -#include "finch.h" #include "gntaccount.h" #include "gntblist.h" #include "gntconv.h" @@ -64,7 +65,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 +314,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)); } @@ -445,6 +441,16 @@ } static void +gained_focus_cb(GntWindow *window, FinchConv *fc) +{ + GList *iter; + for (iter = fc->list; iter; iter = iter->next) { + purple_conversation_set_data(iter->data, "unseen-count", 0); + purple_conversation_update(iter->data, PURPLE_CONV_UPDATE_UNSEEN); + } +} + +static void finch_create_conversation(PurpleConversation *conv) { FinchConv *ggc = conv->ui_data; @@ -479,7 +485,24 @@ gnt_box_set_title(GNT_BOX(ggc->window), title); gnt_box_set_toplevel(GNT_BOX(ggc->window), TRUE); gnt_box_set_pad(GNT_BOX(ggc->window), 0); - gnt_widget_set_name(ggc->window, "conversation-window"); + + switch(conv->type){ + case PURPLE_CONV_TYPE_UNKNOWN: + gnt_widget_set_name(ggc->window, "conversation-window-unknown" ); + break; + case PURPLE_CONV_TYPE_IM: + gnt_widget_set_name(ggc->window, "conversation-window-im" ); + break; + case PURPLE_CONV_TYPE_CHAT: + gnt_widget_set_name(ggc->window, "conversation-window-chat" ); + break; + case PURPLE_CONV_TYPE_MISC: + gnt_widget_set_name(ggc->window, "conversation-window-misc" ); + break; + case PURPLE_CONV_TYPE_ANY: + gnt_widget_set_name(ggc->window, "conversation-window-any" ); + break; + } gg_create_menu(ggc); @@ -534,6 +557,7 @@ g_free(title); gnt_box_give_focus_to_child(GNT_BOX(ggc->window), ggc->entry); + g_signal_connect(G_OBJECT(ggc->window), "gained-focus", G_CALLBACK(gained_focus_cb), ggc); } static void @@ -547,7 +571,8 @@ if (ggc->list == NULL) { g_free(ggc->u.chat); - gnt_widget_destroy(ggc->window); + if (ggc->window) + gnt_widget_destroy(ggc->window); g_free(ggc); } } @@ -627,6 +652,11 @@ if (flags & (PURPLE_MESSAGE_RECV | PURPLE_MESSAGE_NICK | PURPLE_MESSAGE_ERROR)) gnt_widget_set_urgent(ggconv->tv); + if (flags & PURPLE_MESSAGE_RECV && !gnt_widget_has_focus(ggconv->window)) { + int count = GPOINTER_TO_INT(purple_conversation_get_data(conv, "unseen-count")); + purple_conversation_set_data(conv, "unseen-count", GINT_TO_POINTER(count + 1)); + purple_conversation_update(conv, PURPLE_CONV_UPDATE_UNSEEN); + } } static void
--- a/finch/gntft.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/gntft.c Wed Jun 27 21:43:18 2007 +0000 @@ -86,7 +86,7 @@ static void update_title_progress() { - const GList *list; + GList *list; int num_active_xfers = 0; guint64 total_bytes_xferred = 0; guint64 total_file_size = 0; @@ -172,7 +172,7 @@ void finch_xfer_dialog_new(void) { - const GList *iter; + GList *iter; GntWidget *window; GntWidget *bbox; GntWidget *button; @@ -411,7 +411,7 @@ size_str = purple_str_size_to_units(purple_xfer_get_size(xfer)); remaining_str = purple_str_size_to_units(purple_xfer_get_bytes_remaining(xfer)); - kbsec = g_strdup_printf(_("%.2f KB/s"), kbps); + kbsec = g_strdup_printf(_("%.2f KiB/s"), kbps); gnt_tree_change_text(GNT_TREE(xfer_dialog->tree), xfer, COLUMN_PROGRESS, g_ascii_dtostr(prog_str, sizeof(prog_str), purple_xfer_get_progress(xfer) * 100.));
--- a/finch/gntnotify.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/gntnotify.c Wed Jun 27 21:43:18 2007 +0000 @@ -29,10 +29,11 @@ #include <gnttree.h> #include <gntutils.h> +#include "finch.h" + #include <util.h> #include "gntnotify.h" -#include "finch.h" static struct { @@ -69,6 +70,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 +170,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 +270,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 +282,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/gntplugin.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/gntplugin.c Wed Jun 27 21:43:18 2007 +0000 @@ -29,10 +29,11 @@ #include <gntline.h> #include <gnttree.h> +#include "finch.h" + #include "notify.h" #include "request.h" -#include "finch.h" #include "gntplugin.h" #include "gntrequest.h" @@ -46,7 +47,7 @@ static GHashTable *confwins; -static void process_pref_frame(PurplePluginPrefFrame *frame); +static GntWidget *process_pref_frame(PurplePluginPrefFrame *frame); static void decide_conf_button(PurplePlugin *plugin) @@ -83,7 +84,7 @@ gnt_tree_set_choice(GNT_TREE(tree), plugin, TRUE); } - if ((win = g_hash_table_lookup(confwins, plugin)) != NULL) + if (confwins && (win = g_hash_table_lookup(confwins, plugin)) != NULL) { gnt_widget_destroy(win); } @@ -106,6 +107,9 @@ char *text; GList *list = NULL, *iter = NULL; + if (!plugin) + return; + /* If the selected plugin was unseen before, mark it as seen. But save the list * only when the plugin list is closed. So if the user enables a plugin, and it * crashes, it won't get marked as seen so the user can fix the bug and still @@ -217,7 +221,11 @@ else if (plugin->info->prefs_info && plugin->info->prefs_info->get_plugin_pref_frame) { - process_pref_frame(plugin->info->prefs_info->get_plugin_pref_frame(plugin)); + GntWidget *win = process_pref_frame(plugin->info->prefs_info->get_plugin_pref_frame(plugin)); + if (confwins == NULL) + confwin_init(); + g_signal_connect(G_OBJECT(win), "destroy", G_CALLBACK(remove_confwin), plugin); + g_hash_table_insert(confwins, plugin, win); return; } else @@ -269,6 +277,14 @@ { PurplePlugin *plug = iter->data; + if (plug->info->type == PURPLE_PLUGIN_LOADER) { + GList *cur; + for (cur = PURPLE_PLUGIN_LOADER_INFO(plug)->exts; cur != NULL; + cur = cur->next) + purple_plugins_probe(cur->data); + continue; + } + if (plug->info->type != PURPLE_PLUGIN_STANDARD || (plug->info->flags & PURPLE_PLUGIN_FLAG_INVISIBLE) || plug->error) @@ -304,7 +320,7 @@ decide_conf_button(gnt_tree_get_selection_data(GNT_TREE(tree))); } -static void +static GntWidget* process_pref_frame(PurplePluginPrefFrame *frame) { PurpleRequestField *field; @@ -356,7 +372,7 @@ } } - purple_request_fields(NULL, _("Preferences"), NULL, NULL, fields, + return purple_request_fields(NULL, _("Preferences"), NULL, NULL, fields, _("Save"), G_CALLBACK(finch_request_save_in_prefs), _("Cancel"), NULL, NULL, NULL, NULL, NULL);
--- a/finch/gntpounce.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/gntpounce.c Wed Jun 27 21:43:18 2007 +0000 @@ -137,12 +137,12 @@ static void populate_pounces_list(PouncesManager *dialog) { - const GList *pounces; + GList *pounces; gnt_tree_remove_all(GNT_TREE(dialog->tree)); - for (pounces = purple_pounces_get_all(); pounces != NULL; - pounces = g_list_next(pounces)) + for (pounces = purple_pounces_get_all_for_ui(FINCH_UI); pounces != NULL; + pounces = g_list_delete_link(pounces, pounces)) { add_pounce_to_treeview(GNT_TREE(dialog->tree), pounces->data); }
--- a/finch/gntprefs.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/gntprefs.c Wed Jun 27 21:43:18 2007 +0000 @@ -22,10 +22,11 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "finch.h" + #include <prefs.h> #include <savedstatuses.h> -#include "finch.h" #include "gntprefs.h" #include "gntrequest.h" @@ -89,7 +90,7 @@ get_status_titles() { GList *list = NULL; - const GList *iter; + GList *iter; for (iter = purple_savedstatuses_get_all(); iter; iter = iter->next) { char *str; if (purple_savedstatus_is_transient(iter->data))
--- a/finch/gntrequest.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/gntrequest.c Wed Jun 27 21:43:18 2007 +0000 @@ -297,7 +297,7 @@ GList *list = NULL; if (purple_request_field_list_get_multi_select(field)) { - const GList *iter; + GList *iter; GntWidget *tree = field->ui_data; iter = purple_request_field_list_get_items(field); @@ -399,7 +399,7 @@ purple_request_field_string_get_default_value(field)); gnt_entry_set_masked(GNT_ENTRY(entry), purple_request_field_string_is_masked(field)); - if (purple_str_has_prefix(hint, "screenname")) { + if (hint && purple_str_has_prefix(hint, "screenname")) { PurpleBlistNode *node = purple_blist_get_root(); gboolean offline = purple_str_has_suffix(hint, "all"); for (; node; node = purple_blist_node_next(node, offline)) { @@ -433,7 +433,7 @@ else if (type == PURPLE_REQUEST_FIELD_CHOICE) { int id; - const GList *list; + GList *list; GntWidget *combo = gnt_combo_box_new(); gnt_box_add_widget(GNT_BOX(hbox), combo); field->ui_data = combo; @@ -449,7 +449,7 @@ } else if (type == PURPLE_REQUEST_FIELD_LIST) { - const GList *list; + GList *list; gboolean multi = purple_request_field_list_get_multi_select(field); if (multi) { @@ -673,7 +673,7 @@ switch (pt) { case PURPLE_PREF_INT: { - long int tmp; + long int tmp = GPOINTER_TO_INT(val); if (type == PURPLE_REQUEST_FIELD_LIST) /* Lists always return string */ sscanf(val, "%ld", &tmp); purple_prefs_set_int(id, (gint)tmp);
--- a/finch/gntstatus.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/gntstatus.c Wed Jun 27 21:43:18 2007 +0000 @@ -31,10 +31,11 @@ #include <gntline.h> #include <gnttree.h> +#include "finch.h" + #include <notify.h> #include <request.h> -#include "finch.h" #include "gntstatus.h" static struct @@ -83,7 +84,7 @@ static void populate_statuses(GntTree *tree) { - const GList *list; + GList *list; for (list = purple_savedstatuses_get_all(); list; list = list->next) { @@ -244,7 +245,7 @@ static void set_substatuses(EditStatus *edit) { - const GList *iter; + GList *iter; for (iter = gnt_tree_get_rows(GNT_TREE(edit->tree)); iter; iter = iter->next) { RowInfo *key = iter->data; if (gnt_tree_get_choice(GNT_TREE(edit->tree), key)) { @@ -410,7 +411,7 @@ EditSubStatus *sub; GntWidget *window, *combo, *entry, *box, *button, *l; PurpleSavedStatusSub *substatus = NULL; - const GList *iter; + GList *iter; char *name; RowInfo *selected = gnt_tree_get_selection_data(tree); PurpleAccount *account = selected->account;
--- a/finch/gntui.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/gntui.c Wed Jun 27 21:43:18 2007 +0000 @@ -19,6 +19,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "internal.h" + #include "gntui.h" #include "gntaccount.h" @@ -33,7 +35,6 @@ #include "gntprefs.h" #include "gntrequest.h" #include "gntstatus.h" -#include "internal.h" #include <prefs.h>
--- a/finch/libgnt/Makefile.am Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/Makefile.am Wed Jun 27 21:43:18 2007 +0000 @@ -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/configure.ac Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/configure.ac Wed Jun 27 21:43:18 2007 +0000 @@ -24,11 +24,11 @@ # Make sure to update ../../configure.ac with libgnt version changes. # -m4_define([gnt_lt_current], [0]) -m4_define([gnt_major_version], [1]) +m4_define([gnt_lt_current], [1]) +m4_define([gnt_major_version], [2]) m4_define([gnt_minor_version], [0]) m4_define([gnt_micro_version], [0]) -m4_define([gnt_version_suffix], [beta7]) +m4_define([gnt_version_suffix], [devel]) m4_define([gnt_version], [gnt_major_version.gnt_minor_version.gnt_micro_version]) m4_define([gnt_display_version], gnt_version[]m4_ifdef([gnt_version_suffix],[gnt_version_suffix]))
--- a/finch/libgnt/gnt-skel.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gnt-skel.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include "gnt-skel.h" enum
--- a/finch/libgnt/gnt-skel.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gnt-skel.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNT_SKEL_H #define GNT_SKEL_H @@ -38,9 +60,19 @@ G_BEGIN_DECLS +/** + * + * + * @return + */ GType gnt_skel_get_gtype(void); -GntWidget *gnt_skel_new(); +/** + * + * + * @return + */ +GntWidget * gnt_skel_new(); G_END_DECLS
--- a/finch/libgnt/gnt.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gnt.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,39 +1,141 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include <glib.h> #include "gntwidget.h" #include "gntclipboard.h" #include "gntcolors.h" #include "gntkeys.h" +/** + * + */ void gnt_init(void); +/** + * + */ void gnt_main(void); +/** + * + * + * @return + */ gboolean gnt_ascii_only(void); +void gnt_window_present(GntWidget *window); +/** + * + * @param widget + */ void gnt_screen_occupy(GntWidget *widget); +/** + * + * @param widget + */ void gnt_screen_release(GntWidget *widget); +/** + * + * @param widget + */ void gnt_screen_update(GntWidget *widget); +/** + * + * @param widget + * @param width + * @param height + */ void gnt_screen_resize_widget(GntWidget *widget, int width, int height); +/** + * + * @param widget + * @param x + * @param y + */ void gnt_screen_move_widget(GntWidget *widget, int x, int y); +/** + * + * @param widget + * @param text + */ void gnt_screen_rename_widget(GntWidget *widget, const char *text); +/** + * + * @param widget + * + * @return + */ gboolean gnt_widget_has_focus(GntWidget *widget); +/** + * + * @param widget + */ void gnt_widget_set_urgent(GntWidget *widget); +/** + * + * @param label + * @param callback)() + */ void gnt_register_action(const char *label, void (*callback)()); +/** + * + * @param menu + * + * @return + */ gboolean gnt_screen_menu_show(gpointer menu); +/** + * + */ void gnt_quit(void); -GntClipboard *gnt_get_clipboard(void); +/** + * + * + * @return + */ +GntClipboard * gnt_get_clipboard(void); -gchar *gnt_get_clipboard_string(void); +/** + * + * + * @return + */ +gchar * gnt_get_clipboard_string(void); +/** + * + * @param string + */ void gnt_set_clipboard_string(gchar *string); +
--- a/finch/libgnt/gntbindable.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntbindable.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include "gntbindable.h" #include "gntstyle.h" #include "gnt.h"
--- a/finch/libgnt/gntbindable.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntbindable.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNT_BINDABLE_H #define GNT_BINDABLE_H @@ -39,6 +61,11 @@ G_BEGIN_DECLS +/** + * + * + * @return + */ GType gnt_bindable_get_gtype(void); /******************/ @@ -70,18 +97,53 @@ GList *list; }; - /*GntBindableAction *gnt_bindable_action_parse(const char *name);*/ +/** + * + * @param action + */ void gnt_bindable_action_free(GntBindableAction *action); + +/** + * + * @param param + */ void gnt_bindable_action_param_free(GntBindableActionParam *param); -void gnt_bindable_class_register_action(GntBindableClass *klass, const char *name, - GntBindableActionCallback callback, const char *trigger, ...); -void gnt_bindable_register_binding(GntBindableClass *klass, const char *name, - const char *trigger, ...); +/** + * + * @param klass + * @param name + * @param callback + * @param trigger + */ +void gnt_bindable_class_register_action(GntBindableClass *klass, const char *name, GntBindableActionCallback callback, const char *trigger, ...); + +/** + * + * @param klass + * @param name + * @param trigger + */ +void gnt_bindable_register_binding(GntBindableClass *klass, const char *name, const char *trigger, ...); +/** + * + * @param bindable + * @param keys + * + * @return + */ gboolean gnt_bindable_perform_action_key(GntBindable *bindable, const char *keys); + +/** + * + * @param bindable + * @param name + * + * @return + */ gboolean gnt_bindable_perform_action_named(GntBindable *bindable, const char *name, ...); G_END_DECLS
--- a/finch/libgnt/gntbox.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntbox.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include "gntbox.h" #include "gntutils.h" @@ -293,6 +315,10 @@ { find_next_focus(box); } + else if (strcmp(text, GNT_KEY_BACK_TAB) == 0) + { + find_prev_focus(box); + } } else if (text[0] == '\t') {
--- a/finch/libgnt/gntbox.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntbox.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNT_BOX_H #define GNT_BOX_H @@ -61,35 +83,110 @@ G_BEGIN_DECLS +/** + * + * + * @return + */ GType gnt_box_get_gtype(void); #define gnt_vbox_new(homo) gnt_box_new(homo, TRUE) #define gnt_hbox_new(homo) gnt_box_new(homo, FALSE) -GntWidget *gnt_box_new(gboolean homo, gboolean vert); +/** + * + * @param homo + * @param vert + * + * @return + */ +GntWidget * gnt_box_new(gboolean homo, gboolean vert); +/** + * + * @param box + * @param widget + */ void gnt_box_add_widget(GntBox *box, GntWidget *widget); +/** + * + * @param box + * @param title + */ void gnt_box_set_title(GntBox *box, const char *title); +/** + * + * @param box + * @param pad + */ void gnt_box_set_pad(GntBox *box, int pad); +/** + * + * @param box + * @param set + */ void gnt_box_set_toplevel(GntBox *box, gboolean set); +/** + * + * @param box + */ void gnt_box_sync_children(GntBox *box); +/** + * + * @param box + * @param alignment + */ void gnt_box_set_alignment(GntBox *box, GntAlignment alignment); -void gnt_box_remove(GntBox *box, GntWidget *widget); /* XXX: does NOT destroy widget */ +/** + * + * @param box + * @param widget + */ +void gnt_box_remove(GntBox *box, GntWidget *widget); + + /* XXX: does NOT destroy widget */ -void gnt_box_remove_all(GntBox *box); /* Removes AND destroys all the widgets in it */ +/** + * + * @param box + */ +void gnt_box_remove_all(GntBox *box); + /* Removes AND destroys all the widgets in it */ + +/** + * + * @param box + */ void gnt_box_readjust(GntBox *box); +/** + * + * @param box + * @param fill + */ void gnt_box_set_fill(GntBox *box, gboolean fill); -void gnt_box_move_focus(GntBox *box, int dir); /* +1 to move forward, -1 for backward */ +/** + * + * @param box + * @param dir + */ +void gnt_box_move_focus(GntBox *box, int dir); + /* +1 to move forward, -1 for backward */ + +/** + * + * @param box + * @param widget + */ void gnt_box_give_focus_to_child(GntBox *box, GntWidget *widget); G_END_DECLS
--- a/finch/libgnt/gntbutton.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntbutton.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include <string.h> #include "gntbutton.h"
--- a/finch/libgnt/gntbutton.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntbutton.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNT_BUTTON_H #define GNT_BUTTON_H @@ -46,9 +68,20 @@ G_BEGIN_DECLS +/** + * + * + * @return + */ GType gnt_button_get_gtype(void); -GntWidget *gnt_button_new(const char *text); +/** + * + * @param text + * + * @return + */ +GntWidget * gnt_button_new(const char *text); G_END_DECLS
--- a/finch/libgnt/gntcheckbox.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntcheckbox.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include "gntcheckbox.h" enum
--- a/finch/libgnt/gntcheckbox.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntcheckbox.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNT_CHECK_BOX_H #define GNT_CHECK_BOX_H @@ -41,12 +63,34 @@ G_BEGIN_DECLS +/** + * + * + * @return + */ GType gnt_check_box_get_gtype(void); -GntWidget *gnt_check_box_new(const char *text); +/** + * + * @param text + * + * @return + */ +GntWidget * gnt_check_box_new(const char *text); +/** + * + * @param box + * @param set + */ void gnt_check_box_set_checked(GntCheckBox *box, gboolean set); +/** + * + * @param box + * + * @return + */ gboolean gnt_check_box_get_checked(GntCheckBox *box); G_END_DECLS
--- a/finch/libgnt/gntclipboard.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntclipboard.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,6 +1,26 @@ -#include "gntclipboard.h" +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ -gchar *string; +#include "gntclipboard.h" enum { SIG_CLIPBOARD = 0,
--- a/finch/libgnt/gntclipboard.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntclipboard.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNT_CLIPBOARD_H #define GNT_CLIPBOARD_H @@ -33,10 +55,26 @@ G_BEGIN_DECLS +/** + * + * + * @return + */ GType gnt_clipboard_get_gtype(void); -gchar *gnt_clipboard_get_string(GntClipboard *clip); +/** + * + * @param clip + * + * @return + */ +gchar * gnt_clipboard_get_string(GntClipboard *clip); +/** + * + * @param clip + * @param string + */ void gnt_clipboard_set_string(GntClipboard *clip, gchar *string); G_END_DECLS
--- a/finch/libgnt/gntcolors.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntcolors.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include "config.h" #include <ncurses.h>
--- a/finch/libgnt/gntcolors.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntcolors.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNT_COLORS_H #define GNT_COLORS_H @@ -33,14 +55,29 @@ }; /* populate some default colors */ +/** + * + */ void gnt_init_colors(void); +/** + * + */ void gnt_uninit_colors(void); #if GLIB_CHECK_VERSION(2,6,0) +/** + * + * @param kfile + */ void gnt_colors_parse(GKeyFile *kfile); +/** + * + * @param kfile + */ void gnt_color_pairs_parse(GKeyFile *kfile); + #endif #endif
--- a/finch/libgnt/gntcombobox.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntcombobox.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include "gntbox.h" #include "gntcombobox.h" #include "gnttree.h"
--- a/finch/libgnt/gntcombobox.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntcombobox.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNT_COMBO_BOX_H #define GNT_COMBO_BOX_H @@ -42,18 +64,54 @@ G_BEGIN_DECLS +/** + * + * + * @return + */ GType gnt_combo_box_get_gtype(void); -GntWidget *gnt_combo_box_new(void); +/** + * + * + * @return + */ +GntWidget * gnt_combo_box_new(void); +/** + * + * @param box + * @param key + * @param text + */ void gnt_combo_box_add_data(GntComboBox *box, gpointer key, const char *text); +/** + * + * @param box + * @param key + */ void gnt_combo_box_remove(GntComboBox *box, gpointer key); +/** + * + * @param box + */ void gnt_combo_box_remove_all(GntComboBox *box); +/** + * + * @param box + * + * @return + */ gpointer gnt_combo_box_get_selected_data(GntComboBox *box); +/** + * + * @param box + * @param key + */ void gnt_combo_box_set_selected(GntComboBox *box, gpointer key); G_END_DECLS
--- a/finch/libgnt/gntentry.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntentry.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include <ctype.h> #include <string.h>
--- a/finch/libgnt/gntentry.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntentry.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNT_ENTRY_H #define GNT_ENTRY_H @@ -73,32 +95,97 @@ G_BEGIN_DECLS +/** + * + * + * @return + */ GType gnt_entry_get_gtype(void); -GntWidget *gnt_entry_new(const char *text); +/** + * + * @param text + * + * @return + */ +GntWidget * gnt_entry_new(const char *text); +/** + * + * @param entry + * @param max + */ void gnt_entry_set_max(GntEntry *entry, int max); +/** + * + * @param entry + * @param text + */ void gnt_entry_set_text(GntEntry *entry, const char *text); +/** + * + * @param entry + * @param flag + */ void gnt_entry_set_flag(GntEntry *entry, GntEntryFlag flag); const char *gnt_entry_get_text(GntEntry *entry); +/** + * + * @param entry + */ void gnt_entry_clear(GntEntry *entry); +/** + * + * @param entry + * @param set + */ void gnt_entry_set_masked(GntEntry *entry, gboolean set); +/** + * + * @param entry + * @param text + */ void gnt_entry_add_to_history(GntEntry *entry, const char *text); +/** + * + * @param entry + * @param num + */ void gnt_entry_set_history_length(GntEntry *entry, int num); +/** + * + * @param entry + * @param word + */ void gnt_entry_set_word_suggest(GntEntry *entry, gboolean word); +/** + * + * @param entry + * @param always + */ void gnt_entry_set_always_suggest(GntEntry *entry, gboolean always); +/** + * + * @param entry + * @param text + */ void gnt_entry_add_suggest(GntEntry *entry, const char *text); +/** + * + * @param entry + * @param text + */ void gnt_entry_remove_suggest(GntEntry *entry, const char *text); G_END_DECLS
--- a/finch/libgnt/gntfilesel.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntfilesel.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include "gntbutton.h" #include "gntentry.h" #include "gntfilesel.h" @@ -24,6 +46,7 @@ static GntWindowClass *parent_class = NULL; static guint signals[SIGS] = { 0 }; static void (*orig_map)(GntWidget *widget); +static void (*orig_size_request)(GntWidget *widget); static void gnt_file_sel_destroy(GntWidget *widget) @@ -530,6 +553,19 @@ } static void +gnt_file_sel_size_request(GntWidget *widget) +{ + GntFileSel *sel; + if (widget->priv.height > 0) + return; + + sel = GNT_FILE_SEL(widget); + sel->dirs->priv.height = 16; + sel->files->priv.height = 16; + orig_size_request(widget); +} + +static void gnt_file_sel_class_init(GntFileSelClass *klass) { GntBindableClass *bindable = GNT_BINDABLE_CLASS(klass); @@ -538,6 +574,8 @@ kl->destroy = gnt_file_sel_destroy; orig_map = kl->map; kl->map = gnt_file_sel_map; + orig_size_request = kl->size_request; + kl->size_request = gnt_file_sel_size_request; signals[SIG_FILE_SELECTED] = g_signal_new("file_selected",
--- a/finch/libgnt/gntfilesel.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntfilesel.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNT_FILE_SEL_H #define GNT_FILE_SEL_H @@ -71,32 +93,115 @@ G_BEGIN_DECLS +/** + * + * + * @return + */ GType gnt_file_sel_get_gtype(void); -GntWidget *gnt_file_sel_new(void); +/** + * + * + * @return + */ +GntWidget * gnt_file_sel_new(void); +/** + * + * @param sel + * @param path + * + * @return + */ gboolean gnt_file_sel_set_current_location(GntFileSel *sel, const char *path); +/** + * + * @param sel + * @param dirs + */ void gnt_file_sel_set_dirs_only(GntFileSel *sel, gboolean dirs); +/** + * + * @param sel + * + * @return + */ gboolean gnt_file_sel_get_dirs_only(GntFileSel *sel); +/** + * + * @param sel + * @param must + */ void gnt_file_sel_set_must_exist(GntFileSel *sel, gboolean must); +/** + * + * @param sel + * + * @return + */ gboolean gnt_file_sel_get_must_exist(GntFileSel *sel); -char *gnt_file_sel_get_selected_file(GntFileSel *sel); /* The returned value should be free'd */ +/** + * + * @param sel + * + * @return + */ +char * gnt_file_sel_get_selected_file(GntFileSel *sel); + + /* The returned value should be free'd */ -GList *gnt_file_sel_get_selected_multi_files(GntFileSel *sel); +/** + * + * @param sel + * + * @return + */ +GList * gnt_file_sel_get_selected_multi_files(GntFileSel *sel); +/** + * + * @param sel + * @param set + */ void gnt_file_sel_set_multi_select(GntFileSel *sel, gboolean set); +/** + * + * @param sel + * @param suggest + */ void gnt_file_sel_set_suggested_filename(GntFileSel *sel, const char *suggest); +/** + * + * @param sel + * @param path + * @param files + * @param error) + */ void gnt_file_sel_set_read_fn(GntFileSel *sel, gboolean (*read_fn)(const char *path, GList **files, GError **error)); +/** + * + * @param name + * @param size + * + * @return + */ GntFile* gnt_file_new(const char *name, unsigned long size); +/** + * + * @param name + * + * @return + */ GntFile* gnt_file_new_dir(const char *name); G_END_DECLS
--- a/finch/libgnt/gntkeys.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntkeys.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include "gntkeys.h" #include <glib.h> @@ -50,6 +72,7 @@ INSERT_KEY("pagedown", GNT_KEY_PGDOWN); INSERT_KEY("insert", GNT_KEY_INS); INSERT_KEY("delete", GNT_KEY_DEL); + INSERT_KEY("back_tab", GNT_KEY_BACK_TAB); INSERT_KEY("left", GNT_KEY_LEFT); INSERT_KEY("right", GNT_KEY_RIGHT); @@ -183,8 +206,8 @@ node->flags |= IS_END; return; } - while (*path && node->next[*path]) { - node = node->next[*path]; + while (*path && node->next[(unsigned char)*path]) { + node = node->next[(unsigned char)*path]; node->ref++; path++; } @@ -192,7 +215,7 @@ return; n = g_new0(struct _node, 1); n->ref = 1; - node->next[*path++] = n; + node->next[(unsigned char)*path++] = n; add_path(n, path); } @@ -207,13 +230,13 @@ if (!*path) return; - next = node->next[*path]; + next = node->next[(unsigned char)*path]; if (!next) return; del_path(next, path + 1); next->ref--; if (next->ref == 0) { - node->next[*path] = NULL; + node->next[(unsigned char)*path] = NULL; g_free(next); } } @@ -229,12 +252,12 @@ struct _node *n = &root; root.flags &= ~IS_END; - while (*path && n->next[*path] && !(n->flags & IS_END)) { + while (*path && n->next[(unsigned char)*path] && !(n->flags & IS_END)) { if (!g_ascii_isspace(*path) && !g_ascii_iscntrl(*path) && !g_ascii_isgraph(*path)) return 0; - n = n->next[*path++]; + n = n->next[(unsigned char)*path++]; depth++; }
--- a/finch/libgnt/gntkeys.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntkeys.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNT_KEYS_H #define GNT_KEYS_H @@ -39,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" @@ -79,16 +102,40 @@ /** * This will do stuff with the terminal settings and stuff. */ +/** + * + */ void gnt_init_keys(void); + +/** + * + * @param text + */ void gnt_keys_refine(char *text); + const char *gnt_key_translate(const char *name); const char *gnt_key_lookup(const char *key); +/** + * + * @param path + */ void gnt_keys_add_combination(const char *path); + +/** + * + * @param path + */ void gnt_keys_del_combination(const char *path); + +/** + * + * @param path + * + * @return + */ int gnt_keys_find_combination(const char *path); - /* A lot of commonly used variable names are defined in <term.h>. * #undef them to make life easier for everyone. */
--- a/finch/libgnt/gntlabel.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntlabel.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include "gntlabel.h" #include "gntutils.h"
--- a/finch/libgnt/gntlabel.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntlabel.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNT_LABEL_H #define GNT_LABEL_H @@ -40,12 +62,35 @@ G_BEGIN_DECLS +/** + * + * + * @return + */ GType gnt_label_get_gtype(void); -GntWidget *gnt_label_new(const char *text); +/** + * + * @param text + * + * @return + */ +GntWidget * gnt_label_new(const char *text); -GntWidget *gnt_label_new_with_format(const char *text, GntTextFormatFlags flags); +/** + * + * @param text + * @param flags + * + * @return + */ +GntWidget * gnt_label_new_with_format(const char *text, GntTextFormatFlags flags); +/** + * + * @param label + * @param text + */ void gnt_label_set_text(GntLabel *label, const char *text); G_END_DECLS
--- a/finch/libgnt/gntline.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntline.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include "gntline.h" enum
--- a/finch/libgnt/gntline.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntline.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNT_LINE_H #define GNT_LINE_H @@ -40,12 +62,23 @@ G_BEGIN_DECLS +/** + * + * + * @return + */ GType gnt_line_get_gtype(void); #define gnt_hline_new() gnt_line_new(FALSE) #define gnt_vline_new() gnt_line_new(TRUE) -GntWidget *gnt_line_new(gboolean vertical); +/** + * + * @param vertical + * + * @return + */ +GntWidget * gnt_line_new(gboolean vertical); G_END_DECLS
--- a/finch/libgnt/gntmain.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntmain.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,5 +1,27 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #define _GNU_SOURCE -#if defined(__APPLE__) +#if defined(__APPLE__) || defined(__unix__) #define _XOPEN_SOURCE_EXTENDED #endif @@ -51,7 +73,7 @@ static gboolean refresh_screen(); -GntWM *wm; +static GntWM *wm; static GntClipboard *clipboard; #define HOLDING_ESCAPE (escape_stuff.timer != 0) @@ -94,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++; @@ -150,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) { @@ -161,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); @@ -359,8 +381,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); @@ -408,10 +429,14 @@ setup_io(); +#ifdef NO_WIDECHAR + ascii_only = TRUE; +#else if (locale && (strstr(locale, "UTF") || strstr(locale, "utf"))) ascii_only = FALSE; else ascii_only = TRUE; +#endif initscr(); typeahead(-1); @@ -463,6 +488,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); @@ -494,7 +523,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; @@ -507,7 +536,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/gntmenu.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntmenu.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include "gntmenu.h" #include "gntmenuitemcheck.h" @@ -220,7 +242,10 @@ static void gnt_menu_hide(GntWidget *widget) { - GntMenu *menu = GNT_MENU(widget); + GntMenu *sub, *menu = GNT_MENU(widget); + + while ((sub = menu->submenu)) + gnt_widget_hide(GNT_WIDGET(sub)); if (menu->parentmenu) menu->parentmenu->submenu = NULL; }
--- a/finch/libgnt/gntmenu.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntmenu.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNT_MENU_H #define GNT_MENU_H @@ -59,10 +81,26 @@ G_BEGIN_DECLS +/** + * + * + * @return + */ GType gnt_menu_get_gtype(void); -GntWidget *gnt_menu_new(GntMenuType type); +/** + * + * @param type + * + * @return + */ +GntWidget * gnt_menu_new(GntMenuType type); +/** + * + * @param menu + * @param item + */ void gnt_menu_add_item(GntMenu *menu, GntMenuItem *item); G_END_DECLS
--- a/finch/libgnt/gntmenuitem.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntmenuitem.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include "gntmenu.h" #include "gntmenuitem.h"
--- a/finch/libgnt/gntmenuitem.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntmenuitem.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNT_MENUITEM_H #define GNT_MENUITEM_H @@ -58,12 +80,34 @@ G_BEGIN_DECLS +/** + * + * + * @return + */ GType gnt_menuitem_get_gtype(void); -GntMenuItem *gnt_menuitem_new(const char *text); +/** + * + * @param text + * + * @return + */ +GntMenuItem * gnt_menuitem_new(const char *text); +/** + * + * @param item + * @param callback + * @param data + */ void gnt_menuitem_set_callback(GntMenuItem *item, GntMenuItemCallback callback, gpointer data); +/** + * + * @param item + * @param menu + */ void gnt_menuitem_set_submenu(GntMenuItem *item, GntMenu *menu); G_END_DECLS
--- a/finch/libgnt/gntmenuitemcheck.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntmenuitemcheck.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include "gntmenuitemcheck.h" static GntMenuItemClass *parent_class = NULL;
--- a/finch/libgnt/gntmenuitemcheck.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntmenuitemcheck.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNT_MENU_ITEM_CHECK_H #define GNT_MENU_ITEM_CHECK_H @@ -39,12 +61,34 @@ G_BEGIN_DECLS +/** + * + * + * @return + */ GType gnt_menuitem_check_get_gtype(void); -GntMenuItem *gnt_menuitem_check_new(const char *text); +/** + * + * @param text + * + * @return + */ +GntMenuItem * gnt_menuitem_check_new(const char *text); +/** + * + * @param item + * + * @return + */ gboolean gnt_menuitem_check_get_checked(GntMenuItemCheck *item); +/** + * + * @param item + * @param set + */ void gnt_menuitem_check_set_checked(GntMenuItemCheck *item, gboolean set); G_END_DECLS
--- a/finch/libgnt/gntstyle.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntstyle.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,13 +1,42 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #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]; @@ -17,6 +46,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; @@ -87,6 +121,44 @@ 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 = gnt_ws_new(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) @@ -221,6 +293,10 @@ 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); } @@ -231,6 +307,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)) { @@ -260,6 +337,7 @@ for (i = 0; i < GNT_STYLES; i++) g_free(str_styles[i]); + g_hash_table_destroy(unknowns); #if GLIB_CHECK_VERSION(2,6,0) g_key_file_free(gkfile); #endif
--- a/finch/libgnt/gntstyle.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntstyle.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,4 +1,27 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include "gnt.h" +#include "gntwm.h" typedef enum { @@ -10,18 +33,49 @@ GNT_STYLES } GntStyle; +/** + * + * @param filename + */ void gnt_style_read_configure_file(const char *filename); const char *gnt_style_get(GntStyle style); +const char *gnt_style_get_from_name(const char *key); + +/** + * + * @param style + * @param def + * + * @return + */ gboolean gnt_style_get_bool(GntStyle style, gboolean def); /* This should be called only once for the each type */ +/** + * + * @param type + * @param hash + */ void gnt_styles_get_keyremaps(GType type, GHashTable *hash); +/** + * + * @param type + * @param klass + */ void gnt_style_read_actions(GType type, GntBindableClass *klass); +void gnt_style_read_workspaces(GntWM *wm); + +/** + * + */ void gnt_init_styles(void); +/** + * + */ void gnt_uninit_styles(void);
--- a/finch/libgnt/gnttextview.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gnttextview.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include "gnttextview.h" #include "gntutils.h"
--- a/finch/libgnt/gnttextview.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gnttextview.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNT_TEXT_VIEW_H #define GNT_TEXT_VIEW_H @@ -53,34 +75,102 @@ G_BEGIN_DECLS +/** + * + * + * @return + */ GType gnt_text_view_get_gtype(void); /* XXX: For now, don't set a textview to have any border. * If you want borders real bad, put it in a box. */ -GntWidget *gnt_text_view_new(void); +/** + * + * + * @return + */ +GntWidget * gnt_text_view_new(void); /* scroll > 0 means scroll up, < 0 means scroll down, == 0 means scroll to the end */ +/** + * + * @param view + * @param scroll + */ void gnt_text_view_scroll(GntTextView *view, int scroll); +/** + * + * @param view + * @param text + * @param flags + */ void gnt_text_view_append_text_with_flags(GntTextView *view, const char *text, GntTextFormatFlags flags); +/** + * + * @param view + * @param text + * @param flags + * @param tag + */ void gnt_text_view_append_text_with_tag(GntTextView *view, const char *text, GntTextFormatFlags flags, const char *tag); /* Move the cursor to the beginning of the next line and resets text-attributes. * It first completes the current line with the current text-attributes. */ +/** + * + * @param view + */ void gnt_text_view_next_line(GntTextView *view); +/** + * + * @param flags + * + * @return + */ chtype gnt_text_format_flag_to_chtype(GntTextFormatFlags flags); +/** + * + * @param view + */ void gnt_text_view_clear(GntTextView *view); +/** + * + * @param view + * + * @return + */ int gnt_text_view_get_lines_below(GntTextView *view); +/** + * + * @param view + * + * @return + */ int gnt_text_view_get_lines_above(GntTextView *view); /* If text is NULL, then the tag is removed. */ +/** + * + * @param view + * @param name + * @param text + * @param all + * + * @return + */ int gnt_text_view_tag_change(GntTextView *view, const char *name, const char *text, gboolean all); +/** + * + * @param view + * @param widget + */ void gnt_text_view_attach_scroll_widget(GntTextView *view, GntWidget *widget); G_END_DECLS
--- a/finch/libgnt/gnttree.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gnttree.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include "gntmarshal.h" #include "gntstyle.h" #include "gnttree.h" @@ -731,15 +753,16 @@ g_signal_emit(tree, signals[SIG_TOGGLED], 0, row->key); redraw_tree(tree); } + } else { + return FALSE; } if (old != tree->current) { tree_selection_changed(tree, old, tree->current); - return TRUE; } - return FALSE; + return TRUE; } static void @@ -994,7 +1017,7 @@ return ret; } -const GList *gnt_tree_get_rows(GntTree *tree) +GList *gnt_tree_get_rows(GntTree *tree) { return tree->list; }
--- a/finch/libgnt/gnttree.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gnttree.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNT_TREE_H #define GNT_TREE_H @@ -74,75 +96,273 @@ G_BEGIN_DECLS +/** + * + * + * @return + */ GType gnt_tree_get_gtype(void); -GntWidget *gnt_tree_new(void); /* A tree with just one column */ +/** + * + * + * @return + */ +GntWidget * gnt_tree_new(void); + + /* A tree with just one column */ -GntWidget *gnt_tree_new_with_columns(int columns); +/** + * + * @param columns + * + * @return + */ +GntWidget * gnt_tree_new_with_columns(int columns); +/** + * + * @param tree + * @param rows + */ void gnt_tree_set_visible_rows(GntTree *tree, int rows); +/** + * + * @param tree + * + * @return + */ int gnt_tree_get_visible_rows(GntTree *tree); +/** + * + * @param tree + * @param count + */ void gnt_tree_scroll(GntTree *tree, int count); -GntTreeRow *gnt_tree_add_row_after(GntTree *tree, void *key, GntTreeRow *row, void *parent, void *bigbro); +/** + * + * @param tree + * @param key + * @param row + * @param parent + * @param bigbro + * + * @return + */ +GntTreeRow * gnt_tree_add_row_after(GntTree *tree, void *key, GntTreeRow *row, void *parent, void *bigbro); -GntTreeRow *gnt_tree_add_row_last(GntTree *tree, void *key, GntTreeRow *row, void *parent); +/** + * + * @param tree + * @param key + * @param row + * @param parent + * + * @return + */ +GntTreeRow * gnt_tree_add_row_last(GntTree *tree, void *key, GntTreeRow *row, void *parent); +/** + * + * @param tree + * + * @return + */ gpointer gnt_tree_get_selection_data(GntTree *tree); /* Returned string needs to be freed */ -char *gnt_tree_get_selection_text(GntTree *tree); +/** + * + * @param tree + * + * @return + */ +char * gnt_tree_get_selection_text(GntTree *tree); -GList *gnt_tree_get_selection_text_list(GntTree *tree); +/** + * + * @param tree + * + * @return + */ +GList * gnt_tree_get_selection_text_list(GntTree *tree); -const GList *gnt_tree_get_rows(GntTree *tree); +/** + * + * @param tree + * + * @constreturn + */ +GList *gnt_tree_get_rows(GntTree *tree); +/** + * + * @param tree + * @param key + */ void gnt_tree_remove(GntTree *tree, gpointer key); +/** + * + * @param tree + */ void gnt_tree_remove_all(GntTree *tree); /* Returns the visible line number of the selected row */ +/** + * + * @param tree + * + * @return + */ int gnt_tree_get_selection_visible_line(GntTree *tree); +/** + * + * @param tree + * @param key + * @param colno + * @param text + */ void gnt_tree_change_text(GntTree *tree, gpointer key, int colno, const char *text); -GntTreeRow *gnt_tree_add_choice(GntTree *tree, void *key, GntTreeRow *row, void *parent, void *bigbro); +/** + * + * @param tree + * @param key + * @param row + * @param parent + * @param bigbro + * + * @return + */ +GntTreeRow * gnt_tree_add_choice(GntTree *tree, void *key, GntTreeRow *row, void *parent, void *bigbro); +/** + * + * @param tree + * @param key + * @param set + */ void gnt_tree_set_choice(GntTree *tree, void *key, gboolean set); +/** + * + * @param tree + * @param key + * + * @return + */ gboolean gnt_tree_get_choice(GntTree *tree, void *key); +/** + * + * @param tree + * @param key + * @param flags + */ void gnt_tree_set_row_flags(GntTree *tree, void *key, GntTextFormatFlags flags); +/** + * + * @param key + */ void gnt_tree_set_selected(GntTree *tree , void *key); -GntTreeRow *gnt_tree_create_row(GntTree *tree, ...); +/** + * + * @param tree + * + * @return + */ +GntTreeRow * gnt_tree_create_row(GntTree *tree, ...); -GntTreeRow *gnt_tree_create_row_from_list(GntTree *tree, GList *list); +/** + * + * @param tree + * @param list + * + * @return + */ +GntTreeRow * gnt_tree_create_row_from_list(GntTree *tree, GList *list); +/** + * + * @param tree + * @param col + * @param width + */ void gnt_tree_set_col_width(GntTree *tree, int col, int width); +/** + * + * @param tree + */ void gnt_tree_set_column_titles(GntTree *tree, ...); +/** + * + * @param tree + * @param set + */ void gnt_tree_set_show_title(GntTree *tree, gboolean set); +/** + * + * @param tree + * @param func + */ void gnt_tree_set_compare_func(GntTree *tree, GCompareFunc func); +/** + * + * @param tree + * @param key + * @param expanded + */ void gnt_tree_set_expanded(GntTree *tree, void *key, gboolean expanded); +/** + * + * @param tree + * @param set + */ void gnt_tree_set_show_separator(GntTree *tree, gboolean set); +/** + * + * @param tree + * @param row + */ void gnt_tree_sort_row(GntTree *tree, void *row); /* This will try to automatically adjust the width of the columns in the tree */ +/** + * + * @param tree + */ void gnt_tree_adjust_columns(GntTree *tree); +/** + * + * @param tree + * @param hash + * @param eq + * @param kd + */ void gnt_tree_set_hash_fns(GntTree *tree, gpointer hash, gpointer eq, gpointer kd); /* This can be useful when, for example, we want to store some data * which we don't want/need to display. */ +/** + * + * @param tree + * @param col + * @param vis + */ void gnt_tree_set_column_visible(GntTree *tree, int col, gboolean vis); G_END_DECLS @@ -150,6 +370,11 @@ /* The following functions should NOT be used by applications. */ /* This should be called by the subclasses of GntTree's in their _new function */ +/** + * + * @param tree + * @param col + */ void _gnt_tree_init_internals(GntTree *tree, int col); #endif /* GNT_TREE_H */
--- a/finch/libgnt/gntutils.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntutils.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include "gntbutton.h" #include "gntcheckbox.h" #include "gntcombobox.h"
--- a/finch/libgnt/gntutils.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntutils.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include <glib.h> #include "gnt.h" @@ -5,9 +27,22 @@ typedef gpointer (*GDupFunc)(gconstpointer data); +/** + * + * @param text + * @param width + * @param height + */ void gnt_util_get_text_bound(const char *text, int *width, int *height); /* excluding *end */ +/** + * + * @param start + * @param end + * + * @return + */ int gnt_util_onscreen_width(const char *start, const char *end); const char *gnt_util_onscreen_width_to_pointer(const char *str, int len, int *w); @@ -18,29 +53,62 @@ * * Returns a newly allocated string. */ -char *gnt_util_onscreen_fit_string(const char *string, int maxw); +/** + * + * @param string + * @param maxw + * + * @return + */ +char * gnt_util_onscreen_fit_string(const char *string, int maxw); -GHashTable *g_hash_table_duplicate(GHashTable *src, GHashFunc hash, - GEqualFunc equal, GDestroyNotify key_d, GDestroyNotify value_d, - GDupFunc key_dup, GDupFunc value_dup); - +/** + * + * @param src + * @param hash + * @param equal + * @param key_d + * @param value_d + * @param key_dup + * @param value_dup + * + * @return + */ +GHashTable * g_hash_table_duplicate(GHashTable *src, GHashFunc hash, GEqualFunc equal, GDestroyNotify key_d, GDestroyNotify value_d, GDupFunc key_dup, GDupFunc value_dup); /** * To be used with g_signal_new. Look in the key_pressed signal-definition in * gntwidget.c for usage. */ -gboolean gnt_boolean_handled_accumulator(GSignalInvocationHint *ihint, - GValue *return_accu, - const GValue *handler_return, - gpointer dummy); +/** + * + * @param ihint + * @param return_accu + * @param handler_return + * @param dummy + * + * @return + */ +gboolean gnt_boolean_handled_accumulator(GSignalInvocationHint *ihint, GValue *return_accu, const GValue *handler_return, gpointer dummy); /** * Returns a GntTree populated with "key" -> "binding" for the widget. */ -GntWidget *gnt_widget_bindings_view(GntWidget *widget); +/** + * + * @param widget + * + * @return + */ +GntWidget * gnt_widget_bindings_view(GntWidget *widget); /** * Parse widgets from 'string'. */ +/** + * + * @param string + * @param num + */ void gnt_util_parse_widgets(const char *string, int num, ...);
--- a/finch/libgnt/gntwidget.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntwidget.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + /* Stuff brutally ripped from Gflib */ #include "gntwidget.h" @@ -554,7 +576,7 @@ GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_HAS_FOCUS); g_signal_emit(widget, signals[SIG_GIVE_FOCUS], 0); } - else if (!set) + else if (!set && GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_HAS_FOCUS)) { GNT_WIDGET_UNSET_FLAGS(widget, GNT_WIDGET_HAS_FOCUS); g_signal_emit(widget, signals[SIG_LOST_FOCUS], 0);
--- a/finch/libgnt/gntwidget.h Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntwidget.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNT_WIDGET_H #define GNT_WIDGET_H @@ -112,39 +134,169 @@ G_BEGIN_DECLS +/** + * + * + * @return + */ GType gnt_widget_get_gtype(void); + +/** + * + * @param widget + */ void gnt_widget_destroy(GntWidget *widget); + +/** + * + * @param widget + */ void gnt_widget_show(GntWidget *widget); + +/** + * + * @param widget + */ void gnt_widget_draw(GntWidget *widget); + +/** + * + * @param widget + * @param x + * @param y + * @param width + * @param height + */ void gnt_widget_expose(GntWidget *widget, int x, int y, int width, int height); + +/** + * + * @param widget + */ void gnt_widget_hide(GntWidget *widget); +/** + * + * @param widget + * @param x + * @param y + */ void gnt_widget_get_position(GntWidget *widget, int *x, int *y); + +/** + * + * @param widget + * @param x + * @param y + */ void gnt_widget_set_position(GntWidget *widget, int x, int y); + +/** + * + * @param widget + */ void gnt_widget_size_request(GntWidget *widget); + +/** + * + * @param widget + * @param width + * @param height + */ void gnt_widget_get_size(GntWidget *widget, int *width, int *height); + +/** + * + * @param widget + * @param width + * @param height + * + * @return + */ gboolean gnt_widget_set_size(GntWidget *widget, int width, int height); + +/** + * + * @param widget + * @param width + * @param height + * + * @return + */ gboolean gnt_widget_confirm_size(GntWidget *widget, int width, int height); +/** + * + * @param widget + * @param keys + * + * @return + */ gboolean gnt_widget_key_pressed(GntWidget *widget, const char *keys); +/** + * + * @param widget + * @param event + * @param x + * @param y + * + * @return + */ gboolean gnt_widget_clicked(GntWidget *widget, GntMouseEvent event, int x, int y); +/** + * + * @param widget + * @param set + * + * @return + */ gboolean gnt_widget_set_focus(GntWidget *widget, gboolean set); + +/** + * + * @param widget + */ void gnt_widget_activate(GntWidget *widget); +/** + * + * @param widget + * @param name + */ void gnt_widget_set_name(GntWidget *widget, const char *name); const char *gnt_widget_get_name(GntWidget *widget); /* Widget-subclasses should call this from the draw-callback. * Applications should just call gnt_widget_draw instead of this. */ +/** + * + * @param widget + */ void gnt_widget_queue_update(GntWidget *widget); +/** + * + * @param widget + * @param set + */ void gnt_widget_set_take_focus(GntWidget *widget, gboolean set); +/** + * + * @param widget + * @param set + */ void gnt_widget_set_visible(GntWidget *widget, gboolean set); +/** + * + * @param widget + * + * @return + */ gboolean gnt_widget_has_shadow(GntWidget *widget); G_END_DECLS
--- a/finch/libgnt/gntwindow.c Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntwindow.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #include "gntstyle.h" #include "gntwindow.h" @@ -5,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); @@ -42,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); @@ -109,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 Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntwindow.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,3 +1,25 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNT_WINDOW_H #define GNT_WINDOW_H @@ -40,17 +62,42 @@ G_BEGIN_DECLS +/** + * + * + * @return + */ GType gnt_window_get_gtype(void); #define gnt_vwindow_new(homo) gnt_window_box_new(homo, TRUE) #define gnt_hwindow_new(homo) gnt_window_box_new(homo, FALSE) -GntWidget *gnt_window_new(void); +/** + * + * + * @return + */ +GntWidget * gnt_window_new(void); -GntWidget *gnt_window_box_new(gboolean homo, gboolean vert); +/** + * + * @param homo + * @param vert + * + * @return + */ +GntWidget * gnt_window_box_new(gboolean homo, gboolean vert); +/** + * + * @param window + * @param menu + */ 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntwm.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,11 +1,35 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 + */ + #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> @@ -47,6 +71,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); @@ -56,6 +82,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 GList * g_list_bring_to_front(GList *list, gpointer data) @@ -74,61 +101,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; @@ -199,6 +173,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) { @@ -329,9 +330,18 @@ 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 = gnt_ws_new("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); @@ -340,6 +350,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 @@ -351,23 +362,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); @@ -398,7 +409,7 @@ GList *l; int n; - if (!wm->ordered) + if (!wm->cws->ordered) return TRUE; if (list) @@ -406,9 +417,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); } @@ -427,17 +438,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; @@ -451,10 +462,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; @@ -462,7 +473,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; @@ -476,8 +487,8 @@ 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; @@ -490,10 +501,10 @@ GntWidget *widget, *tree, *win, *active; char *title; - if (!wm->ordered) + if (!wm->cws->ordered) return TRUE; - widget = wm->ordered->data; + widget = wm->cws->ordered->data; if (!GNT_IS_BOX(widget)) return TRUE; active = GNT_BOX(widget)->active; @@ -540,85 +551,123 @@ 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 void +list_of_windows(GntWM *wm, gboolean workspace) +{ + GntWidget *tree, *win; + setup__list(wm); + wm->windows = &wm->_list; + + win = wm->windows->window; + tree = wm->windows->tree; + + gnt_box_set_title(GNT_BOX(win), workspace ? "Workspace List" : "Window List"); + + populate_window_list(wm, workspace); + + 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); - GntWidget *tree, *win; if (wm->_list.window || wm->menu) return TRUE; - if (!wm->ordered) + if (!wm->cws->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"); - - populate_window_list(wm); + list_of_windows(wm, FALSE); - gnt_tree_set_selected(GNT_TREE(tree), wm->ordered->data); - 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); - - 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); return TRUE; } @@ -758,7 +807,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) @@ -778,8 +827,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 @@ -789,7 +838,7 @@ if (wm->_list.window) return TRUE; - shift_window(wm, wm->ordered->data, -1); + shift_window(wm, wm->cws->ordered->data, -1); return TRUE; } @@ -800,7 +849,7 @@ if (wm->_list.window) return TRUE; - shift_window(wm, wm->ordered->data, 1); + shift_window(wm, wm->cws->ordered->data, 1); return TRUE; } @@ -924,7 +973,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); } @@ -934,11 +983,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; } @@ -949,11 +998,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; } @@ -980,12 +1029,11 @@ GntWM *wm = GNT_WM(bindable); endwin(); - refresh(); - curs_set(0); /* endwin resets the cursor to normal */ g_hash_table_foreach(wm->nodes, (GHFunc)refresh_node, NULL); update_screen(wm); - draw_taskbar(wm, TRUE); + gnt_ws_draw_taskbar(wm->cws, TRUE); + curs_set(0); /* endwin resets the cursor to normal */ return FALSE; } @@ -1016,6 +1064,78 @@ 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 +workspace_new(GntBindable *bindable, GList *null) +{ + GntWM *wm = GNT_WM(bindable); + GntWS *ws = gnt_ws_new(NULL); + gnt_wm_add_workspace(wm, ws); + gnt_wm_switch_workspace(wm, g_list_index(wm->workspaces, ws)); + return TRUE; +} + static void gnt_wm_class_init(GntWMClass *klass) { @@ -1150,6 +1270,18 @@ "\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-new", workspace_new, + GNT_KEY_F9, 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); @@ -1194,6 +1326,119 @@ 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) { @@ -1202,7 +1447,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; @@ -1210,6 +1455,56 @@ gnt_tree_set_row_flags(GNT_TREE(wm->windows->tree), wid, flag); } +static gboolean +match_title(gpointer title, gpointer n, gpointer wid_title) +{ + /* XXX: do any regex magic here. */ + if (g_strrstr((gchar *)wid_title, (gchar *)title)) + return TRUE; + return FALSE; +} + +#if !GLIB_CHECK_VERSION(2,4,0) +struct +{ + gpointer data; + gpointer value; +} table_find_data; + +static void +table_find_helper(gpointer key, gpointer value, gpointer data) +{ + GHRFunc func = data; + if (func(key, value, table_find_data.data)) + table_find_data.value = value; +} + +static gpointer +g_hash_table_find(GHashTable * table, GHRFunc func, gpointer data) +{ + table_find_data.data = data; + table_find_data.value = NULL; + g_hash_table_foreach(table, table_find_helper, func); + return table_find_data.value; +} +#endif + +static GntWS * +new_widget_find_workspace(GntWM *wm, GntWidget *widget) +{ + GntWS *ret = NULL; + const gchar *name, *title; + title = GNT_BOX(widget)->title; + if (title) + ret = g_hash_table_find(wm->title_places, match_title, (gpointer)title); + if (ret) + return ret; + name = gnt_widget_get_name(widget); + if (name) + ret = g_hash_table_find(wm->name_places, match_title, (gpointer)name); + return ret ? ret : wm->cws; +} + static void gnt_wm_new_window_real(GntWM *wm, GntWidget *widget) { @@ -1256,7 +1551,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 @@ -1264,18 +1559,24 @@ 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)) { + ws = new_widget_find_workspace(wm, widget); + } - 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) @@ -1286,7 +1587,10 @@ gnt_wm_raise_window(wm, node->me); } else { bottom_panel(node->panel); /* New windows should not grab focus */ + gnt_widget_set_focus(node->me, FALSE); gnt_widget_set_urgent(node->me); + if (wm->cws != ws) + gnt_ws_widget_hide(widget, wm->nodes); } } } @@ -1320,13 +1624,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) @@ -1336,9 +1640,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; @@ -1349,18 +1656,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() @@ -1381,10 +1690,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); @@ -1460,8 +1769,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; } @@ -1595,9 +1904,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); } @@ -1611,27 +1920,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) @@ -1644,6 +1961,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 Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/gntwm.h Wed Jun 27 21:43:18 2007 +0000 @@ -1,6 +1,31 @@ +/** + * GNT - The GLib Ncurses Toolkit + * + * GNT is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This library 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 GNTWM_H +#define GNTWM_H #include "gntwidget.h" #include "gntmenu.h" +#include "gntws.h" #include <panel.h> #include <time.h> @@ -26,9 +51,10 @@ WINDOW *window; int scroll; PANEL *panel; + GntWS *ws; } GntNode; -typedef struct _GnttWM GntWM; +typedef struct _GntWM GntWM; typedef struct _GntPosition { @@ -45,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; @@ -62,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 */ @@ -134,7 +163,7 @@ /* List of windows. Although the WM can keep a list of its own for the windows, * it'd be better if there was a way to share between the 'core' and the WM. */ - /*const GList *(*window_list)();*/ + /*GList *(*window_list)();*/ void (*res1)(void); void (*res2)(void); @@ -144,28 +173,111 @@ G_BEGIN_DECLS +/** + * + * + * @return + */ 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 + * @param widget + */ void gnt_wm_new_window(GntWM *wm, GntWidget *widget); +/** + * + * @param wm + * @param widget + */ void gnt_wm_window_decorate(GntWM *wm, GntWidget *widget); +/** + * + * @param wm + * @param widget + */ void gnt_wm_window_close(GntWM *wm, GntWidget *widget); +/** + * + * @param wm + * @param string + * + * @return + */ gboolean gnt_wm_process_input(GntWM *wm, const char *string); +/** + * + * @param wm + * @param event + * @param x + * @param y + * @param widget + * + * @return + */ gboolean gnt_wm_process_click(GntWM *wm, GntMouseEvent event, int x, int y, GntWidget *widget); +/** + * + * @param wm + * @param widget + * @param width + * @param height + */ void gnt_wm_resize_window(GntWM *wm, GntWidget *widget, int width, int height); +/** + * + * @param wm + * @param widget + * @param x + * @param y + */ void gnt_wm_move_window(GntWM *wm, GntWidget *widget, int x, int y); +/** + * + * @param wm + * @param widget + */ void gnt_wm_update_window(GntWM *wm, GntWidget *widget); +/** + * + * @param wm + * @param widget + */ void gnt_wm_raise_window(GntWM *wm, GntWidget *widget); +/** + * + * @param wm + * @param set + */ void gnt_wm_set_event_stack(GntWM *wm, gboolean set); +void gnt_wm_copy_win(GntWidget *widget, GntNode *node); + +/** + * + * + * @return + */ 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 Wed Jun 27 21:43:18 2007 +0000 @@ -0,0 +1,172 @@ +#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) +{ + GntWidget *oldfocus; + oldfocus = ws->ordered ? ws->ordered->data : NULL; + ws->list = g_list_append(ws->list, wid); + ws->ordered = g_list_prepend(ws->ordered, wid); + if (oldfocus) + gnt_widget_set_focus(oldfocus, FALSE); +} + +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; +} + +GntWS *gnt_ws_new(const char *name) +{ + GntWS *ws = GNT_WS(g_object_new(GNT_TYPE_WS, NULL)); + ws->name = g_strdup(name ? name : "(noname)"); + return ws; +} + +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 Wed Jun 27 21:43:18 2007 +0000 @@ -0,0 +1,60 @@ +#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); + +GntWS *gnt_ws_new(const char *name); +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 Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/wms/Makefile.am Wed Jun 27 21:43:18 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 Wed Jun 27 21:43:18 2007 +0000 @@ -0,0 +1,320 @@ +/** + * 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", + GPOINTER_TO_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->cws->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->cws->ordered == NULL || is_budddylist(win = GNT_WIDGET(wm->cws->ordered->data))) + return FALSE; + + find_window_position(irssi, win, &hor, &vert); + + switch (GPOINTER_TO_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 Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/libgnt/wms/s.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,6 +1,8 @@ #include <string.h> #include <sys/types.h> +#include "internal.h" + #include "gnt.h" #include "gntbox.h" #include "gntmenu.h" @@ -121,7 +123,7 @@ static GntWidget * find_widget(GntWM *wm, const char *wname) { - const GList *iter = wm->list; + GList *iter = wm->cws->list; for (; iter; iter = iter->next) { GntWidget *widget = iter->data; const char *name = gnt_widget_get_name(widget);
--- a/finch/plugins/Makefile.am Sun Jun 03 09:40:38 2007 +0000 +++ b/finch/plugins/Makefile.am Wed Jun 27 21:43:18 2007 +0000 @@ -28,7 +28,7 @@ endif # PLUGINS -EXTRA_DIST = +EXTRA_DIST = pietray.py AM_CPPFLAGS = \ -DDATADIR=\"$(datadir)\" \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/plugins/pietray.py Wed Jun 27 21:43:18 2007 +0000 @@ -0,0 +1,183 @@ +#!/usr/bin/env python + +# This is a dbus script to show a docklet for Finch. This should work +# for any 'compatible' purple client. +# +# By 'compatible', I mean any client that sets and updates the +# "unseen-count" data on the conversations. +# +# It allows doing the following things: +# - It allows changing status. +# - It shows the current status and info about unread messages in +# the tooltip. +# - It can blink on unread IM/Chat messages, and it allows canging +# the preference for that. +# +# It requires GTK+ 2.10 or above, since it uses GtkStatusIcon. +# +# Sadrul <sadrul@pidgin.im> + +import pygtk +pygtk.require("2.0") +import gtk +import dbus, gobject, dbus.glib +import os # to get the pkg-config output + +bus = dbus.SessionBus() +obj = bus.get_object( + "im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject") +purple = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface") + +def pack_image_label(menu, image, label): + item = gtk.ImageMenuItem(label) + if image: + img = gtk.Image() + img.set_from_stock(image, 1) + item.set_image(img) + menu.append(item) + return item + +def activate_primitive_status(item, status): + saved = purple.PurpleSavedstatusFindTransientByTypeAndMessage(status, "") + if not saved: + saved = purple.PurpleSavedstatusNew("", status) + purple.PurpleSavedstatusActivate(saved) + +def activate_popular_status(item, time): + saved = purple.PurpleSavedstatusFindByCreationTime(time) + if saved: + purple.PurpleSavedstatusActivate(saved) + +def generate_status_menu(menu): + item = gtk.MenuItem("Available") + item.connect("activate", activate_primitive_status, 2) + menu.append(item) + + item = gtk.MenuItem("Away") + item.connect("activate", activate_primitive_status, 5) + menu.append(item) + + item = gtk.MenuItem("Invisible") + item.connect("activate", activate_primitive_status, 4) + menu.append(item) + + item = gtk.MenuItem("Offline") + item.connect("activate", activate_primitive_status, 1) + menu.append(item) + + menu.append(gtk.MenuItem()) + + popular = purple.PurpleSavedstatusesGetPopular(10) + for pop in popular: + title = purple.PurpleSavedstatusGetTitle(pop).replace('_', '__') + item = gtk.MenuItem(title) + item.set_data("timestamp", purple.PurpleSavedstatusGetCreationTime(pop)) + item.connect("activate", activate_popular_status, purple.PurpleSavedstatusGetCreationTime(pop)) + menu.append(item) + +def toggle_pref(item, pref): + purple.PurplePrefsSetBool(pref, item.get_active()) + +def popup_menu(icon, button, tm, none): + menu = gtk.Menu() + + item = gtk.CheckMenuItem("Blink for unread IM") + item.set_active(purple.PurplePrefsGetBool("/plugins/dbus/docklet/blink/im")) + item.connect("activate", toggle_pref, "/plugins/dbus/docklet/blink/im") + menu.append(item) + + item = gtk.CheckMenuItem("Blink for unread Chats") + item.set_active(purple.PurplePrefsGetBool("/plugins/dbus/docklet/blink/chat")) + item.connect("activate", toggle_pref, "/plugins/dbus/docklet/blink/chat") + menu.append(item) + + menu.append(gtk.MenuItem()) + + #item = pack_image_label(menu, None, "Change Status...") + item = gtk.MenuItem("Change Status...") + menu.append(item) + submenu = gtk.Menu() + item.set_submenu(submenu) + generate_status_menu(submenu) + + menu.show_all() + menu.popup(None, None, None, button, tm) + +def get_status_message(): + status = purple.PurpleSavedstatusGetCurrent() + msg = purple.PurpleSavedstatusGetMessage(status) + if msg and len(msg) > 0: + text = msg + " " + else: + text = "" + text = text + "(" + { + 2: "Available", + 5: "Away", + 4: "Invisible", + 1: "Offline" + }[purple.PurpleSavedstatusGetType(status)] + ")" + return text + +def detect_unread_conversations(): + im = purple.PurplePrefsGetBool("/plugins/dbus/docklet/blink/im") + chat = purple.PurplePrefsGetBool("/plugins/dbus/docklet/blink/chat") + tooltip = "" + blink = False + if im and chat: + convs = purple.PurpleGetConversations() + elif im: + convs = purple.PurpleGetIms() + elif chat: + convs = purple.PurpleGetChats() + else: + convs = None + for conv in convs: + count = purple.PurpleConversationGetData(conv, "unseen-count") + if count and count > 0: + blink = True + tooltip = tooltip + "\n" + purple.PurpleConversationGetName(conv) + " (" + str(count) + ")" + t.set_from_file(path + "/share/pixmaps/pidgin.png") + if blink: + # I hate this icon + # t.set_from_file(path + "/share/pixmaps/pidgin/tray/22/tray-message.png") + tooltip = "\nUnread Messages:" + tooltip + # There's going to be some way to expose the client's display name in 2.1.0. + # Use that instead of hardcoding Finch here. + t.set_tooltip("Finch: " + get_status_message() + tooltip) + t.set_blinking(blink) + +def conversation_updated(conv, type): + detect_unread_conversations() + +def savedstatus_changed(new, old): + # Change the icon for status perhaps? + detect_unread_conversations() + +def init_prefs(): + if not purple.PurplePrefsExists("/plugins/dbus/docklet/blink"): + purple.PurplePrefsAddNone("/plugins") + purple.PurplePrefsAddNone("/plugins/dbus") + purple.PurplePrefsAddNone("/plugins/dbus/docklet") + purple.PurplePrefsAddNone("/plugins/dbus/docklet/blink") + purple.PurplePrefsAddBool("/plugins/dbus/docklet/blink/im", True) + purple.PurplePrefsAddBool("/plugins/dbus/docklet/blink/chat", True) + +pkg = os.popen("pkg-config --variable=prefix pidgin") +path = pkg.readline().rstrip() + +bus.add_signal_receiver(conversation_updated, + dbus_interface="im.pidgin.purple.PurpleInterface", + signal_name="ConversationUpdated") + +bus.add_signal_receiver(savedstatus_changed, + dbus_interface="im.pidgin.purple.PurpleInterface", + signal_name="SavedstatusChanged") + +t = gtk.StatusIcon() +t.connect("popup-menu", popup_menu, None) + +init_prefs() +detect_unread_conversations() + +gtk.main () +
--- a/libpurple/Makefile.am Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/Makefile.am Wed Jun 27 21:43:18 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/Makefile.mingw Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/Makefile.mingw Wed Jun 27 21:43:18 2007 +0000 @@ -72,6 +72,7 @@ upnp.c \ util.c \ value.c \ + version.c \ xmlnode.c \ whiteboard.c \ win32/giowin32.c \
--- a/libpurple/account.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/account.c Wed Jun 27 21:43:18 2007 +0000 @@ -205,7 +205,7 @@ { PurpleStatusType *type = purple_status_get_type(status); xmlnode *node, *child; - const GList *attrs, *attr; + GList *attrs, *attr; node = xmlnode_new("attributes"); @@ -241,7 +241,7 @@ statuses_to_xmlnode(const PurplePresence *presence) { xmlnode *node, *child; - const GList *statuses, *status; + GList *statuses, *status; node = xmlnode_new("statuses"); @@ -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); } @@ -1805,7 +1805,7 @@ PurpleStatusType * purple_account_get_status_type(const PurpleAccount *account, const char *id) { - const GList *l; + GList *l; g_return_val_if_fail(account != NULL, NULL); g_return_val_if_fail(id != NULL, NULL); @@ -1824,7 +1824,7 @@ PurpleStatusType * purple_account_get_status_type_with_primitive(const PurpleAccount *account, PurpleStatusPrimitive primitive) { - const GList *l; + GList *l; g_return_val_if_fail(account != NULL, NULL); @@ -1857,7 +1857,7 @@ return purple_presence_is_status_active(account->presence, status_id); } -const GList * +GList * purple_account_get_status_types(const PurpleAccount *account) { g_return_val_if_fail(account != NULL, NULL);
--- a/libpurple/account.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/account.h Wed Jun 27 21:43:18 2007 +0000 @@ -369,7 +369,7 @@ * as a NULL-terminated list of id/value pairs. */ void purple_account_set_status(PurpleAccount *account, const char *status_id, - gboolean active, ...); + gboolean active, ...) G_GNUC_NULL_TERMINATED; /** @@ -672,9 +672,9 @@ * * @param account The account. * - * @return The account's status types. + * @constreturn The account's status types. */ -const GList *purple_account_get_status_types(const PurpleAccount *account); +GList *purple_account_get_status_types(const PurpleAccount *account); /** * Returns a protocol-specific integer setting for an account. @@ -884,7 +884,7 @@ /** * Returns a list of all accounts. * - * @return A list of all accounts. + * @constreturn A list of all accounts. */ GList *purple_accounts_get_all(void);
--- a/libpurple/accountopt.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/accountopt.c Wed Jun 27 21:43:18 2007 +0000 @@ -22,6 +22,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "internal.h" + #include "accountopt.h" #include "util.h" @@ -282,7 +284,7 @@ return option->masked; } -const GList * +GList * purple_account_option_get_list(const PurpleAccountOption *option) { g_return_val_if_fail(option != NULL, NULL); @@ -308,6 +310,7 @@ split->text = g_strdup(text); split->field_sep = sep; split->default_value = g_strdup(default_value); + split->reverse = TRUE; return split; } @@ -345,3 +348,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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/accountopt.h Wed Jun 27 21:43:18 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; @@ -293,9 +296,9 @@ * * @param option The account option. * - * @return The list values. + * @constreturn The list values. */ -const GList *purple_account_option_get_list(const PurpleAccountOption *option); +GList *purple_account_option_get_list(const PurpleAccountOption *option); /*@}*/ @@ -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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/blist.c Wed Jun 27 21:43:18 2007 +0000 @@ -35,8 +35,6 @@ #include "value.h" #include "xmlnode.h" -#define PATHSIZE 1024 - static PurpleBlistUiOps *blist_ui_ops = NULL; static PurpleBuddyList *purplebuddylist = NULL; @@ -365,7 +363,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); } @@ -1774,7 +1772,7 @@ node = (PurpleBlistNode *)buddy; cnode = node->parent; - gnode = cnode->parent; + gnode = (cnode != NULL) ? cnode->parent : NULL; contact = (PurpleContact *)cnode; group = (PurpleGroup *)gnode; @@ -1783,35 +1781,37 @@ node->prev->next = node->next; if (node->next) node->next->prev = node->prev; - if (cnode->child == node) + if ((cnode != NULL) && (cnode->child == node)) cnode->child = node->next; /* Adjust size counts */ - if (PURPLE_BUDDY_IS_ONLINE(buddy)) { - contact->online--; - if (contact->online == 0) - group->online--; + if (contact != NULL) { + if (PURPLE_BUDDY_IS_ONLINE(buddy)) { + contact->online--; + if (contact->online == 0) + group->online--; + } + if (purple_account_is_connected(buddy->account)) { + contact->currentsize--; + if (contact->currentsize == 0) + group->currentsize--; + } + contact->totalsize--; + + /* Re-sort the contact */ + if (cnode->child && contact->priority == buddy) { + purple_contact_invalidate_priority_buddy(contact); + if (ops && ops->update) + ops->update(purplebuddylist, cnode); + } } - if (purple_account_is_connected(buddy->account)) { - contact->currentsize--; - if (contact->currentsize == 0) - group->currentsize--; - } - contact->totalsize--; purple_blist_schedule_save(); - /* Re-sort the contact */ - if (cnode->child && contact->priority == buddy) { - purple_contact_invalidate_priority_buddy(contact); - if (ops && ops->update) - ops->update(purplebuddylist, cnode); - } - /* Remove this buddy from the buddies hash table */ hb.name = g_strdup(purple_normalize(buddy->account, buddy->name)); hb.account = buddy->account; - hb.group = ((PurpleBlistNode*)buddy)->parent->parent; + hb.group = gnode; g_hash_table_remove(purplebuddylist->buddies, &hb); g_free(hb.name); @@ -1841,7 +1841,7 @@ while (g_source_remove_by_user_data((gpointer *)buddy)); /* If the contact is empty then remove it */ - if (!cnode->child) + if ((contact != NULL) && !cnode->child) purple_blist_remove_contact(contact); } @@ -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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/blist.h Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/buddyicon.c Wed Jun 27 21:43:18 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",
--- a/libpurple/buddyicon.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/buddyicon.h Wed Jun 27 21:43:18 2007 +0000 @@ -31,6 +31,7 @@ #include "blist.h" #include "imgstore.h" #include "prpl.h" +#include "util.h" #ifdef __cplusplus extern "C" {
--- a/libpurple/cmds.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/cmds.c Wed Jun 27 21:43:18 2007 +0000 @@ -22,6 +22,8 @@ #include <string.h> +#include "internal.h" + #include "account.h" #include "util.h" #include "cmds.h"
--- a/libpurple/connection.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/connection.c Wed Jun 27 21:43:18 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"); }
--- a/libpurple/connection.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/connection.h Wed Jun 27 21:43:18 2007 +0000 @@ -259,14 +259,14 @@ * Returns a list of all active connections. This does not * include connections that are in the process of connecting. * - * @return A list of all active connections. + * @constreturn A list of all active connections. */ GList *purple_connections_get_all(void); /** * Returns a list of all connections in the process of connecting. * - * @return A list of connecting connections. + * @constreturn A list of connecting connections. */ GList *purple_connections_get_connecting(void);
--- a/libpurple/conversation.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/conversation.c Wed Jun 27 21:43:18 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)) @@ -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 @@ -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 @@ -1283,7 +1288,7 @@ 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); @@ -1535,20 +1540,22 @@ PurpleConvChatBuddyFlags flag = GPOINTER_TO_INT(fl->data); const char *extra_msg = (extra_msgs ? extra_msgs->data : NULL); - if (!strcmp(chat->nick, purple_normalize(conv->account, user))) { - const char *alias2 = purple_account_get_alias(conv->account); - if (alias2 != NULL) - alias = alias2; - else - { - const char *display_name = purple_connection_get_display_name(gc); - if (display_name != NULL) - alias = display_name; + if(!(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) { + if (!strcmp(chat->nick, purple_normalize(conv->account, user))) { + const char *alias2 = purple_account_get_alias(conv->account); + if (alias2 != NULL) + alias = alias2; + else + { + const char *display_name = purple_connection_get_display_name(gc); + if (display_name != NULL) + alias = display_name; + } + } else { + PurpleBuddy *buddy; + if ((buddy = purple_find_buddy(gc->account, user)) != NULL) + alias = purple_buddy_get_contact_alias(buddy); } - } else if (!(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) { - PurpleBuddy *buddy; - if ((buddy = purple_find_buddy(gc->account, user)) != NULL) - alias = purple_buddy_get_contact_alias(buddy); } quiet = GPOINTER_TO_INT(purple_signal_emit_return_1(purple_conversations_get_handle(), @@ -1558,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); @@ -1576,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); } @@ -1625,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; @@ -1633,14 +1642,16 @@ /* Note this for later. */ is_me = TRUE; - alias = purple_account_get_alias(conv->account); - if (alias != NULL) - new_alias = alias; - else - { - const char *display_name = purple_connection_get_display_name(gc); - if (display_name != NULL) - alias = display_name; + if(!(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) { + alias = purple_account_get_alias(conv->account); + if (alias != NULL) + new_alias = alias; + else + { + const char *display_name = purple_connection_get_display_name(gc); + if (display_name != NULL) + alias = display_name; + } } } else if (!(prpl_info->options & OPT_PROTO_UNIQUE_CHATNAME)) { PurpleBuddy *buddy; @@ -1655,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); } @@ -1700,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)); } } @@ -1747,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); } @@ -1777,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); } @@ -1794,19 +1809,20 @@ { PurpleConversation *conv; PurpleConversationUiOps *ops; - GList *users, *names = NULL; + GList *users; 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); @@ -1960,7 +1976,7 @@ for (l = purple_conv_chat_get_users(chat); l; l = l->next) { cb = l->data; - if (!purple_utf8_strcasecmp(cb->name, name)) + if (!g_utf8_collate(cb->name, name)) return cb; } @@ -1989,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) { @@ -2252,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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/conversation.h Wed Jun 27 21:43:18 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; @@ -500,21 +502,21 @@ * * This list includes both IMs and chats. * - * @return A GList of all conversations. + * @constreturn A GList of all conversations. */ GList *purple_get_conversations(void); /** * Returns a list of all IMs. * - * @return A GList of all IMs. + * @constreturn A GList of all IMs. */ GList *purple_get_ims(void); /** * Returns a list of all chats. * - * @return A GList of all chats. + * @constreturn A GList of all chats. */ GList *purple_get_chats(void); @@ -873,7 +875,7 @@ * * @param chat The chat. * - * @return The list of users. + * @constreturn The list of users. */ GList *purple_conv_chat_get_users(const PurpleConvChat *chat); @@ -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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/core.c Wed Jun 27 21:43:18 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 @@ -218,11 +222,10 @@ purple_sound_uninit(); purple_plugins_uninit(); - purple_signals_uninit(); - #ifdef HAVE_DBUS purple_dbus_uninit(); #endif + purple_signals_uninit(); g_free(core->ui); g_free(core); @@ -276,6 +279,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) { @@ -400,13 +488,33 @@ /* We're only going to duplicate a logs symlink. */ if (!strcmp(entry, "logs")) { + char *link; +#if GLIB_CHECK_VERSION(2,4,0) + GError *err = NULL; + + if ((link = g_file_read_link(name, &err)) == NULL) + { + char *name_utf8 = g_filename_to_utf8(name, -1, NULL, NULL, NULL); + purple_debug_error("core", "Error reading symlink %s: %s. Please report this at http://developer.pidgin.im\n", + name_utf8 ? name_utf8 : name, err->message); + g_free(name_utf8); + g_error_free(err); + g_free(name); + g_dir_close(dir); + g_free(status_file); + g_free(old_user_dir); + return FALSE; + } +#else char buf[MAXPATHLEN]; size_t linklen; if ((linklen = readlink(name, buf, sizeof(buf) - 1) == -1)) { + char *name_utf8 = g_filename_to_utf8(name, -1, NULL, NULL, NULL); purple_debug_error("core", "Error reading symlink %s: %s. Please report this at http://developer.pidgin.im\n", - name, strerror(errno)); + name_utf8, strerror(errno)); + g_free(name_utf8); g_free(name); g_dir_close(dir); g_free(status_file); @@ -415,13 +523,18 @@ } buf[linklen] = '\0'; - logs_dir = g_strconcat(user_dir, G_DIR_SEPARATOR_S "logs", NULL); + /* This way we don't have to GLIB_VERSION_CHECK every g_free(link) below. */ + link = g_strdup(buf); +#endif - if (!strcmp(buf, "../.purple/logs") || !strcmp(buf, logs_dir)) + logs_dir = g_build_filename(user_dir, "logs", NULL); + + if (!strcmp(link, "../.purple/logs") || !strcmp(link, logs_dir)) { /* If the symlink points to the new directory, we're * likely just trying again after a failed migration, * so there's no need to fail here. */ + g_free(link); g_free(logs_dir); continue; } @@ -433,12 +546,13 @@ g_unlink(logs_dir); /* Relative links will most likely still be - * valid from ~/.purple, though not it's not + * valid from ~/.purple, though it's not * guaranteed. Oh well. */ - if (symlink(buf, logs_dir)) + if (symlink(link, logs_dir)) { purple_debug_error("core", "Error symlinking %s to %s: %s. Please report this at http://developer.pidgin.im\n", - logs_dir, buf, strerror(errno)); + logs_dir, link, strerror(errno)); + g_free(link); g_free(name); g_free(logs_dir); g_dir_close(dir); @@ -447,6 +561,7 @@ return FALSE; } + g_free(link); g_free(logs_dir); continue; }
--- a/libpurple/core.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/core.h Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/dbus-analyze-functions.py Wed Jun 27 21:43:18 2007 +0000 @@ -3,7 +3,7 @@ import sys # types translated into "int" -simpletypes = ["int", "gint", "guint", "gboolean"] +simpletypes = ["int", "gint", "guint", "gboolean", "gpointer", "size_t", "gssize", "time_t"] # List "excluded" contains functions that shouldn't be exported via # DBus. If you remove a function from this list, please make sure @@ -32,10 +32,40 @@ "purple_log_read", ] -# 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 = [] +# This is a list of functions that return a GList* or GSList * whose elements +# are strings, 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", +] + +# This is a list of functions that return a GList* or GSList* that should +# not be freed. Ideally, this information should be obtained from the Doxygen +# documentation at some point. +constlists = [ + "purple_account_get_status_types", + "purple_accounts_get_all", + "purple_account_option_get_list", + "purple_connections_get_all", + "purple_connections_get_connecting", + "purple_get_conversations", + "purple_get_ims", + "purple_get_chats", + "purple_conv_chat_get_users", + "purple_conv_chat_get_ignored", + "purple_mime_document_get_fields", + "purple_mime_document_get_parts", + "purple_mime_part_get_fields", + "purple_notify_user_info_get_entries", + "purple_request_fields_get_required", + "purple_request_field_list_get_selected", + "purple_request_field_list_get_items", + "purple_savedstatuses_get_all", + "purple_status_type_get_attrs", + "purple_presence_get_statuses", +] pointer = "#pointer#" myexception = "My Exception" @@ -89,21 +119,26 @@ def processinput(self, type, name): const = False + unsigned = False if type[0] == "const": type = type[1:] const = True + if type[0] == "unsigned": + type = type[1:] + unsigned = True + if len(type) == 1: # simple types (int, gboolean, etc.) and enums if (type[0] in simpletypes) or ((type[0].startswith("Purple") and not type[0].endswith("Callback"))): - return self.inputsimple(type, name) + return self.inputsimple(type, name, unsigned) # pointers ... if (len(type) == 2) and (type[1] == pointer): # strings if type[0] in ["char", "gchar"]: if const: - return self.inputstring(type, name) + return self.inputstring(type, name, unsigned) else: raise myexception @@ -165,9 +200,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), @@ -202,12 +237,18 @@ print "typedef struct _%s %s;" % (type[0], type[0]) self.knowntypes.append(type[0]) - def inputsimple(self, type, name): + def inputsimple(self, type, name, us): self.paramshdr.append("%s %s" % (type[0], name)) - self.inputparams.append(("G_TYPE_INT", name)) + if us: + self.inputparams.append(("G_TYPE_UINT", name)) + else: + self.inputparams.append(("G_TYPE_INT", name)) - def inputstring(self, type, name): - self.paramshdr.append("const char *%s" % name) + def inputstring(self, type, name, us): + if us: + self.paramshdr.append("const unsigned char *%s" % name) + else: + self.paramshdr.append("const char *%s" % name) self.inputparams.append(("G_TYPE_STRING", name)) def inputpurplestructure(self, type, name): @@ -279,7 +320,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 +330,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: @@ -318,15 +359,23 @@ # input parameters - def inputsimple(self, type, name): - self.cdecls.append("\tdbus_int32_t %s;" % name) - self.cparams.append(("INT32", name)) - self.addintype("i", name) + def inputsimple(self, type, name, us): + if us: + self.cdecls.append("\tdbus_int32_t %s;" % name) + self.cparams.append(("INT32", name)) + self.addintype("i", name) + else: + self.cdecls.append("\tdbus_uint32_t %s;" % name) + self.cparams.append(("UINT32", name)) + self.addintype("u", name) - def inputstring(self, type, name): - self.cdecls.append("\tconst char *%s;" % name) + def inputstring(self, type, name, us): + if us: + self.cdecls.append("\tconst unsigned char *%s;" % name) + else: + self.cdecls.append("\tconst char *%s;" % name) self.cparams.append(("STRING", name)) - self.ccode .append("\tNULLIFY(%s);" % name) + self.ccode.append("\t%s = (%s && %s[0]) ? %s : NULL;" % (name,name,name,name)) self.addintype("s", name) def inputhash(self, type, name): @@ -385,29 +434,33 @@ 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 - + # unless the function is in stringlists def outputlist(self, type, name): self.cdecls.append("\tdbus_int32_t %s_LEN;" % name) self.ccodeout.append("\tg_free(%s);" % name) + 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.cparamsout.append("\tDBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &%s, %s_LEN" \ + self.ccode.append("\tlist = %s;" % self.call) + self.ccode.append("\t%s = (char **)purple_%s_to_array(list, FALSE, &%s_LEN);" % \ + (name, type[0], name)) + self.cparamsout.append("DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &%s, %s_LEN" \ % (name, name)) + if (not (self.function.name in constlists)): + 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.cparamsout.append("\tDBUS_TYPE_ARRAY, DBUS_TYPE_INT32, &%s, %s_LEN" \ + self.ccode.append("\tlist = %s;" % self.call) + self.ccode.append("\t%s = purple_dbusify_%s(list, FALSE, &%s_LEN);" % \ + (name, type[0], name)) + if (not (self.function.name in constlists)): + self.ccode.append("\tg_%s_free(list);" % type[0].lower()[1:]) + self.cparamsout.append("DBUS_TYPE_ARRAY, DBUS_TYPE_INT32, &%s, %s_LEN" \ % (name, name)) self.addouttype("ai", name)
--- a/libpurple/dbus-server.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/dbus-server.c Wed Jun 27 21:43:18 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 @@ -114,8 +120,8 @@ if ((id == 0) && (node != NULL)) { purple_debug_warning("dbus", - "Need to register an object with the dbus subsystem.\n"); - g_return_val_if_reached(0); + "Need to register an object with the dbus subsystem. (If you are not a developer, please ignore this message.)\n"); + return 0; } return id; } @@ -291,7 +297,7 @@ GList *elem; *len = g_list_length(list); - array = g_new0(dbus_int32_t, g_list_length(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); @@ -309,7 +315,7 @@ GSList *elem; *len = g_slist_length(list); - array = g_new0(dbus_int32_t, g_slist_length(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); @@ -327,7 +333,7 @@ GList *elem; *len = g_list_length(list); - array = g_new0(gpointer, g_list_length(list)); + array = g_new0(gpointer, *len); for (i = 0, elem = list; elem != NULL; elem = elem->next, i++) array[i] = elem->data; @@ -345,7 +351,7 @@ GSList *elem; *len = g_slist_length(list); - array = g_new0(gpointer, g_slist_length(list)); + array = g_new0(gpointer, *len); for (i = 0, elem = list; elem != NULL; elem = elem->next, i++) array[i] = elem->data; @@ -592,6 +598,7 @@ return; } + dbus_request_name_reply = result = dbus_bus_request_name(purple_dbus_connection, DBUS_SERVICE_PURPLE, 0, &error); @@ -753,7 +760,7 @@ dbus_message_iter_init_append(signal, &iter); if (purple_dbus_message_append_purple_values(&iter, num_values, values, vargs)) - purple_debug_warning("dbus", "The signal \"%s\" caused some dbus error.\n", name); + purple_debug_warning("dbus", "The signal \"%s\" caused some dbus error. (If you are not a developer, please ignore this message.)\n", name); dbus_connection_send(purple_dbus_connection, signal, NULL); @@ -790,8 +797,18 @@ void purple_dbus_uninit(void) { - /* Surely we must do SOME kind of uninitialization? */ + DBusError error; + if (!purple_dbus_connection) + return; + dbus_error_init(&error); + dbus_connection_unregister_object_path(purple_dbus_connection, DBUS_PATH_PURPLE); + dbus_bus_release_name(purple_dbus_connection, DBUS_SERVICE_PURPLE, &error); + dbus_error_free(&error); + dbus_connection_unref(purple_dbus_connection); + purple_dbus_connection = NULL; + purple_signals_disconnect_by_handle(purple_dbus_get_handle()); g_free(init_error); init_error = NULL; } +
--- a/libpurple/dbus-server.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/dbus-server.h Wed Jun 27 21:43:18 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/eventloop.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/eventloop.c Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/eventloop.h Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/example/nullclient.c Wed Jun 27 21:43:18 2007 +0000 @@ -20,6 +20,10 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ + +/* XXX: we probably shouldn't include internal.h in examples */ +#include "internal.h" + #include "account.h" #include "conversation.h" #include "core.h" @@ -108,11 +112,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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/ft.c Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/ft.h Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/idle.c Wed Jun 27 21:43:18 2007 +0000 @@ -92,7 +92,7 @@ } -static int no_away = 0; +static gboolean no_away = FALSE; static gint time_until_next_idle_event; /* * This function should be called when you think your idle state @@ -118,15 +118,15 @@ time_t time_idle; gboolean auto_away; const gchar *idle_reporting; - gboolean report_idle; - GList *l; + gboolean report_idle = TRUE; gint away_seconds = 0; - gint idle_recheck_interval; + gint idle_recheck_interval = 0; purple_signal_emit(purple_blist_get_handle(), "update-idle"); idle_reporting = purple_prefs_get_string("/purple/away/idle_reporting"); - report_idle = TRUE; + auto_away = purple_prefs_get_bool("/purple/away/away_when_idle"); + if (!strcmp(idle_reporting, "system") && (idle_ui_ops != NULL) && (idle_ui_ops->get_time_idle != NULL)) { @@ -145,15 +145,9 @@ /* Don't report idle time */ time_idle = 0; report_idle = FALSE; - } - /* Auto-away stuff */ - auto_away = purple_prefs_get_bool("/purple/away/away_when_idle"); - - /* If we're not reporting idle, we can still do auto-away. - * First try "system" and if that isn't possible, use "purple" */ - if (!report_idle) - { + /* If we're not reporting idle, we can still do auto-away. + * First try "system" and if that isn't possible, use "purple" */ if (auto_away) { if ((idle_ui_ops != NULL) && (idle_ui_ops->get_time_idle != NULL)) @@ -172,7 +166,7 @@ if (!no_away) { purple_savedstatus_set_idleaway(FALSE); - no_away = 1; + no_away = TRUE; } time_until_next_idle_event = 0; return; @@ -192,11 +186,11 @@ if (auto_away && time_idle > away_seconds) { purple_savedstatus_set_idleaway(TRUE); - no_away = 0; + no_away = FALSE; } else if (!no_away && time_idle < away_seconds) { - no_away = 1; + no_away = TRUE; purple_savedstatus_set_idleaway(FALSE); if (time_until_next_idle_event == 0 || (away_seconds - time_idle) < time_until_next_idle_event) time_until_next_idle_event = away_seconds - time_idle; @@ -205,6 +199,7 @@ /* Idle reporting stuff */ if (report_idle && (time_idle >= IDLEMARK)) { + GList *l; for (l = purple_connections_get_all(); l != NULL; l = l->next) { PurpleConnection *gc = l->data; @@ -229,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; } @@ -305,11 +304,21 @@ return &handle; } +static gboolean _do_purple_idle_touch_cb(gpointer data) +{ + purple_idle_touch(); + + return FALSE; +} + + 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(), @@ -324,7 +333,10 @@ purple_prefs_connect_callback(purple_idle_get_handle(), "/purple/away/idle_reporting", idle_reporting_cb, NULL); - purple_idle_touch(); + /* Initialize the idleness asynchronously so it doesn't check idleness, + * and potentially try to change the status before the UI is initialized */ + g_idle_add(_do_purple_idle_touch_cb, NULL); + } void
--- a/libpurple/imgstore.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/imgstore.c Wed Jun 27 21:43:18 2007 +0000 @@ -25,6 +25,9 @@ */ #include <glib.h> +#include "internal.h" + +#include "dbus-maybe.h" #include "debug.h" #include "imgstore.h" #include "util.h" @@ -56,6 +59,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 +163,7 @@ g_free(img->data); g_free(img->filename); + PURPLE_DBUS_UNREGISTER_POINTER(img); g_free(img); img = NULL; }
--- a/libpurple/imgstore.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/imgstore.h Wed Jun 27 21:43:18 2007 +0000 @@ -28,7 +28,6 @@ #include <glib.h> -struct _PurpleStoredImage; typedef struct _PurpleStoredImage PurpleStoredImage; #ifdef __cplusplus
--- a/libpurple/internal.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/internal.h Wed Jun 27 21:43:18 2007 +0000 @@ -112,15 +112,17 @@ #endif #ifndef MAXPATHLEN -# define MAXPATHLEN 1024 +# ifdef PATH_MAX +# define MAXPATHLEN PATH_MAX +# else +# define MAXPATHLEN 1024 +# endif #endif #ifndef HOST_NAME_MAX # define HOST_NAME_MAX 255 #endif -#define PATHSIZE 1024 - #include <glib.h> #if !GLIB_CHECK_VERSION(2,4,0) # define G_MAXUINT32 ((guint32) 0xffffffff) @@ -176,6 +178,14 @@ # endif #endif +#ifndef G_GNUC_NULL_TERMINATED +# if __GNUC__ >= 4 +# define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__)) +# else +# define G_GNUC_NULL_TERMINATED +# endif +#endif + /* Safer ways to work with static buffers. When using non-static * buffers, either use g_strdup_* functions (preferred) or use * g_strlcpy/g_strlcpy directly. */
--- a/libpurple/log.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/log.c Wed Jun 27 21:43:18 2007 +0000 @@ -32,6 +32,7 @@ #include "prefs.h" #include "util.h" #include "stringref.h" +#include "imgstore.h" static GSList *loggers = NULL; @@ -405,6 +406,9 @@ if (g_slist_find(loggers, logger)) return; loggers = g_slist_append(loggers, logger); + if (strcmp(purple_prefs_get_string("/purple/logging/format"), logger->id) == 0) { + purple_prefs_trigger_callback("/purple/logging/format"); + } } void purple_log_logger_remove (PurpleLogLogger *logger) @@ -690,6 +694,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; @@ -1191,6 +1296,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 +1337,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/mime.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/mime.c Wed Jun 27 21:43:18 2007 +0000 @@ -29,6 +29,8 @@ #include <glib/glist.h> #include <glib/gstring.h> +#include "internal.h" + /* this should become "util.h" if we ever get this into purple proper */ #include "debug.h" #include "mime.h" @@ -277,7 +279,7 @@ } -const GList * +GList * purple_mime_part_get_fields(PurpleMimePart *part) { g_return_val_if_fail(part != NULL, NULL); @@ -513,7 +515,7 @@ } -const GList * +GList * purple_mime_document_get_fields(PurpleMimeDocument *doc) { g_return_val_if_fail(doc != NULL, NULL); @@ -537,7 +539,7 @@ } -const GList * +GList * purple_mime_document_get_parts(PurpleMimeDocument *doc) { g_return_val_if_fail(doc != NULL, NULL);
--- a/libpurple/mime.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/mime.h Wed Jun 27 21:43:18 2007 +0000 @@ -90,10 +90,10 @@ * * @param doc The MIME document. * - * @returns A list of strings indicating the fields (but not the values of - * the fields) in the header of doc. + * @constreturn A list of strings indicating the fields (but not the values + * of the fields) in the header of doc. */ -const GList *purple_mime_document_get_fields(PurpleMimeDocument *doc); +GList *purple_mime_document_get_fields(PurpleMimeDocument *doc); /** * Get the value of a specific field in the header of a document. @@ -125,9 +125,9 @@ * * @param doc The MIME document. * - * @returns List of PurpleMimePart contained within doc. + * @constreturn List of PurpleMimePart contained within doc. */ -const GList *purple_mime_document_get_parts(PurpleMimeDocument *doc); +GList *purple_mime_document_get_parts(PurpleMimeDocument *doc); /** * Create and insert a new part into a MIME document. @@ -142,10 +142,10 @@ * * @param part The MIME document part. * - * @returns List of strings indicating the fields (but not the values - * of the fields) in the header of part. + * @constreturn List of strings indicating the fields (but not the values + * of the fields) in the header of part. */ -const GList *purple_mime_part_get_fields(PurpleMimePart *part); +GList *purple_mime_part_get_fields(PurpleMimePart *part); /**
--- a/libpurple/network.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/network.c Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/notify.c Wed Jun 27 21:43:18 2007 +0000 @@ -749,18 +749,16 @@ void purple_notify_close_with_handle(void *handle) { - GList *l, *l_next; + GList *l, *prev = NULL; PurpleNotifyUiOps *ops; g_return_if_fail(handle != NULL); ops = purple_notify_get_ui_ops(); - for (l = handles; l != NULL; l = l_next) { + for (l = handles; l != NULL; l = prev ? prev->next : handles) { PurpleNotifyInfo *info = l->data; - l_next = l->next; - if (info->handle == handle) { handles = g_list_remove(handles, info); @@ -771,7 +769,8 @@ info->cb(info->cb_user_data); g_free(info); - } + } else + prev = l; } }
--- a/libpurple/notify.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/notify.h Wed Jun 27 21:43:18 2007 +0000 @@ -456,7 +456,7 @@ * * @param user_info The PurpleNotifyUserInfo * - * @result A GList of PurpleNotifyUserInfoEntry objects + * @constreturn A GList of PurpleNotifyUserInfoEntry objects */ GList *purple_notify_user_info_get_entries(PurpleNotifyUserInfo *user_info);
--- a/libpurple/ntlm.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/ntlm.c Wed Jun 27 21:43:18 2007 +0000 @@ -25,6 +25,8 @@ #include <glib.h> #include <stdlib.h> +#include "internal.h" + #include "util.h" #include "ntlm.h" #include "cipher.h"
--- a/libpurple/plugins/Makefile.mingw Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/Makefile.mingw Wed Jun 27 21:43:18 2007 +0000 @@ -63,6 +63,7 @@ autoaccept.dll \ buddynote.dll \ idle.dll \ + joinpart.dll \ log_reader.dll \ newline.dll \ offlinemsg.dll \
--- a/libpurple/plugins/buddynote.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/buddynote.c Wed Jun 27 21:43:18 2007 +0000 @@ -58,6 +58,9 @@ { PurpleMenuAction *bna = NULL; + if (purple_blist_node_get_flags(node) & PURPLE_BLIST_NODE_FLAG_NO_SAVE) + return; + *m = g_list_append(*m, bna); bna = purple_menu_action_new(_("Edit Notes..."), PURPLE_CALLBACK(buddynote_edit_cb), NULL, NULL); *m = g_list_append(*m, bna);
--- a/libpurple/plugins/joinpart.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/joinpart.c Wed Jun 27 21:43:18 2007 +0000 @@ -210,7 +210,7 @@ * we don't have to worry one will be called after this. */ g_hash_table_destroy((GHashTable *)data[0]); - g_source_remove(GPOINTER_TO_UINT(data[1])); + purple_timeout_remove(GPOINTER_TO_UINT(data[1])); g_free(data); return TRUE;
--- a/libpurple/plugins/log_reader.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/log_reader.c Wed Jun 27 21:43:18 2007 +0000 @@ -1290,9 +1290,16 @@ list = g_list_delete_link(list, last); } } - } else if (line[0] && line[1] && line [3] && + } else if (line[0] && line[1] && line[2] && purple_str_has_prefix(&line[3], "sion Start ")) { - + /* The conditional is to make sure we're not reading off + * the end of the string. We don't want strlen(), as that'd + * have to count the whole string needlessly. + * + * The odd check here is because a Session Start at the + * beginning of the file can be overwritten with a UTF-8 + * byte order mark. Yes, it's weird. + */ char *their_nickname = line; char *timestamp; @@ -1425,7 +1432,7 @@ char *escaped; GString *formatted; char *c; - char *line; + const char *line; g_return_val_if_fail(log != NULL, g_strdup("")); @@ -1460,221 +1467,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> - */ - link_temp_line = NULL; - while ((link = g_strstr_len(line, strlen(line), "(Link: "))) { - GString *temp; - - if (!*link) - continue; - - *link = '\0'; - link++; - - temp = g_string_new(line); - g_string_append(temp, "<a href=\""); - - if (strlen(link) >= 6) { - link += (sizeof("(Link: ") - 1); + if ((c = strstr(c, "\n"))) + { + *c = '\0'; + c++; + } - while (*link && *link != ')') { - g_string_append_c(temp, *link); - link++; - } - if (link) { - link++; - - g_string_append(temp, "\">"); - while (*link && *link != ' ') { - g_string_append_c(temp, *link); - link++; - } - g_string_append(temp, "</a>"); - } - - g_string_append(temp, link); - - /* Free the last round's line. */ - if (link_temp_line) - g_free(line); + /* 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; - line = temp->str; - g_string_free(temp, FALSE); - - /* Save this memory location so we can free it later. */ - link_temp_line = line; - } - } + link += 7; + if (*link) + { + char *end_paren; + char *space; - 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_c(formatted, '\n'); + g_string_append(formatted, line); - if (link_temp_line) - 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/newline.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/newline.c Wed Jun 27 21:43:18 2007 +0000 @@ -32,7 +32,7 @@ PurpleConversation *conv, int *flags, void *data) { if (g_ascii_strncasecmp(*message, "/me ", strlen("/me "))) { - char *tmp = g_strdup_printf("\n%s", *message); + char *tmp = g_strdup_printf("<br/>%s", *message); g_free(*message); *message = tmp; }
--- a/libpurple/plugins/perl/common/Account.xs Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/perl/common/Account.xs Wed Jun 27 21:43:18 2007 +0000 @@ -184,7 +184,7 @@ purple_account_get_status_types(account) Purple::Account account PREINIT: - const GList *l; + GList *l; PPCODE: for (l = purple_account_get_status_types(account); l != NULL; l = l->next) { XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::StatusType"))); @@ -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)
--- a/libpurple/plugins/perl/common/AccountOpts.xs Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/perl/common/AccountOpts.xs Wed Jun 27 21:43:18 2007 +0000 @@ -88,7 +88,7 @@ purple_account_option_get_list(option) Purple::Account::Option option PREINIT: - const GList *l; + GList *l; PPCODE: for (l = purple_account_option_get_list(option); l != NULL; l = l->next) { /* XXX These are actually PurpleKeyValuePairs but we don't have a
--- a/libpurple/plugins/perl/common/BuddyList.xs Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/perl/common/BuddyList.xs Wed Jun 27 21:43:18 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/Conversation.xs Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/perl/common/Conversation.xs Wed Jun 27 21:43:18 2007 +0000 @@ -62,6 +62,7 @@ const_iv(RAW), const_iv(IMAGES), const_iv(NOTIFY), + const_iv(NO_LINKIFY), }; static const constiv cbflags_const_iv[] = { #undef const_iv @@ -218,6 +219,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
--- a/libpurple/plugins/perl/common/Notify.xs Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/perl/common/Notify.xs Wed Jun 27 21:43:18 2007 +0000 @@ -140,7 +140,7 @@ purple_notify_user_info_get_entries(user_info) Purple::NotifyUserInfo user_info PREINIT: - const GList *l; + GList *l; PPCODE: l = purple_notify_user_info_get_entries(user_info); for (; l != NULL; l = l->next) {
--- a/libpurple/plugins/perl/common/Prefs.xs Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/perl/common/Prefs.xs Wed Jun 27 21:43:18 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/Request.xs Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/perl/common/Request.xs Wed Jun 27 21:43:18 2007 +0000 @@ -396,7 +396,7 @@ purple_request_field_list_get_items(field) Purple::Request::Field field PREINIT: - const GList *l; + GList *l; PPCODE: for (l = purple_request_field_list_get_items(field); l != NULL; l = l->next) { XPUSHs(sv_2mortal(newSVpv(l->data, 0))); @@ -410,7 +410,7 @@ purple_request_field_list_get_selected(field) Purple::Request::Field field PREINIT: - const GList *l; + GList *l; PPCODE: for (l = purple_request_field_list_get_selected(field); l != NULL; l = l->next) { XPUSHs(sv_2mortal(newSVpv(l->data, 0))); @@ -625,7 +625,7 @@ purple_request_fields_get_required(fields) Purple::Request::Fields fields PREINIT: - const GList *l; + GList *l; PPCODE: for (l = purple_request_fields_get_required(fields); l != NULL; l = l->next) { XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::Request::Field")));
--- a/libpurple/plugins/perl/common/SavedStatuses.xs Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/perl/common/SavedStatuses.xs Wed Jun 27 21:43:18 2007 +0000 @@ -130,7 +130,7 @@ void purple_savedstatuses_get_all() PREINIT: - const GList *l; + GList *l; PPCODE: for (l = purple_savedstatuses_get_all(); l != NULL; l = l->next) { XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::SavedStatus"))); @@ -140,7 +140,7 @@ purple_savedstatuses_get_popular(how_many) unsigned int how_many PREINIT: - const GList *l; + GList *l; PPCODE: for (l = purple_savedstatuses_get_popular(how_many); l != NULL; l = l->next) { XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::SavedStatus")));
--- a/libpurple/plugins/perl/common/Status.xs Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/perl/common/Status.xs Wed Jun 27 21:43:18 2007 +0000 @@ -142,7 +142,7 @@ purple_presence_get_statuses(presence) Purple::Presence presence PREINIT: - const GList *l; + GList *l; PPCODE: for (l = purple_presence_get_statuses(presence); l != NULL; l = l->next) { XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::Status"))); @@ -392,7 +392,7 @@ purple_status_type_get_attrs(status_type) Purple::StatusType status_type PREINIT: - const GList *l; + GList *l; PPCODE: for (l = purple_status_type_get_attrs(status_type); l != NULL; l = l->next) { XPUSHs(sv_2mortal(purple_perl_bless_object(l->data, "Purple::StatusAttr")));
--- a/libpurple/plugins/perl/common/module.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/perl/common/module.h Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/perl/common/typemap Wed Jun 27 21:43:18 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/perl/perl-common.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/perl/perl-common.h Wed Jun 27 21:43:18 2007 +0000 @@ -9,6 +9,9 @@ #include <EXTERN.h> #include <perl.h> +/* XXX: perl defines it's own _ but I think it's safe to undef it */ +#undef _ +#include "internal.h" #include "plugin.h" #include "value.h"
--- a/libpurple/plugins/perl/perl-handlers.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/perl/perl-handlers.c Wed Jun 27 21:43:18 2007 +0000 @@ -184,7 +184,7 @@ timeout_handlers = g_list_remove(timeout_handlers, handler); if (handler->iotag > 0) - g_source_remove(handler->iotag); + purple_timeout_remove(handler->iotag); if (handler->callback != NULL) SvREFCNT_dec(handler->callback); @@ -214,6 +214,7 @@ perl_timeout_cb(gpointer data) { PurplePerlTimeoutHandler *handler = (PurplePerlTimeoutHandler *)data; + gboolean ret = FALSE; dSP; ENTER; @@ -224,16 +225,16 @@ call_sv(handler->callback, G_EVAL | G_SCALAR); SPAGAIN; + ret = POPi; + PUTBACK; FREETMPS; LEAVE; - /* We're returning FALSE, so no need to manually remove the source */ - handler->iotag = 0; + if (ret == FALSE) + destroy_timeout_handler(handler); - destroy_timeout_handler(handler); - - return FALSE; + return ret; } typedef void *DATATYPE; @@ -405,7 +406,7 @@ timeout_handlers = g_list_append(timeout_handlers, handler); - handler->iotag = g_timeout_add(seconds * 1000, perl_timeout_cb, handler); + handler->iotag = purple_timeout_add(seconds * 1000, perl_timeout_cb, handler); } void
--- a/libpurple/plugins/perl/scripts/signals-test.pl Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/perl/scripts/signals-test.pl Wed Jun 27 21:43:18 2007 +0000 @@ -7,7 +7,7 @@ name => "Perl: $MODULE_NAME", version => "0.1", summary => "Signals Test plugin for the Perl interpreter.", - description => "Demonstrate the user of purple signals from " . + description => "Demonstrate the use of purple signals from " . "a perl plugin.", author => "Sadrul Habib Chowdhury <sadrul\@pidgin.im>", url => "http://developer.pidgin.im/wiki/sadrul/",
--- a/libpurple/plugins/ssl/ssl-nss.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/ssl/ssl-nss.c Wed Jun 27 21:43:18 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/startup.py Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/startup.py Wed Jun 27 21:43:18 2007 +0000 @@ -25,6 +25,13 @@ import dbus import os +if len(sys.argv) == 1: + print "Usage:", sys.argv[0], """<purple-client> [arguments] + +Example: + """, sys.argv[0], "pidgin -d -c /my/home" + sys.exit(1) + home = os.path.expanduser('~/.purple/') for arg in range(1, len(sys.argv[1:])): if sys.argv[arg] == "-c":
--- a/libpurple/plugins/tcl/tcl.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/tcl/tcl.c Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/plugins/tcl/tcl_cmds.c Wed Jun 27 21:43:18 2007 +0000 @@ -113,7 +113,7 @@ const char *listopts[] = { "-all", "-online", NULL }; enum { CMD_ACCOUNTLIST_ALL, CMD_ACCOUNTLIST_ONLINE } listopt; const char *alias; - const GList *cur; + GList *cur; PurpleAccount *account; PurpleStatus *status; PurpleStatusType *status_type; @@ -1082,7 +1082,7 @@ Tcl_Obj *result = Tcl_GetObjResult(interp); Tcl_Obj *list, *elem; PurplePresence *presence; - const GList *cur; + GList *cur; int error, idle, idle_time, login_time; if (objc < 2) { @@ -1524,7 +1524,7 @@ Tcl_Obj *result = Tcl_GetObjResult(interp); PurpleStatusType *status_type; Tcl_Obj *list, *elem; - const GList *cur; + GList *cur; int error; if (objc < 2) {
--- a/libpurple/pounce.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/pounce.c Wed Jun 27 21:43:18 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); } @@ -1014,6 +1014,20 @@ return pounces; } +GList *purple_pounces_get_all_for_ui(const char *ui) +{ + GList *list = NULL, *iter; + g_return_val_if_fail(ui != NULL, NULL); + + for (iter = pounces; iter; iter = iter->next) { + PurplePounce *pounce = iter->data; + if (pounce->ui_type && strcmp(pounce->ui_type, ui) == 0) + list = g_list_prepend(list, pounce); + } + list = g_list_reverse(list); + return list; +} + static void free_pounce_handler(gpointer user_data) {
--- a/libpurple/pounce.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/pounce.h Wed Jun 27 21:43:18 2007 +0000 @@ -343,6 +343,17 @@ GList *purple_pounces_get_all(void); /** + * Returns a list of registered buddy pounces for the ui-type. + * + * @param ui The ID of the UI using the core. + * + * @return The list of buddy pounces. The list should be freed by + * the caller when it's no longer used. + * @since 2.1.0 + */ +GList *purple_pounces_get_all_for_ui(const char *ui); + +/** * Returns the buddy pounce subsystem handle. * * @return The subsystem handle.
--- a/libpurple/prefs.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/prefs.c Wed Jun 27 21:43:18 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); } @@ -1355,7 +1355,6 @@ purple_prefs_remove("/plugins/core/autorecon/hide_reconnecting_dialog"); purple_prefs_remove("/plugins/core/autorecon/restore_state"); purple_prefs_remove("/plugins/core/autorecon"); - purple_prefs_remove("/purple/debug/timestamps"); /* Convert old sounds while_away pref to new 3-way pref. */ if (purple_prefs_exists("/purple/sound/while_away") &&
--- a/libpurple/protocols/Makefile.am Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/Makefile.am Wed Jun 27 21:43:18 2007 +0000 @@ -1,5 +1,5 @@ -EXTRA_DIST = Makefile.mingw null/ +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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/Makefile.mingw Wed Jun 27 21:43:18 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 silc10 simple yahoo bonjour .PHONY: all install clean
--- a/libpurple/protocols/bonjour/Makefile.am Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/bonjour/Makefile.am Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/bonjour/Makefile.mingw Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/bonjour/bonjour.c Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/bonjour/bonjour.h Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/bonjour/buddy.c Wed Jun 27 21:43:18 2007 +0000 @@ -17,6 +17,7 @@ #include <glib.h> #include <stdlib.h> +#include "internal.h" #include "buddy.h" #include "account.h" #include "blist.h" @@ -27,51 +28,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 +98,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 +133,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 +145,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 +163,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 +173,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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/bonjour/buddy.h Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 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 Sun Jun 03 09:40:38 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 Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/bonjour/issues.txt Wed Jun 27 21:43:18 2007 +0000 @@ -6,4 +6,3 @@ * Avatars * File transfers * Typing notifications -* Check if it works on win32
--- a/libpurple/protocols/bonjour/jabber.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/bonjour/jabber.c Wed Jun 27 21:43:18 2007 +0000 @@ -28,10 +28,10 @@ #endif #include <sys/types.h> #include <glib.h> -#include <glib/gprintf.h> #include <unistd.h> #include <fcntl.h> +#include "internal.h" #include "network.h" #include "eventloop.h" #include "connection.h" @@ -45,29 +45,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 +75,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 +107,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 +144,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 +170,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 +229,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 +346,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 +377,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 +632,224 @@ 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; + PurpleProxyInfo *proxy_info; + + /* Make sure that the account always has a proxy of "none". + * This is kind of dirty, but proxy_connect_none() isn't exposed. */ + proxy_info = purple_account_get_proxy_info(data->account); + if (proxy_info == NULL) { + proxy_info = purple_proxy_info_new(); + purple_account_set_proxy_info(data->account, proxy_info); + } + purple_proxy_info_set_type(proxy_info, PURPLE_PROXY_NONE); + + 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/bonjour/jabber.h Wed Jun 27 21:43:18 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 Wed Jun 27 21:43:18 2007 +0000 @@ -0,0 +1,176 @@ +/* + * 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 "internal.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 Wed Jun 27 21:43:18 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 Wed Jun 27 21:43:18 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 Wed Jun 27 21:43:18 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 Wed Jun 27 21:43:18 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 Wed Jun 27 21:43:18 2007 +0000 @@ -0,0 +1,297 @@ +/* + * 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 "internal.h" +#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 Wed Jun 27 21:43:18 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/gg/gg.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/gg/gg.c Wed Jun 27 21:43:18 2007 +0000 @@ -823,7 +823,7 @@ { GGPInfo *info = gc->proto_data; PurpleRequestField *field; - const GList *sel; + GList *sel; field = purple_request_fields_get_field(fields, "name"); sel = purple_request_field_list_get_selected(field);
--- a/libpurple/protocols/gg/gg.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/gg/gg.h Wed Jun 27 21:43:18 2007 +0000 @@ -25,6 +25,7 @@ #define _PURPLE_GG_H #include <libgadu.h> +#include "internal.h" #include "search.h" #include "connection.h"
--- a/libpurple/protocols/irc/irc.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/irc/irc.c Wed Jun 27 21:43:18 2007 +0000 @@ -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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/jabber/.todo Wed Jun 27 21:43:18 2007 +0000 @@ -1,13 +1,4 @@ <todo version="0.1.19"> - <note priority="verylow" time="1036043981" done="1089237837"> - *sigh* file transfer (do we really need/want this?) - <comment> - faceprint did this - </comment> - </note> - <note priority="high" time="1036043427"> - problem seeing buddies with long blist? - </note> <note priority="medium" time="1036044198"> Browsing </note> @@ -17,12 +8,6 @@ <note priority="medium" time="1036044448"> Add option for user info to be published or not in JUD. </note> - <note priority="medium" time="1036044571"> - Show self on buddylist - <comment> - is this done? - </comment> - </note> <note priority="medium" time="1036044583"> Delete server account. </note> @@ -37,9 +22,6 @@ <note priority="medium" time="1037893000"> 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 - </note> </note> <note priority="verylow" time="1036044192"> Jabber Transports (having them show up on the buddy list should be fairly easy; having an appropriate right-click menu for them should also be somewhat easy. Providing a UI for adding transports should be rather difficult.)
--- a/libpurple/protocols/jabber/Makefile.am Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/jabber/Makefile.am Wed Jun 27 21:43:18 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/buddy.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/jabber/buddy.c Wed Jun 27 21:43:18 2007 +0000 @@ -385,6 +385,11 @@ xmlnode *vc_node; struct tag_attr *tag_attr; + /* if we have't grabbed the remote vcard yet, we can't + * assume that what we have here is correct */ + if(!js->vcard_fetched) + return; + g_free(js->avatar_hash); js->avatar_hash = NULL; @@ -414,6 +419,10 @@ avatar_data = purple_imgstore_get_data(img); avatar_len = purple_imgstore_get_size(img); + /* have to get rid of the old PHOTO if it exists */ + if((photo = xmlnode_get_child(vc_node, "PHOTO"))) { + xmlnode_free(photo); + } photo = xmlnode_new_child(vc_node, "PHOTO"); binval = xmlnode_new_child(photo, "BINVAL"); enc = purple_base64_encode(avatar_data, avatar_len); @@ -443,14 +452,9 @@ void jabber_set_buddy_icon(PurpleConnection *gc, PurpleStoredImage *img) { - PurplePresence *gpresence; - PurpleStatus *status; - jabber_set_info(gc, purple_account_get_user_info(gc->account)); - gpresence = purple_account_get_presence(gc->account); - status = purple_presence_get_active_status(gpresence); - jabber_presence_send(gc->account, status); + jabber_presence_send(gc->account, NULL); } /* @@ -739,6 +743,40 @@ } } +static void jabber_vcard_save_mine(JabberStream *js, xmlnode *packet, gpointer data) +{ + xmlnode *vcard; + char *txt; + PurpleStoredImage *img; + + if((vcard = xmlnode_get_child(packet, "vCard")) || + (vcard = xmlnode_get_child_with_namespace(packet, "query", "vcard-temp"))) + { + txt = xmlnode_to_str(vcard, NULL); + purple_account_set_user_info(purple_connection_get_account(js->gc), txt); + + g_free(txt); + } else { + /* if we have no vCard, then lets not overwrite what we might have locally */ + } + + js->vcard_fetched = TRUE; + + if(NULL != (img = purple_buddy_icons_find_account_icon(js->gc->account))) { + jabber_set_buddy_icon(js->gc, img); + purple_imgstore_unref(img); + } +} + +void jabber_vcard_fetch_mine(JabberStream *js) +{ + JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_GET, "vcard-temp"); + + jabber_iq_set_callback(iq, jabber_vcard_save_mine, NULL); + + jabber_iq_send(iq); +} + static void jabber_vcard_parse(JabberStream *js, xmlnode *packet, gpointer data) { const char *id, *from;
--- a/libpurple/protocols/jabber/buddy.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/jabber/buddy.h Wed Jun 27 21:43:18 2007 +0000 @@ -103,4 +103,6 @@ void jabber_buddy_remove_all_pending_buddy_info_requests(JabberStream *js); +void jabber_vcard_fetch_mine(JabberStream *js); + #endif /* _PURPLE_JABBER_BUDDY_H_ */
--- a/libpurple/protocols/jabber/disco.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/jabber/disco.c Wed Jun 27 21:43:18 2007 +0000 @@ -83,6 +83,7 @@ SUPPORT_FEATURE("jabber:iq:last") SUPPORT_FEATURE("jabber:iq:oob") SUPPORT_FEATURE("jabber:iq:time") + SUPPORT_FEATURE("xmpp:urn:time") SUPPORT_FEATURE("jabber:iq:version") SUPPORT_FEATURE("jabber:x:conference") SUPPORT_FEATURE("http://jabber.org/protocol/bytestreams") @@ -96,6 +97,7 @@ SUPPORT_FEATURE("http://jabber.org/protocol/si") SUPPORT_FEATURE("http://jabber.org/protocol/si/profile/file-transfer") SUPPORT_FEATURE("http://jabber.org/protocol/xhtml-im") + SUPPORT_FEATURE("urn:xmpp:ping") } else { xmlnode *error, *inf; @@ -217,18 +219,15 @@ static void jabber_disco_finish_server_info_result_cb(JabberStream *js) { - PurplePresence *gpresence; - PurpleStatus *status; + + jabber_vcard_fetch_mine(js); if (!(js->server_caps & JABBER_CAP_GOOGLE_ROSTER)) { /* If the server supports JABBER_CAP_GOOGLE_ROSTER; we will have already requested it */ jabber_roster_request(js); } - /* Send initial presence; this will trigger receipt of presence for contacts on the roster */ - gpresence = purple_account_get_presence(js->gc->account); - status = purple_presence_get_active_status(gpresence); - jabber_presence_send(js->gc->account, status); + /* when we get the roster back, we'll send our initial presence */ } static void
--- a/libpurple/protocols/jabber/google.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/jabber/google.c Wed Jun 27 21:43:18 2007 +0000 @@ -1,4 +1,3 @@ - /** * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this @@ -30,7 +29,7 @@ #include "presence.h" #include "iq.h" -static void +static void jabber_gmail_parse(JabberStream *js, xmlnode *packet, gpointer nul) { const char *type = xmlnode_get_attrib(packet, "type"); @@ -40,57 +39,57 @@ const char *in_str; char *to_name; int i, count = 1, returned_count; - + const char **tos, **froms, **subjects, **urls; - + if (strcmp(type, "result")) return; - + child = xmlnode_get_child(packet, "mailbox"); if (!child) return; in_str = xmlnode_get_attrib(child, "total-matched"); - if (in_str && *in_str) + if (in_str && *in_str) count = atoi(in_str); - - if (count == 0) + + if (count == 0) return; message = xmlnode_get_child(child, "mail-thread-info"); - + /* Loop once to see how many messages were returned so we can allocate arrays * accordingly */ - if (!message) + if (!message) return; for (returned_count = 0; message; returned_count++, message=xmlnode_get_next_twin(message)); - + froms = g_new0(const char* , returned_count); tos = g_new0(const char* , returned_count); subjects = g_new0(const char* , returned_count); urls = g_new0(const char* , returned_count); - + to = xmlnode_get_attrib(packet, "to"); to_name = jabber_get_bare_jid(to); url = xmlnode_get_attrib(child, "url"); if (!url || !*url) url = "http://www.gmail.com"; - + message= xmlnode_get_child(child, "mail-thread-info"); for (i=0; message; message = xmlnode_get_next_twin(message), i++) { subject_node = xmlnode_get_child(message, "subject"); sender_node = xmlnode_get_child(message, "senders"); sender_node = xmlnode_get_child(sender_node, "sender"); - while (sender_node && (!xmlnode_get_attrib(sender_node, "unread") || + while (sender_node && (!xmlnode_get_attrib(sender_node, "unread") || !strcmp(xmlnode_get_attrib(sender_node, "unread"),"0"))) sender_node = xmlnode_get_next_twin(sender_node); - + if (!sender_node) { i--; continue; } - + from = xmlnode_get_attrib(sender_node, "name"); if (!from || !*from) from = xmlnode_get_attrib(sender_node, "address"); @@ -102,18 +101,18 @@ froms[i] = (from != NULL ? from : ""); subjects[i] = (subject != NULL ? subject : ""); urls[i] = (url != NULL ? url : ""); - + tid = xmlnode_get_attrib(message, "tid"); - if (tid && + if (tid && (js->gmail_last_tid == NULL || strcmp(tid, js->gmail_last_tid) > 0)) { g_free(js->gmail_last_tid); js->gmail_last_tid = g_strdup(tid); } } - if (i>0) - purple_notify_emails(js->gc, count, count == returned_count, subjects, froms, tos, - urls, NULL, NULL); + if (i>0) + purple_notify_emails(js->gc, count, count == returned_count, subjects, froms, tos, + urls, NULL, NULL); g_free(to_name); g_free(tos); @@ -128,19 +127,19 @@ } } -void -jabber_gmail_poke(JabberStream *js, xmlnode *packet) +void +jabber_gmail_poke(JabberStream *js, xmlnode *packet) { const char *type; xmlnode *query; JabberIq *iq; - + /* bail if the user isn't interested */ if (!purple_account_get_check_mail(js->gc->account)) return; type = xmlnode_get_attrib(packet, "type"); - + /* Is this an initial incoming mail notification? If so, send a request for more info */ if (strcmp(type, "set") || !xmlnode_get_child(packet, "new-mail")) @@ -165,7 +164,7 @@ void jabber_gmail_init(JabberStream *js) { JabberIq *iq; - if (!purple_account_get_check_mail(js->gc->account)) + if (!purple_account_get_check_mail(js->gc->account)) return; iq = jabber_iq_new_query(js, JABBER_IQ_GET, "google:mail:notify"); @@ -180,7 +179,7 @@ iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:roster"); query = xmlnode_get_child(iq->node, "query"); - + xmlnode_set_attrib(query, "xmlns:gr", "google:roster"); xmlnode_set_attrib(query, "gr:ext", "2"); @@ -218,14 +217,14 @@ const char *grt = xmlnode_get_attrib_with_namespace(item, "t", "google:roster"); const char *subscription = xmlnode_get_attrib(item, "subscription"); - + if (!subscription || !strcmp(subscription, "none")) { /* The Google Talk servers will automatically add people from your Gmail address book * with subscription=none. If we see someone with subscription=none, ignore them. */ return FALSE; } - + while (list) { if (!strcmp(jid_norm, (char*)list->data)) { on_block_list = TRUE; @@ -233,13 +232,13 @@ } list = list->next; } - + if (grt && (*grt == 'H' || *grt == 'h')) { PurpleBuddy *buddy = purple_find_buddy(account, jid_norm); purple_blist_remove_buddy(buddy); return FALSE; } - + if (!on_block_list && (grt && (*grt == 'B' || *grt == 'b'))) { purple_debug_info("jabber", "Blocking %s\n", jid_norm); purple_privacy_deny_add(account, jid_norm, TRUE); @@ -250,7 +249,7 @@ return TRUE; } -void jabber_google_roster_add_deny(PurpleConnection *gc, const char *who) +void jabber_google_roster_add_deny(PurpleConnection *gc, const char *who) { JabberStream *js; GSList *buddies; @@ -262,7 +261,7 @@ JabberBuddy *jb; js = (JabberStream*)(gc->proto_data); - + if (!js || !js->server_caps & JABBER_CAP_GOOGLE_ROSTER) return; @@ -271,11 +270,11 @@ buddies = purple_find_buddies(js->gc->account, who); if(!buddies) return; - + b = buddies->data; iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:roster"); - + query = xmlnode_get_child(iq->node, "query"); item = xmlnode_new_child(query, "item"); @@ -287,7 +286,7 @@ group = xmlnode_new_child(item, "group"); xmlnode_insert_data(group, g->name, -1); - + buddies = buddies->next; } @@ -333,20 +332,20 @@ g_return_if_fail(gc != NULL); g_return_if_fail(who != NULL); - + js = (JabberStream*)(gc->proto_data); - + if (!js || !js->server_caps & JABBER_CAP_GOOGLE_ROSTER) return; - + buddies = purple_find_buddies(js->gc->account, who); if(!buddies) return; - + b = buddies->data; iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:roster"); - + query = xmlnode_get_child(iq->node, "query"); item = xmlnode_new_child(query, "item"); @@ -358,7 +357,7 @@ group = xmlnode_new_child(item, "group"); xmlnode_insert_data(group, g->name, -1); - + buddies = buddies->next; } @@ -440,15 +439,15 @@ for (p = text; *p != '\0'; p = g_utf8_next_char(p)) { gunichar c = g_utf8_get_char(p); - + if (bold_count < 2 && italic_count < 2 && !in_bold && !in_italic) { g_string_append(str, p); return g_string_free(str, FALSE); } - + if (c == '*' && !in_tag) { - if (in_bold && + if (in_bold && (g_unichar_isspace(*(p+1))||*(p+1)=='<')) { /* This is safe in UTF-8 */ str = g_string_append(str, "</b>"); in_bold = FALSE; @@ -490,6 +489,6 @@ } else { str = g_string_append_unichar(str, c); } - } + } return g_string_free(str, FALSE); }
--- a/libpurple/protocols/jabber/iq.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/jabber/iq.c Wed Jun 27 21:43:18 2007 +0000 @@ -169,7 +169,7 @@ static void jabber_iq_time_parse(JabberStream *js, xmlnode *packet) { - const char *type, *from, *id; + const char *type, *from, *id, *xmlns; JabberIq *iq; xmlnode *query; time_t now_t; @@ -182,28 +182,64 @@ from = xmlnode_get_attrib(packet, "from"); id = xmlnode_get_attrib(packet, "id"); + /* we're gonna throw this away in a moment, but we need it + * to get the xmlns, so we can figure out if this is + * jabber:iq:time or urn:xmpp:time */ + query = xmlnode_get_child(packet, "query"); + xmlns = xmlnode_get_namespace(query); + if(type && !strcmp(type, "get")) { + xmlnode *utc; const char *date; - iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, "jabber:iq:time"); + iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, xmlns); jabber_iq_set_id(iq, id); xmlnode_set_attrib(iq->node, "to", from); query = xmlnode_get_child(iq->node, "query"); date = purple_utf8_strftime("%Y%m%dT%T", now); - xmlnode_insert_data(xmlnode_new_child(query, "utc"), date, -1); + utc = xmlnode_new_child(query, "utc"); + xmlnode_insert_data(utc, date, -1); + + if(!strcmp("urn:xmpp:time", xmlns)) { + xmlnode_insert_data(utc, "Z", 1); /* of COURSE the thing that is the same is different */ - date = purple_utf8_strftime("%Z", now); - xmlnode_insert_data(xmlnode_new_child(query, "tz"), date, -1); + date = purple_get_tzoff_str(now, TRUE); + xmlnode_insert_data(xmlnode_new_child(query, "tzo"), date, -1); + } else { /* jabber:iq:time */ + date = purple_utf8_strftime("%Z", now); + xmlnode_insert_data(xmlnode_new_child(query, "tz"), date, -1); - date = purple_utf8_strftime("%d %b %Y %T", now); - xmlnode_insert_data(xmlnode_new_child(query, "display"), date, -1); + date = purple_utf8_strftime("%d %b %Y %T", now); + xmlnode_insert_data(xmlnode_new_child(query, "display"), date, -1); + } jabber_iq_send(iq); } } +static void urn_xmpp_ping_parse(JabberStream *js, xmlnode *packet) +{ + const char *type, *id, *from; + JabberIq *iq; + + type = xmlnode_get_attrib(packet, "type"); + from = xmlnode_get_attrib(packet, "from"); + id = xmlnode_get_attrib(packet, "id"); + + if(type && !strcmp(type, "get")) { + iq = jabber_iq_new_query(js, JABBER_IQ_RESULT, "urn:xmpp:ping"); + + jabber_iq_set_id(iq, id); + xmlnode_set_attrib(iq->node, "to", from); + + jabber_iq_send(iq); + } else { + /* XXX: error */ + } +} + static void jabber_iq_version_parse(JabberStream *js, xmlnode *packet) { JabberIq *iq; @@ -232,7 +268,8 @@ query = xmlnode_get_child(iq->node, "query"); - xmlnode_insert_data(xmlnode_new_child(query, "name"), PACKAGE, -1); + /* TODO: ask the core for the version of libpurple and the name and version of the UI */ + xmlnode_insert_data(xmlnode_new_child(query, "name"), "libpurple", -1); xmlnode_insert_data(xmlnode_new_child(query, "version"), VERSION, -1); if(os) { xmlnode_insert_data(xmlnode_new_child(query, "os"), os, -1); @@ -323,10 +360,12 @@ jabber_iq_register_handler("http://jabber.org/protocol/bytestreams", jabber_bytestreams_parse); jabber_iq_register_handler("jabber:iq:last", jabber_iq_last_parse); jabber_iq_register_handler("jabber:iq:time", jabber_iq_time_parse); + jabber_iq_register_handler("urn:xmpp:time", jabber_iq_time_parse); jabber_iq_register_handler("jabber:iq:version", jabber_iq_version_parse); jabber_iq_register_handler("http://jabber.org/protocol/disco#info", jabber_disco_info_parse); jabber_iq_register_handler("http://jabber.org/protocol/disco#items", jabber_disco_items_parse); jabber_iq_register_handler("jabber:iq:register", jabber_register_parse); + jabber_iq_register_handler("urn:xmpp:ping", urn_xmpp_ping_parse); } void jabber_iq_uninit(void)
--- a/libpurple/protocols/jabber/jabber.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.c Wed Jun 27 21:43:18 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; } @@ -1034,8 +1038,6 @@ void jabber_stream_set_state(JabberStream *js, JabberStreamState state) { - PurpleStoredImage *img; - js->state = state; switch(state) { case JABBER_STREAM_OFFLINE: @@ -1067,12 +1069,6 @@ break; case JABBER_STREAM_CONNECTED: - /* 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); - /* now we can alert the core that we're ready to send status */ purple_connection_set_state(js->gc, PURPLE_CONNECTED); jabber_disco_items_server(js); @@ -1120,9 +1116,11 @@ char *jabber_status_text(PurpleBuddy *b) { - JabberBuddy *jb = jabber_buddy_find(b->account->gc->proto_data, b->name, - FALSE); char *ret = NULL; + JabberBuddy *jb = NULL; + + if (b->account->gc && b->account->gc->proto_data) + jb = jabber_buddy_find(b->account->gc->proto_data, b->name, FALSE); if(jb && !PURPLE_BUDDY_IS_ONLINE(b) && (jb->subscription & JABBER_SUB_PENDING || !(jb->subscription & JABBER_SUB_TO))) { ret = g_strdup(_("Not Authorized"));
--- a/libpurple/protocols/jabber/jabber.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.h Wed Jun 27 21:43:18 2007 +0000 @@ -142,11 +142,17 @@ #ifdef HAVE_CYRUS_SASL sasl_conn_t *sasl; sasl_callback_t *sasl_cb; +#else /* keep the struct the same size */ + void *sasl; + void *sasl_cb; +#endif + int sasl_state; int sasl_maxbuf; GString *sasl_mechs; char *serverFQDN; -#endif + + gboolean vcard_fetched; } JabberStream;
--- a/libpurple/protocols/jabber/libxmpp.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Wed Jun 27 21:43:18 2007 +0000 @@ -25,8 +25,9 @@ * share code. */ +#include "internal.h" + #include "accountopt.h" -#include "internal.h" #include "version.h" #include "iq.h" @@ -43,9 +44,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 */ @@ -64,7 +67,7 @@ jabber_set_info, /* set_info */ jabber_send_typing, /* send_typing */ jabber_buddy_get_info, /* get_info */ - jabber_presence_send, /* set_away */ + jabber_presence_send, /* set_status */ jabber_idle_set, /* set_idle */ NULL, /* change_passwd */ jabber_roster_add_buddy, /* add_buddy */ @@ -193,9 +196,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/presence.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/jabber/presence.c Wed Jun 27 21:43:18 2007 +0000 @@ -102,6 +102,11 @@ JabberBuddyState state; int priority; + if(NULL == status) { + PurplePresence *gpresence = purple_account_get_presence(account); + status = purple_presence_get_active_status(gpresence); + } + if(!purple_status_is_active(status)) return; @@ -115,6 +120,12 @@ gc = purple_account_get_connection(account); js = gc->proto_data; + /* we don't want to send presence before we've gotten our roster */ + if(!js->roster_parsed) { + purple_debug_info("jabber", "attempt to send presence before roster retrieved\n"); + return; + } + purple_status_to_jabber(status, &state, &stripped, &priority); @@ -379,8 +390,7 @@ } else if(xmlns && !strcmp(xmlns, "vcard-temp:x:update")) { xmlnode *photo = xmlnode_get_child(y, "photo"); if(photo) { - if(avatar_hash) - g_free(avatar_hash); + g_free(avatar_hash); avatar_hash = xmlnode_get_data(photo); } } @@ -409,8 +419,7 @@ jabber_id_free(jid); g_free(status); g_free(room_jid); - if(avatar_hash) - g_free(avatar_hash); + g_free(avatar_hash); return; } @@ -426,8 +435,7 @@ jabber_id_free(jid); g_free(status); g_free(room_jid); - if(avatar_hash) - g_free(avatar_hash); + g_free(avatar_hash); return; } @@ -507,11 +515,10 @@ buddy_name = g_strdup_printf("%s%s%s", jid->node ? jid->node : "", jid->node ? "@" : "", jid->domain); if((b = purple_find_buddy(js->gc->account, buddy_name)) == NULL) { - purple_debug_warning("jabber", "Got presence for unknown buddy %s on account %s (%x)", + purple_debug_warning("jabber", "Got presence for unknown buddy %s on account %s (%x)\n", buddy_name, purple_account_get_username(js->gc->account), js->gc->account); jabber_id_free(jid); - if(avatar_hash) - g_free(avatar_hash); + g_free(avatar_hash); g_free(buddy_name); g_free(status); return; @@ -558,9 +565,7 @@ } if((found_jbr = jabber_buddy_find_resource(jb, NULL))) { - if(!jbr || jbr == found_jbr) { - purple_prpl_got_user_status(js->gc->account, buddy_name, jabber_buddy_state_get_status_id(state), "priority", found_jbr->priority, found_jbr->status ? "message" : NULL, found_jbr->status, NULL); - } + purple_prpl_got_user_status(js->gc->account, buddy_name, jabber_buddy_state_get_status_id(state), "priority", found_jbr->priority, found_jbr->status ? "message" : NULL, found_jbr->status, NULL); } else { purple_prpl_got_user_status(js->gc->account, buddy_name, "offline", status ? "message" : NULL, status, NULL); } @@ -568,8 +573,7 @@ } g_free(status); jabber_id_free(jid); - if(avatar_hash) - g_free(avatar_hash); + g_free(avatar_hash); } void jabber_presence_subscription_set(JabberStream *js, const char *who, const char *type) @@ -608,7 +612,7 @@ formatted_msg = NULL; if(formatted_msg) - purple_markup_html_to_xhtml(formatted_msg, NULL, msg); + *msg = purple_markup_strip_html(formatted_msg); } if(priority)
--- a/libpurple/protocols/jabber/roster.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/jabber/roster.c Wed Jun 27 21:43:18 2007 +0000 @@ -153,8 +153,6 @@ if(!query) return; - js->roster_parsed = TRUE; - for(item = xmlnode_get_child(query, "item"); item; item = xmlnode_get_next_twin(item)) { const char *jid, *name, *subscription, *ask; @@ -232,6 +230,14 @@ add_purple_buddies_to_groups(js, jid, name, groups); } } + + /* if we're just now parsing the roster for the first time, + * then now would be the time to send our initial presence */ + if(!js->roster_parsed) { + js->roster_parsed = TRUE; + + jabber_presence_send(js->gc->account, NULL); + } } static void jabber_roster_update(JabberStream *js, const char *name,
--- a/libpurple/protocols/jabber/si.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/jabber/si.c Wed Jun 27 21:43:18 2007 +0000 @@ -20,9 +20,9 @@ * */ -#include "blist.h" +#include "internal.h" -#include "internal.h" +#include "blist.h" #include "cipher.h" #include "debug.h" #include "ft.h" @@ -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/jabber/xdata.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/jabber/xdata.c Wed Jun 27 21:43:18 2007 +0000 @@ -93,7 +93,7 @@ case JABBER_X_DATA_LIST_SINGLE: case JABBER_X_DATA_LIST_MULTI: { - const GList *selected = purple_request_field_list_get_selected(field); + GList *selected = purple_request_field_list_get_selected(field); char *value; fieldnode = xmlnode_new_child(result, "field"); xmlnode_set_attrib(fieldnode, "var", id);
--- a/libpurple/protocols/msn/msn.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/msn/msn.c Wed Jun 27 21:43:18 2007 +0000 @@ -1423,6 +1423,22 @@ if (found) \ sect_info = TRUE; +#define MSN_GOT_INFO_GET_FIELD_NO_SEARCH(a, b) \ + found = purple_markup_extract_info_field(stripped, stripped_len, user_info, \ + "\n" a ":", 0, "\n", 0, "Undisclosed", b, 0, NULL, msn_info_strip_search_link); \ + if (found) \ + sect_info = TRUE; + +static char * +msn_info_strip_search_link(const char *field, size_t len) +{ + const char *c; + if ((c = strstr(field, " (http://spaces.live.com/default.aspx?page=searchresults")) == NULL && + (c = strstr(field, " (http://spaces.msn.com/default.aspx?page=searchresults")) == NULL) + return g_strndup(field, len); + return g_strndup(field, c - field); +} + static void msn_got_info(PurpleUtilFetchUrlData *url_data, gpointer data, const gchar *url_text, size_t len, const gchar *error_message) @@ -1538,10 +1554,10 @@ /* General */ MSN_GOT_INFO_GET_FIELD("Nickname", _("Nickname")); - MSN_GOT_INFO_GET_FIELD("Age", _("Age")); - MSN_GOT_INFO_GET_FIELD("Gender", _("Gender")); - MSN_GOT_INFO_GET_FIELD("Occupation", _("Occupation")); - MSN_GOT_INFO_GET_FIELD("Location", _("Location")); + MSN_GOT_INFO_GET_FIELD_NO_SEARCH("Age", _("Age")); + MSN_GOT_INFO_GET_FIELD_NO_SEARCH("Gender", _("Gender")); + MSN_GOT_INFO_GET_FIELD_NO_SEARCH("Occupation", _("Occupation")); + MSN_GOT_INFO_GET_FIELD_NO_SEARCH("Location", _("Location")); /* Extract their Interests and put it in */ found = purple_markup_extract_info_field(stripped, stripped_len, user_info, @@ -1802,7 +1818,10 @@ /* This doesn't work with the new spaces profiles - Stu 3/2/06 char *p = strstr(url_buffer, "Unknown Member </TITLE>"); * This might not work for long either ... */ + /* Nope, it failed some time before 5/2/07 :( char *p = strstr(url_buffer, "form id=\"SpacesSearch\" name=\"SpacesSearch\""); + * Let's see how long this one holds out for ... */ + char *p = strstr(url_buffer, "<form id=\"profile_form\" name=\"profile_form\" action=\"http://spaces.live.com/profile.aspx?cid=0\""); PurpleBuddy *b = purple_find_buddy (purple_connection_get_account(info_data->gc), info_data->name); purple_notify_user_info_add_pair(user_info, _("Error retrieving profile"),
--- a/libpurple/protocols/msn/msn.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/msn/msn.h Wed Jun 27 21:43:18 2007 +0000 @@ -67,7 +67,7 @@ #define HOTMAIL_URL "http://www.hotmail.com/cgi-bin/folders" #define PASSPORT_URL "http://lc1.law13.hotmail.passport.com/cgi-bin/dologin?login=" -#define PROFILE_URL "http://spaces.msn.com/profile.aspx?mem=" +#define PROFILE_URL "http://spaces.live.com/profile.aspx?mem=" #define USEROPT_HOTMAIL 0
--- a/libpurple/protocols/msn/nexus.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/msn/nexus.c Wed Jun 27 21:43:18 2007 +0000 @@ -247,6 +247,8 @@ temp = g_strndup(error, c - error); error = purple_url_decode(temp); g_free(temp); + if ((temp = strstr(error, " Do one of the following or try again:")) != NULL) + *temp = '\0'; } }
--- a/libpurple/protocols/msn/notification.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/msn/notification.c Wed Jun 27 21:43:18 2007 +0000 @@ -591,12 +591,23 @@ static void qng_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) { + MsnSession *session; static int count = 0; - MsnSession *session = cmdproc->session; + const char *passport; + PurpleAccount *account; + + session = cmdproc->session; + account = session->account; if (session->passport_info.file == NULL) return; + passport = purple_normalize(account, purple_account_get_username(account)); + + if ((strstr(passport, "@hotmail.") != NULL) || + (strstr(passport, "@msn.com") != NULL)) + return; + if (count++ < 26) return;
--- a/libpurple/protocols/msn/servconn.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/msn/servconn.c Wed Jun 27 21:43:18 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); } } @@ -351,7 +352,7 @@ if (ret < 0 && errno == EAGAIN) ret = 0; - if (ret < len) { + if (ret >= 0 && ret < len) { if (servconn->tx_handler == -1) servconn->tx_handler = purple_input_add( servconn->fd, PURPLE_INPUT_WRITE,
--- a/libpurple/protocols/msn/session.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/msn/session.c Wed Jun 27 21:43:18 2007 +0000 @@ -316,6 +316,7 @@ "temporarily.")); break; case MSN_ERROR_AUTH: + gc->wants_to_die = TRUE; msg = g_strdup_printf(_("Unable to authenticate: %s"), (info == NULL ) ? _("Unknown error") : info); @@ -385,6 +386,7 @@ PurpleAccount *account; PurpleConnection *gc; PurpleStoredImage *img; + const char *passport; if (session->logged_in) return; @@ -408,5 +410,12 @@ * and @msn.com accounts don't automatically get the initial email * notification so we always request it on login */ - msn_cmdproc_send(session->notification->cmdproc, "URL", "%s", "INBOX"); + + passport = purple_normalize(account, purple_account_get_username(account)); + + if ((strstr(passport, "@hotmail.") != NULL) || + (strstr(passport, "@msn.com") != NULL)) + { + msn_cmdproc_send(session->notification->cmdproc, "URL", "%s", "INBOX"); + } }
--- a/libpurple/protocols/msn/switchboard.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/msn/switchboard.c Wed Jun 27 21:43:18 2007 +0000 @@ -419,7 +419,14 @@ case MSN_SB_ERROR_TOO_FAST: str_reason = _("Message could not be sent " "because we are sending too quickly:"); - break; + break; + case MSN_SB_ERROR_AUTHFAILED: + str_reason = _("Message could not be sent " + "because we were unable to establish a " + "session with the server. This is " + "likely a server problem, try again in " + "a few minutes:"); + break; default: str_reason = _("Message could not be sent " "because an error with " @@ -963,9 +970,13 @@ * Connect stuff **************************************************************************/ static void +ans_usr_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error); + +static void connect_cb(MsnServConn *servconn) { MsnSwitchBoard *swboard; + MsnTransaction *trans; MsnCmdProc *cmdproc; PurpleAccount *account; @@ -980,16 +991,44 @@ { swboard->empty = FALSE; - msn_cmdproc_send(cmdproc, "ANS", "%s %s %s", - purple_account_get_username(account), - swboard->auth_key, swboard->session_id); + trans = msn_transaction_new(cmdproc, "ANS", "%s %s %s", + purple_account_get_username(account), + swboard->auth_key, swboard->session_id); } else { - msn_cmdproc_send(cmdproc, "USR", "%s %s", - purple_account_get_username(account), - swboard->auth_key); + trans = msn_transaction_new(cmdproc, "USR", "%s %s", + purple_account_get_username(account), + swboard->auth_key); } + + msn_transaction_set_error_cb(trans, ans_usr_error); + msn_transaction_set_data(trans, swboard); + msn_cmdproc_send_trans(cmdproc, trans); +} + +static void +ans_usr_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error) +{ + MsnSwitchBoard *swboard; + char **params; + char *passport; + int reason = MSN_SB_ERROR_UNKNOWN; + + if (error == 911) + { + reason = MSN_SB_ERROR_AUTHFAILED; + } + + purple_debug_warning("msn", "ans_usr_error: command %s gave error %i\n", trans->command, error); + + params = g_strsplit(trans->params, " ", 0); + passport = params[0]; + swboard = trans->data; + + swboard_error_helper(swboard, reason, passport); + + g_strfreev(params); } static void
--- a/libpurple/protocols/msn/switchboard.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/msn/switchboard.h Wed Jun 27 21:43:18 2007 +0000 @@ -46,6 +46,7 @@ MSN_SB_ERROR_USER_OFFLINE, /**< The user to call is offline. */ MSN_SB_ERROR_CONNECTION, /**< There was a connection error. */ MSN_SB_ERROR_TOO_FAST, /**< We are sending too fast */ + MSN_SB_ERROR_AUTHFAILED, /**< Authentication failed joining the switchboard session */ MSN_SB_ERROR_UNKNOWN /**< An unknown error occurred. */ } MsnSBErrorType;
--- a/libpurple/protocols/null/nullprpl.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/null/nullprpl.c Wed Jun 27 21:43:18 2007 +0000 @@ -432,7 +432,7 @@ from_username, who, message); /* is the sender blocked by the recipient's privacy settings? */ - if (!purple_privacy_check(to_acct, gc->account->username)) { + if (to_acct && !purple_privacy_check(to_acct, gc->account->username)) { char *msg = g_strdup_printf( _("Your message was blocked by %s's privacy settings."), who); purple_debug_info("nullprpl",
--- a/libpurple/protocols/oscar/.todo Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/oscar/.todo Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/oscar/AUTHORS Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/oscar/Makefile.am Wed Jun 27 21:43:18 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/family_auth.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/oscar/family_auth.c Wed Jun 27 21:43:18 2007 +0000 @@ -211,7 +211,7 @@ #ifdef USE_XOR_FOR_ICQ /* If we're signing on an ICQ account then use the older, XOR login method */ - if (isdigit(sn[0])) + if (aim_sn_is_icq(sn)) return goddamnicq2(od, conn, sn, password, ci); #endif @@ -224,7 +224,7 @@ /* Truncate ICQ and AOL passwords, if necessary */ password_len = strlen(password); - if (isdigit(sn[0]) && (password_len > MAXICQPASSLEN)) + if (aim_sn_is_icq(sn) && (password_len > MAXICQPASSLEN)) password_len = MAXICQPASSLEN; else if (truncate_pass && password_len > 8) password_len = 8; @@ -477,7 +477,7 @@ return -EINVAL; #ifdef USE_XOR_FOR_ICQ - if (isdigit(sn[0])) + if (aim_sn_is_icq(sn)) return goddamnicq(od, conn, sn); #endif
--- a/libpurple/protocols/oscar/flap_connection.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/oscar/flap_connection.c Wed Jun 27 21:43:18 2007 +0000 @@ -303,7 +303,7 @@ } } - if (conn->fd != -1) + if (conn->fd >= 0) { if (conn->type == SNAC_FAMILY_LOCATE) flap_connection_send_close(od, conn); @@ -792,7 +792,7 @@ } /* If there was an error then close the connection */ - if (read == -1) + if (read < 0) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) /* No worries */ @@ -853,7 +853,7 @@ break; } - if (read == -1) + if (read < 0) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) /* No worries */ @@ -902,7 +902,7 @@ ret = send(conn->fd, conn->buffer_outgoing->outptr, writelen, 0); if (ret <= 0) { - if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) + if (ret < 0 && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) /* No worries */ return; @@ -936,7 +936,7 @@ purple_circ_buffer_append(conn->buffer_outgoing, bs->data, count); /* If we haven't already started writing stuff, then start the cycle */ - if ((conn->watcher_outgoing == 0) && (conn->fd != -1)) + if ((conn->watcher_outgoing == 0) && (conn->fd >= 0)) { conn->watcher_outgoing = purple_input_add(conn->fd, PURPLE_INPUT_WRITE, send_cb, conn);
--- a/libpurple/protocols/oscar/odc.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/oscar/odc.c Wed Jun 27 21:43:18 2007 +0000 @@ -447,7 +447,7 @@ return; } - if (read == -1) + if (read < 0) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) /* No worries */
--- a/libpurple/protocols/oscar/oscar.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/oscar/oscar.c Wed Jun 27 21:43:18 2007 +0000 @@ -298,7 +298,7 @@ } gchar * -oscar_encoding_to_utf8(const char *encoding, const char *text, int textlen) +oscar_encoding_to_utf8(PurpleAccount *account, const char *encoding, const char *text, int textlen) { gchar *utf8 = NULL; @@ -311,7 +311,25 @@ { utf8 = g_convert(text, textlen, "UTF-8", "Windows-1252", NULL, NULL, NULL); } else if (!g_ascii_strcasecmp(encoding, "unicode-2-0")) { - utf8 = g_convert(text, textlen, "UTF-8", "UCS-2BE", NULL, NULL, NULL); + /* Some official ICQ clients are apparently total crack, + * and have been known to save a UTF-8 string converted + * from the locale character set to UCS-2 (not from UTF-8 + * to UCS-2!) in the away message. This hack should find + * and do something (un)reasonable with that, and not + * mess up too much else. */ + const gchar *charset = purple_account_get_string(account, "encoding", NULL); + if (charset) { + gsize len; + utf8 = g_convert(text, textlen, charset, "UCS-2BE", &len, NULL, NULL); + if (!utf8 || len != textlen || !g_utf8_validate(utf8, -1, NULL)) { + g_free(utf8); + utf8 = NULL; + } else { + purple_debug_info("oscar", "Used broken ICQ fallback encoding\n"); + } + } + if (!utf8) + utf8 = g_convert(text, textlen, "UTF-8", "UCS-2BE", NULL, NULL, NULL); } else if (g_ascii_strcasecmp(encoding, "utf-8")) { purple_debug_warning("oscar", "Unrecognized character encoding \"%s\", " "attempting to convert to UTF-8 anyway\n", encoding); @@ -407,7 +425,7 @@ charsetstr1 = "UCS-2BE"; charsetstr2 = "UTF-8"; } else if (charset == AIM_CHARSET_CUSTOM) { - if ((sourcesn != NULL) && isdigit(sourcesn[0])) + if ((sourcesn != NULL) && aim_sn_is_icq(sourcesn)) charsetstr1 = purple_account_get_string(account, "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING); else charsetstr1 = "ISO-8859-1"; @@ -1240,7 +1258,7 @@ if (!aim_snvalid(purple_account_get_username(account))) { gchar *buf; - buf = g_strdup_printf(_("Unable to login: Could not sign on as %s because the screen name is invalid. Screen names must either start with a letter and contain only letters, numbers and spaces, or contain only numbers."), purple_account_get_username(account)); + buf = g_strdup_printf(_("Unable to login: Could not sign on as %s because the screen name is invalid. Screen names must be a valid email address, or start with a letter and contain only letters, numbers and spaces, or contain only numbers."), purple_account_get_username(account)); gc->wants_to_die = TRUE; purple_connection_error(gc, buf); g_free(buf); @@ -1604,8 +1622,7 @@ straight_to_hell, pos) == NULL) { char buf[256]; - if (pos->modname) - g_free(pos->modname); + g_free(pos->modname); g_free(pos); g_snprintf(buf, sizeof(buf), _("You may be disconnected shortly. " "Check %s for updates."), PURPLE_WEBSITE); @@ -1775,7 +1792,7 @@ { have_status_message = TRUE; if (info->status[0] != '\0') - message = oscar_encoding_to_utf8(info->status_encoding, + message = oscar_encoding_to_utf8(account, info->status_encoding, info->status, info->status_len); } @@ -1791,7 +1808,7 @@ if ((status_id == OSCAR_STATUS_ID_AVAILABLE) && (info->itmsurl != NULL)) { char *itmsurl; - itmsurl = oscar_encoding_to_utf8(info->itmsurl_encoding, + itmsurl = oscar_encoding_to_utf8(account, info->itmsurl_encoding, info->itmsurl, info->itmsurl_len); purple_prpl_got_user_status(account, info->sn, status_id, "message", message, "itmsurl", itmsurl, NULL); @@ -1826,9 +1843,8 @@ signon = time(NULL) - info->sessionlen; if (!aim_sncmp(purple_account_get_username(account), info->sn)) { purple_connection_set_display_name(gc, info->sn); - od->timeoffset = signon - purple_presence_get_login_time(presence); - } - purple_prpl_got_user_login_time(account, info->sn, signon - od->timeoffset); + } + purple_prpl_got_user_login_time(account, info->sn, signon); /* Idle time stuff */ /* info->idletime is the number of minutes that this user has been idle */ @@ -2049,7 +2065,8 @@ { char *encoding = NULL; encoding = oscar_encoding_extract(args->encoding); - message = oscar_encoding_to_utf8(encoding, args->msg, args->msglen); + message = oscar_encoding_to_utf8(account, encoding, args->msg, + args->msglen); g_free(encoding); } else { if (g_utf8_validate(args->msg, args->msglen, NULL)) @@ -2067,7 +2084,7 @@ return 1; } encoding = args->encoding ? oscar_encoding_extract(args->encoding) : NULL; - utf8name = oscar_encoding_to_utf8(encoding, + utf8name = oscar_encoding_to_utf8(account, encoding, args->info.chat.roominfo.name, args->info.chat.roominfo.namelen); g_free(encoding); @@ -2810,6 +2827,7 @@ va_list ap; guint16 reason; char *destn; + PurpleNotifyUserInfo *user_info; va_start(ap, fr); reason = (guint16) va_arg(ap, unsigned int); @@ -2819,12 +2837,12 @@ if (destn == NULL) return 1; + user_info = purple_notify_user_info_new(); buf = g_strdup_printf(_("User information not available: %s"), (reason < msgerrreasonlen) ? _(msgerrreason[reason]) : _("Unknown reason.")); - if (!purple_conv_present_error(destn, purple_connection_get_account((PurpleConnection*)od->gc), buf)) { - g_free(buf); - buf = g_strdup_printf(_("User information for %s unavailable:"), destn); - purple_notify_error(od->gc, NULL, buf, (reason < msgerrreasonlen) ? _(msgerrreason[reason]) : _("Unknown reason.")); - } + purple_notify_user_info_add_pair(user_info, NULL, buf); + purple_notify_userinfo(od->gc, destn, user_info, NULL, NULL); + purple_notify_user_info_destroy(user_info); + purple_conv_present_error(destn, purple_connection_get_account(od->gc), buf); g_free(buf); return 1; @@ -2850,12 +2868,12 @@ g_free(tmp); if (userinfo->present & AIM_USERINFO_PRESENT_ONLINESINCE) { - time_t t = userinfo->onlinesince - od->timeoffset; + time_t t = userinfo->onlinesince; oscar_user_info_add_pair(user_info, _("Online Since"), purple_date_format_full(localtime(&t))); } if (userinfo->present & AIM_USERINFO_PRESENT_MEMBERSINCE) { - time_t t = userinfo->membersince - od->timeoffset; + time_t t = userinfo->membersince; oscar_user_info_add_pair(user_info, _("Member Since"), purple_date_format_full(localtime(&t))); } @@ -2877,12 +2895,12 @@ if ((userinfo->status != NULL) && !(userinfo->flags & AIM_FLAG_AWAY)) { if (userinfo->status[0] != '\0') - tmp = oscar_encoding_to_utf8(userinfo->status_encoding, + tmp = oscar_encoding_to_utf8(account, userinfo->status_encoding, userinfo->status, userinfo->status_len); #if defined (_WIN32) || defined (__APPLE__) if (userinfo->itmsurl && (userinfo->itmsurl[0] != '\0')) { gchar *itmsurl, *tmp2; - itmsurl = oscar_encoding_to_utf8(userinfo->itmsurl_encoding, + itmsurl = oscar_encoding_to_utf8(account, userinfo->itmsurl_encoding, userinfo->itmsurl, userinfo->itmsurl_len); tmp2 = g_strdup_printf("<a href=\"%s\">%s</a>", itmsurl, tmp); @@ -2898,7 +2916,8 @@ /* Away message */ if ((userinfo->flags & AIM_FLAG_AWAY) && (userinfo->away_len > 0) && (userinfo->away != NULL) && (userinfo->away_encoding != NULL)) { tmp = oscar_encoding_extract(userinfo->away_encoding); - away_utf8 = oscar_encoding_to_utf8(tmp, userinfo->away, userinfo->away_len); + away_utf8 = oscar_encoding_to_utf8(account, tmp, userinfo->away, + userinfo->away_len); g_free(tmp); if (away_utf8 != NULL) { tmp = purple_str_sub_away_formatters(away_utf8, purple_account_get_username(account)); @@ -2912,7 +2931,8 @@ /* Info */ if ((userinfo->info_len > 0) && (userinfo->info != NULL) && (userinfo->info_encoding != NULL)) { tmp = oscar_encoding_extract(userinfo->info_encoding); - info_utf8 = oscar_encoding_to_utf8(tmp, userinfo->info, userinfo->info_len); + info_utf8 = oscar_encoding_to_utf8(account, tmp, userinfo->info, + userinfo->info_len); g_free(tmp); if (info_utf8 != NULL) { tmp = purple_str_sub_away_formatters(info_utf8, purple_account_get_username(account)); @@ -2932,6 +2952,7 @@ static int purple_got_infoblock(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { PurpleConnection *gc = od->gc; + PurpleAccount *account = purple_connection_get_account(gc); PurpleBuddy *b; PurplePresence *presence; PurpleStatus *status; @@ -2944,7 +2965,7 @@ userinfo = va_arg(ap, aim_userinfo_t *); va_end(ap); - b = purple_find_buddy(purple_connection_get_account(gc), userinfo->sn); + b = purple_find_buddy(account, userinfo->sn); if (b == NULL) return 1; @@ -2964,7 +2985,9 @@ if ((userinfo->flags & AIM_FLAG_AWAY) && (userinfo->away_len > 0) && (userinfo->away != NULL) && (userinfo->away_encoding != NULL)) { gchar *charset = oscar_encoding_extract(userinfo->away_encoding); - message = oscar_encoding_to_utf8(charset, userinfo->away, userinfo->away_len); + message = oscar_encoding_to_utf8(account, charset, + userinfo->away, + userinfo->away_len); g_free(charset); purple_status_set_attr_string(status, "message", message); g_free(message); @@ -3160,6 +3183,7 @@ static int purple_conv_chat_incoming_msg(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { PurpleConnection *gc = od->gc; + PurpleAccount *account = purple_connection_get_account(gc); struct chat_connection *ccon = find_oscar_chat_by_conn(gc, conn); gchar *utf8; va_list ap; @@ -3178,7 +3202,7 @@ charset = va_arg(ap, char *); va_end(ap); - utf8 = oscar_encoding_to_utf8(charset, msg, len); + utf8 = oscar_encoding_to_utf8(account, charset, msg, len); if (utf8 == NULL) /* The conversion failed! */ utf8 = g_strdup(_("[Unable to display a message from this user because it contained invalid characters.]")); @@ -4168,6 +4192,7 @@ PeerConnection *conn; int ret; char *tmp1, *tmp2; + gboolean is_html; od = (OscarData *)gc->proto_data; account = purple_connection_get_account(gc); @@ -4186,7 +4211,6 @@ } else { struct buddyinfo *bi; struct aim_sendimext_args args; - gsize len; PurpleConversation *conv; PurpleStoredImage *img; @@ -4277,22 +4301,43 @@ if (aim_sn_is_sms(name)) { /* Messaging an SMS (mobile) user */ tmp2 = purple_markup_strip_html(tmp1); + is_html = FALSE; } else if (aim_sn_is_icq(purple_account_get_username(account))) { - if (aim_sn_is_icq(name)) + if (aim_sn_is_icq(name)) { /* From ICQ to ICQ */ tmp2 = purple_markup_strip_html(tmp1); - else + is_html = FALSE; + } else { /* From ICQ to AIM */ tmp2 = g_strdup(tmp1); + is_html = TRUE; + } } else { /* From AIM to AIM and AIM to ICQ */ tmp2 = g_strdup(tmp1); + is_html = TRUE; } g_free(tmp1); tmp1 = tmp2; - len = strlen(tmp1); purple_plugin_oscar_convert_to_best_encoding(gc, name, tmp1, (char **)&args.msg, &args.msglen, &args.charset, &args.charsubset); + if (is_html && (args.msglen > MAXMSGLEN)) { + /* If the length was too long, try stripping the HTML and then running it back through + * purple_strdup_withhtml() and the encoding process. The result may be shorter. */ + g_free((char *)args.msg); + + tmp2 = purple_markup_strip_html(tmp1); + g_free(tmp1); + + tmp1 = purple_strdup_withhtml(tmp2); + g_free(tmp2); + + purple_plugin_oscar_convert_to_best_encoding(gc, name, tmp1, (char **)&args.msg, &args.msglen, &args.charset, &args.charsubset); + + purple_debug_info("oscar", "Sending %s as %s because the original was too long.", + message, (char *)args.msg); + } + purple_debug_info("oscar", "Sending IM, charset=0x%04hx, charsubset=0x%04hx, length=%d\n", args.charset, args.charsubset, args.msglen); ret = aim_im_sendch1_ext(od, &args); @@ -4387,7 +4432,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; @@ -4578,7 +4623,7 @@ if (!aim_snvalid(buddy->name)) { gchar *buf; - buf = g_strdup_printf(_("Could not add the buddy %s because the screen name is invalid. Screen names must either start with a letter and contain only letters, numbers and spaces, or contain only numbers."), buddy->name); + buf = g_strdup_printf(_("Could not add the buddy %s because the screen name is invalid. Screen names must be a valid email address, or start with a letter and contain only letters, numbers and spaces, or contain only numbers."), buddy->name); if (!purple_conv_present_error(buddy->name, purple_connection_get_account(gc), buf)) purple_notify_error(gc, NULL, _("Unable To Add"), buf); g_free(buf); @@ -5015,7 +5060,8 @@ default: { /* La la la */ gchar *buf; purple_debug_error("oscar", "ssi: Action 0x%04hx was unsuccessful with error 0x%04hx\n", retval->action, retval->ack); - buf = g_strdup_printf(_("Could not add the buddy %s for an unknown reason. The most common reason for this is that you have the maximum number of allowed buddies in your buddy list."), (retval->name ? retval->name : _("(no name)"))); + buf = g_strdup_printf(_("Could not add the buddy %s for an unknown reason."), + (retval->name ? retval->name : _("(no name)"))); if ((retval->name != NULL) && !purple_conv_present_error(retval->name, purple_connection_get_account(gc), buf)) purple_notify_error(gc, NULL, _("Unable To Add"), buf); g_free(buf); @@ -5324,7 +5370,7 @@ OscarData *od = (OscarData *)gc->proto_data; PurpleConversation *conv = NULL; struct chat_connection *c = NULL; - char *buf, *buf2; + char *buf, *buf2, *buf3; guint16 charset, charsubset; char *charsetstr = NULL; int len; @@ -5336,7 +5382,6 @@ return -EINVAL; buf = purple_strdup_withhtml(message); - len = strlen(buf); if (strstr(buf, "<IMG ")) purple_conversation_write(conv, "", @@ -5350,8 +5395,28 @@ * visible characters" and not "number of bytes" */ if ((len > c->maxlen) || (len > c->maxvis)) { + /* If the length was too long, try stripping the HTML and then running it back through + * purple_strdup_withhtml() and the encoding process. The result may be shorter. */ g_free(buf2); - return -E2BIG; + + buf3 = purple_markup_strip_html(buf); + g_free(buf); + + buf = purple_strdup_withhtml(buf3); + g_free(buf3); + + purple_plugin_oscar_convert_to_best_encoding(gc, NULL, buf, &buf2, &len, &charset, &charsubset); + + if ((len > c->maxlen) || (len > c->maxvis)) { + purple_debug_warning("oscar", "Could not send %s because (%i > maxlen %i) or (%i > maxvis %i)", + buf2, len, c->maxlen, len, c->maxvis); + g_free(buf); + g_free(buf2); + return -E2BIG; + } + + purple_debug_info("oscar", "Sending %s as %s because the original was too long.", + message, buf2); } if (charset == AIM_CHARSET_ASCII) @@ -5362,6 +5427,7 @@ charsetstr = "iso-8859-1"; aim_chat_send_im(od, c->conn, 0, buf2, len, charsetstr, "en"); g_free(buf2); + g_free(buf); return 0; } @@ -5508,7 +5574,7 @@ status = purple_presence_get_active_status(presence); id = purple_status_get_id(status); - if (!purple_presence_is_online(presence)) + if ((od != NULL) && !purple_presence_is_online(presence)) { char *gname = aim_ssi_itemlist_findparentname(od->ssi.local, b->name); if (aim_ssi_waitingforauth(od->ssi.local, gname, b->name))
--- a/libpurple/protocols/oscar/oscar.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/oscar/oscar.h Wed Jun 27 21:43:18 2007 +0000 @@ -447,7 +447,6 @@ guint icontimer; guint getblisttimer; guint getinfotimer; - gint timeoffset; struct { guint maxwatchers; /* max users who can watch you */ @@ -935,7 +934,7 @@ /* 0x0014 */ int aim_im_sendmtn(OscarData *od, guint16 type1, const char *sn, guint16 type2); void aim_icbm_makecookie(guchar* cookie); gchar *oscar_encoding_extract(const char *encoding); -gchar *oscar_encoding_to_utf8(const char *encoding, const char *text, int textlen); +gchar *oscar_encoding_to_utf8(PurpleAccount *account, const char *encoding, const char *text, int textlen); gchar *purple_plugin_oscar_decode_im_part(PurpleAccount *account, const char *sourcesn, guint16 charset, guint16 charsubset, const gchar *data, gsize datalen);
--- a/libpurple/protocols/oscar/peer.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/oscar/peer.c Wed Jun 27 21:43:18 2007 +0000 @@ -173,12 +173,12 @@ purple_input_remove(conn->watcher_outgoing); conn->watcher_outgoing = 0; } - if (conn->listenerfd != -1) + if (conn->listenerfd >= 0) { close(conn->listenerfd); conn->listenerfd = -1; } - if (conn->fd != -1) + if (conn->fd >= 0) { close(conn->fd); conn->fd = -1; @@ -310,7 +310,7 @@ } /* If there was an error then close the connection */ - if (read == -1) + if (read < 0) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) /* No worries */ @@ -360,7 +360,7 @@ return; } - if (read == -1) + if (read < 0) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) /* No worries */ @@ -422,7 +422,7 @@ wrotelen = send(conn->fd, conn->buffer_outgoing->outptr, writelen, 0); if (wrotelen <= 0) { - if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) + if (wrotelen < 0 && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) /* No worries */ return; @@ -462,7 +462,7 @@ purple_circ_buffer_append(conn->buffer_outgoing, bs->data, bs->len); /* If we haven't already started writing stuff, then start the cycle */ - if ((conn->watcher_outgoing == 0) && (conn->fd != -1)) + if ((conn->watcher_outgoing == 0) && (conn->fd >= 0)) { conn->watcher_outgoing = purple_input_add(conn->fd, PURPLE_INPUT_WRITE, send_cb, conn); @@ -596,7 +596,7 @@ purple_debug_info("oscar", "Accepting connection on listener socket.\n"); conn->fd = accept(conn->listenerfd, &addr, &addrlen); - if (conn->fd == -1) + if (conn->fd < 0) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) /* No connection yet--no worries */ @@ -640,7 +640,7 @@ conn = data; conn->listen_data = NULL; - if (listenerfd == -1) + if (listenerfd < 0) { /* Could not open listener socket */ peer_connection_trynext(conn); @@ -1019,7 +1019,7 @@ PURPLE_DEFAULT_ACTION_NONE, account, sn, NULL, conn, 2, - _("_Connect"), G_CALLBACK(peer_connection_got_proposition_yes_cb), + _("C_onnect"), G_CALLBACK(peer_connection_got_proposition_yes_cb), _("Cancel"), G_CALLBACK(peer_connection_got_proposition_no_cb)); } else if (args->type == OSCAR_CAPABILITY_SENDFILE)
--- a/libpurple/protocols/oscar/peer_proxy.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/oscar/peer_proxy.c Wed Jun 27 21:43:18 2007 +0000 @@ -224,7 +224,7 @@ } /* If there was an error then close the connection */ - if (read == -1) + if (read < 0) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) /* No worries */ @@ -285,7 +285,8 @@ return; } - if (read == -1) + /* If there was an error then close the connection */ + if (read < 0) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) /* No worries */
--- a/libpurple/protocols/oscar/util.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/oscar/util.c Wed Jun 27 21:43:18 2007 +0000 @@ -37,6 +37,7 @@ * -- DMP. * */ +/* TODO: Get rid of this and use glib functions */ int aimutil_tokslen(char *toSearch, int theindex, char dl) { @@ -138,6 +139,7 @@ * Check if the given screen name is a valid AIM screen name. * Example: BobDole * Example: Henry_Ford@mac.com + * Example: 1KrazyKat@example.com * * @return TRUE if the screen name is valid, FALSE if not. */ @@ -146,9 +148,16 @@ { int i; + if (purple_email_is_valid(sn)) + return TRUE; + + /* Normal AIM screen names can't start with a number */ + if (isdigit(sn[0])) + return FALSE; + for (i = 0; sn[i] != '\0'; i++) { if (!isalnum(sn[i]) && (sn[i] != ' ') && - (sn[i] != '@') && (sn[i] != '.') && + (sn[i] != '.') && (sn[i] != '_') && (sn[i] != '-')) return FALSE; } @@ -169,10 +178,10 @@ for (i = 0; sn[i] != '\0'; i++) { if (!isdigit(sn[i])) - return 0; + return FALSE; } - return 1; + return TRUE; } /** @@ -187,14 +196,14 @@ int i; if (sn[0] != '+') - return 0; + return FALSE; for (i = 1; sn[i] != '\0'; i++) { if (!isdigit(sn[i])) - return 0; + return FALSE; } - return 1; + return TRUE; } /** @@ -206,70 +215,37 @@ aim_snvalid(const char *sn) { if ((sn == NULL) || (*sn == '\0')) - return 0; + return FALSE; - if (isalpha(sn[0])) - return aim_snvalid_aim(sn); - else if (isdigit(sn[0])) - return aim_snvalid_icq(sn); - else if (sn[0] == '+') - return aim_snvalid_sms(sn); - - return 0; + return aim_snvalid_icq(sn) || aim_snvalid_sms(sn) || aim_snvalid_aim(sn); } /** * Determine if a given screen name is an ICQ screen name - * (i.e. it begins with a number). + * (i.e. it is composed of only numbers). * - * @sn A valid AIM or ICQ screen name. + * @param sn A valid AIM or ICQ screen name. * @return TRUE if the screen name is an ICQ screen name. Otherwise * FALSE is returned. */ gboolean aim_sn_is_icq(const char *sn) { - if (isalpha(sn[0])) - return FALSE; - return TRUE; + return aim_snvalid_icq(sn); } /** * Determine if a given screen name is an SMS number * (i.e. it begins with a +). * - * @sn A valid AIM or ICQ screen name. + * @param sn A valid AIM or ICQ screen name. * @return TRUE if the screen name is an SMS number. Otherwise * FALSE is returned. */ gboolean aim_sn_is_sms(const char *sn) { - if (sn[0] != '+') - return FALSE; - return TRUE; -} - -/** - * This takes a screen name and returns its length without - * spaces. If there are no spaces in the SN, then the - * return is equal to that of strlen(). - */ -int -aim_snlen(const char *sn) -{ - int i = 0; - - if (!sn) - return 0; - - while (*sn != '\0') { - if (*sn != ' ') - i++; - sn++; - } - - return i; + return (sn[0] == '+'); } /** @@ -279,9 +255,9 @@ * ignored, with the exception that screen names can not start with * a space). * - * Return: 0 if equal - * non-0 if different + * @return 0 if equal, non-0 if different */ +/* TODO: Do something different for email addresses. */ int aim_sncmp(const char *sn1, const char *sn2) {
--- a/libpurple/protocols/qq/AUTHORS Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/AUTHORS Wed Jun 27 21:43:18 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/qq/Makefile.am Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/Makefile.am Wed Jun 27 21:43:18 2007 +0000 @@ -34,8 +34,6 @@ group_info.h \ group_join.c \ group_join.h \ - group_misc.c \ - group_misc.h \ group_network.c \ group_network.h \ group_opt.c \
--- a/libpurple/protocols/qq/Makefile.mingw Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/Makefile.mingw Wed Jun 27 21:43:18 2007 +0000 @@ -54,7 +54,6 @@ group_im.c \ group_info.c \ group_join.c \ - group_misc.c \ group_network.c \ group_opt.c \ group_search.c \
--- a/libpurple/protocols/qq/buddy_list.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/buddy_list.c Wed Jun 27 21:43:18 2007 +0000 @@ -23,8 +23,10 @@ */ #include <string.h> + +#include "qq.h" + #include "debug.h" - #include "notify.h" #include "utils.h" #include "packet_parse.h" @@ -35,9 +37,8 @@ #include "char_conv.h" #include "crypt.h" #include "header_info.h" -#include "keep_alive.h" +#include "keep_alive.h" #include "send_core.h" -#include "qq.h" #include "group.h" #include "group_find.h" #include "group_internal.h"
--- a/libpurple/protocols/qq/buddy_status.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/buddy_status.c Wed Jun 27 21:43:18 2007 +0000 @@ -23,6 +23,7 @@ */ #include <string.h> +#include "internal.h" #include "debug.h" #include "prefs.h"
--- a/libpurple/protocols/qq/file_trans.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/file_trans.c Wed Jun 27 21:43:18 2007 +0000 @@ -26,6 +26,8 @@ #define random rand #endif +#include "internal.h" + #include "debug.h" #include "ft.h" #include "cipher.h"
--- a/libpurple/protocols/qq/group_conv.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/group_conv.c Wed Jun 27 21:43:18 2007 +0000 @@ -23,11 +23,12 @@ */ #include <glib.h> +#include "qq.h" + #include "conversation.h" #include "buddy_status.h" #include "group_conv.h" -#include "qq.h" #include "utils.h" /* show group conversation window */
--- a/libpurple/protocols/qq/group_find.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/group_find.c Wed Jun 27 21:43:18 2007 +0000 @@ -22,13 +22,14 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "qq.h" + #include "conversation.h" #include "debug.h" #include "util.h" #include "group_find.h" #include "group_network.h" -#include "qq.h" #include "utils.h" /* find the internal_group_id by the reply packet sequence
--- a/libpurple/protocols/qq/group_free.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/group_free.c Wed Jun 27 21:43:18 2007 +0000 @@ -22,6 +22,8 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "internal.h" + #include "debug.h" #include "buddy_status.h"
--- a/libpurple/protocols/qq/group_im.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/group_im.c Wed Jun 27 21:43:18 2007 +0000 @@ -22,6 +22,8 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "internal.h" + #include "conversation.h" #include "debug.h" #include "notify.h" @@ -341,7 +343,7 @@ read_packet_dw(data, cursor, data_len, &(im_group->member_uid)); read_packet_w(data, cursor, data_len, &unknown); /* 0x0001? */ read_packet_w(data, cursor, data_len, &(im_group->msg_seq)); - read_packet_dw(data, cursor, data_len, (guint32 *) & (im_group->send_time)); + read_packet_time(data, cursor, data_len, &im_group->send_time); read_packet_dw(data, cursor, data_len, &unknown4); /* versionID */ /* * length includes font_attr
--- a/libpurple/protocols/qq/group_info.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/group_info.c Wed Jun 27 21:43:18 2007 +0000 @@ -22,6 +22,8 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "internal.h" + #include "conversation.h" #include "debug.h"
--- a/libpurple/protocols/qq/group_internal.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/group_internal.c Wed Jun 27 21:43:18 2007 +0000 @@ -22,13 +22,13 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "internal.h" #include "blist.h" #include "debug.h" #include "buddy_opt.h" #include "group_free.h" #include "group_internal.h" -#include "group_misc.h" #include "utils.h" static gchar *_qq_group_set_my_status_desc(qq_group *group)
--- a/libpurple/protocols/qq/group_join.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/group_join.c Wed Jun 27 21:43:18 2007 +0000 @@ -22,6 +22,8 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "internal.h" + #include "debug.h" #include "notify.h" #include "request.h"
--- a/libpurple/protocols/qq/group_misc.c Sun Jun 03 09:40:38 2007 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -/** - * @file group_misc.c - * - * purple - * - * Purple is the legal property of its developers, whose names are too numerous - * to list here. Please refer to the COPYRIGHT file distributed with this - * source distribution. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include "debug.h" - -#include "buddy_status.h" -#include "group_misc.h" -#include "utils.h"
--- a/libpurple/protocols/qq/group_misc.h Sun Jun 03 09:40:38 2007 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -/** - * @file group_misc.h - * - * purple - * - * Purple is the legal property of its developers, whose names are too numerous - * to list here. Please refer to the COPYRIGHT file distributed with this - * source distribution. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifndef _QQ_GROUP_MISC_H_ -#define _QQ_GROUP_MISC_H_ - -#include <glib.h> -#include "group.h" - -#endif
--- a/libpurple/protocols/qq/group_network.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/group_network.c Wed Jun 27 21:43:18 2007 +0000 @@ -22,6 +22,8 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "internal.h" + #include "debug.h" #include "notify.h"
--- a/libpurple/protocols/qq/group_opt.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/group_opt.c Wed Jun 27 21:43:18 2007 +0000 @@ -22,6 +22,8 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "qq.h" + #include "debug.h" #include "notify.h" #include "request.h" @@ -35,7 +37,6 @@ #include "group_network.h" #include "group_opt.h" #include "packet_parse.h" -#include "qq.h" #include "utils.h" /* TODO: can't we use qsort here? */
--- a/libpurple/protocols/qq/group_search.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/group_search.c Wed Jun 27 21:43:18 2007 +0000 @@ -22,6 +22,8 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "internal.h" + #include "debug.h" #include "char_conv.h"
--- a/libpurple/protocols/qq/header_info.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/header_info.c Wed Jun 27 21:43:18 2007 +0000 @@ -22,6 +22,8 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "internal.h" + #include "header_info.h" #define QQ_CLIENT_062E 0x062e /* GB QQ2000c build 0630 */
--- a/libpurple/protocols/qq/header_info.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/header_info.h Wed Jun 27 21:43:18 2007 +0000 @@ -33,7 +33,7 @@ #define QQ_PACKET_TAG 0x02 /* all QQ text packets starts with it */ #define QQ_PACKET_TAIL 0x03 /* all QQ text packets end with it */ -#define QQ_CLIENT 0x0f15 +#define QQ_CLIENT 0x0E1B /* list of known QQ commands */ enum {
--- a/libpurple/protocols/qq/im.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/im.c Wed Jun 27 21:43:18 2007 +0000 @@ -22,6 +22,8 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "internal.h" + #include "conversation.h" #include "debug.h" #include "internal.h"
--- a/libpurple/protocols/qq/keep_alive.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/keep_alive.c Wed Jun 27 21:43:18 2007 +0000 @@ -28,6 +28,8 @@ * Puzzlebird, Nov-Dec 2002 */ +#include "internal.h" + #include "debug.h" #include "server.h"
--- a/libpurple/protocols/qq/login_logout.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/login_logout.c Wed Jun 27 21:43:18 2007 +0000 @@ -181,7 +181,7 @@ /* 031-032: server listening port */ bytes += read_packet_w(data, &cursor, len, &lrop.server_port); /* 033-036: login time for current session */ - bytes += read_packet_dw(data, &cursor, len, (guint32 *) &lrop.login_time); + bytes += read_packet_time(data, &cursor, len, &lrop.login_time); /* 037-062: 26 bytes, unknown */ bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.unknown1, 26); /* 063-066: unknown server1 ip address */ @@ -203,7 +203,7 @@ /* 123-126: login IP of last session */ bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.last_client_ip, 4); /* 127-130: login time of last session */ - bytes += read_packet_dw(data, &cursor, len, (guint32 *) &lrop.last_login_time); + bytes += read_packet_time(data, &cursor, len, &lrop.last_login_time); /* 131-138: 8 bytes unknown */ bytes += read_packet_data(data, &cursor, len, (guint8 *) &lrop.unknown6, 8);
--- a/libpurple/protocols/qq/packet_parse.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/packet_parse.c Wed Jun 27 21:43:18 2007 +0000 @@ -65,6 +65,19 @@ } } +/* read four bytes as "time_t" from buf, + * return the number of bytes read if succeeds, otherwise return -1 + * This function is a wrapper around read_packet_dw() to avoid casting. */ +gint read_packet_time(guint8 *buf, guint8 **cursor, gint buflen, time_t *t) +{ + guint32 time; + gint ret = read_packet_dw(buf, cursor, buflen, &time); + if (ret != -1 ) { + *t = time; + } + return ret; +} + /* read datalen bytes from buf, * return the number of bytes read if succeeds, otherwise return -1 */ gint read_packet_data(guint8 *buf, guint8 **cursor, gint buflen, guint8 *data, gint datalen) {
--- a/libpurple/protocols/qq/packet_parse.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/packet_parse.h Wed Jun 27 21:43:18 2007 +0000 @@ -26,6 +26,7 @@ #define _QQ_PACKED_PARSE_H_ #include <glib.h> +#include <time.h> /* According to "UNIX Network Programming", all TCP/IP implementations * must support a minimum IP datagram size of 576 bytes, regardless of the MTU. @@ -39,6 +40,7 @@ gint read_packet_b(guint8 *buf, guint8 **cursor, gint buflen, guint8 *b); gint read_packet_w(guint8 *buf, guint8 **cursor, gint buflen, guint16 *w); gint read_packet_dw(guint8 *buf, guint8 **cursor, gint buflen, guint32 *dw); +gint read_packet_time(guint8 *buf, guint8 **cursor, gint buflen, time_t *t); gint read_packet_data(guint8 *buf, guint8 **cursor, gint buflen, guint8 *data, gint datalen); gint create_packet_b(guint8 *buf, guint8 **cursor, guint8 b); gint create_packet_w(guint8 *buf, guint8 **cursor, guint16 w);
--- a/libpurple/protocols/qq/qq.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/qq.h Wed Jun 27 21:43:18 2007 +0000 @@ -26,8 +26,8 @@ #define _QQ_QQ_H_ #include <glib.h> +#include "internal.h" #include "ft.h" -#include "internal.h" #include "proxy.h" #include "roomlist.h"
--- a/libpurple/protocols/qq/send_file.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/send_file.c Wed Jun 27 21:43:18 2007 +0000 @@ -22,6 +22,8 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "qq.h" + #include "send_file.h" #include "debug.h" #include "network.h" @@ -34,7 +36,6 @@ #include "im.h" #include "keep_alive.h" #include "packet_parse.h" -#include "qq.h" #include "send_core.h" #include "utils.h"
--- a/libpurple/protocols/qq/sendqueue.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/qq/sendqueue.c Wed Jun 27 21:43:18 2007 +0000 @@ -22,9 +22,10 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "internal.h" + #include "connection.h" #include "debug.h" -#include "internal.h" #include "notify.h" #include "prefs.h" #include "request.h"
--- a/libpurple/protocols/sametime/sametime.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/sametime/sametime.c Wed Jun 27 21:43:18 2007 +0000 @@ -2660,7 +2660,7 @@ GString *str; PurpleMimeDocument *doc; - const GList *parts; + GList *parts; img_by_cid = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); images = NULL; @@ -3401,7 +3401,7 @@ static void conf_select_prompt_invite(PurpleBuddy *buddy, PurpleRequestFields *fields) { PurpleRequestField *f; - const GList *l; + GList *l; const char *msg; f = purple_request_fields_get_field(fields, CHAT_KEY_INVITE); @@ -5268,7 +5268,7 @@ PurpleRequestFields *fields) { PurpleRequestField *f; - const GList *l; + GList *l; f = purple_request_fields_get_field(fields, "group"); l = purple_request_field_list_get_items(f); @@ -5334,7 +5334,7 @@ static void remote_group_multi_cb(struct mwPurplePluginData *pd, PurpleRequestFields *fields) { PurpleRequestField *f; - const GList *l; + GList *l; f = purple_request_fields_get_field(fields, "group"); l = purple_request_field_list_get_selected(f);
--- a/libpurple/protocols/silc/README Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/silc/README Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/silc/TODO Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/silc/buddy.c Wed Jun 27 21:43:18 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,24 +1103,23 @@ 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 silcpurple_add_buddy_select_cb(SilcPurpleBuddyRes r, PurpleRequestFields *fields) { PurpleRequestField *f; - const GList *list; + 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/silc/chat.c Wed Jun 27 21:43:18 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); } @@ -278,7 +291,7 @@ SilcClient client = sg->client; SilcClientConnection conn = sg->conn; PurpleRequestField *f; - const GList *list; + GList *list; SilcPublicKey public_key; SilcBuffer chpks, pk, chidp; SilcUInt16 c = 0, ct; @@ -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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/silc/ft.c Wed Jun 27 21:43:18 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 timed out")); + } 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/silc/ops.c Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/silc/pk.c Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/silc/silc.c Wed Jun 27 21:43:18 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 int +static gboolean silcpurple_scheduler(gpointer *context) { - SilcPurple sg = (SilcPurple)context; - silc_client_run_one(sg->client); - return 1; + 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,63 +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 */ -#ifndef _WIN32 - sg->scheduler = g_timeout_add(5, (GSourceFunc)silcpurple_scheduler, sg); -#else - sg->scheduler = g_timeout_add(300, (GSourceFunc)silcpurple_scheduler, sg); -#endif + 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; } @@ -391,13 +472,13 @@ /* 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); - g_source_remove(sg->scheduler); - g_timeout_add(1, (GSourceFunc)silcpurple_close_final, sg); + purple_timeout_remove(sg->scheduler); + purple_timeout_add(1, (GSourceFunc)silcpurple_close_final, sg); } @@ -599,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) @@ -726,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); @@ -865,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); @@ -945,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); @@ -967,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 @@ -986,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); @@ -1032,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 */ @@ -1083,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: @@ -1110,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; @@ -1145,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; } @@ -1161,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)); @@ -1175,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); @@ -1195,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; } @@ -1223,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)) { @@ -1552,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; } @@ -1726,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, @@ -1815,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 */ @@ -1864,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++) { @@ -1897,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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/silc/silcpurple.h Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/silc/util.c Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/silc/wb.c Wed Jun 27 21:43:18 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 Wed Jun 27 21:43:18 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 Wed Jun 27 21:43:18 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 Wed Jun 27 21:43:18 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 Wed Jun 27 21:43:18 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 Wed Jun 27 21:43:18 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; + 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 Wed Jun 27 21:43:18 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; + 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 Wed Jun 27 21:43:18 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 Wed Jun 27 21:43:18 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 Wed Jun 27 21:43:18 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 Wed Jun 27 21:43:18 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 Wed Jun 27 21:43:18 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 Wed Jun 27 21:43:18 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 Wed Jun 27 21:43:18 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 Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/simple/simple.c Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/toc/PROTOCOL Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo.c Wed Jun 27 21:43:18 2007 +0000 @@ -2310,7 +2310,7 @@ * are you trying to pull? */ guchar *start; - purple_debug_warning("yahoo", "Error in YMSG stream, got something not a YMSG packet!"); + purple_debug_warning("yahoo", "Error in YMSG stream, got something not a YMSG packet!\n"); start = memchr(yd->rxqueue + 1, 'Y', yd->rxlen - 1); if (start) { @@ -2377,7 +2377,11 @@ } if (source < 0) { - purple_connection_error(gc, _("Unable to connect.")); + gchar *tmp; + tmp = g_strdup_printf(_("Could not establish a connection with the server:\n%s"), + error_message); + purple_connection_error(gc, tmp); + g_free(tmp); return; } @@ -2405,7 +2409,11 @@ } if (source < 0) { - purple_connection_error(gc, _("Unable to connect.")); + gchar *tmp; + tmp = g_strdup_printf(_("Could not establish a connection with the server:\n%s"), + error_message); + purple_connection_error(gc, tmp); + g_free(tmp); return; } @@ -2507,12 +2515,16 @@ if (written < 0 && errno == EAGAIN) written = 0; else if (written <= 0) { + gchar *tmp; g_free(yd->auth); yd->auth = NULL; if (gc->inpa) purple_input_remove(gc->inpa); gc->inpa = 0; - purple_connection_error(gc, _("Unable to connect.")); + tmp = g_strdup_printf(_("Lost connection with %s:\n%s"), + "login.yahoo.com:80", strerror(errno)); + purple_connection_error(gc, tmp); + g_free(tmp); return; } @@ -2533,7 +2545,11 @@ PurpleConnection *gc = data; if (source < 0) { - purple_connection_error(gc, _("Unable to connect.")); + gchar *tmp; + tmp = g_strdup_printf(_("Could not establish a connection with %s:\n%s"), + "login.yahoo.com:80", error_message); + purple_connection_error(gc, tmp); + g_free(tmp); return; } @@ -2616,8 +2632,7 @@ if (error_message != NULL) { - /* TODO: Include error_message in the message below */ - purple_connection_error(gc, _("Unable to connect.")); + purple_connection_error(gc, error_message); return; }
--- a/libpurple/protocols/yahoo/yahoo_auth.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo_auth.c Wed Jun 27 21:43:18 2007 +0000 @@ -7,6 +7,8 @@ * */ +#include "internal.h" + #include "yahoo.h" #include "yahoo_auth.h"
--- a/libpurple/protocols/yahoo/yahoo_doodle.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo_doodle.c Wed Jun 27 21:43:18 2007 +0000 @@ -491,7 +491,8 @@ /* g_debug_debug("yahoo", "doodle: yahoo_doodle_end()\n"); */ - yahoo_doodle_command_send_shutdown(gc, wb->who); + if (gc) + yahoo_doodle_command_send_shutdown(gc, wb->who); g_free(wb->proto_data); }
--- a/libpurple/protocols/yahoo/yahoo_filexfer.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo_filexfer.c Wed Jun 27 21:43:18 2007 +0000 @@ -20,8 +20,9 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "internal.h" + #include "prpl.h" -#include "internal.h" #include "util.h" #include "debug.h" #include "notify.h"
--- a/libpurple/protocols/yahoo/yahoo_packet.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo_packet.c Wed Jun 27 21:43:18 2007 +0000 @@ -110,6 +110,29 @@ return len; } +/* + * 'len' is the value given to us by the server that is supposed to + * be the length of 'data'. But apparently there's a time when this + * length is incorrect. Christopher Layne thinks it might be a bug + * in their server code. + * + * The following information is from Christopher: + * + * It sometimes happens when Yahoo! sends a packet continuation within + * chat. Sometimes when joining a large chatroom the initial + * SERVICE_CHATJOIN packet will be so large that it will need to be + * split into multiple packets. That's fine, except that the length + * of the second packet is wrong. The packet has the same length as + * the first packet, and the length given in the header is the same, + * however the actual data in the packet is shorter than this length. + * So half of the packet contains good, valid data, and then the rest + * of the packet is junk. Luckily there is a null terminator after + * the valid data and before the invalid data. + * + * What does all this mean? It means that we parse through the data + * pulling out key/value pairs until we've parsed 'len' bytes, or until + * we run into a null terminator, whichever comes first. + */ void yahoo_packet_read(struct yahoo_packet *pkt, const guchar *data, int len) { int pos = 0; @@ -121,18 +144,8 @@ while (pos + 1 < len) { - /* this is weird, and in one of the chat packets, and causes us to - * think all the values are keys and all the keys are values after - * this point if we don't handle it */ - if (data[pos] == '\0') { - while (pos + 1 < len) { - if (data[pos] == 0xc0 && data[pos + 1] == 0x80) - break; - pos++; - } - pos += 2; - continue; - } + if (data[pos] == '\0') + break; pair = g_new0(struct yahoo_pair, 1);
--- a/libpurple/protocols/yahoo/yahoochat.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoochat.c Wed Jun 27 21:43:18 2007 +0000 @@ -26,6 +26,8 @@ * */ +#include "internal.h" + #ifdef HAVE_CONFIG_H #include "config.h" #endif @@ -37,7 +39,6 @@ #include "conversation.h" #include "notify.h" #include "util.h" -#include "internal.h" #include "yahoo.h" #include "yahoo_packet.h"
--- a/libpurple/protocols/zephyr/ZLocations.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/zephyr/ZLocations.c Wed Jun 27 21:43:18 2007 +0000 @@ -36,7 +36,8 @@ return (Z_SendLocation(LOGIN_CLASS, LOGIN_USER_FLUSH, ZAUTH, "")); } -static char host[MAXHOSTNAMELEN], mytty[MAXPATHLEN]; +static char host[MAXHOSTNAMELEN]; +static char *mytty = NULL; static int reenter = 0; Code_t Z_SendLocation(class, opcode, auth, format) @@ -87,21 +88,20 @@ } #ifndef X_DISPLAY_MISSING if ((display = getenv("DISPLAY")) && *display) { - (void) strncpy(mytty, display, sizeof(mytty)); + mytty = g_strdup(display); } else { #endif #ifdef WIN32 - strncpy(mytty, "WinPurple", sizeof(mytty)); + mytty = g_strdup("WinPurple"); #else ttyp = ttyname(0); if (ttyp && *ttyp) { p = strchr(ttyp + 1, '/'); - strcpy(mytty, (p) ? p + 1 : ttyp); + mytty = g_strdup((p) ? p + 1 : ttyp); } else { - strncpy(mytty, "unknown", sizeof(mytty)); + mytty = g_strdup("unknown"); } #endif - mytty[sizeof(mytty)-1] = '\0'; #ifndef X_DISPLAY_MISSING } #endif @@ -114,7 +114,6 @@ bptr[1][strlen(bptr[1])-1] = '\0'; bptr[2] = mytty; - if ((retval = ZSendList(¬ice, bptr, 3, auth)) != ZERR_NONE) return (retval);
--- a/libpurple/protocols/zephyr/ZVariables.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/protocols/zephyr/ZVariables.c Wed Jun 27 21:43:18 2007 +0000 @@ -9,6 +9,7 @@ * "mit-copyright.h". */ +#include "libpurple/internal.h" #include "internal.h" #include "util.h"
--- a/libpurple/prpl.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/prpl.c Wed Jun 27 21:43:18 2007 +0000 @@ -83,44 +83,50 @@ purple_prpl_got_user_idle(PurpleAccount *account, const char *name, gboolean idle, time_t idle_time) { - PurpleBuddy *buddy; PurplePresence *presence; + GSList *list; g_return_if_fail(account != NULL); g_return_if_fail(name != NULL); g_return_if_fail(purple_account_is_connected(account)); - if ((buddy = purple_find_buddy(account, name)) == NULL) + if ((list = purple_find_buddies(account, name)) == NULL) return; - presence = purple_buddy_get_presence(buddy); - - purple_presence_set_idle(presence, idle, idle_time); + while (list) { + presence = purple_buddy_get_presence(list->data); + list = g_slist_delete_link(list, list); + purple_presence_set_idle(presence, idle, idle_time); + } } void purple_prpl_got_user_login_time(PurpleAccount *account, const char *name, time_t login_time) { - PurpleBuddy *buddy; + GSList *list; PurplePresence *presence; g_return_if_fail(account != NULL); g_return_if_fail(name != NULL); - if ((buddy = purple_find_buddy(account, name)) == NULL) + if ((list = purple_find_buddies(account, name)) == NULL) return; if (login_time == 0) login_time = time(NULL); - presence = purple_buddy_get_presence(buddy); + while (list) { + PurpleBuddy *buddy = list->data; + presence = purple_buddy_get_presence(buddy); + list = g_slist_delete_link(list, list); - if (purple_presence_get_login_time(presence) != login_time) - { - purple_presence_set_login_time(presence, login_time); + if (purple_presence_get_login_time(presence) != login_time) + { + purple_presence_set_login_time(presence, login_time); - purple_signal_emit(purple_blist_get_handle(), "buddy-got-login-time", buddy); + purple_signal_emit(purple_blist_get_handle(), "buddy-got-login-time", buddy); + } } } @@ -259,7 +265,7 @@ purple_prpl_get_statuses(PurpleAccount *account, PurplePresence *presence) { GList *statuses = NULL; - const GList *l; + GList *l; PurpleStatus *status; g_return_val_if_fail(account != NULL, NULL);
--- a/libpurple/prpl.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/prpl.h Wed Jun 27 21:43:18 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; /** @@ -372,7 +378,7 @@ * beginning with the value for @a attr_id. */ void purple_prpl_got_account_status(PurpleAccount *account, - const char *status_id, ...); + const char *status_id, ...) G_GNUC_NULL_TERMINATED; /** * Notifies Purple that a user's idle state and time have changed. * @@ -412,7 +418,7 @@ * beginning with the value for @a attr_id. */ void purple_prpl_got_user_status(PurpleAccount *account, const char *name, - const char *status_id, ...); + const char *status_id, ...) G_GNUC_NULL_TERMINATED; /** * Notifies libpurple that a user's status has been deactivated
--- a/libpurple/purple-remote Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/purple-remote Wed Jun 27 21:43:18 2007 +0000 @@ -31,7 +31,7 @@ return result def show_help(): - print """This program uses DBus to communicate with purple. + print """This program uses D-Bus to communicate with purple. Usage: @@ -96,8 +96,6 @@ protocol = match.group(2) if protocol == "xmpp": protocol = "jabber" - if protocol == "aim" or protocol == "icq": - protocol = "oscar" if protocol is not None: protocol = "prpl-" + protocol command = match.group(5) @@ -159,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/request.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/request.c Wed Jun 27 21:43:18 2007 +0000 @@ -22,6 +22,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "internal.h" + #include "notify.h" #include "request.h" #include "debug.h" @@ -111,7 +113,7 @@ return (g_hash_table_lookup(fields->fields, id) != NULL); } -const GList * +GList * purple_request_fields_get_required(const PurpleRequestFields *fields) { g_return_val_if_fail(fields != NULL, NULL); @@ -872,9 +874,9 @@ } void -purple_request_field_list_set_selected(PurpleRequestField *field, const GList *items) +purple_request_field_list_set_selected(PurpleRequestField *field, GList *items) { - const GList *l; + GList *l; g_return_if_fail(field != NULL); g_return_if_fail(items != NULL); @@ -883,7 +885,7 @@ purple_request_field_list_clear_selected(field); if (!purple_request_field_list_get_multi_select(field) && - g_list_length((GList*)items) > 1) + g_list_length(items) > 1) { purple_debug_warning("request", "More than one item added to non-multi-select " @@ -913,7 +915,7 @@ item, NULL, NULL); } -const GList * +GList * purple_request_field_list_get_selected(const PurpleRequestField *field) { g_return_val_if_fail(field != NULL, NULL); @@ -922,7 +924,7 @@ return field->u.list.selected; } -const GList * +GList * purple_request_field_list_get_items(const PurpleRequestField *field) { g_return_val_if_fail(field != NULL, NULL);
--- a/libpurple/request.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/request.h Wed Jun 27 21:43:18 2007 +0000 @@ -285,9 +285,9 @@ * * @param fields The fields list. * - * @return The list of required fields. + * @constreturn The list of required fields. */ -const GList *purple_request_fields_get_required(const PurpleRequestFields *fields); +GList *purple_request_fields_get_required(const PurpleRequestFields *fields); /** * Returns whether or not a field with the specified ID is required. @@ -917,10 +917,10 @@ * Sets a list of selected items in a list field. * * @param field The field. - * @param items The list of selected items. + * @param items The list of selected items, which is not modified or freed. */ void purple_request_field_list_set_selected(PurpleRequestField *field, - const GList *items); + GList *items); /** * Returns whether or not a particular item is selected in a list field. @@ -941,9 +941,9 @@ * * @param field The field. * - * @return The list of selected items. + * @constreturn The list of selected items. */ -const GList *purple_request_field_list_get_selected( +GList *purple_request_field_list_get_selected( const PurpleRequestField *field); /** @@ -951,9 +951,9 @@ * * @param field The field. * - * @return The list of items. + * @constreturn The list of items. */ -const GList *purple_request_field_list_get_items(const PurpleRequestField *field); +GList *purple_request_field_list_get_items(const PurpleRequestField *field); /*@}*/ @@ -1228,7 +1228,7 @@ const char *ok_text, GCallback ok_cb, const char *cancel_text, GCallback cancel_cb, PurpleAccount *account, const char *who, PurpleConversation *conv, - void *user_data, ...); + void *user_data, ...) G_GNUC_NULL_TERMINATED; /** * Prompts the user for multiple-choice input.
--- a/libpurple/roomlist.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/roomlist.c Wed Jun 27 21:43:18 2007 +0000 @@ -25,6 +25,8 @@ #include <glib.h> +#include "internal.h" + #include "account.h" #include "connection.h" #include "debug.h"
--- a/libpurple/savedstatuses.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/savedstatuses.c Wed Jun 27 21:43:18 2007 +0000 @@ -29,6 +29,7 @@ #include "notify.h" #include "savedstatuses.h" #include "dbus-maybe.h" +#include "request.h" #include "status.h" #include "util.h" #include "xmlnode.h" @@ -110,6 +111,7 @@ g_return_if_fail(substatus != NULL); g_free(substatus->message); + purple_request_close_with_handle(substatus); PURPLE_DBUS_UNREGISTER_POINTER(substatus); g_free(substatus); } @@ -128,7 +130,7 @@ status->substatuses = g_list_remove(status->substatuses, substatus); free_saved_status_sub(substatus); } - + purple_request_close_with_handle(status); PURPLE_DBUS_UNREGISTER_POINTER(status); g_free(status); } @@ -357,7 +359,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); } @@ -569,6 +571,9 @@ schedule_save(); + purple_signal_emit(purple_savedstatuses_get_handle(), "savedstatus-added", + status); + return status; } @@ -584,6 +589,9 @@ status->title = g_strdup(title); schedule_save(); + + purple_signal_emit(purple_savedstatuses_get_handle(), + "savedstatus-modified", status); } void @@ -594,6 +602,8 @@ status->type = type; schedule_save(); + purple_signal_emit(purple_savedstatuses_get_handle(), + "savedstatus-modified", status); } void @@ -608,6 +618,9 @@ status->message = g_strdup(message); schedule_save(); + + purple_signal_emit(purple_savedstatuses_get_handle(), + "savedstatus-modified", status); } void @@ -637,6 +650,8 @@ substatus->message = g_strdup(message); schedule_save(); + purple_signal_emit(purple_savedstatuses_get_handle(), + "savedstatus-modified", saved_status); } void @@ -660,6 +675,9 @@ return; } } + + purple_signal_emit(purple_savedstatuses_get_handle(), + "savedstatus-modified", saved_status); } /* @@ -683,16 +701,12 @@ } } -gboolean -purple_savedstatus_delete(const char *title) +void +purple_savedstatus_delete_by_status(PurpleSavedStatus *status) { - PurpleSavedStatus *status; time_t creation_time, current, idleaway; - status = purple_savedstatus_find(title); - - if (status == NULL) - return FALSE; + g_return_if_fail(status != NULL); saved_statuses = g_list_remove(saved_statuses, status); creation_time = purple_savedstatus_get_creation_time(status); @@ -713,10 +727,29 @@ if (idleaway == creation_time) purple_prefs_set_int("/purple/savedstatus/idleaway", 0); + purple_signal_emit(purple_savedstatuses_get_handle(), + "savedstatus-deleted", status); +} + +gboolean +purple_savedstatus_delete(const char *title) +{ + PurpleSavedStatus *status; + + status = purple_savedstatus_find(title); + + if (status == NULL) + return FALSE; + + if (purple_savedstatus_get_current() == status) + return FALSE; + + purple_savedstatus_delete_by_status(status); + return TRUE; } -const GList * +GList * purple_savedstatuses_get_all(void) { return saved_statuses; @@ -1171,6 +1204,21 @@ purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_SAVEDSTATUS)); + purple_signal_register(handle, "savedstatus-added", + purple_marshal_VOID__POINTER, NULL, 1, + purple_value_new(PURPLE_TYPE_SUBTYPE, + PURPLE_SUBTYPE_SAVEDSTATUS)); + + purple_signal_register(handle, "savedstatus-deleted", + purple_marshal_VOID__POINTER, NULL, 1, + purple_value_new(PURPLE_TYPE_SUBTYPE, + PURPLE_SUBTYPE_SAVEDSTATUS)); + + purple_signal_register(handle, "savedstatus-modified", + purple_marshal_VOID__POINTER, NULL, 1, + purple_value_new(PURPLE_TYPE_SUBTYPE, + PURPLE_SUBTYPE_SAVEDSTATUS)); + purple_signal_connect(purple_accounts_get_handle(), "account-removed", handle, PURPLE_CALLBACK(purple_savedstatus_unset_all_substatuses),
--- a/libpurple/savedstatuses.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/savedstatuses.h Wed Jun 27 21:43:18 2007 +0000 @@ -149,11 +149,21 @@ gboolean purple_savedstatus_delete(const char *title); /** + * Delete a saved status. This removes the saved status from the list + * of saved statuses, and writes the revised list to status.xml. + * + * @param saved_status the status to delete, the pointer is invalid after + * the call + * + */ +void purple_savedstatus_delete_by_status(PurpleSavedStatus *saved_status); + +/** * Returns all saved statuses. * - * @return A list of saved statuses. + * @constreturn A list of saved statuses. */ -const GList *purple_savedstatuses_get_all(void); +GList *purple_savedstatuses_get_all(void); /** * Returns the n most popular saved statuses. "Popularity" is
--- a/libpurple/server.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/server.c Wed Jun 27 21:43:18 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; @@ -198,7 +198,7 @@ { PurplePluginProtocolInfo *prpl_info = NULL; - if (b != NULL && b->account->gc->prpl != NULL) + if (b != NULL && b->account->gc && b->account->gc->prpl != NULL) prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(b->account->gc->prpl); if (b && prpl_info && prpl_info->alias_buddy) { @@ -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/status.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/status.c Wed Jun 27 21:43:18 2007 +0000 @@ -152,10 +152,10 @@ { PURPLE_STATUS_UNSET, "unset", N_("Unset") }, { PURPLE_STATUS_OFFLINE, "offline", N_("Offline") }, { PURPLE_STATUS_AVAILABLE, "available", N_("Available") }, - { PURPLE_STATUS_UNAVAILABLE, "unavailable", N_("Unavailable") }, + { PURPLE_STATUS_UNAVAILABLE, "unavailable", N_("Do not disturb") }, { PURPLE_STATUS_INVISIBLE, "invisible", N_("Invisible") }, { PURPLE_STATUS_AWAY, "away", N_("Away") }, - { PURPLE_STATUS_EXTENDED_AWAY, "extended_away", N_("Extended Away") }, + { PURPLE_STATUS_EXTENDED_AWAY, "extended_away", N_("Extended away") }, { PURPLE_STATUS_MOBILE, "mobile", N_("Mobile") } }; @@ -451,7 +451,7 @@ return NULL; } -const GList * +GList * purple_status_type_get_attrs(const PurpleStatusType *status_type) { g_return_val_if_fail(status_type != NULL, NULL); @@ -548,7 +548,7 @@ purple_status_new(PurpleStatusType *status_type, PurplePresence *presence) { PurpleStatus *status; - const GList *l; + GList *l; g_return_val_if_fail(status_type != NULL, NULL); g_return_val_if_fail(presence != NULL, NULL); @@ -719,10 +719,10 @@ void purple_status_set_active_with_attrs_list(PurpleStatus *status, gboolean active, - const GList *attrs) + GList *attrs) { gboolean changed = FALSE; - const GList *l; + GList *l; GList *specified_attr_ids = NULL; PurpleStatusType *status_type; @@ -1168,9 +1168,9 @@ } void -purple_presence_add_list(PurplePresence *presence, const GList *source_list) +purple_presence_add_list(PurplePresence *presence, GList *source_list) { - const GList *l; + GList *l; g_return_if_fail(presence != NULL); g_return_if_fail(source_list != NULL); @@ -1396,7 +1396,7 @@ return presence->u.buddy.buddy; } -const GList * +GList * purple_presence_get_statuses(const PurplePresence *presence) { g_return_val_if_fail(presence != NULL, NULL); @@ -1408,7 +1408,7 @@ purple_presence_get_status(const PurplePresence *presence, const char *status_id) { PurpleStatus *status; - const GList *l = NULL; + GList *l = NULL; g_return_val_if_fail(presence != NULL, NULL); g_return_val_if_fail(status_id != NULL, NULL); @@ -1487,7 +1487,7 @@ purple_presence_is_status_primitive_active(const PurplePresence *presence, PurpleStatusPrimitive primitive) { - const GList *l; + GList *l; g_return_val_if_fail(presence != NULL, FALSE); g_return_val_if_fail(primitive != PURPLE_STATUS_UNSET, FALSE); @@ -1535,7 +1535,7 @@ gboolean idle1, idle2; time_t idle_time_1, idle_time_2; int score1 = 0, score2 = 0; - const GList *l; + GList *l; if (presence1 == presence2) return 0;
--- a/libpurple/status.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/status.h Wed Jun 27 21:43:18 2007 +0000 @@ -227,7 +227,7 @@ gboolean independent, const char *attr_id, const char *attr_name, - PurpleValue *attr_value, ...); + PurpleValue *attr_value, ...) G_GNUC_NULL_TERMINATED; /** * Destroys a status type. @@ -270,7 +270,7 @@ * @param ... Additional attribute information. */ void purple_status_type_add_attrs(PurpleStatusType *status_type, const char *id, - const char *name, PurpleValue *value, ...); + const char *name, PurpleValue *value, ...) G_GNUC_NULL_TERMINATED; /** * Adds multiple attributes to a status type using a va_list. @@ -387,9 +387,9 @@ * * @param status_type The status type. * - * @return The list of attributes. + * @constreturn The list of attributes. */ -const GList *purple_status_type_get_attrs(const PurpleStatusType *status_type); +GList *purple_status_type_get_attrs(const PurpleStatusType *status_type); /** * Find the PurpleStatusType with the given id. @@ -515,10 +515,11 @@ * @param active The active state. * @param attrs A list of attributes to set on the status. This list is * composed of key/value pairs, where each key is a valid - * attribute name for this PurpleStatusType. + * attribute name for this PurpleStatusType. The list is + * not modified or freed by this function. */ void purple_status_set_active_with_attrs_list(PurpleStatus *status, gboolean active, - const GList *attrs); + GList *attrs); /** * Sets the boolean value of an attribute in a status with the specified ID. @@ -768,9 +769,10 @@ * Adds a list of statuses to the presence. * * @param presence The presence. - * @param source_list The source list of statuses to add. + * @param source_list The source list of statuses to add, which is not + * modified or freed by this function. */ -void purple_presence_add_list(PurplePresence *presence, const GList *source_list); +void purple_presence_add_list(PurplePresence *presence, GList *source_list); /** * Sets the active state of a status in a presence. @@ -869,9 +871,9 @@ * * @param presence The presence. * - * @return The statuses. + * @constreturn The statuses. */ -const GList *purple_presence_get_statuses(const PurplePresence *presence); +GList *purple_presence_get_statuses(const PurplePresence *presence); /** * Returns the status with the specified ID from a presence.
--- a/libpurple/util.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/util.c Wed Jun 27 21:43:18 2007 +0000 @@ -22,6 +22,7 @@ */ #include "internal.h" +#include "cipher.h" #include "conversation.h" #include "core.h" #include "debug.h" @@ -46,6 +47,7 @@ } website; char *url; + int num_times_redirected; gboolean full; char *user_agent; gboolean http11; @@ -64,8 +66,8 @@ unsigned long data_len; }; -static char custom_home_dir[MAXPATHLEN]; -static char home_dir[MAXPATHLEN] = ""; +static char *custom_user_dir = NULL; +static char *home_dir = NULL; PurpleMenuAction * purple_menu_action_new(const char *label, PurpleCallback callback, gpointer data, @@ -155,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 **************************************************************************/ @@ -504,10 +531,9 @@ } #endif -#ifndef HAVE_STRFTIME_Z_FORMAT -static const char *get_tmoff(const struct tm *tm) +const char *purple_get_tzoff_str(const struct tm *tm, gboolean iso) { - static char buf[6]; + static char buf[7]; long off; gint8 min; gint8 hrs; @@ -527,7 +553,7 @@ # else # ifdef HAVE_TIMEZONE tzset(); - off = -timezone; + off = -1 * timezone; # endif /* HAVE_TIMEZONE */ # endif /* !HAVE_TM_GMTOFF */ #endif /* _WIN32 */ @@ -535,12 +561,22 @@ min = (off / 60) % 60; hrs = ((off / 60) - min) / 60; - if (g_snprintf(buf, sizeof(buf), "%+03d%02d", hrs, ABS(min)) > 5) - g_return_val_if_reached(""); + if(iso) { + if (0 == off) { + strcpy(buf, "Z"); + } else { + /* please leave the colons...they're optional for iso, but jabber + * wants them */ + if(g_snprintf(buf, sizeof(buf), "%+03d:%02d", hrs, ABS(min)) > 6) + g_return_val_if_reached(""); + } + } else { + if (g_snprintf(buf, sizeof(buf), "%+03d%02d", hrs, ABS(min)) > 5) + g_return_val_if_reached(""); + } return buf; } -#endif /* Windows doesn't HAVE_STRFTIME_Z_FORMAT, but this seems clearer. -- rlaager */ #if !defined(HAVE_STRFTIME_Z_FORMAT) || defined(_WIN32) @@ -573,7 +609,7 @@ fmt ? fmt : "", c - start - 1, start, - get_tmoff(tm)); + purple_get_tzoff_str(tm, FALSE)); g_free(fmt); fmt = tmp; start = c + 1; @@ -986,7 +1022,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); @@ -1259,14 +1294,17 @@ pt->dest_tag = y; \ tags = g_list_prepend(tags, pt); \ } \ - xhtml = g_string_append(xhtml, "<" y); \ - c += strlen("<" x ); \ - xhtml = g_string_append(xhtml, innards->str); \ - xhtml = g_string_append_c(xhtml, '>'); \ + if(xhtml) { \ + xhtml = g_string_append(xhtml, "<" y); \ + xhtml = g_string_append(xhtml, innards->str); \ + xhtml = g_string_append_c(xhtml, '>'); \ + } \ c = p + 1; \ } else { \ - xhtml = g_string_append(xhtml, "<"); \ - plain = g_string_append_c(plain, '<'); \ + if(xhtml) \ + xhtml = g_string_append(xhtml, "<"); \ + if(plain) \ + plain = g_string_append_c(plain, '<'); \ c++; \ } \ g_string_free(innards, TRUE); \ @@ -1275,16 +1313,19 @@ if(!g_ascii_strncasecmp(c, "<" x, strlen("<" x)) && \ (*(c+strlen("<" x)) == '>' || \ !g_ascii_strncasecmp(c+strlen("<" x), "/>", 2))) { \ - xhtml = g_string_append(xhtml, "<" y); \ + if(xhtml) \ + xhtml = g_string_append(xhtml, "<" y); \ c += strlen("<" x); \ if(*c != '/') { \ struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1); \ pt->src_tag = x; \ pt->dest_tag = y; \ tags = g_list_prepend(tags, pt); \ - xhtml = g_string_append_c(xhtml, '>'); \ + if(xhtml) \ + xhtml = g_string_append_c(xhtml, '>'); \ } else { \ - xhtml = g_string_append(xhtml, "/>");\ + if(xhtml) \ + xhtml = g_string_append(xhtml, "/>");\ } \ c = strchr(c, '>') + 1; \ continue; \ @@ -1294,11 +1335,18 @@ purple_markup_html_to_xhtml(const char *html, char **xhtml_out, char **plain_out) { - GString *xhtml = g_string_new(""); - GString *plain = g_string_new(""); + GString *xhtml = NULL; + GString *plain = NULL; GList *tags = NULL, *tag; const char *c = html; + g_return_if_fail(xhtml_out != NULL || plain_out != NULL); + + if(xhtml_out) + xhtml = g_string_new(""); + if(plain_out) + plain = g_string_new(""); + while(c && *c) { if(*c == '<') { if(*(c+1) == '/') { /* closing tag */ @@ -1314,7 +1362,8 @@ if(tag) { while(tags) { struct purple_parse_tag *pt = tags->data; - g_string_append_printf(xhtml, "</%s>", pt->dest_tag); + if(xhtml) + g_string_append_printf(xhtml, "</%s>", pt->dest_tag); if(tags == tag) break; tags = g_list_remove(tags, pt); @@ -1332,8 +1381,10 @@ if(*end == '>') { c = end+1; } else { - xhtml = g_string_append(xhtml, "<"); - plain = g_string_append_c(plain, '<'); + if(xhtml) + xhtml = g_string_append(xhtml, "<"); + if(plain) + plain = g_string_append_c(plain, '<'); c++; } } @@ -1362,7 +1413,8 @@ ALLOW_TAG("span"); ALLOW_TAG("strong"); ALLOW_TAG("ul"); - + ALLOW_TAG("img"); + /* we skip <HR> because it's not legal in XHTML-IM. However, * we still want to send something sensible, so we put a * linebreak in its place. <BR> also needs special handling @@ -1373,18 +1425,54 @@ !g_ascii_strncasecmp(c+3, "/>", 2) || !g_ascii_strncasecmp(c+3, " />", 3))) { c = strchr(c, '>') + 1; - xhtml = g_string_append(xhtml, "<br/>"); - if(*c != '\n') + if(xhtml) + xhtml = g_string_append(xhtml, "<br/>"); + if(plain && *c != '\n') 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"; pt->dest_tag = "span"; tags = g_list_prepend(tags, pt); c = strchr(c, '>') + 1; - xhtml = g_string_append(xhtml, "<span style='font-weight: bold;'>"); + if(xhtml) + xhtml = g_string_append(xhtml, "<span style='font-weight: bold;'>"); continue; } if(!g_ascii_strncasecmp(c, "<u>", 3) || !g_ascii_strncasecmp(c, "<underline>", strlen("<underline>"))) { @@ -1393,7 +1481,8 @@ pt->dest_tag = "span"; tags = g_list_prepend(tags, pt); c = strchr(c, '>') + 1; - xhtml = g_string_append(xhtml, "<span style='text-decoration: underline;'>"); + if (xhtml) + xhtml = g_string_append(xhtml, "<span style='text-decoration: underline;'>"); continue; } if(!g_ascii_strncasecmp(c, "<s>", 3) || !g_ascii_strncasecmp(c, "<strike>", strlen("<strike>"))) { @@ -1402,7 +1491,8 @@ pt->dest_tag = "span"; tags = g_list_prepend(tags, pt); c = strchr(c, '>') + 1; - xhtml = g_string_append(xhtml, "<span style='text-decoration: line-through;'>"); + if(xhtml) + xhtml = g_string_append(xhtml, "<span style='text-decoration: line-through;'>"); continue; } if(!g_ascii_strncasecmp(c, "<sub>", 5)) { @@ -1411,7 +1501,8 @@ pt->dest_tag = "span"; tags = g_list_prepend(tags, pt); c = strchr(c, '>') + 1; - xhtml = g_string_append(xhtml, "<span style='vertical-align:sub;'>"); + if(xhtml) + xhtml = g_string_append(xhtml, "<span style='vertical-align:sub;'>"); continue; } if(!g_ascii_strncasecmp(c, "<sup>", 5)) { @@ -1420,7 +1511,8 @@ pt->dest_tag = "span"; tags = g_list_prepend(tags, pt); c = strchr(c, '>') + 1; - xhtml = g_string_append(xhtml, "<span style='vertical-align:super;'>"); + if(xhtml) + xhtml = g_string_append(xhtml, "<span style='vertical-align:super;'>"); continue; } if(!g_ascii_strncasecmp(c, "<font", 5) && (*(c+5) == '>' || *(c+5) == ' ')) { @@ -1534,7 +1626,8 @@ color = g_string_append_c(color, *q); q++; } - g_string_append_printf(xhtml, "<span style='background: %s;'>", g_strstrip(color->str)); + if(xhtml) + g_string_append_printf(xhtml, "<span style='background: %s;'>", g_strstrip(color->str)); g_string_free(color, TRUE); if ((c = strchr(c, '>')) != NULL) c++; @@ -1555,14 +1648,17 @@ if(!g_ascii_strncasecmp(c, "<!--", strlen("<!--"))) { char *p = strstr(c + strlen("<!--"), "-->"); if(p) { - xhtml = g_string_append(xhtml, "<!--"); + if(xhtml) + xhtml = g_string_append(xhtml, "<!--"); c += strlen("<!--"); continue; } } - xhtml = g_string_append(xhtml, "<"); - plain = g_string_append_c(plain, '<'); + if(xhtml) + xhtml = g_string_append(xhtml, "<"); + if(plain) + plain = g_string_append_c(plain, '<'); c++; } } else if(*c == '&') { @@ -1575,29 +1671,31 @@ g_snprintf(buf, sizeof(buf), "%c", *c); pln = buf; } - xhtml = g_string_append_len(xhtml, c, len); - plain = g_string_append(plain, pln); + if(xhtml) + xhtml = g_string_append_len(xhtml, c, len); + if(plain) + plain = g_string_append(plain, pln); c += len; } else { - xhtml = g_string_append_c(xhtml, *c); - plain = g_string_append_c(plain, *c); + if(xhtml) + xhtml = g_string_append_c(xhtml, *c); + if(plain) + plain = g_string_append_c(plain, *c); c++; } } - tag = tags; - while(tag) { - struct purple_parse_tag *pt = tag->data; - if(!pt->ignore) - g_string_append_printf(xhtml, "</%s>", pt->dest_tag); - tag = tag->next; + if(xhtml) { + for (tag = tags; tag ; tag = tag->next) { + struct purple_parse_tag *pt = tag->data; + if(!pt->ignore) + g_string_append_printf(xhtml, "</%s>", pt->dest_tag); + } } g_list_free(tags); if(xhtml_out) - *xhtml_out = g_strdup(xhtml->str); + *xhtml_out = g_string_free(xhtml, FALSE); if(plain_out) - *plain_out = g_strdup(plain->str); - g_string_free(xhtml, TRUE); - g_string_free(plain, TRUE); + *plain_out = g_string_free(plain, FALSE); } /* The following are probably reasonable changes: @@ -2307,27 +2405,22 @@ const char * purple_user_dir(void) { - if (custom_home_dir != NULL && *custom_home_dir) { - strcpy ((char*) &home_dir, (char*) &custom_home_dir); - } else if (!*home_dir) { - const gchar *hd = purple_home_dir(); - - if (hd) { - g_strlcpy((char*) &home_dir, hd, sizeof(home_dir)); - g_strlcat((char*) &home_dir, G_DIR_SEPARATOR_S ".purple", - sizeof(home_dir)); - } - } + if (custom_user_dir != NULL) + return custom_user_dir; + else if (!home_dir) + home_dir = g_build_filename(purple_home_dir(), ".purple", NULL); return home_dir; } void purple_util_set_user_dir(const char *dir) { - if (dir != NULL && strlen(dir) > 0) { - g_strlcpy((char*) &custom_home_dir, dir, - sizeof(custom_home_dir)); - } + g_free(custom_user_dir); + + if (dir != NULL && *dir) + custom_user_dir = g_strdup(dir); + else + custom_user_dir = NULL; } int purple_build_dir (const char *path, int mode) @@ -2621,19 +2714,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) { @@ -3016,7 +3139,7 @@ char * purple_str_size_to_units(size_t size) { - static const char *size_str[4] = { "bytes", "KB", "MB", "GB" }; + static const char *size_str[4] = { "bytes", "KiB", "MiB", "GiB" }; float size_mag; int size_index = 0; @@ -3186,6 +3309,17 @@ g_hash_table_destroy(params); } +/* + * TODO: Should probably add a "gboolean *ret_ishttps" parameter that + * is set to TRUE if this URL is https, otherwise it is set to + * FALSE. But that change will break the API. + * + * This is important for Yahoo! web messenger login. They now + * force https login, and if you access the web messenger login + * page via http then it redirects you to the https version, but + * purple_util_fetch_url() ignores the "https" and attempts to + * fetch the URL via http again, which gets redirected again. + */ gboolean purple_url_parse(const char *url, char **ret_host, int *ret_port, char **ret_path, char **ret_user, char **ret_passwd) @@ -3206,12 +3340,16 @@ g_return_val_if_fail(url != NULL, FALSE); - if ((turl = strstr(url, "http://")) != NULL || - (turl = strstr(url, "HTTP://")) != NULL) + if ((turl = purple_strcasestr(url, "http://")) != NULL) { turl += 7; url = turl; } + else if ((turl = purple_strcasestr(url, "https://")) != NULL) + { + turl += 8; + url = turl; + } /* parse out authentication information if supplied */ /* Only care about @ char BEFORE the first / */ @@ -3291,85 +3429,92 @@ PurpleUtilFetchUrlData *gfud) { gchar *s; - - if ((s = g_strstr_len(data, data_len, "Location: ")) != NULL) + gchar *new_url, *temp_url, *end; + gboolean full; + int len; + + if ((s = g_strstr_len(data, data_len, "Location: ")) == NULL) + /* We're not being redirected */ + return FALSE; + + s += strlen("Location: "); + end = strchr(s, '\r'); + + /* Just in case :) */ + if (end == NULL) + end = strchr(s, '\n'); + + if (end == NULL) + return FALSE; + + len = end - s; + + new_url = g_malloc(len + 1); + strncpy(new_url, s, len); + new_url[len] = '\0'; + + full = gfud->full; + + if (*new_url == '/' || g_strstr_len(new_url, len, "://") == NULL) { - gchar *new_url, *temp_url, *end; - gboolean full; - int len; - - s += strlen("Location: "); - end = strchr(s, '\r'); - - /* Just in case :) */ - if (end == NULL) - end = strchr(s, '\n'); - - if (end == NULL) - return FALSE; - - len = end - s; - - new_url = g_malloc(len + 1); - strncpy(new_url, s, len); - new_url[len] = '\0'; - - full = gfud->full; - - if (*new_url == '/' || g_strstr_len(new_url, len, "://") == NULL) - { - temp_url = new_url; - - new_url = g_strdup_printf("%s:%d%s", gfud->website.address, - gfud->website.port, temp_url); - - g_free(temp_url); - - full = FALSE; - } - - purple_debug_info("util", "Redirecting to %s\n", new_url); - - /* - * Try again, with this new location. This code is somewhat - * ugly, but we need to reuse the gfud because whoever called - * us is holding a reference to it. - */ - g_free(gfud->url); - gfud->url = new_url; - gfud->full = full; - g_free(gfud->request); - gfud->request = NULL; - - purple_input_remove(gfud->inpa); - gfud->inpa = 0; - close(gfud->fd); - gfud->fd = -1; - gfud->request_written = 0; - gfud->len = 0; - gfud->data_len = 0; - - g_free(gfud->website.user); - g_free(gfud->website.passwd); - g_free(gfud->website.address); - g_free(gfud->website.page); - purple_url_parse(new_url, &gfud->website.address, &gfud->website.port, - &gfud->website.page, &gfud->website.user, &gfud->website.passwd); - - gfud->connect_data = purple_proxy_connect(NULL, NULL, - gfud->website.address, gfud->website.port, - url_fetch_connect_cb, gfud); - - if (gfud->connect_data == NULL) - { - purple_util_fetch_url_error(gfud, _("Unable to connect to %s"), - gfud->website.address); - } - + temp_url = new_url; + + new_url = g_strdup_printf("%s:%d%s", gfud->website.address, + gfud->website.port, temp_url); + + g_free(temp_url); + + full = FALSE; + } + + purple_debug_info("util", "Redirecting to %s\n", new_url); + + gfud->num_times_redirected++; + if (gfud->num_times_redirected >= 5) + { + purple_util_fetch_url_error(gfud, + _("Could not open %s: Redirected too many times"), + gfud->url); return TRUE; } - return FALSE; + /* + * Try again, with this new location. This code is somewhat + * ugly, but we need to reuse the gfud because whoever called + * us is holding a reference to it. + */ + g_free(gfud->url); + gfud->url = new_url; + gfud->full = full; + g_free(gfud->request); + gfud->request = NULL; + + purple_input_remove(gfud->inpa); + gfud->inpa = 0; + close(gfud->fd); + gfud->fd = -1; + gfud->request_written = 0; + gfud->len = 0; + gfud->data_len = 0; + + g_free(gfud->website.user); + g_free(gfud->website.passwd); + g_free(gfud->website.address); + g_free(gfud->website.page); + purple_url_parse(new_url, &gfud->website.address, &gfud->website.port, + &gfud->website.page, &gfud->website.user, &gfud->website.passwd); + + gfud->connect_data = purple_proxy_connect(NULL, NULL, + gfud->website.address, gfud->website.port, + url_fetch_connect_cb, gfud); + + if (gfud->connect_data == NULL) + { + purple_util_fetch_url_error(gfud, _("Unable to connect to %s"), + gfud->website.address); + } + + return TRUE; } static size_t
--- a/libpurple/util.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/util.h Wed Jun 27 21:43:18 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); + + /*@}*/ /**************************************************************************/ @@ -241,6 +257,16 @@ const char *purple_utf8_strftime(const char *format, const struct tm *tm); /** + * Gets a string representation of the local timezone offset + * + * @param tm The time to get the timezone for + * @param iso TRUE to format the offset according to ISO-8601, FALSE to + * not substitute 'Z' for 0 offset, and to not separate + * hours and minutes with a colon. + */ +const char *purple_get_tzoff_str(const struct tm *tm, gboolean iso); + +/** * Formats a time into the user's preferred short date format. * * The returned string is stored in a static buffer, so the result @@ -608,6 +634,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/whiteboard.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/whiteboard.c Wed Jun 27 21:43:18 2007 +0000 @@ -23,6 +23,7 @@ #include <string.h> +#include "internal.h" #include "whiteboard.h" #include "prpl.h"
--- a/libpurple/win32/global.mak Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/win32/global.mak Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/win32/libc_interface.c Wed Jun 27 21:43:18 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/win32/win32dep.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/win32/win32dep.c Wed Jun 27 21:43:18 2007 +0000 @@ -32,6 +32,7 @@ #include <glib.h> #include <glib/gstdio.h> +#include "internal.h" #include "debug.h" #include "notify.h"
--- a/libpurple/xmlnode.c Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/xmlnode.c Wed Jun 27 21:43:18 2007 +0000 @@ -268,6 +268,22 @@ g_return_if_fail(node != NULL); + /* if we're part of a tree, remove ourselves from the tree first */ + if(NULL != node->parent) { + if(node->parent->child == node) { + node->parent->child = node->next; + } else { + xmlnode *prev = node->parent->child; + while(prev && prev->next != node) { + prev = prev->next; + } + if(prev) { + prev->next = node->next; + } + } + } + + /* now free our children */ x = node->child; while(x) { y = x->next; @@ -275,6 +291,7 @@ x = y; } + /* now dispose of ourselves */ g_free(node->name); g_free(node->data); g_free(node->xmlns); @@ -333,8 +350,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 +362,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) { @@ -519,38 +549,38 @@ } static xmlSAXHandler xmlnode_parser_libxml = { - .internalSubset = NULL, - .isStandalone = NULL, - .hasInternalSubset = NULL, - .hasExternalSubset = NULL, - .resolveEntity = NULL, - .getEntity = NULL, - .entityDecl = NULL, - .notationDecl = NULL, - .attributeDecl = NULL, - .elementDecl = NULL, - .unparsedEntityDecl = NULL, - .setDocumentLocator = NULL, - .startDocument = NULL, - .endDocument = NULL, - .startElement = NULL, - .endElement = NULL, - .reference = NULL, - .characters = xmlnode_parser_element_text_libxml, - .ignorableWhitespace = NULL, - .processingInstruction = NULL, - .comment = NULL, - .warning = NULL, - .error = xmlnode_parser_error_libxml, - .fatalError = NULL, - .getParameterEntity = NULL, - .cdataBlock = NULL, - .externalSubset = NULL, - .initialized = XML_SAX2_MAGIC, - ._private = NULL, - .startElementNs = xmlnode_parser_element_start_libxml, - .endElementNs = xmlnode_parser_element_end_libxml, - .serror = NULL + NULL, /* internalSubset */ + NULL, /* isStandalone */ + NULL, /* hasInternalSubset */ + NULL, /* hasExternalSubset */ + NULL, /* resolveEntity */ + NULL, /* getEntity */ + NULL, /* entityDecl */ + NULL, /* notationDecl */ + NULL, /* attributeDecl */ + NULL, /* elementDecl */ + NULL, /* unparsedEntityDecl */ + NULL, /* setDocumentLocator */ + NULL, /* startDocument */ + NULL, /* endDocument */ + NULL, /* startElement */ + NULL, /* endElement */ + NULL, /* reference */ + xmlnode_parser_element_text_libxml, /* characters */ + NULL, /* ignorableWhitespace */ + NULL, /* processingInstruction */ + NULL, /* comment */ + NULL, /* warning */ + xmlnode_parser_error_libxml, /* error */ + NULL, /* fatalError */ + NULL, /* getParameterEntity */ + NULL, /* cdataBlock */ + NULL, /* externalSubset */ + XML_SAX2_MAGIC, /* initialized */ + NULL, /* _private */ + xmlnode_parser_element_start_libxml, /* startElementNs */ + xmlnode_parser_element_end_libxml, /* endElementNs */ + NULL, /* serror */ }; xmlnode *
--- a/libpurple/xmlnode.h Sun Jun 03 09:40:38 2007 +0000 +++ b/libpurple/xmlnode.h Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin.spec.in Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/Makefile.am Wed Jun 27 21:43:18 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 \ @@ -193,6 +197,7 @@ -DLIBDIR=\"$(libdir)/pidgin/\" \ -DLOCALEDIR=\"$(datadir)/locale\" \ -DSYSCONFDIR=\"$(sysconfdir)\" \ + -I$(top_builddir)/libpurple \ -I$(top_srcdir)/libpurple/ \ $(GLIB_CFLAGS) \ $(GSTREAMER_CFLAGS) \
--- a/pidgin/Makefile.mingw Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/Makefile.mingw Wed Jun 27 21:43:18 2007 +0000 @@ -85,6 +85,7 @@ gtkscrollbook.c \ gtksound.c \ gtksourceiter.c \ + gtksourceundomanager.c \ gtkstatusbox.c \ gtkthemes.c \ gtkutils.c \
--- a/pidgin/eggtrayicon.c Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/eggtrayicon.c Wed Jun 27 21:43:18 2007 +0000 @@ -152,6 +152,26 @@ } } +static Display * +egg_tray_icon_get_x_display(EggTrayIcon *icon) +{ + Display *xdisplay = NULL; + +#if GTK_CHECK_VERSION(2,1,0) + { + GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (icon)); + if (!GDK_IS_DISPLAY (display)) + display = gdk_display_get_default (); + + xdisplay = GDK_DISPLAY_XDISPLAY (display); + } +#else + xdisplay = gdk_display; +#endif + + return xdisplay; +} + static void egg_tray_icon_get_orientation_property (EggTrayIcon *icon) { @@ -168,11 +188,10 @@ g_return_if_fail(icon->manager_window != None); -#if GTK_CHECK_VERSION(2,1,0) - xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon))); -#else - xdisplay = gdk_display; -#endif + xdisplay = egg_tray_icon_get_x_display(icon); + + if (xdisplay == NULL) + return; gdk_error_trap_push (); type = None; @@ -321,11 +340,10 @@ if (icon->manager_window != None) return; -#if GTK_CHECK_VERSION(2,1,0) - xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon))); -#else - xdisplay = gdk_display; -#endif + xdisplay = egg_tray_icon_get_x_display(icon); + + if (xdisplay == NULL) + return; XGrabServer (xdisplay); @@ -424,12 +442,15 @@ make_transparent (widget, NULL); + xdisplay = egg_tray_icon_get_x_display(icon); + + if (xdisplay == NULL) + return; + #if GTK_CHECK_VERSION(2,1,0) screen = gdk_screen_get_number (gtk_widget_get_screen (widget)); - xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (widget)); #else screen = XScreenNumberOfScreen (DefaultScreenOfDisplay (gdk_display)); - xdisplay = gdk_display; #endif /* Now see if there's a manager window around */ @@ -519,11 +540,10 @@ XClientMessageEvent ev; Display *xdisplay; -#if GTK_CHECK_VERSION(2,1,0) - xdisplay = GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (GTK_WIDGET (icon))); -#else - xdisplay = gdk_display; -#endif + xdisplay = egg_tray_icon_get_x_display(icon); + + if (xdisplay == NULL) + return; ev.type = ClientMessage; ev.window = (Window)gtk_plug_get_id (GTK_PLUG (icon));
--- a/pidgin/gtkaccount.c Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtkaccount.c Wed Jun 27 21:43:18 2007 +0000 @@ -184,6 +184,7 @@ gtk_size_group_add_widget(dialog->sg, label); gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), widget); gtk_widget_show(label); gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, PIDGIN_HIG_BORDER); @@ -426,16 +427,18 @@ gtk_widget_ref(dialog->protocol_menu); } - hbox = add_pref_box(dialog, vbox, _("Protocol:"), dialog->protocol_menu); + hbox = add_pref_box(dialog, vbox, _("Pro_tocol:"), dialog->protocol_menu); g_object_set_data(G_OBJECT(dialog->protocol_menu), "container", hbox); gtk_widget_unref(dialog->protocol_menu); /* Screen name */ dialog->screenname_entry = gtk_entry_new(); +#if GTK_CHECK_VERSION(2,10,0) g_object_set(G_OBJECT(dialog->screenname_entry), "truncate-multiline", TRUE, NULL); - - add_pref_box(dialog, vbox, _("Screen name:"), dialog->screenname_entry); +#endif + + add_pref_box(dialog, vbox, _("Screen _name:"), dialog->screenname_entry); g_signal_connect(G_OBJECT(dialog->screenname_entry), "changed", G_CALLBACK(screenname_changed_cb), dialog); @@ -458,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(); @@ -477,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) { @@ -497,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) @@ -517,16 +523,16 @@ gtk_entry_set_visibility(GTK_ENTRY(dialog->password_entry), FALSE); if (gtk_entry_get_invisible_char(GTK_ENTRY(dialog->password_entry)) == '*') gtk_entry_set_invisible_char(GTK_ENTRY(dialog->password_entry), PIDGIN_INVISIBLE_CHAR); - dialog->password_box = add_pref_box(dialog, vbox, _("Password:"), + dialog->password_box = add_pref_box(dialog, vbox, _("_Password:"), dialog->password_entry); /* 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 = - gtk_check_button_new_with_label(_("Remember password")); + gtk_check_button_new_with_mnemonic(_("Remember pass_word")); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(dialog->remember_pass_check), FALSE); gtk_box_pack_start(GTK_BOX(vbox), dialog->remember_pass_check, @@ -597,12 +603,12 @@ /* New mail notifications */ dialog->new_mail_check = - gtk_check_button_new_with_label(_("New mail notifications")); + gtk_check_button_new_with_mnemonic(_("New _mail notifications")); gtk_box_pack_start(GTK_BOX(vbox), dialog->new_mail_check, FALSE, FALSE, 0); gtk_widget_show(dialog->new_mail_check); /* Buddy icon */ - dialog->icon_check = gtk_check_button_new_with_label(_("Use this buddy icon for this account:")); + dialog->icon_check = gtk_check_button_new_with_mnemonic(_("Use this buddy _icon for this account:")); g_signal_connect(G_OBJECT(dialog->icon_check), "toggled", G_CALLBACK(icon_check_cb), dialog); gtk_widget_show(dialog->icon_check); gtk_box_pack_start(GTK_BOX(vbox), dialog->icon_check, FALSE, FALSE, 0); @@ -691,7 +697,7 @@ PurpleAccountOption *option; PurpleAccount *account; GtkWidget *frame, *vbox, *check, *entry, *combo, *menu, *item; - const GList *list, *node; + GList *list, *node; gint i, idx, int_value; GtkListStore *model; GtkTreeIter iter; @@ -699,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) { @@ -756,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); @@ -789,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); @@ -826,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); @@ -895,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); @@ -1464,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); @@ -2319,7 +2315,6 @@ GtkWidget *button; int width, height; - if (accounts_window != NULL) { gtk_window_present(GTK_WINDOW(accounts_window->window)); return; @@ -2330,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); @@ -2694,3 +2686,4 @@ purple_signals_unregister_by_instance(pidgin_account_get_handle()); } +
--- a/pidgin/gtkblist.c Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtkblist.c Wed Jun 27 21:43:18 2007 +0000 @@ -108,6 +108,7 @@ static guint visibility_manager_count = 0; static gboolean gtk_blist_obscured = FALSE; +static gboolean editing_blist = FALSE; static GList *pidgin_blist_sort_methods = NULL; static struct pidgin_blist_sort_method *current_sort_method = NULL; @@ -150,11 +151,7 @@ if (!gtkblist) return "dim grey"; if (!dim_grey_string[0]) { - GtkStyle *style = gtk_widget_get_style(gtkblist->treeview); - snprintf(dim_grey_string, sizeof(dim_grey_string), "#%02x%02x%02x", - style->text_aa[GTK_STATE_NORMAL].red >> 8, - style->text_aa[GTK_STATE_NORMAL].green >> 8, - style->text_aa[GTK_STATE_NORMAL].blue >> 8); + snprintf(dim_grey_string, sizeof(dim_grey_string), "%s", pidgin_get_dim_grey_string(gtkblist->treeview)); } return dim_grey_string; } @@ -177,6 +174,7 @@ static gboolean gtk_blist_window_state_cb(GtkWidget *w, GdkEventWindowState *event, gpointer data) { +#if GTK_CHECK_VERSION(2,2,0) if(event->changed_mask & GDK_WINDOW_STATE_WITHDRAWN) { if(event->new_window_state & GDK_WINDOW_STATE_WITHDRAWN) purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", FALSE); @@ -198,6 +196,28 @@ if (!(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED)) pidgin_blist_refresh_timer(purple_get_blist()); } +#else + /* At least gtk+ 2.0.6 does not properly set the change_mask when unsetting a + * GdkWindowState flag. To work around, the window state will be explicitly + * queried on these older versions of gtk+. See pidgin ticket #739. + */ + GdkWindowState new_window_state = gdk_window_get_state(G_OBJECT(gtkblist->window->window)); + + if(new_window_state & GDK_WINDOW_STATE_WITHDRAWN) { + purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", FALSE); + } else { + purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_visible", TRUE); + pidgin_blist_refresh_timer(purple_get_blist()); + } + + if(new_window_state & GDK_WINDOW_STATE_MAXIMIZED) + purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", TRUE); + else + purple_prefs_set_bool(PIDGIN_PREFS_ROOT "/blist/list_maximized", FALSE); + + if (!(new_window_state & GDK_WINDOW_STATE_ICONIFIED)) + pidgin_blist_refresh_timer(purple_get_blist()); +#endif return FALSE; } @@ -266,20 +286,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) @@ -301,15 +314,30 @@ static void gtk_blist_join_chat(PurpleChat *chat) { PurpleConversation *conv; - - conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, - purple_chat_get_name(chat), + PurplePluginProtocolInfo *prpl_info; + const char *name; + char *chat_name; + + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(chat->account))); + + if (prpl_info && prpl_info->get_chat_name) + chat_name = prpl_info->get_chat_name(chat->components); + else + chat_name = NULL; + + if (chat_name) + name = chat_name; + else + name = purple_chat_get_name(chat); + + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, name, chat->account); if (conv != NULL) purple_conversation_present(conv); serv_join_chat(chat->account->gc, chat->components); + g_free(chat_name); } static void gtk_blist_menu_join_cb(GtkWidget *w, PurpleChat *chat) @@ -317,6 +345,13 @@ gtk_blist_join_chat(chat); } +#if GTK_CHECK_VERSION(2,6,0) +static void gtk_blist_renderer_editing_cancelled_cb(GtkCellRenderer *renderer, PurpleBuddyList *list) +{ + editing_blist = FALSE; + pidgin_blist_refresh(list); +} + static void gtk_blist_renderer_editing_started_cb(GtkCellRenderer *renderer, GtkCellEditable *editable, gchar *path_str, @@ -353,10 +388,12 @@ GtkEntry *entry = GTK_ENTRY (editable); gtk_entry_set_text(entry, text); } -} + editing_blist = TRUE; +} +#endif static void gtk_blist_renderer_edited_cb(GtkCellRendererText *text_rend, char *arg1, - char *arg2, gpointer nada) + char *arg2, PurpleBuddyList *list) { GtkTreeIter iter; GtkTreePath *path; @@ -364,6 +401,7 @@ PurpleBlistNode *node; PurpleGroup *dest; + editing_blist = FALSE; path = gtk_tree_path_new_from_string (arg1); gtk_tree_model_get_iter (GTK_TREE_MODEL(gtkblist->treemodel), &iter, path); gtk_tree_path_free (path); @@ -408,6 +446,7 @@ default: break; } + pidgin_blist_refresh(list); } static void gtk_blist_menu_alias_cb(GtkWidget *w, PurpleBlistNode *node) @@ -488,6 +527,11 @@ pidgin_syslog_show(); } +static void gtk_blist_show_onlinehelp_cb() +{ + purple_notify_uri(NULL, PURPLE_WEBSITE "documentation"); +} + static void do_join_chat(PidginJoinChatData *data) { @@ -1124,10 +1168,10 @@ if (((PurpleBlistNode*)buddy)->parent->child->next && !sub && !contact_expanded) { pidgin_separator(menu); pidgin_append_blist_node_privacy_menu(menu, (PurpleBlistNode *)buddy); - pidgin_new_item_from_stock(menu, _("Alias..."), PIDGIN_STOCK_ALIAS, + pidgin_new_item_from_stock(menu, _("_Alias..."), PIDGIN_STOCK_ALIAS, G_CALLBACK(gtk_blist_menu_alias_cb), contact, 0, 0, NULL); - pidgin_new_item_from_stock(menu, _("Remove"), GTK_STOCK_REMOVE, + pidgin_new_item_from_stock(menu, _("_Remove"), GTK_STOCK_REMOVE, G_CALLBACK(pidgin_blist_remove_cb), contact, 0, 0, NULL); } else if (!sub || contact_expanded) { @@ -1142,7 +1186,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 +1214,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 +1468,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; } @@ -2083,51 +2128,6 @@ } } -static void -roundify(GdkPixbuf *pixbuf) { - int width, height, rowstride; - guchar *pixels; - - if (!gdk_pixbuf_get_has_alpha(pixbuf)) - return; - - width = gdk_pixbuf_get_width(pixbuf); - height = gdk_pixbuf_get_height(pixbuf); - rowstride = gdk_pixbuf_get_rowstride(pixbuf); - pixels = gdk_pixbuf_get_pixels(pixbuf); - - if (width < 6 || height < 6) - return; - - /* Top left */ - pixels[3] = 0; - pixels[7] = 0x80; - pixels[11] = 0xC0; - pixels[rowstride + 3] = 0x80; - pixels[rowstride * 2 + 3] = 0xC0; - - /* Top right */ - pixels[width * 4 - 1] = 0; - pixels[width * 4 - 5] = 0x80; - pixels[width * 4 - 9] = 0xC0; - pixels[rowstride + (width * 4) - 1] = 0x80; - pixels[(2 * rowstride) + (width * 4) - 1] = 0xC0; - - /* Bottom left */ - pixels[(height - 1) * rowstride + 3] = 0; - pixels[(height - 1) * rowstride + 7] = 0x80; - pixels[(height - 1) * rowstride + 11] = 0xC0; - pixels[(height - 2) * rowstride + 3] = 0x80; - pixels[(height - 3) * rowstride + 3] = 0xC0; - - /* Bottom right */ - pixels[height * rowstride - 1] = 0; - pixels[(height - 1) * rowstride - 1] = 0x80; - pixels[(height - 2) * rowstride - 1] = 0xC0; - pixels[height * rowstride - 5] = 0x80; - pixels[height * rowstride - 9] = 0xC0; -} - /* Altered from do_colorshift in gnome-panel */ static void do_alphashift (GdkPixbuf *dest, GdkPixbuf *src, int shift) @@ -2264,7 +2264,7 @@ gdk_pixbuf_fill(ret, 0x00000000); gdk_pixbuf_scale(buf, ret, (32-scale_width)/2, (32-scale_height)/2, scale_width, scale_height, (32-scale_width)/2, (32-scale_height)/2, (double)scale_width/(double)orig_width, (double)scale_height/(double)orig_height, GDK_INTERP_BILINEAR); if (pidgin_gdk_pixbuf_is_opaque(ret)) - roundify(ret); + pidgin_gdk_pixbuf_make_round(ret); } else { ret = gdk_pixbuf_scale_simple(buf,scale_width,scale_height, GDK_INTERP_BILINEAR); } @@ -2851,7 +2851,6 @@ { N_("/Buddies/Add C_hat..."), NULL, pidgin_blist_add_chat_cb, 0, "<StockItem>", GTK_STOCK_ADD }, { N_("/Buddies/Add _Group..."), NULL, purple_blist_request_add_group, 0, "<StockItem>", GTK_STOCK_ADD }, { "/Buddies/sep3", NULL, NULL, 0, "<Separator>", NULL }, - { N_("/Buddies/_About Pidgin"), NULL, pidgin_dialogs_about, 0, "<Item>", NULL }, { N_("/Buddies/_Quit"), "<CTL>Q", purple_core_quit, 0, "<StockItem>", GTK_STOCK_QUIT }, /* Accounts menu */ @@ -2868,9 +2867,13 @@ { N_("/Tools/_File Transfers"), "<CTL>T", pidgin_xfer_dialog_show, 0, "<Item>", NULL }, { N_("/Tools/R_oom List"), NULL, pidgin_roomlist_dialog_show, 0, "<Item>", NULL }, { N_("/Tools/System _Log"), NULL, gtk_blist_show_systemlog_cb, 0, "<Item>", NULL }, - { N_("/Tools/_Debug Window"), NULL, toggle_debug, 0, "<Item>", NULL }, { "/Tools/sep3", NULL, NULL, 0, "<Separator>", NULL }, { N_("/Tools/Mute _Sounds"), "<CTL>S", pidgin_blist_mute_sounds_cb, 0, "<CheckItem>", NULL }, + /* Help */ + { N_("/_Help"), NULL, NULL, 0, "<Branch>", NULL }, + { N_("/Help/Online _Help"), "F1", gtk_blist_show_onlinehelp_cb, 0, "<StockItem>", GTK_STOCK_HELP }, + { N_("/Help/_Debug Window"), NULL, toggle_debug, 0, "<Item>", NULL }, + { N_("/Help/_About"), NULL, pidgin_dialogs_about, 0, "<Item>", NULL }, }; /********************************************************* @@ -2996,7 +2999,15 @@ signon = purple_presence_get_login_time(presence); if (full && PURPLE_BUDDY_IS_ONLINE(b) && signon > 0) { - tmp = purple_str_seconds_to_string(time(NULL) - signon); + if (signon > time(NULL)) { + /* + * They signed on in the future?! Our local clock + * must be wrong, show the actual date instead of + * "4 days", etc. + */ + tmp = g_strdup(purple_date_format_long(localtime(&signon))); + } else + tmp = purple_str_seconds_to_string(time(NULL) - signon); purple_notify_user_info_add_pair(user_info, _("Logged In"), tmp); g_free(tmp); } @@ -3263,7 +3274,7 @@ return ret; } -static gchar *pidgin_blist_get_name_markup(PurpleBuddy *b, gboolean selected) +gchar *pidgin_blist_get_name_markup(PurpleBuddy *b, gboolean selected, gboolean aliased) { const char *name; char *esc, *text = NULL; @@ -3288,15 +3299,19 @@ } /* XXX Good luck cleaning up this crap */ - - contact = (PurpleContact*)((PurpleBlistNode*)b)->parent; - if(contact) - gtkcontactnode = ((PurpleBlistNode*)contact)->ui_data; - - if(gtkcontactnode && !gtkcontactnode->contact_expanded && contact->alias) - name = contact->alias; - else - name = purple_buddy_get_alias(b); + if (aliased) { + contact = (PurpleContact*)((PurpleBlistNode*)b)->parent; + if(contact) + gtkcontactnode = ((PurpleBlistNode*)contact)->ui_data; + + if(gtkcontactnode && !gtkcontactnode->contact_expanded && contact->alias) + name = contact->alias; + else + name = purple_buddy_get_alias(b); + } else { + name = b->name; + } + esc = g_markup_escape_text(name, strlen(name)); presence = purple_buddy_get_presence(b); @@ -3735,8 +3750,11 @@ gboolean pidgin_blist_node_is_contact_expanded(PurpleBlistNode *node) { - if PURPLE_BLIST_NODE_IS_BUDDY(node) + if (PURPLE_BLIST_NODE_IS_BUDDY(node)) { node = node->parent; + if (node == NULL) + return FALSE; + } g_return_val_if_fail(PURPLE_BLIST_NODE_IS_CONTACT(node), FALSE); @@ -3837,15 +3855,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; } @@ -4217,9 +4237,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; @@ -4441,8 +4459,11 @@ #endif "markup", NAME_COLUMN, NULL); +#if GTK_CHECK_VERSION(2,6,0) g_signal_connect(G_OBJECT(rend), "editing-started", G_CALLBACK(gtk_blist_renderer_editing_started_cb), NULL); - g_signal_connect(G_OBJECT(rend), "edited", G_CALLBACK(gtk_blist_renderer_edited_cb), NULL); + g_signal_connect(G_OBJECT(rend), "editing-canceled", G_CALLBACK(gtk_blist_renderer_editing_cancelled_cb), list); +#endif + g_signal_connect(G_OBJECT(rend), "edited", G_CALLBACK(gtk_blist_renderer_edited_cb), list); g_object_set(rend, "ypad", 0, "yalign", 0.5, NULL); #if GTK_CHECK_VERSION(2,6,0) g_object_set(rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL); @@ -4737,8 +4758,15 @@ NODE_COLUMN, &new_selection, -1); } - /* we set this up as a timeout, otherwise the blist flickers */ - g_timeout_add(0, (GSourceFunc)do_selection_changed, new_selection); + /* we set this up as a timeout, otherwise the blist flickers ... + * but we don't do it for groups, because it causes total bizarness - + * the previously selected buddy node might rendered at half height. + */ + if ((new_selection != NULL) && PURPLE_BLIST_NODE_IS_GROUP(new_selection)) { + do_selection_changed(new_selection); + } else { + g_timeout_add(0, (GSourceFunc)do_selection_changed, new_selection); + } } static gboolean insert_node(PurpleBuddyList *list, PurpleBlistNode *node, GtkTreeIter *iter) @@ -4777,9 +4805,10 @@ gtk_tree_path_free(newpath); - gtk_tree_store_set(gtkblist->treemodel, iter, - NODE_COLUMN, node, - -1); + if (!editing_blist) + gtk_tree_store_set(gtkblist->treemodel, iter, + NODE_COLUMN, node, + -1); if(node->parent) { GtkTreePath *expand = NULL; @@ -4812,6 +4841,9 @@ g_return_if_fail(node != NULL); + if (editing_blist) + return; + if (PURPLE_BLIST_NODE_IS_GROUP(node)) gnode = node; else if (PURPLE_BLIST_NODE_IS_BUDDY(node)) @@ -4913,6 +4945,9 @@ gboolean biglist = purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons"); presence = purple_buddy_get_presence(buddy); + if (editing_blist) + return; + status = pidgin_blist_get_status_icon((PurpleBlistNode*)buddy, PIDGIN_STATUS_ICON_SMALL); @@ -4930,7 +4965,7 @@ } emblem = pidgin_blist_get_emblem((PurpleBlistNode*) buddy); - mark = pidgin_blist_get_name_markup(buddy, selected); + mark = pidgin_blist_get_name_markup(buddy, selected, TRUE); if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/blist/show_idle_time") && purple_presence_is_idle(presence) && @@ -4992,6 +5027,9 @@ PurpleBuddy *buddy; struct _pidgin_blist_node *gtknode; + if (editing_blist) + return; + if (PURPLE_BLIST_NODE_IS_BUDDY(node)) cnode = node->parent; else @@ -5088,6 +5126,9 @@ g_return_if_fail(PURPLE_BLIST_NODE_IS_CHAT(node)); + if (editing_blist) + return; + /* First things first, update the group */ pidgin_blist_update_group(list, node->parent); @@ -5399,12 +5440,25 @@ gtk_container_set_border_width(GTK_CONTAINER(table), 0); gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); - label = gtk_label_new(_("Screen name:")); + /* Set up stuff for the account box */ + label = gtk_label_new_with_mnemonic(_("_Account:")); gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 0, 1); + data->account_box = pidgin_account_option_menu_new(account, FALSE, + G_CALLBACK(add_buddy_select_account_cb), NULL, data); + + gtk_table_attach_defaults(GTK_TABLE(table), data->account_box, 1, 2, 0, 1); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), data->account_box); + pidgin_set_accessible_label (data->account_box, label); + /* End of account box */ + + label = gtk_label_new_with_mnemonic(_("_Screen name:")); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2); + data->entry = gtk_entry_new(); - gtk_table_attach_defaults(GTK_TABLE(table), data->entry, 1, 2, 0, 1); + gtk_table_attach_defaults(GTK_TABLE(table), data->entry, 1, 2, 1, 2); gtk_widget_grab_focus(data->entry); if (username != NULL) @@ -5414,19 +5468,20 @@ GTK_RESPONSE_OK, FALSE); gtk_entry_set_activates_default (GTK_ENTRY(data->entry), TRUE); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), data->entry); pidgin_set_accessible_label (data->entry, label); g_signal_connect(G_OBJECT(data->entry), "changed", G_CALLBACK(pidgin_set_sensitive_if_input), data->window); - label = gtk_label_new(_("Alias:")); + label = gtk_label_new_with_mnemonic(_("A_lias:")); gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); - gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2); + gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 2, 3); data->entry_for_alias = gtk_entry_new(); gtk_table_attach_defaults(GTK_TABLE(table), - data->entry_for_alias, 1, 2, 1, 2); + data->entry_for_alias, 1, 2, 2, 3); if (alias != NULL) gtk_entry_set_text(GTK_ENTRY(data->entry_for_alias), alias); @@ -5435,29 +5490,19 @@ gtk_widget_grab_focus(GTK_WIDGET(data->entry_for_alias)); gtk_entry_set_activates_default (GTK_ENTRY(data->entry_for_alias), TRUE); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), data->entry_for_alias); pidgin_set_accessible_label (data->entry_for_alias, label); - label = gtk_label_new(_("Group:")); + label = gtk_label_new_with_mnemonic(_("_Group:")); gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); - gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 2, 3); + gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 3, 4); data->combo = gtk_combo_new(); gtk_combo_set_popdown_strings(GTK_COMBO(data->combo), groups_tree()); - gtk_table_attach_defaults(GTK_TABLE(table), data->combo, 1, 2, 2, 3); + gtk_table_attach_defaults(GTK_TABLE(table), data->combo, 1, 2, 3, 4); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), GTK_COMBO(data->combo)->entry); pidgin_set_accessible_label (data->combo, label); - /* Set up stuff for the account box */ - label = gtk_label_new(_("Account:")); - gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); - gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 3, 4); - - data->account_box = pidgin_account_option_menu_new(account, FALSE, - G_CALLBACK(add_buddy_select_account_cb), NULL, data); - - gtk_table_attach_defaults(GTK_TABLE(table), data->account_box, 1, 2, 3, 4); - pidgin_set_accessible_label (data->account_box, label); - /* End of account box */ - g_signal_connect(G_OBJECT(data->window), "response", G_CALLBACK(add_buddy_cb), data); @@ -5763,7 +5808,7 @@ rowbox = gtk_hbox_new(FALSE, 5); gtk_box_pack_start(GTK_BOX(vbox), rowbox, FALSE, FALSE, 0); - label = gtk_label_new(_("Account:")); + label = gtk_label_new_with_mnemonic(_("_Account:")); gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); gtk_size_group_add_widget(data->sg, label); gtk_box_pack_start(GTK_BOX(rowbox), label, FALSE, FALSE, 0); @@ -5772,6 +5817,7 @@ G_CALLBACK(addchat_select_account_cb), chat_account_filter_func, data); gtk_box_pack_start(GTK_BOX(rowbox), data->account_menu, TRUE, TRUE, 0); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), data->account_menu); pidgin_set_accessible_label (data->account_menu, label); data->entries_box = gtk_vbox_new(FALSE, 5); @@ -5783,7 +5829,7 @@ rowbox = gtk_hbox_new(FALSE, 5); gtk_box_pack_start(GTK_BOX(vbox), rowbox, FALSE, FALSE, 0); - label = gtk_label_new(_("Alias:")); + label = gtk_label_new_with_mnemonic(_("A_lias:")); gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); gtk_size_group_add_widget(data->sg, label); gtk_box_pack_start(GTK_BOX(rowbox), label, FALSE, FALSE, 0); @@ -5793,6 +5839,7 @@ gtk_entry_set_text(GTK_ENTRY(data->alias_entry), alias); gtk_box_pack_end(GTK_BOX(rowbox), data->alias_entry, TRUE, TRUE, 0); gtk_entry_set_activates_default(GTK_ENTRY(data->alias_entry), TRUE); + gtk_label_set_mnemonic_widget(GTK_LABEL(label), data->alias_entry); pidgin_set_accessible_label (data->alias_entry, label); if (name != NULL) gtk_widget_grab_focus(data->alias_entry); @@ -5800,7 +5847,7 @@ rowbox = gtk_hbox_new(FALSE, 5); gtk_box_pack_start(GTK_BOX(vbox), rowbox, FALSE, FALSE, 0); - label = gtk_label_new(_("Group:")); + label = gtk_label_new_with_mnemonic(_("_Group:")); gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); gtk_size_group_add_widget(data->sg, label); gtk_box_pack_start(GTK_BOX(rowbox), label, FALSE, FALSE, 0); @@ -5814,6 +5861,7 @@ gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(data->group_combo)->entry), group->name); } + gtk_label_set_mnemonic_widget(GTK_LABEL(label), GTK_COMBO(data->group_combo)->entry); pidgin_set_accessible_label (data->group_combo, label); g_signal_connect(G_OBJECT(data->window), "response", @@ -6685,3 +6733,4 @@ if (activeitem) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(activeitem), TRUE); } +
--- a/pidgin/gtkblist.h Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtkblist.h Wed Jun 27 21:43:18 2007 +0000 @@ -360,4 +360,14 @@ void pidgin_blist_set_headline(const char *text, GdkPixbuf *pixbuf, GCallback callback, gpointer user_data, GDestroyNotify destroy); +/** + * Returns a buddy's Pango markup appropriate for setting in a GtkCellRenderer. + * + * @param buddy The buddy to return markup from + * @param selected Whether this buddy is selected. If TRUE, the markup will not change the color. + * @param aliased TRUE to return the appropriate alias of this buddy, FALSE to return its screenname + * @return The markup for this buddy + */ +gchar *pidgin_blist_get_name_markup(PurpleBuddy *buddy, gboolean selected, gboolean aliased); + #endif /* _PIDGINBLIST_H_ */
--- a/pidgin/gtkconv.c Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtkconv.c Wed Jun 27 21:43:18 2007 +0000 @@ -49,6 +49,7 @@ #include "prpl.h" #include "request.h" #include "util.h" +#include "version.h" #include "gtkdnd-hints.h" #include "gtkblist.h" @@ -81,6 +82,12 @@ PIDGIN_CONV_COLORIZE_TITLE = 1 << 6 }PidginConvFields; +enum { + ICON_COLUMN, + TEXT_COLUMN, + NUM_COLUMNS +} PidginInfopaneColumns; + #define PIDGIN_CONV_ALL ((1 << 7) - 1) #define SEND_COLOR "#204a87" @@ -177,6 +184,8 @@ static void pidgin_conv_update_fields(PurpleConversation *conv, PidginConvFields fields); static void focus_out_from_menubar(GtkWidget *wid, PidginWindow *win); static void pidgin_conv_tab_pack(PidginWindow *win, PidginConversation *gtkconv); +static gboolean infopane_release_cb(GtkWidget *widget, GdkEventButton *e, PidginConversation *conv); +static gboolean infopane_press_cb(GtkWidget *widget, GdkEventButton *e, PidginConversation *conv); static GdkColor *get_nick_color(PidginConversation *gtkconv, const char *name) { static GdkColor col; @@ -271,65 +280,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 +428,7 @@ char *cmd; const char *prefix; GtkTextIter start; + gboolean retval = FALSE; gtkconv = PIDGIN_CONVERSATION(conv); prefix = pidgin_get_cmd_prefix(); @@ -500,24 +452,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 +503,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 @@ -650,23 +630,10 @@ static void chat_do_info(PidginConversation *gtkconv, const char *who) { PurpleConversation *conv = gtkconv->active_conv; - PurplePluginProtocolInfo *prpl_info = NULL; PurpleConnection *gc; if ((gc = purple_conversation_get_gc(conv))) { - prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); - - /* - * If there are special needs for getting info on users in - * buddy chat "rooms"... - */ - if (prpl_info->get_cb_info != NULL) - { - prpl_info->get_cb_info(gc, - purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv)), who); - } - else - prpl_info->get_info(gc, who); + pidgin_retrieve_user_info_in_chat(gc, who, purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv))); } } @@ -677,14 +644,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 +1208,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; @@ -1825,6 +1817,9 @@ if (!gtkconv->send_history) break; + if (gtkconv->entry != entry) + break; + if (!gtkconv->send_history->prev) { GtkTextIter start, end; @@ -1873,6 +1868,9 @@ if (!gtkconv->send_history) break; + if (gtkconv->entry != entry) + break; + if (gtkconv->send_history->prev && gtkconv->send_history->prev->data) { GObject *object; GtkTextIter iter; @@ -1976,6 +1974,8 @@ switch (event->keyval) { case GDK_Tab: + if (gtkconv->entry != entry) + break; return tab_complete(conv); break; @@ -2052,6 +2052,9 @@ return TRUE; } +static void +regenerate_options_items(PidginWindow *win); + void pidgin_conv_switch_active_conversation(PurpleConversation *conv) { @@ -2152,6 +2155,8 @@ gray_stuff_out(gtkconv); update_typing_icon(gtkconv); + g_object_set_data(G_OBJECT(entry), "transient_buddy", NULL); + regenerate_options_items(gtkconv->win); gtk_window_set_title(GTK_WINDOW(gtkconv->win->window), gtk_label_get_text(GTK_LABEL(gtkconv->tab_label))); @@ -2228,34 +2233,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; } @@ -2356,6 +2345,10 @@ gtk_image_set_from_pixbuf(GTK_IMAGE(gtkconv->icon), status); gtk_image_set_from_pixbuf(GTK_IMAGE(gtkconv->menu_icon), status); + gtk_list_store_set(GTK_LIST_STORE(gtkconv->infopane_model), + &(gtkconv->infopane_iter), + ICON_COLUMN, status, -1); + if (status != NULL) g_object_unref(status); @@ -2411,16 +2404,22 @@ gdk_pixbuf_animation_iter_advance(gtkconv->u.im->iter, NULL); buf = gdk_pixbuf_animation_iter_get_pixbuf(gtkconv->u.im->iter); - pidgin_buddy_icon_get_scale_size(buf, &prpl_info->icon_spec, - PURPLE_ICON_SCALE_DISPLAY, &scale_width, &scale_height); - - /* this code is ugly, and scares me */ - scale = gdk_pixbuf_scale_simple(buf, - MAX(gdk_pixbuf_get_width(buf) * scale_width / - gdk_pixbuf_animation_get_width(gtkconv->u.im->anim), 1), - MAX(gdk_pixbuf_get_height(buf) * scale_height / - gdk_pixbuf_animation_get_height(gtkconv->u.im->anim), 1), + scale_width = gdk_pixbuf_get_width(buf); + scale_height = gdk_pixbuf_get_height(buf); + if (scale_width == scale_height) { + scale_width = scale_height = 32; + } else if (scale_height > scale_width) { + scale_width = 32 * scale_width / scale_height; + scale_height = 32; + } else { + scale_height = 32 * scale_height / scale_width; + scale_width = 32; + } + + scale = gdk_pixbuf_scale_simple(buf, scale_width, scale_height, GDK_INTERP_BILINEAR); + if (pidgin_gdk_pixbuf_is_opaque(scale)) + pidgin_gdk_pixbuf_make_round(scale); gtk_image_set_from_pixbuf(GTK_IMAGE(gtkconv->u.im->icon), scale); g_object_unref(G_OBJECT(scale)); @@ -2818,6 +2817,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 +2904,59 @@ 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); + + if ((chat == NULL) && (gtkconv->imhtml != NULL)) { + chat = g_object_get_data(G_OBJECT(gtkconv->imhtml), "transient_chat"); + } + + if ((chat == NULL) && (gtkconv->imhtml != NULL)) { + GHashTable *components; + components = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + g_hash_table_replace(components, g_strdup("channel"), + g_strdup(conv->name)); + chat = purple_chat_new(conv->account, NULL, components); + purple_blist_node_set_flags((PurpleBlistNode *)chat, + PURPLE_BLIST_NODE_FLAG_NO_SAVE); + g_object_set_data_full(G_OBJECT(gtkconv->imhtml), "transient_chat", + chat, (GDestroyNotify)purple_blist_remove_chat); + } + } else { + buddy = purple_find_buddy(conv->account, conv->name); + + /* gotta remain bug-compatible :( libpurple < 2.0.2 didn't handle + * removing "isolated" buddy nodes well */ + if (purple_version_check(2, 0, 2) == NULL) { + if ((buddy == NULL) && (gtkconv->imhtml != NULL)) { + buddy = g_object_get_data(G_OBJECT(gtkconv->imhtml), "transient_buddy"); + } + + if ((buddy == NULL) && (gtkconv->imhtml != NULL)) { + buddy = purple_buddy_new(conv->account, conv->name, NULL); + purple_blist_node_set_flags((PurpleBlistNode *)buddy, + PURPLE_BLIST_NODE_FLAG_NO_SAVE); + g_object_set_data_full(G_OBJECT(gtkconv->imhtml), "transient_buddy", + buddy, (GDestroyNotify)purple_blist_remove_buddy); + } + } + } + + 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 +2966,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 +2983,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 +3138,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")); @@ -4135,52 +4253,22 @@ } } -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); label = gtk_label_new(_("Topic:")); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); - gtk_widget_show(label); gtkchat->topic_text = gtk_entry_new(); @@ -4192,37 +4280,22 @@ } 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); + g_signal_connect(G_OBJECT(gtkchat->topic_text), "key_press_event", + G_CALLBACK(entry_key_press_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 +4343,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 +4374,115 @@ gtkchat->list = list; gtk_container_add(GTK_CONTAINER(sw), list); +} + +static GtkWidget * +setup_common_pane(PidginConversation *gtkconv) +{ + GtkWidget *paned, *vbox, *frame, *imhtml_sw, *event_box; + GtkCellRenderer *rend; + GtkTreePath *path; + 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 info pane */ + event_box = gtk_event_box_new(); + gtk_widget_show(event_box); + gtkconv->infopane_hbox = gtk_hbox_new(FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), event_box, FALSE, FALSE, 0); + gtk_container_add(GTK_CONTAINER(event_box), gtkconv->infopane_hbox); + gtk_widget_show(gtkconv->infopane_hbox); + gtk_widget_add_events(event_box, + GDK_BUTTON1_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK); + g_signal_connect(G_OBJECT(event_box), "button_press_event", + G_CALLBACK(infopane_press_cb), gtkconv); + g_signal_connect(G_OBJECT(event_box), "button_release_event", + G_CALLBACK(infopane_release_cb), gtkconv); + + + gtkconv->infopane = gtk_cell_view_new(); + gtkconv->infopane_model = gtk_list_store_new(NUM_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING); + gtk_cell_view_set_model(GTK_CELL_VIEW(gtkconv->infopane), + GTK_TREE_MODEL(gtkconv->infopane_model)); + gtk_list_store_append(gtkconv->infopane_model, &(gtkconv->infopane_iter)); + gtk_box_pack_start(GTK_BOX(gtkconv->infopane_hbox), gtkconv->infopane, TRUE, TRUE, 0); + path = gtk_tree_path_new_from_string("0"); + gtk_cell_view_set_displayed_row(GTK_CELL_VIEW(gtkconv->infopane), path); + gtk_tree_path_free(path); + gtk_widget_set_size_request(gtkconv->infopane, -1, 32); + gtk_widget_show(gtkconv->infopane); + + rend = gtk_cell_renderer_pixbuf_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv->infopane), rend, FALSE); + gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv->infopane), rend, "pixbuf", ICON_COLUMN, NULL); + g_object_set(rend, "xalign", 0.0, "xpad", 6, "ypad", 0, NULL); + + rend = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(gtkconv->infopane), rend, TRUE); + gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(gtkconv->infopane), rend, "markup", TEXT_COLUMN, NULL); + g_object_set(rend, "ypad", 0, "yalign", 0.5, NULL); + +#if GTK_CHECK_VERSION(2, 6, 0) + g_object_set(rend, "ellipsize", PANGO_ELLIPSIZE_END, NULL); +#endif + + /* 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 +4498,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 +4516,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 +4733,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 +5134,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"), @@ -5897,6 +5969,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 +5996,8 @@ gtk_widget_show(win->menu.remove); } + gtk_widget_show(win->menu.insert_link); + gtk_widget_show(win->menu.insert_image); } /* @@ -5950,8 +6026,10 @@ buttons = GTK_IMHTML_SMILEY | GTK_IMHTML_IMAGE; } - if (!(prpl_info->options & OPT_PROTO_IM_IMAGE) || - conv->features & PURPLE_CONNECTION_NO_IMAGES) + if (!(prpl_info->options & OPT_PROTO_IM_IMAGE)) + conv->features |= PURPLE_CONNECTION_NO_IMAGES; + + if(conv->features & PURPLE_CONNECTION_NO_IMAGES) buttons &= ~GTK_IMHTML_IMAGE; gtk_imhtml_set_format_functions(GTK_IMHTML(gtkconv->entry), buttons); @@ -5963,6 +6041,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, !(conv->features & PURPLE_CONNECTION_NO_IMAGES)); if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) { @@ -5997,6 +6077,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); } /* @@ -6080,11 +6162,13 @@ pidgin_themes_smiley_themeize(PIDGIN_CONVERSATION(conv)->imhtml); if ((fields & PIDGIN_CONV_COLORIZE_TITLE) || - (fields & PIDGIN_CONV_SET_TITLE)) + (fields & PIDGIN_CONV_SET_TITLE) || + (fields & PIDGIN_CONV_TOPIC)) { char *title; PurpleConvIm *im = NULL; PurpleAccount *account = purple_conversation_get_account(conv); + char *markup = NULL; AtkObject *accessibility_obj; /* I think this is a little longer than it needs to be but I'm lazy. */ char style[51]; @@ -6100,6 +6184,25 @@ else title = g_strdup(purple_conversation_get_title(conv)); + if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) { + PurpleBuddy *buddy = purple_find_buddy(account, conv->name); + if (buddy) + markup = pidgin_blist_get_name_markup(buddy, FALSE, FALSE); + else + markup = title; + } else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) { + PurpleConvChat *chat = PURPLE_CONV_CHAT(conv); + markup = g_strdup_printf("%s\n<span color='%s' size='smaller'>%s</span>", + purple_conversation_get_title(conv), + pidgin_get_dim_grey_string(gtkconv->infopane), + purple_conv_chat_get_topic(chat)); + } + gtk_list_store_set(gtkconv->infopane_model, &(gtkconv->infopane_iter), + TEXT_COLUMN, markup, -1); + + if (title != markup) + g_free(markup); + *style = '\0'; if (!GTK_WIDGET_REALIZED(gtkconv->tab_label)) @@ -6259,7 +6362,6 @@ GdkPixbuf *buf; GtkWidget *event; - GtkWidget *frame; GdkPixbuf *scale; int scale_width, scale_height; @@ -6358,54 +6460,55 @@ } if (gdk_pixbuf_animation_is_static_image(gtkconv->u.im->anim)) { + GdkPixbuf *stat; gtkconv->u.im->iter = NULL; - buf = gdk_pixbuf_animation_get_static_image(gtkconv->u.im->anim); + stat = gdk_pixbuf_animation_get_static_image(gtkconv->u.im->anim); + buf = gdk_pixbuf_add_alpha(stat, FALSE, 0, 0, 0); } else { + GdkPixbuf *stat; gtkconv->u.im->iter = gdk_pixbuf_animation_get_iter(gtkconv->u.im->anim, NULL); /* LEAK */ - buf = gdk_pixbuf_animation_iter_get_pixbuf(gtkconv->u.im->iter); + stat = gdk_pixbuf_animation_iter_get_pixbuf(gtkconv->u.im->iter); + buf = gdk_pixbuf_add_alpha(stat, FALSE, 0, 0, 0); if (gtkconv->u.im->animate) start_anim(NULL, gtkconv); } - pidgin_buddy_icon_get_scale_size(buf, &prpl_info->icon_spec, - PURPLE_ICON_SCALE_DISPLAY, &scale_width, &scale_height); - scale = gdk_pixbuf_scale_simple(buf, - MAX(gdk_pixbuf_get_width(buf) * scale_width / - gdk_pixbuf_animation_get_width(gtkconv->u.im->anim), 1), - MAX(gdk_pixbuf_get_height(buf) * scale_height / - gdk_pixbuf_animation_get_height(gtkconv->u.im->anim), 1), + scale_width = gdk_pixbuf_get_width(buf); + scale_height = gdk_pixbuf_get_height(buf); + if (scale_width == scale_height) { + scale_width = scale_height = 32; + } else if (scale_height > scale_width) { + scale_width = 32 * scale_width / scale_height; + scale_height = 32; + } else { + scale_height = 32 * scale_height / scale_width; + scale_width = 32; + } + scale = gdk_pixbuf_scale_simple(buf, scale_width, scale_height, GDK_INTERP_BILINEAR); - + g_object_unref(buf); + if (pidgin_gdk_pixbuf_is_opaque(scale)) + pidgin_gdk_pixbuf_make_round(scale); gtkconv->u.im->icon_container = gtk_vbox_new(FALSE, 0); - frame = gtk_frame_new(NULL); - gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE); - gtk_box_pack_start(GTK_BOX(gtkconv->u.im->icon_container), frame, - FALSE, FALSE, 0); - event = gtk_event_box_new(); - gtk_container_add(GTK_CONTAINER(frame), event); + gtk_container_add(GTK_CONTAINER(gtkconv->u.im->icon_container), event); g_signal_connect(G_OBJECT(event), "button-press-event", G_CALLBACK(icon_menu), gtkconv); gtk_widget_show(event); gtkconv->u.im->icon = gtk_image_new_from_pixbuf(scale); gtkconv->auto_resize = TRUE; - /* Reset the size request to allow the buddy icon to resize */ - gtk_widget_set_size_request(gtkconv->lower_hbox, -1, -1); - g_idle_add(reset_auto_resize_cb, gtkconv); - gtk_widget_set_size_request(gtkconv->u.im->icon, scale_width, scale_height); gtk_container_add(GTK_CONTAINER(event), gtkconv->u.im->icon); gtk_widget_show(gtkconv->u.im->icon); g_object_unref(G_OBJECT(scale)); - gtk_box_pack_start(GTK_BOX(gtkconv->lower_hbox), + gtk_box_pack_start(GTK_BOX(gtkconv->infopane_hbox), gtkconv->u.im->icon_container, FALSE, FALSE, 0); gtk_widget_show(gtkconv->u.im->icon_container); - gtk_widget_show(frame); /* The buddy icon code needs badly to be fixed. */ if(pidgin_conv_window_is_active_conversation(conv)) @@ -7503,6 +7606,45 @@ /* * THANK YOU GALEON! */ + +static gboolean +infopane_press_cb(GtkWidget *widget, GdkEventButton *e, PidginConversation *gtkconv) +{ + int nb_x, nb_y; + + if (e->button != 1 || e->type != GDK_BUTTON_PRESS) + return FALSE; + + if (gtkconv->win->in_drag) { + purple_debug(PURPLE_DEBUG_WARNING, "gtkconv", + "Already in the middle of a window drag at tab_press_cb\n"); + return TRUE; + } + + gtkconv->win->in_predrag = TRUE; + gtkconv->win->drag_tab = gtk_notebook_page_num(GTK_NOTEBOOK(gtkconv->win->notebook), gtkconv->tab_cont); + + gdk_window_get_origin(gtkconv->infopane_hbox->window, &nb_x, &nb_y); + + gtkconv->win->drag_min_x = gtkconv->infopane_hbox->allocation.x + nb_x; + gtkconv->win->drag_min_y = gtkconv->infopane_hbox->allocation.y + nb_y; + gtkconv->win->drag_max_x = gtkconv->infopane_hbox->allocation.width + gtkconv->win->drag_min_x; + gtkconv->win->drag_max_y = gtkconv->infopane_hbox->allocation.height + gtkconv->win->drag_min_y; + + + /* Connect the new motion signals. */ + gtkconv->win->drag_motion_signal = + g_signal_connect(G_OBJECT(gtkconv->win->notebook), "motion_notify_event", + G_CALLBACK(notebook_motion_cb), gtkconv->win); + + gtkconv->win->drag_leave_signal = + g_signal_connect(G_OBJECT(gtkconv->win->notebook), "leave_notify_event", + G_CALLBACK(notebook_leave_cb), gtkconv->win); + + return FALSE; + +} + static gboolean notebook_press_cb(GtkWidget *widget, GdkEventButton *e, PidginWindow *win) { @@ -7592,6 +7734,12 @@ } static gboolean +infopane_release_cb(GtkWidget *widget, GdkEventButton *e, PidginConversation *gtkconv) +{ + return FALSE; +} + +static gboolean notebook_release_cb(GtkWidget *widget, GdkEventButton *e, PidginWindow *win) { PidginWindow *dest_win; @@ -7922,6 +8070,11 @@ return FALSE; } + if (!purple_account_is_connected(gtkconv->active_conv->account)) { + /* Do not allow aliasing someone on a disconnected account. */ + return FALSE; + } + /* alias label */ entry = gtk_entry_new(); gtk_entry_set_has_frame(GTK_ENTRY(entry), FALSE); @@ -7973,6 +8126,7 @@ generate_send_to_items(win); regenerate_options_items(win); + regenerate_plugins_items(win); pidgin_conv_switch_active_conversation(conv); @@ -8043,6 +8197,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 +8216,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 +8274,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 +8320,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); } @@ -8271,8 +8436,7 @@ /* Er, bug in notebooks? Switch to the page manually. */ gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), 0); - gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), - purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/tabs")); + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), FALSE); } else gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), TRUE); @@ -8366,7 +8530,7 @@ gtk_notebook_set_tab_label_packing(GTK_NOTEBOOK(win->notebook), gtkconv->tab_cont, !tabs_side && !angle, TRUE, GTK_PACK_START); /* show the widgets */ - gtk_widget_show(gtkconv->icon); +/* XXX gtk_widget_show(gtkconv->icon); */ gtk_widget_show(gtkconv->tab_label); if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/close_on_tabs")) gtk_widget_show(gtkconv->close); @@ -8388,14 +8552,16 @@ gtk_notebook_remove_page(GTK_NOTEBOOK(win->notebook), index); - /* go back to tabless if need be */ + /* go back to tabless */ if (pidgin_conv_window_get_gtkconv_count(win) <= 2) { - gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), - purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/tabs")); + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), FALSE); } win->gtkconvs = g_list_remove(win->gtkconvs, gtkconv); + if (!win->gtkconvs || !win->gtkconvs->next) + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), FALSE); + if (!win->gtkconvs && win != hidden_convwin) pidgin_conv_window_destroy(win); }
--- a/pidgin/gtkconv.h Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtkconv.h Wed Jun 27 21:43:18 2007 +0000 @@ -158,6 +158,10 @@ } u; time_t newday; + GtkWidget *infopane_hbox; + GtkWidget *infopane; + GtkListStore *infopane_model; + GtkTreeIter infopane_iter; }; /*@}*/
--- a/pidgin/gtkdialogs.c Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtkdialogs.c Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtkdialogs.h Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtkdocklet-x11.c Wed Jun 27 21:43:18 2007 +0000 @@ -125,8 +125,12 @@ int icon_size; if (docklet_height < 22) icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL); + else if (docklet_height < 32) + icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL); + else if (docklet_height < 48) + icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_MEDIUM); else - icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_SMALL); + icon_size = gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_LARGE); gtk_image_set_from_stock(GTK_IMAGE(image), icon_name, icon_size); } @@ -135,9 +139,9 @@ static void docklet_x11_resize_icon(GtkWidget *widget) { - if (docklet_height == widget->allocation.height) + if (docklet_height == MIN(widget->allocation.height, widget->allocation.width)) return; - docklet_height = widget->allocation.height; + docklet_height = MIN(widget->allocation.height, widget->allocation.width); pidgin_docklet_update_icon(); } @@ -281,7 +285,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/gtkeventloop.c Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtkeventloop.c Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtkft.c Wed Jun 27 21:43:18 2007 +0000 @@ -130,7 +130,7 @@ kbps = (elapsed > 0 ? (kb_sent / elapsed) : 0); if (kbsec != NULL) { - *kbsec = g_strdup_printf(_("%.2f KB/s"), kbps); + *kbsec = g_strdup_printf(_("%.2f KiB/s"), kbps); } if (time_elapsed != NULL) @@ -248,10 +248,10 @@ get_xfer_info_strings(xfer, &kbsec, &time_elapsed, &time_remaining); - status = g_strdup_printf("%ld%% (%ld of %ld bytes)", - (unsigned long)(purple_xfer_get_progress(xfer)*100), - (unsigned long)purple_xfer_get_bytes_sent(xfer), - (unsigned long)purple_xfer_get_size(xfer)); + status = g_strdup_printf("%d%% (%" G_GSIZE_FORMAT " of %" G_GSIZE_FORMAT " bytes)", + (int)(purple_xfer_get_progress(xfer)*100), + purple_xfer_get_bytes_sent(xfer), + purple_xfer_get_size(xfer)); if (purple_xfer_is_completed(xfer)) { @@ -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 Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtkimhtml.c Wed Jun 27 21:43:18 2007 +0000 @@ -27,10 +27,16 @@ #ifdef HAVE_CONFIG_H #include <config.h> #endif + +#include "pidgin.h" +#include "internal.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> @@ -48,19 +54,6 @@ #include <windows.h> #endif -#ifdef ENABLE_NLS -# include <libintl.h> -# define _(x) gettext(x) -# ifdef gettext_noop -# define N_(String) gettext_noop (String) -# else -# define N_(String) (String) -# endif -#else -# define N_(String) (String) -# define _(x) (x) -#endif - #include <pango/pango-font.h> /* GTK+ < 2.4.x hack, see pidgin.h for details. */ @@ -136,10 +129,16 @@ CLEAR_FORMAT, UPDATE_FORMAT, MESSAGE_SEND, + UNDO, + REDO, LAST_SIGNAL }; static guint signals [LAST_SIGNAL] = { 0 }; +static char *html_clipboard = NULL; +static char *text_clipboard = NULL; +GtkClipboard *clipboard_selection = NULL; + static GtkTargetEntry selection_targets[] = { #ifndef _WIN32 { "text/html", 0, TARGET_HTML }, @@ -871,14 +870,17 @@ static void gtk_imhtml_clipboard_get(GtkClipboard *clipboard, GtkSelectionData *selection_data, guint info, GtkIMHtml *imhtml) { char *text = NULL; - gboolean primary; + gboolean primary = (clipboard != clipboard_selection); GtkTextIter start, end; - GtkTextMark *sel = gtk_text_buffer_get_selection_bound(imhtml->text_buffer); - GtkTextMark *ins = gtk_text_buffer_get_insert(imhtml->text_buffer); - - gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &start, sel); - gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &end, ins); - primary = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_PRIMARY) == clipboard; + GtkTextMark *sel = NULL; + GtkTextMark *ins = NULL; + + if (primary) { + ins = gtk_text_buffer_get_insert(imhtml->text_buffer); + sel = gtk_text_buffer_get_selection_bound(imhtml->text_buffer); + gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &start, sel); + gtk_text_buffer_get_iter_at_mark(imhtml->text_buffer, &end, ins); + } if (info == TARGET_HTML) { char *selection; @@ -888,7 +890,7 @@ if (primary) { text = gtk_imhtml_get_markup_range(imhtml, &start, &end); } else - text = imhtml->clipboard_html_string; + text = html_clipboard; /* Mozilla asks that we start our text/html with the Unicode byte order mark */ str = g_string_append_unichar(str, 0xfeff); @@ -906,7 +908,7 @@ if (primary) { text = gtk_imhtml_get_text(imhtml, &start, &end); } else - text = imhtml->clipboard_text_string; + text = text_clipboard; gtk_selection_data_set_text(selection_data, text, strlen(text)); } if (primary) /* This was allocated here */ @@ -929,20 +931,32 @@ &insert); } +static void gtk_imhtml_clipboard_clear (GtkClipboard *clipboard, GtkSelectionData *sel_data, + guint info, gpointer user_data_or_owner) +{ + clipboard_selection = NULL; +} + static void copy_clipboard_cb(GtkIMHtml *imhtml, gpointer unused) { GtkTextIter start, end; if (gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) { - gtk_clipboard_set_with_owner(gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD), + if (!clipboard_selection) + clipboard_selection = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD); + gtk_clipboard_set_with_owner(clipboard_selection, selection_targets, sizeof(selection_targets) / sizeof(GtkTargetEntry), (GtkClipboardGetFunc)gtk_imhtml_clipboard_get, - (GtkClipboardClearFunc)NULL, G_OBJECT(imhtml)); + (GtkClipboardClearFunc)gtk_imhtml_clipboard_clear, G_OBJECT(imhtml)); g_free(imhtml->clipboard_html_string); g_free(imhtml->clipboard_text_string); imhtml->clipboard_html_string = gtk_imhtml_get_markup_range(imhtml, &start, &end); imhtml->clipboard_text_string = gtk_imhtml_get_text(imhtml, &start, &end); + + text_clipboard = imhtml->clipboard_text_string; + html_clipboard = imhtml->clipboard_html_string; + } g_signal_stop_emission_by_name(imhtml, "copy-clipboard"); @@ -952,10 +966,12 @@ { GtkTextIter start, end; if (gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, &start, &end)) { - gtk_clipboard_set_with_owner(gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD), + if (!clipboard_selection) + clipboard_selection = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD); + gtk_clipboard_set_with_owner(clipboard_selection, selection_targets, sizeof(selection_targets) / sizeof(GtkTargetEntry), (GtkClipboardGetFunc)gtk_imhtml_clipboard_get, - (GtkClipboardClearFunc)NULL, G_OBJECT(imhtml)); + (GtkClipboardClearFunc)gtk_imhtml_clipboard_clear, G_OBJECT(imhtml)); g_free(imhtml->clipboard_html_string); g_free(imhtml->clipboard_text_string); @@ -963,6 +979,9 @@ imhtml->clipboard_html_string = gtk_imhtml_get_markup_range(imhtml, &start, &end); imhtml->clipboard_text_string = gtk_imhtml_get_text(imhtml, &start, &end); + text_clipboard = imhtml->clipboard_text_string; + html_clipboard = imhtml->clipboard_html_string; + if (imhtml->editable) gtk_text_buffer_delete_selection(imhtml->text_buffer, FALSE, FALSE); } @@ -1126,6 +1145,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; @@ -1199,17 +1235,19 @@ g_free(img_data); } - if (imhtml->clipboard_text_string) { - g_free(imhtml->clipboard_text_string); - g_free(imhtml->clipboard_html_string); - } - g_list_free(imhtml->scalables); g_slist_free(imhtml->im_images); 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, + selection_targets, sizeof(selection_targets) / sizeof(GtkTargetEntry), + (GtkClipboardGetFunc)gtk_imhtml_clipboard_get, + (GtkClipboardClearFunc)NULL, G_OBJECT(imhtml)); + } /* Boring GTK+ stuff */ @@ -1272,10 +1310,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; @@ -1300,12 +1360,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); @@ -3088,14 +3153,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; @@ -3107,6 +3171,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; @@ -3148,6 +3282,7 @@ char *basename = g_path_get_basename(filename); char *ext = strrchr(basename, '.'); #endif + char *newfilename; gtk_widget_destroy(image->filesel); image->filesel = NULL; @@ -3162,7 +3297,7 @@ gchar *fmt_ext = extensions[0]; const gchar* file_ext = filename + strlen(filename) - strlen(fmt_ext); - if(!strcmp(fmt_ext, file_ext)){ + if(!g_ascii_strcasecmp(fmt_ext, file_ext)){ type = gdk_pixbuf_format_get_name(format); break; } @@ -3195,6 +3330,7 @@ /* If I can't find a valid type, I will just tell the user about it and then assume it's a png */ if (!type){ + char *basename, *tmp; #if GTK_CHECK_VERSION(2,4,0) GtkWidget *dialog = gtk_message_dialog_new_with_markup(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("<span size='larger' weight='bold'>Unrecognized file type</span>\n\nDefaulting to PNG.")); @@ -3205,10 +3341,26 @@ g_signal_connect_swapped(dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog); gtk_widget_show(dialog); + type = g_strdup("png"); + basename = g_path_get_basename(filename); + tmp = strrchr(basename, '.'); + if (tmp != NULL) + tmp[0] = '\0'; + newfilename = g_strdup_printf("%s.png", basename); + g_free(basename); + } else { + /* + * We're able to save the file in it's original format, so we + * can use the original file name. + */ + newfilename = g_strdup(filename); } - gdk_pixbuf_save(image->pixbuf, filename, type, &error, NULL); + gdk_pixbuf_save(image->pixbuf, newfilename, type, &error, NULL); + + g_free(newfilename); + g_free(type); if (error){ #if GTK_CHECK_VERSION(2,4,0) @@ -3222,8 +3374,6 @@ gtk_widget_show(dialog); g_error_free(error); } - - g_free(type); } #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */ @@ -3365,7 +3515,6 @@ static gboolean gtk_imhtml_smiley_clicked(GtkWidget *w, GdkEvent *event, GtkIMHtmlSmiley *smiley) { GdkPixbufAnimation *anim = NULL; - GdkPixbuf *pix = NULL; GtkIMHtmlScalable *image = NULL; gboolean ret; @@ -3376,11 +3525,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; } @@ -3395,6 +3542,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; @@ -4475,7 +4635,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; @@ -4502,28 +4662,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); @@ -4531,8 +4696,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) @@ -4858,3 +5021,89 @@ g_return_if_fail(imhtml != NULL); imhtml->funcs = f; } + +void gtk_imhtml_setup_entry(GtkIMHtml *imhtml, PurpleConnectionFlags flags) +{ + GtkIMHtmlButtons buttons; + + if (flags & PURPLE_CONNECTION_HTML) { + char color[8]; + GdkColor fg_color, bg_color; + + buttons = GTK_IMHTML_ALL; + + if (flags & PURPLE_CONNECTION_NO_BGCOLOR) + buttons &= ~GTK_IMHTML_BACKCOLOR; + if (flags & PURPLE_CONNECTION_NO_FONTSIZE) + { + buttons &= ~GTK_IMHTML_GROW; + buttons &= ~GTK_IMHTML_SHRINK; + } + if (flags & PURPLE_CONNECTION_NO_URLDESC) + buttons &= ~GTK_IMHTML_LINKDESC; + + 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 { + buttons = GTK_IMHTML_SMILEY | GTK_IMHTML_IMAGE; + imhtml_clear_formatting(imhtml); + } + + if (flags & PURPLE_CONNECTION_NO_IMAGES) + buttons &= ~GTK_IMHTML_IMAGE; + + gtk_imhtml_set_format_functions(imhtml, buttons); +} + +
--- a/pidgin/gtkimhtml.h Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtkimhtml.h Wed Jun 27 21:43:18 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 Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtkimhtmltoolbar.c Wed Jun 27 21:43:18 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; @@ -857,56 +858,38 @@ * Copyright (C) 2003 Ricardo Fernandez Pascual * Copyright (C) 2004 Paolo Borelli */ +static void menu_position_func (GtkMenu *menu, int *x, int *y, gboolean *push_in, - GtkWidget *widget) + gpointer data) { - GtkRequisition req; - GtkRequisition menu_req; - GtkOrientation orientation; - GtkTextDirection direction; - GdkRectangle monitor; - gint monitor_num; - GdkScreen *screen; - - gtk_widget_size_request (GTK_WIDGET (widget), &menu_req); - - direction = gtk_widget_get_direction (widget); + GtkWidget *widget = GTK_WIDGET(data); + GtkRequisition menu_req; + gint ythickness = widget->style->ythickness; + int savy; - 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; + gtk_widget_size_request(GTK_WIDGET (menu), &menu_req); + gdk_window_get_origin(widget->window, x, y); + *x += widget->allocation.x; + *y += widget->allocation.y + widget->allocation.height; + savy = *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; + pidgin_menu_position_func_helper(menu, x, y, push_in, data); - 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 (savy > *y + ythickness + 1) + *y -= widget->allocation.height; } -static void pidgin_menu_clicked(GtkWidget *button, GtkMenu *menu) { - gtk_widget_show_all(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 +1009,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) @@ -1040,8 +1035,28 @@ GtkWidget *font_button; GtkWidget *font_menu; GtkWidget *insert_menu; + GtkWidget *menuitem; 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 +1072,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); @@ -1070,45 +1085,22 @@ gtk_widget_show_all(font_button); 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(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(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(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(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(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(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(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(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(font_menu, button); - + for (i = 0; buttons[i].label; i++) { + GtkWidget *old = *buttons[i].button; + 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 +1127,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); @@ -1148,21 +1140,27 @@ gtk_widget_show_all(insert_button); insert_menu = gtk_menu_new(); - - button = gtk_menu_item_new_with_mnemonic(_("_Smiley")); - g_signal_connect_swapped(G_OBJECT(button), "activate", G_CALLBACK(gtk_button_clicked), toolbar->smiley); - gtk_menu_shell_append(insert_menu, button); - - button = gtk_menu_item_new_with_mnemonic(_("_Image")); - g_signal_connect_swapped(G_OBJECT(button), "activate", G_CALLBACK(gtk_button_clicked), toolbar->image); - gtk_menu_shell_append(insert_menu, button); + + menuitem = gtk_menu_item_new_with_mnemonic(_("_Smiley")); + g_signal_connect_swapped(G_OBJECT(menuitem), "activate", G_CALLBACK(gtk_button_clicked), toolbar->smiley); + gtk_menu_shell_append(GTK_MENU_SHELL(insert_menu), menuitem); + g_signal_connect(G_OBJECT(toolbar->smiley), "notify::sensitive", + G_CALLBACK(button_sensitiveness_changed), menuitem); - button = gtk_menu_item_new_with_mnemonic(_("_Link")); - g_signal_connect_swapped(G_OBJECT(button), "activate", G_CALLBACK(gtk_button_clicked), toolbar->link); - gtk_menu_shell_append(insert_menu, button); - + menuitem = gtk_menu_item_new_with_mnemonic(_("_Image")); + g_signal_connect_swapped(G_OBJECT(menuitem), "activate", G_CALLBACK(gtk_button_clicked), toolbar->image); + gtk_menu_shell_append(GTK_MENU_SHELL(insert_menu), menuitem); + g_signal_connect(G_OBJECT(toolbar->image), "notify::sensitive", + G_CALLBACK(button_sensitiveness_changed), menuitem); + + menuitem = gtk_menu_item_new_with_mnemonic(_("_Link")); + g_signal_connect_swapped(G_OBJECT(menuitem), "activate", G_CALLBACK(gtk_button_clicked), toolbar->link); + gtk_menu_shell_append(GTK_MENU_SHELL(insert_menu), menuitem); + g_signal_connect(G_OBJECT(toolbar->link), "notify::sensitive", + G_CALLBACK(button_sensitiveness_changed), menuitem); + g_signal_connect(G_OBJECT(insert_button), "clicked", G_CALLBACK(pidgin_menu_clicked), insert_menu); - g_signal_connect(G_OBJECT(insert_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), insert_button); + g_signal_connect(G_OBJECT(insert_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), insert_button); toolbar->sml = NULL; }
--- a/pidgin/gtkmain.c Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtkmain.c Wed Jun 27 21:43:18 2007 +0000 @@ -108,21 +108,19 @@ }; #endif -static int +static void dologin_named(const char *name) { PurpleAccount *account; char **names; int i; - int ret = -1; if (name != NULL) { /* list of names given */ names = g_strsplit(name, ",", 64); for (i = 0; names[i] != NULL; i++) { account = purple_accounts_find(names[i], NULL); if (account != NULL) { /* found a user */ - ret = 0; - purple_account_connect(account); + purple_account_set_enabled(account, PIDGIN_UI, TRUE); } } g_strfreev(names); @@ -133,12 +131,9 @@ if (accounts != NULL) { account = (PurpleAccount *)accounts->data; - ret = 0; - purple_account_connect(account); + purple_account_set_enabled(account, PIDGIN_UI, TRUE); } } - - return ret; } #ifdef HAVE_SIGNAL_H @@ -377,6 +372,7 @@ " -c, --config=DIR use DIR for config files\n" " -d, --debug print debugging messages to stdout\n" " -h, --help display this help and exit\n" + " -m, --multiple do not ensure single instance\n" " -n, --nologin don't automatically login\n" " -l, --login[=NAME] automatically login (optional argument NAME specifies\n" " account(s) to use, separated by commas)\n" @@ -436,10 +432,10 @@ gboolean opt_login = FALSE; gboolean opt_nologin = FALSE; gboolean opt_version = FALSE; + gboolean opt_si = TRUE; /* Check for single instance? */ char *opt_config_dir_arg = NULL; char *opt_login_arg = NULL; char *opt_session_arg = NULL; - int dologin_ret = -1; char *search_path; GList *accounts; #ifdef HAVE_SIGNAL_H @@ -456,12 +452,14 @@ gboolean gui_check; gboolean debug_enabled; gboolean migration_failed = FALSE; + GList *active_accounts; struct option long_options[] = { {"config", required_argument, NULL, 'c'}, {"debug", no_argument, NULL, 'd'}, {"help", no_argument, NULL, 'h'}, {"login", optional_argument, NULL, 'l'}, + {"multiple", no_argument, NULL, 'm'}, {"nologin", no_argument, NULL, 'n'}, {"session", required_argument, NULL, 's'}, {"version", no_argument, NULL, 'v'}, @@ -476,7 +474,7 @@ /* This is the first Glib function call. Make sure to initialize GThread bfeore then */ g_thread_init(NULL); - + #ifdef ENABLE_NLS bindtextdomain(PACKAGE, LOCALEDIR); bind_textdomain_codeset(PACKAGE, "UTF-8"); @@ -498,7 +496,7 @@ "no fault of your own.\n\n" "If you can reproduce the crash, please notify the developers\n" "by reporting a bug at:\n" - "%snewticket/\n\n" + "%ssimpleticket/\n\n" "Please make sure to specify what you were doing at the time\n" "and post the backtrace from the core file. If you do not know\n" "how to get the backtrace, please read the instructions at\n" @@ -575,7 +573,7 @@ opterr = 1; while ((opt = getopt_long(argc, argv, #ifndef _WIN32 - "c:dhnl::s:v", + "c:dhmnl::s:v", #else "c:dhnl::v", #endif @@ -607,6 +605,9 @@ case 'v': /* version */ opt_version = TRUE; break; + case 'm': /* do not ensure single instance. */ + opt_si = FALSE; + break; case '?': /* show terse help */ default: show_usage(argv[0], TRUE); @@ -733,6 +734,15 @@ abort(); } + if (opt_si && !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(); @@ -788,14 +798,23 @@ pidgin_debug_window_show(); if (opt_login) { - dologin_ret = dologin_named(opt_login_arg); + /* disable all accounts */ + for (accounts = purple_accounts_get_all(); accounts != NULL; accounts = accounts->next) { + PurpleAccount *account = accounts->data; + purple_account_set_enabled(account, PIDGIN_UI, FALSE); + } + /* honor the startup status preference */ + if (!purple_prefs_get_bool("/purple/savedstatus/startup_current_status")) + purple_savedstatus_activate(purple_savedstatus_get_startup()); + /* now enable the requested ones */ + dologin_named(opt_login_arg); if (opt_login_arg != NULL) { g_free(opt_login_arg); opt_login_arg = NULL; } } - if (opt_nologin) + if (opt_nologin && !opt_login) { /* Set all accounts to "offline" */ PurpleSavedStatus *saved_status; @@ -811,7 +830,7 @@ /* Set the status for each account */ purple_savedstatus_activate(saved_status); } - else + else if (!opt_login) { /* Everything is good to go--sign on already */ if (!purple_prefs_get_bool("/purple/savedstatus/startup_current_status")) @@ -819,13 +838,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 @@ -836,6 +855,7 @@ winpidgin_post_init(); #endif + g_set_application_name(_("Pidgin")); gtk_main(); #ifdef HAVE_SIGNAL_H
--- a/pidgin/gtknotify.c Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtknotify.c Wed Jun 27 21:43:18 2007 +0000 @@ -40,6 +40,12 @@ typedef struct { + GtkWidget *window; + int count; +} PidginUserInfo; + +typedef struct +{ PurpleAccount *account; char *url; GtkWidget *label; @@ -577,10 +583,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); @@ -624,7 +628,7 @@ gtk_widget_show(button); g_signal_connect_swapped(G_OBJECT(button), "clicked", - G_CALLBACK(formatted_close_cb), window); + G_CALLBACK(gtk_widget_destroy), window); g_signal_connect(G_OBJECT(window), "key_press_event", G_CALLBACK(formatted_input_cb), NULL); @@ -712,10 +716,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); @@ -878,6 +880,11 @@ static void remove_userinfo(GtkWidget *widget, gpointer key) { + PidginUserInfo *pinfo = g_hash_table_lookup(userinfo, key); + + while (pinfo->count--) + purple_notify_close(PURPLE_NOTIFY_USERINFO, widget); + g_hash_table_remove(userinfo, key); } @@ -888,26 +895,33 @@ char *info; void *ui_handle; char *key = userinfo_hash(purple_connection_get_account(gc), who); + PidginUserInfo *pinfo = NULL; if (!userinfo) { - userinfo = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + userinfo = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); } info = purple_notify_user_info_get_text_with_newline(user_info, "<br />"); - ui_handle = g_hash_table_lookup(userinfo, key); - if (ui_handle != NULL) { - GtkIMHtml *imhtml = g_object_get_data(G_OBJECT(ui_handle), "info-widget"); + pinfo = g_hash_table_lookup(userinfo, key); + if (pinfo != NULL) { + GtkIMHtml *imhtml = g_object_get_data(G_OBJECT(pinfo->window), "info-widget"); char *linked_text = purple_markup_linkify(info); gtk_imhtml_clear(imhtml); gtk_imhtml_append_text(imhtml, linked_text, notify_imhtml_options()); g_free(linked_text); g_free(key); + ui_handle = pinfo->window; + pinfo->count++; } else { char *primary = g_strdup_printf(_("Info for %s"), who); ui_handle = pidgin_notify_formatted(_("Buddy Information"), primary, NULL, info); - g_hash_table_insert(userinfo, key, ui_handle); + g_signal_handlers_disconnect_by_func(G_OBJECT(ui_handle), G_CALLBACK(formatted_close_cb), NULL); g_signal_connect(G_OBJECT(ui_handle), "destroy", G_CALLBACK(remove_userinfo), key); g_free(primary); + pinfo = g_new0(PidginUserInfo, 1); + pinfo->window = ui_handle; + pinfo->count = 1; + g_hash_table_insert(userinfo, key, pinfo); } g_free(info); return ui_handle;
--- a/pidgin/gtkpounce.c Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtkpounce.c Wed Jun 27 21:43:18 2007 +0000 @@ -38,6 +38,7 @@ #include "gtkblist.h" #include "gtkdialogs.h" +#include "gtkimhtml.h" #include "gtkpounce.h" #include "pidginstock.h" #include "gtkutils.h" @@ -210,12 +211,12 @@ static void populate_pounces_list(PouncesManager *dialog) { - const GList *pounces; + GList *pounces; gtk_list_store_clear(dialog->model); - for (pounces = purple_pounces_get_all(); pounces != NULL; - pounces = g_list_next(pounces)) + for (pounces = purple_pounces_get_all_for_ui(PIDGIN_UI); pounces != NULL; + pounces = g_list_delete_link(pounces, pounces)) { add_pounce_to_treeview(dialog->model, pounces->data); } @@ -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) || @@ -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 Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtkprefs.c Wed Jun 27 21:43:18 2007 +0000 @@ -1026,7 +1026,7 @@ label = gtk_label_new_with_mnemonic(_("Conversation _font:")); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); font_name = purple_prefs_get_string(PIDGIN_PREFS_ROOT "/conversations/custom_font"); - font_button = gtk_font_button_new_with_font(purple_prefs_get_string(font_name ? font_name : NULL)); + font_button = gtk_font_button_new_with_font(font_name ? font_name : NULL); gtk_font_button_set_show_style(GTK_FONT_BUTTON(font_button), TRUE); gtk_box_pack_start(GTK_BOX(hbox), font_button, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); @@ -1054,19 +1054,9 @@ gtk_imhtml_append_text(GTK_IMHTML(imhtml), _("This is how your outgoing message text will appear when you use protocols that support formatting. :)"), 0); - gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 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); @@ -1301,9 +1291,10 @@ } gtk_widget_show_all(ret); - if (proxy_info == NULL || + /* Only hide table if not running gnome otherwise we hide the IP address table! */ + if (!purple_running_gnome() && (proxy_info == NULL || purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_NONE || - purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_USE_ENVVAR) + purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_USE_ENVVAR)) gtk_widget_hide(table); return ret; } @@ -2064,11 +2055,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 Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtkprivacy.c Wed Jun 27 21:43:18 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", TRUE); g_signal_connect(G_OBJECT(dialog->win), "delete_event", G_CALLBACK(destroy_cb), dialog);
--- a/pidgin/gtkrequest.c Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtkrequest.c Wed Jun 27 21:43:18 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); @@ -973,7 +974,7 @@ GtkTreeSelection *sel; GtkTreeViewColumn *column; GtkTreeIter iter; - const GList *l; + GList *l; /* Create the scrolled window */ sw = gtk_scrolled_window_new(NULL, NULL); @@ -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 Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtkroomlist.c Wed Jun 27 21:43:18 2007 +0000 @@ -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 Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtksavedstatuses.c Wed Jun 27 21:43:18 2007 +0000 @@ -23,8 +23,9 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include "internal.h" + #include "account.h" -#include "internal.h" #include "notify.h" #include "request.h" #include "savedstatuses.h" @@ -285,9 +286,11 @@ for (l = sel_titles; l != NULL; l = l->next) { title = l->data; - if (status_window_find_savedstatus(&iter, title)) - gtk_list_store_remove(status_window->model, &iter); - purple_savedstatus_delete(title); + if (purple_savedstatus_find(title) != purple_savedstatus_get_current()) { + if (status_window_find_savedstatus(&iter, title)) + gtk_list_store_remove(status_window->model, &iter); + purple_savedstatus_delete(title); + } g_free(title); } g_list_free(sel_titles); @@ -302,6 +305,7 @@ GList *sel_paths, *l, *sel_titles = NULL; GtkTreeModel *model = GTK_TREE_MODEL(dialog->model); char *title; + gpointer handle; selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview)); #if GTK_CHECK_VERSION(2,2,0) @@ -324,13 +328,16 @@ } g_list_free(sel_paths); - if (g_list_length(sel_titles) == 1) + if (g_list_length(sel_titles) == 1) { title = g_strdup_printf(_("Are you sure you want to delete %s?"), (const gchar *)sel_titles->data); - else + handle = purple_savedstatus_find(sel_titles->data); + } else { title = g_strdup(_("Are you sure you want to delete the selected saved statuses?")); + handle = dialog; + } - purple_request_action(dialog, NULL, title, NULL, 0, + purple_request_action(handle, NULL, title, NULL, 0, NULL, NULL, NULL, sel_titles, 2, _("Delete"), status_window_delete_confirm_cb, @@ -349,17 +356,39 @@ status_selected_cb(GtkTreeSelection *sel, gpointer user_data) { StatusWindow *dialog = user_data; - int num_selected = 0; + GList *sel_paths, *tmp; + gboolean can_use = TRUE, can_delete = TRUE; + int num_selected; + GtkTreeModel *model = GTK_TREE_MODEL(dialog->model); #if GTK_CHECK_VERSION(2,2,0) - num_selected = gtk_tree_selection_count_selected_rows(sel); + sel_paths = gtk_tree_selection_get_selected_rows(sel, NULL); #else - gtk_tree_selection_selected_foreach(sel, count_selected_helper, &num_selected); + gtk_tree_selection_selected_foreach(sel, list_selected_helper, &sel_paths); #endif - gtk_widget_set_sensitive(dialog->use_button, (num_selected == 1)); + for (tmp = sel_paths, num_selected = 0; tmp; tmp = tmp->next, num_selected++) { + GtkTreeIter iter; + char *title; + + if (gtk_tree_model_get_iter(model, &iter, tmp->data)) { + gtk_tree_model_get(model, &iter, + STATUS_WINDOW_COLUMN_TITLE, &title, -1); + if (purple_savedstatus_find(title) == purple_savedstatus_get_current()) { + can_use = can_delete = FALSE; + } + + g_free(title); + } + + gtk_tree_path_free(tmp->data); + } + + gtk_widget_set_sensitive(dialog->use_button, (num_selected == 1) && can_use); gtk_widget_set_sensitive(dialog->modify_button, (num_selected > 0)); - gtk_widget_set_sensitive(dialog->delete_button, (num_selected > 0)); + gtk_widget_set_sensitive(dialog->delete_button, can_delete); + + g_list_free(sel_paths); } static void @@ -389,7 +418,7 @@ static void populate_saved_status_list(StatusWindow *dialog) { - const GList *saved_statuses; + GList *saved_statuses; gtk_list_store_clear(dialog->model); @@ -421,6 +450,12 @@ status_window_modify_cb(NULL, dialog); } +static void +saved_status_updated_cb(PurpleSavedStatus *status, StatusWindow *sw) +{ + populate_saved_status_list(sw); +} + static GtkWidget * create_saved_status_list(StatusWindow *dialog) { @@ -529,6 +564,13 @@ return FALSE; } +static void +current_status_changed(PurpleSavedStatus *old, PurpleSavedStatus *new_status, + StatusWindow *dialog) +{ + status_selected_cb(gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->treeview)), dialog); +} + void pidgin_status_window_show(void) { @@ -551,11 +593,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); @@ -618,6 +657,19 @@ g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(status_window_close_cb), dialog); + purple_signal_connect(purple_savedstatuses_get_handle(), + "savedstatus-changed", status_window, + PURPLE_CALLBACK(current_status_changed), dialog); + purple_signal_connect(purple_savedstatuses_get_handle(), + "savedstatus-added", status_window, + PURPLE_CALLBACK(saved_status_updated_cb), dialog); + purple_signal_connect(purple_savedstatuses_get_handle(), + "savedstatus-deleted", status_window, + PURPLE_CALLBACK(saved_status_updated_cb), dialog); + purple_signal_connect(purple_savedstatuses_get_handle(), + "savedstatus-modified", status_window, + PURPLE_CALLBACK(saved_status_updated_cb), dialog); + gtk_widget_show_all(win); } @@ -632,6 +684,7 @@ purple_request_close_with_handle(status_window); purple_notify_close_with_handle(status_window); + purple_signals_disconnect_by_handle(status_window); g_free(status_window); status_window = NULL; } @@ -807,8 +860,10 @@ gtk_widget_destroy(dialog->window); g_free(dialog->original_title); +/* if (status_window != NULL) add_status_to_saved_status_list(status_window->model, saved_status); +*/ /* If they clicked on "Save & Use" or "Use," then activate the status */ if (button != dialog->save_button) @@ -1085,11 +1140,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", TRUE); g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(status_editor_destroy_cb), dialog); @@ -1137,7 +1188,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); @@ -1400,7 +1451,7 @@ GtkTreeIter iter; GtkCellRenderer *rend; const char *status_id = NULL; - const GList *list; + GList *list; gboolean select = FALSE; g_return_if_fail(status_editor != NULL); @@ -1423,13 +1474,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", TRUE); 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); @@ -1473,7 +1520,7 @@ /* Status mesage */ 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);
--- a/pidgin/gtksound.c Sun Jun 03 09:40:38 2007 +0000 +++ b/pidgin/gtksound.c Wed Jun 27 21:43:18 2007 +0000 @@ -370,12 +370,12 @@ break; case GST_MESSAGE_ERROR: gst_message_parse_error(msg, &err, NULL); - purple_debug_error("gstreamer", err->message); + purple_debug_error("gstreamer", "%s\n", err->message); g_error_free(err); break; case GST_MESSAGE_WARNING: gst_message_parse_warning(msg, &err, NULL); - purple_debug_warning("gstreamer", err->message); + purple_debug_warning("gstreamer", "%s\n", err->message); g_error_free(err); break; default:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtksourceundomanager.c Wed Jun 27 21:43:18 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)