Mercurial > pidgin.yaz
changeset 21924:e9005670e279
merge of 'f6430c7013d08f95c60248eeb22c752a0107499b'
and 'f84b5a4f3cdf2a2a92ff24e79e6aa6c2e0c8c1aa'
author | Etan Reisner <pidgin@unreliablesource.net> |
---|---|
date | Sat, 22 Dec 2007 23:18:08 +0000 |
parents | d7f1231cc21a (diff) da103e8cdb33 (current diff) |
children | 2b6e6dd24a37 |
files | libpurple/protocols/bonjour/mdns_howl.c |
diffstat | 112 files changed, 5070 insertions(+), 2206 deletions(-) [+] |
line wrap: on
line diff
--- a/.mtn-ignore Sat Dec 22 17:54:30 2007 +0000 +++ b/.mtn-ignore Sat Dec 22 23:18:08 2007 +0000 @@ -33,6 +33,7 @@ pidgin.spec$ pidgin-.*.tar.gz pidgin-.*.tar.bz2 +pidgin-*.*.*-win32bin$ pidgin/pidgin$ pidgin/pixmaps/emotes/default/24/theme pidgin/pixmaps/emotes/none/theme @@ -51,6 +52,7 @@ libpurple/plugins/perl/common/const-c.inc libpurple/plugins/perl/common/const-xs.inc libpurple/plugins/perl/common/lib +libpurple/purple.h$ libpurple/purple-client-bindings.c libpurple/purple-client-bindings.h libpurple/purple-client-example
--- a/AUTHORS Sat Dec 22 17:54:30 2007 +0000 +++ b/AUTHORS Sat Dec 22 23:18:08 2007 +0000 @@ -35,6 +35,7 @@ Megan 'Cae' Schneider - support/QA Evan Schoenberg - Developer Kevin 'SimGuy' Stange - Developer & Webmaster +Will 'resiak' Thompson - Developer Stu 'nosnilmot' Tomlinson - Developer Nathan 'faceprint' Walp - Developer @@ -42,8 +43,8 @@ ------------------- Dennis 'EvilDennisR' Ristuccia Peter 'Fmoo' Ruibal +Elliott 'QuLogic' Sales de Andrade Gabriel 'Nix' Schulhof -Will 'resiak' Thompson Retired Developers: ------------------
--- a/COPYRIGHT Sat Dec 22 17:54:30 2007 +0000 +++ b/COPYRIGHT Sat Dec 22 23:18:08 2007 +0000 @@ -309,6 +309,7 @@ Tim Ringenbach Dennis Ristuccia Lee Roach +Eion Robb Rhett Robinson Luciano Miguel Ferreira Rocha Andrew Rodland @@ -326,6 +327,7 @@ Andrew Sayman Alceste Scalas Carsten Schaar +Jonathan Schleifer <js-pidgin@webkeks.org> Matteo Settenvini Colin Seymour Luke Schierer
--- a/ChangeLog Sat Dec 22 17:54:30 2007 +0000 +++ b/ChangeLog Sat Dec 22 23:18:08 2007 +0000 @@ -1,9 +1,17 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul -version 2.3.2 (??/??/????): +version 2.4.0 (??/??/????): libpurple: * Fixed various problems with loss of status messages when going or returning from idle on MySpaceIM. + * Eliminated unmaintained Howl backend implementation for the + Bonjour protocol. Avahi (or Apple's Bonjour runtime on win32) is + now required to use Bonjour. + + Pidgin: + * Added the ability to theme conversation name colors (red and blue) + through your GTK+ theme, and exposed those theme settings to the + Pidgin GTK+ Theme Control plugin (Dustin Howett) Finch: * Color is used in the buddylist to indicate status, and the conversation @@ -13,6 +21,27 @@ request dialog. M-d will properly delete-forward-word, and M-f has been fixed to imitate readline's behavior. +version 2.3.1 (12/7/2007): + http://developer.pidgin.im/query?status=closed&milestone=2.3.1 + NOTE: Due to the way this release was made, it is possible that + bugs marked as fixed in 2.3.1 will not be fixed until the + next release. + + * Fixed a number of MSN bugs introduced in 2.3.0, resolving problems + connecting to MSN and random local display name changes + * Going idle on MySpaceIM will no longer clear your status and message. + * Idle MySpaceIM buddies should now appear online at login. + * Fixed crashes in XMPP when discovering a client's capabilities + * Don't set the current tune title if it's NULL (XMPP/Google Talk) + * Don't allow buddies to be manually added to Bonjour + * Don't advertise IPv6 on Bonjour because we don't support it + * Compile fixes for FreeBSD and Solaris + * Update QQ client version so some accounts can connect again + * Do not allow ISON requests to stack in IRC, preventing flooding IRC + servers when temporary network outages are restored + * Plug several leaks in the perl plugin loader + * Prevent autoaccept plugin overwriting existing files + version 2.3.0 (11/24/2007): http://developer.pidgin.im/query?status=closed&milestone=2.3.0 NOTE: Some bugs marked fixed in 2.2.1, 2.2.2 or 2.2.3 may not
--- a/ChangeLog.API Sat Dec 22 17:54:30 2007 +0000 +++ b/ChangeLog.API Sat Dec 22 23:18:08 2007 +0000 @@ -1,6 +1,29 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul -version 2.3.2 (??/??/????): +version 2.4.0 (??/??/????): + libpurple: + Added: + * purple_certificate_add_ca_search_path. (Florian Quèze) + * purple_gai_strerror. + * purple_major_version, purple_minor_version, + purple_micro_version variables are exported by version.h, + giving the version of libpurple in use at runtime. + + Pidgin: + Added: + * pidgin_create_dialog to create a window that closes on escape. Also + added utility functions pidgin_dialog_get_vbox_with_properties, + pidgin_dialog_get_vbox, pidgin_dialog_get_action_area to access the + contents in the created dialog. (Peter 'fmoo' Ruibal) + * pidgin_dialog_add_button to add buttons to a dialog created by + pidgin_create_dialog. + * GTK_IMHTML_NO_SMILEY for GtkIMHtmlOptions means not to look for + smileys in the text. (Florian 'goutnet' Delizy) + * pidgin_auto_parent_window to make a window transient for a suitable + parent window. + * pidgin_tooltip_setup_for_treeview, pidgin_tooltip_destroy and + pidgin_tooltip_show to simplify the process of drawing tooltips. + Finch: libgnt: * Added gnt_tree_set_row_color to set the color for a row in a tree.
--- a/Makefile.am Sat Dec 22 17:54:30 2007 +0000 +++ b/Makefile.am Sat Dec 22 23:18:08 2007 +0000 @@ -30,10 +30,12 @@ distcheck-hook: libpurple/plugins/perl/common/Purple.pm pidgin/plugins/perl/common/Pidgin.pm # cp libpurple/plugins/perl/common/Gaim.pm $(distdir)/libpurple/plugins/perl/common +if ENABLE_GTK appsdir = $(datadir)/applications apps_in_files = pidgin.desktop.in apps_DATA = $(apps_in_files:.desktop.in=.desktop) @INTLTOOL_DESKTOP_RULE@ +endif if ENABLE_GTK GTK_DIR=pidgin @@ -51,7 +53,7 @@ @doxygen if HAVE_XSLTPROC @echo "Generating devhelp index..." - @xsltproc doxy2devhelp.xsl doc/xml/index.xml > doc/html/pidgin.devhelp + @xsltproc $(top_srcdir)/doxy2devhelp.xsl doc/xml/index.xml > doc/html/pidgin.devhelp @echo "(Symlink doc/html to ~/.local/share/gtk-doc/html/pidgin to make devhelp see the documentation)" else @echo "Not generating devhelp index: xsltproc was not found by configure"
--- a/NEWS Sat Dec 22 17:54:30 2007 +0000 +++ b/NEWS Sat Dec 22 23:18:08 2007 +0000 @@ -1,5 +1,19 @@ Pidgin and Finch: The Pimpin' Penguin IM Clients That're Good for the Soul +2.3.1 (12/7/2007): + Stu: I'm sorry for the MSN problems and the plugin crashes in 2.3.0. + Hopefully this will redeem us. This fixes a number of bugs. I'm a + bit late but I'd like to welcome John to the team. Enjoy! + + Luke: I've done absolutely nothing in the last 2 weeks, except watch + others commit bug and, more, leak fixes. People should be noticing + remarkably fewer memory leaks now than 2 or more releases ago. + + Kevin: I'm not quite sure what happened to our MySpaceIM Summer of + Code student, but I fixed a few MySpace bugs with idle and status. + I will try to fix some of the other more significant bugs, after I + figure out the protocol, especially including grouping issues. + 2.3.0 (11/20/2007): Luke: While this does not have the new MSN code, rest assured that we are working on it and that it is nearing release. This contains
--- a/configure.ac Sat Dec 22 17:54:30 2007 +0000 +++ b/configure.ac Sat Dec 22 23:18:08 2007 +0000 @@ -43,19 +43,19 @@ # # Make sure to update finch/libgnt/configure.ac with libgnt version changes. # -m4_define([purple_lt_current], [3]) +m4_define([purple_lt_current], [4]) m4_define([purple_major_version], [2]) -m4_define([purple_minor_version], [3]) -m4_define([purple_micro_version], [1]) +m4_define([purple_minor_version], [4]) +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], [3]) +m4_define([gnt_lt_current], [4]) m4_define([gnt_major_version], [2]) -m4_define([gnt_minor_version], [3]) -m4_define([gnt_micro_version], [1]) +m4_define([gnt_minor_version], [4]) +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]) @@ -722,59 +722,6 @@ AC_SUBST(AVAHI_CFLAGS) AC_SUBST(AVAHI_LIBS) -AM_CONDITIONAL(MDNS_AVAHI, test "x$avahiincludes" = "xyes" -a "x$avahilibs" = "xyes") - -dnl ####################################################################### -dnl # Check for Howl headers (for Bonjour) -dnl ####################################################################### -AC_ARG_WITH(howl-includes, [AC_HELP_STRING([--with-howl-includes=DIR], [compile the Bonjour plugin against the Howl includes in DIR])], [ac_howl_includes="$withval"], [ac_howl_includes="no"]) -AC_ARG_WITH(howl-libs, [AC_HELP_STRING([--with-howl-libs=DIR], [compile the Bonjour plugin against the Howl libs in DIR])], [ac_howl_libs="$withval"], [ac_howl_libs="no"]) -HOWL_CFLAGS="" -HOWL_LIBS="" - -dnl Attempt to autodetect avahi-compat-howl -dnl TODO: (This should be removed when the native avahi stuff is stable) -PKG_CHECK_MODULES(HOWL, avahi-compat-howl, [ - howlincludes="yes" - howllibs="yes" -], [ - AC_MSG_RESULT(no) - howlincludes="no" - howllibs="no" -]) - -dnl Attempt to autodetect Howl -if test "x$howlincludes" = "xno"; then - PKG_CHECK_MODULES(HOWL, howl, [ - howlincludes="yes" - howllibs="yes" - ], [ - AC_MSG_RESULT(no) - howlincludes="no" - howllibs="no" - ]) -fi - -dnl Override HOWL_CFLAGS if the user specified an include dir -if test "$ac_howl_includes" != "no"; then - HOWL_CFLAGS="-I$ac_howl_includes" -fi -CPPFLAGS_save="$CPPFLAGS" -CPPFLAGS="$CPPFLAGS $HOWL_CFLAGS" -AC_CHECK_HEADER(howl.h, [howlincludes=yes], [howlincludes=no]) -CPPFLAGS="$CPPFLAGS_save" - -dnl Override HOWL_LIBS if the user specified a libs dir -if test "$ac_howl_libs" != "no"; then - HOWL_LIBS="-L$ac_howl_libs -lhowl" -fi -AC_CHECK_LIB(howl, sw_discovery_init, [howllibs=yes], [howllibs=no], $HOWL_LIBS) - -AC_SUBST(HOWL_CFLAGS) -AC_SUBST(HOWL_LIBS) - -AM_CONDITIONAL(MDNS_HOWL, test "x$howlincludes" = "xyes" -a "x$howllibs" = "xyes") - dnl ####################################################################### dnl # Check for SILC client includes and libraries @@ -960,9 +907,7 @@ STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/sametime//'` fi if test "x$avahiincludes" != "xyes" -o "x$avahilibs" != "xyes"; then - if test "x$howlincludes" != "xyes" -o "x$howllibs" != "xyes"; then - STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/bonjour//'` - fi + STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/bonjour//'` fi if test "x$enable_msnp14" != "xyes" ; then STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/msn/msnp9/'` @@ -1049,9 +994,7 @@ DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/sametime//'` fi if test "x$avahiincludes" != "xyes" -o "x$avahilibs" != "xyes"; then - if test "x$howlincludes" != "xyes" -o "x$howllibs" != "xyes"; then - DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/bonjour//'` - fi + DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/bonjour//'` fi if test "x$enable_msnp14" != "xyes" ; then DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/msn/msnp9/'` @@ -1087,7 +1030,7 @@ *) echo "Invalid dynamic protocol $i!!" ; exit ;; esac done -AM_CONDITIONAL(DYNAMIC_BONJOUR, test "x$dynamic_bonjour" = "xyes" -a [ [ "x$avahiincludes" = "xyes" -a "x$avahilibs " = "xyes" ] -o [ "x$howlincludes" = "xyes" -a "x$howllibs" = "xyes" ] ] ) +AM_CONDITIONAL(DYNAMIC_BONJOUR, test "x$dynamic_bonjour" = "xyes" -a [ "x$avahiincludes" = "xyes" -a "x$avahilibs " = "xyes" ] ) AM_CONDITIONAL(DYNAMIC_GG, test "x$dynamic_gg" = "xyes") AM_CONDITIONAL(DYNAMIC_IRC, test "x$dynamic_irc" = "xyes") AM_CONDITIONAL(DYNAMIC_JABBER, test "x$dynamic_jabber" = "xyes") @@ -1336,6 +1279,34 @@ AM_CONDITIONAL(ENABLE_DBUS, test "x$enable_dbus" = "xyes") +dnl Check for Python headers (currently useful only for libgnt) +dnl (Thanks to XChat) +AC_PATH_PROG(pythonpath, python) +if test "_$pythonpath" != _ ; then + AC_MSG_CHECKING(for Python compile flags) + PY_PREFIX=`$pythonpath -c 'import sys ; print sys.prefix'` + PY_EXEC_PREFIX=`$pythonpath -c 'import sys ; print sys.exec_prefix'` + changequote(<<, >>)dnl + PY_VERSION=`$pythonpath -c 'import sys ; print sys.version[0:3]'` + PY_MAJOR=`$pythonpath -c 'import sys ; print sys.version[0:2]'` + changequote([, ])dnl + if test -f $PY_PREFIX/include/python$PY_VERSION/Python.h -a "$PY_MAJOR" = "2."; then + AC_CHECK_LIB(pthread, pthread_create, ) + AC_CHECK_LIB(util, openpty, ) + AC_CHECK_LIB(db, dbopen, ) + PY_LIBS="-lpython$PY_VERSION -L$PY_EXEC_PREFIX/lib/python$PY_VERSION/config" + PY_CFLAGS="-I$PY_PREFIX/include/python$PY_VERSION" + AC_DEFINE(USE_PYTHON, [1], [Define if python headers are available.]) + AC_MSG_RESULT(ok) + else + AC_MSG_RESULT([Can't find Python.h]) + PY_LIBS="" + PY_CFLAGS="" + fi +fi +AC_SUBST(PY_CFLAGS) +AC_SUBST(PY_LIBS) + dnl ####################################################################### dnl # Check for Mono support dnl #######################################################################
--- a/doc/pidgin.1.in Sat Dec 22 17:54:30 2007 +0000 +++ b/doc/pidgin.1.in Sat Dec 22 23:18:08 2007 +0000 @@ -88,8 +88,8 @@ .TP .B Add Buddy Pounce A Buddy Pounce is a configurable automated action to be performed when the -buddy's state changes. This will open the \fBBuddy Pounce\fR dialog to be -discussed later. +buddy's state changes. This will open the \fBBuddy Pounce\fR dialog, which +will be discussed later. .TP .B View Log Pidgin is capable of automatically logging messages. These logs are @@ -118,7 +118,7 @@ .SH ACCOUNT EDITOR The account editor consists of a list of accounts and information about -them. It can be accessed by selecting \fBManage\fR from the Tools menu. +them. It can be accessed by selecting \fBManage\fR from the Accounts menu. Clicking \fIDelete\fR will delete the currently selected account. Clicking \fIAdd\fR or \fIModify\fR will invoke a \fBModify Account\fR window. Here, the user can add or alter account information. When creating @@ -590,6 +590,8 @@ .br Kevin 'SimGuy' Stange (developer and webmaster) .br + Will 'resiak' Thompson (developer) +.br Stu 'nosnilmot' Tomlinson (developer) .br Nathan 'faceprint' Walp (developer) @@ -602,9 +604,9 @@ .br Peter 'fmoo' Ruibal .br - Gabriel 'Nix' Schulhof + Elliott 'QuLogic' Sales de Andrade .br - Will 'resiak' Thompson + Gabriel 'Nix' Schulhof .br
--- a/finch/gntdebug.c Sat Dec 22 17:54:30 2007 +0000 +++ b/finch/gntdebug.c Sat Dec 22 23:18:08 2007 +0000 @@ -344,6 +344,9 @@ REGISTER_G_LOG_HANDLER("GModule"); REGISTER_G_LOG_HANDLER("GLib-GObject"); REGISTER_G_LOG_HANDLER("GThread"); +#ifdef USE_GSTREAMER + REGISTER_G_LOG_HANDLER("GStreamer"); +#endif g_set_print_handler(print_stderr); /* Redirect the debug messages to stderr */ if (!purple_debug_is_enabled())
--- a/finch/gntplugin.c Sat Dec 22 17:54:30 2007 +0000 +++ b/finch/gntplugin.c Sat Dec 22 23:18:08 2007 +0000 @@ -53,6 +53,13 @@ static GntWidget *process_pref_frame(PurplePluginPrefFrame *frame); static void +free_stringlist(GList *list) +{ + g_list_foreach(list, (GFunc)g_free, NULL); + g_list_free(list); +} + +static void decide_conf_button(PurplePlugin *plugin) { if (purple_plugin_is_loaded(plugin) && @@ -426,11 +433,14 @@ PurpleRequestFields *fields; PurpleRequestFieldGroup *group = NULL; GList *prefs; - + GList *stringlist = NULL; + GntWidget *ret = NULL; + fields = purple_request_fields_new(); for (prefs = purple_plugin_pref_frame_get_prefs(frame); prefs; prefs = prefs->next) { PurplePluginPref *pref = prefs->data; + PurplePrefType type; const char *name = purple_plugin_pref_get_name(pref); const char *label = purple_plugin_pref_get_label(pref); if(name == NULL) { @@ -448,20 +458,67 @@ } field = NULL; - switch(purple_prefs_get_type(name)) { - case PURPLE_PREF_BOOLEAN: - field = purple_request_field_bool_new(name, label, purple_prefs_get_bool(name)); - break; - case PURPLE_PREF_INT: - field = purple_request_field_int_new(name, label, purple_prefs_get_int(name)); - break; - case PURPLE_PREF_STRING: - field = purple_request_field_string_new(name, label, purple_prefs_get_string(name), - purple_plugin_pref_get_format_type(pref) & PURPLE_STRING_FORMAT_TYPE_MULTILINE); - break; - default: - break; + type = purple_prefs_get_type(name); + if(purple_plugin_pref_get_type(pref) == PURPLE_PLUGIN_PREF_CHOICE) { + GList *list = purple_plugin_pref_get_choices(pref); + gpointer current_value = NULL; + + switch(type) { + case PURPLE_PREF_BOOLEAN: + current_value = g_strdup_printf("%d", (int)purple_prefs_get_bool(name)); + break; + case PURPLE_PREF_INT: + current_value = g_strdup_printf("%d", (int)purple_prefs_get_int(name)); + break; + case PURPLE_PREF_STRING: + current_value = g_strdup(purple_prefs_get_string(name)); + break; + default: + continue; + } + + field = purple_request_field_list_new(name, label); + purple_request_field_list_set_multi_select(field, FALSE); + while (list && list->next) { + const char *label = list->data; + char *value = NULL; + switch(type) { + case PURPLE_PREF_BOOLEAN: + value = g_strdup_printf("%d", (int)list->next->data); + break; + case PURPLE_PREF_INT: + value = g_strdup_printf("%d", (int)list->next->data); + break; + case PURPLE_PREF_STRING: + value = g_strdup(list->next->data); + break; + default: + break; + } + stringlist = g_list_prepend(stringlist, value); + purple_request_field_list_add(field, label, value); + if (strcmp(value, current_value) == 0) + purple_request_field_list_add_selected(field, label); + list = list->next->next; + } + g_free(current_value); + } else { + switch(type) { + case PURPLE_PREF_BOOLEAN: + field = purple_request_field_bool_new(name, label, purple_prefs_get_bool(name)); + break; + case PURPLE_PREF_INT: + field = purple_request_field_int_new(name, label, purple_prefs_get_int(name)); + break; + case PURPLE_PREF_STRING: + field = purple_request_field_string_new(name, label, purple_prefs_get_string(name), + purple_plugin_pref_get_format_type(pref) & PURPLE_STRING_FORMAT_TYPE_MULTILINE); + break; + default: + break; + } } + if (field) { if (group == NULL) { group = purple_request_field_group_new(_("Preferences")); @@ -471,9 +528,11 @@ } } - return purple_request_fields(NULL, _("Preferences"), NULL, NULL, fields, + ret = purple_request_fields(NULL, _("Preferences"), NULL, NULL, fields, _("Save"), G_CALLBACK(finch_request_save_in_prefs), _("Cancel"), NULL, NULL, NULL, NULL, NULL); + g_signal_connect_swapped(G_OBJECT(ret), "destroy", G_CALLBACK(free_stringlist), stringlist); + return ret; }
--- a/finch/gntrequest.c Sat Dec 22 17:54:30 2007 +0000 +++ b/finch/gntrequest.c Sat Dec 22 23:18:08 2007 +0000 @@ -44,7 +44,7 @@ GntWidget *dialog; GCallback *cbs; gboolean save; -} PurpleGntFileRequest; +} FinchFileRequest; static GntWidget * setup_request_window(const char *title, const char *primary, @@ -387,6 +387,156 @@ } } +static GntWidget* +create_boolean_field(PurpleRequestField *field) +{ + const char *label = purple_request_field_get_label(field); + GntWidget *check = gnt_check_box_new(label); + gnt_check_box_set_checked(GNT_CHECK_BOX(check), + purple_request_field_bool_get_default_value(field)); + return check; +} + +static GntWidget* +create_string_field(PurpleRequestField *field, GntWidget **screenname) +{ + const char *hint = purple_request_field_get_type_hint(field); + GntWidget *entry = gnt_entry_new( + purple_request_field_string_get_default_value(field)); + gnt_entry_set_masked(GNT_ENTRY(entry), + purple_request_field_string_is_masked(field)); + 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)) { + if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) + continue; + gnt_entry_add_suggest(GNT_ENTRY(entry), purple_buddy_get_name((PurpleBuddy*)node)); + } + gnt_entry_set_always_suggest(GNT_ENTRY(entry), TRUE); + if (screenname) + *screenname = entry; + } else if (hint && !strcmp(hint, "group")) { + PurpleBlistNode *node; + for (node = purple_blist_get_root(); node; node = node->next) { + if (PURPLE_BLIST_NODE_IS_GROUP(node)) + gnt_entry_add_suggest(GNT_ENTRY(entry), ((PurpleGroup *)node)->name); + } + } + return entry; +} + +static GntWidget* +create_integer_field(PurpleRequestField *field) +{ + char str[256]; + int val = purple_request_field_int_get_default_value(field); + GntWidget *entry; + + snprintf(str, sizeof(str), "%d", val); + entry = gnt_entry_new(str); + gnt_entry_set_flag(GNT_ENTRY(entry), GNT_ENTRY_FLAG_INT); + return entry; +} + +static GntWidget* +create_choice_field(PurpleRequestField *field) +{ + int id; + GList *list; + GntWidget *combo = gnt_combo_box_new(); + + list = purple_request_field_choice_get_labels(field); + for (id = 1; list; list = list->next, id++) + { + gnt_combo_box_add_data(GNT_COMBO_BOX(combo), + GINT_TO_POINTER(id), list->data); + } + gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), + GINT_TO_POINTER(purple_request_field_choice_get_default_value(field))); + return combo; +} + +static GntWidget* +create_list_field(PurpleRequestField *field) +{ + GntWidget *ret = NULL; + GList *list; + gboolean multi = purple_request_field_list_get_multi_select(field); + if (multi) + { + GntWidget *tree = gnt_tree_new(); + + list = purple_request_field_list_get_items(field); + for (; list; list = list->next) + { + const char *text = list->data; + gpointer key = purple_request_field_list_get_data(field, text); + gnt_tree_add_choice(GNT_TREE(tree), key, + gnt_tree_create_row(GNT_TREE(tree), text), NULL, NULL); + if (purple_request_field_list_is_selected(field, text)) + gnt_tree_set_choice(GNT_TREE(tree), key, TRUE); + } + ret = tree; + } + else + { + GntWidget *combo = gnt_combo_box_new(); + + list = purple_request_field_list_get_items(field); + for (; list; list = list->next) + { + const char *text = list->data; + gpointer key = purple_request_field_list_get_data(field, text); + gnt_combo_box_add_data(GNT_COMBO_BOX(combo), key, text); + if (purple_request_field_list_is_selected(field, text)) + gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), key); + } + ret = combo; + } + return ret; +} + +static GntWidget* +create_account_field(PurpleRequestField *field) +{ + gboolean all; + PurpleAccount *def; + GList *list; + GntWidget *combo = gnt_combo_box_new(); + + all = purple_request_field_account_get_show_all(field); + def = purple_request_field_account_get_value(field); + if (!def) + def = purple_request_field_account_get_default_value(field); + + if (all) + list = purple_accounts_get_all(); + else + list = purple_connections_get_all(); + + for (; list; list = list->next) + { + PurpleAccount *account; + char *text; + + if (all) + account = list->data; + else + account = purple_connection_get_account(list->data); + + text = g_strdup_printf("%s (%s)", + purple_account_get_username(account), + purple_account_get_protocol_name(account)); + gnt_combo_box_add_data(GNT_COMBO_BOX(combo), account, text); + g_free(text); + if (account == def) + gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), account); + } + gnt_widget_set_size(combo, 20, 3); /* ew */ + return combo; +} + static void * finch_request_fields(const char *title, const char *primary, const char *secondary, PurpleRequestFields *allfields, @@ -424,10 +574,10 @@ PurpleRequestField *field = fields->data; PurpleRequestFieldType type = purple_request_field_get_type(field); const char *label = purple_request_field_get_label(field); - + hbox = gnt_hbox_new(TRUE); /* hrm */ gnt_box_add_widget(GNT_BOX(box), hbox); - + if (type != PURPLE_REQUEST_FIELD_BOOLEAN && label) { GntWidget *l = gnt_label_new(label); @@ -437,154 +587,35 @@ if (type == PURPLE_REQUEST_FIELD_BOOLEAN) { - GntWidget *check = gnt_check_box_new(label); - gnt_check_box_set_checked(GNT_CHECK_BOX(check), - purple_request_field_bool_get_default_value(field)); - gnt_box_add_widget(GNT_BOX(hbox), check); - field->ui_data = check; + field->ui_data = create_boolean_field(field); } else if (type == PURPLE_REQUEST_FIELD_STRING) { - const char *hint = purple_request_field_get_type_hint(field); - GntWidget *entry = gnt_entry_new( - purple_request_field_string_get_default_value(field)); - gnt_entry_set_masked(GNT_ENTRY(entry), - purple_request_field_string_is_masked(field)); - 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)) { - if (!PURPLE_BLIST_NODE_IS_BUDDY(node)) - continue; - gnt_entry_add_suggest(GNT_ENTRY(entry), purple_buddy_get_name((PurpleBuddy*)node)); - } - gnt_entry_set_always_suggest(GNT_ENTRY(entry), TRUE); - screenname = entry; - } else if (hint && !strcmp(hint, "group")) { - PurpleBlistNode *node; - for (node = purple_blist_get_root(); node; node = node->next) { - if (PURPLE_BLIST_NODE_IS_GROUP(node)) - gnt_entry_add_suggest(GNT_ENTRY(entry), ((PurpleGroup *)node)->name); - } - } - gnt_box_add_widget(GNT_BOX(hbox), entry); - field->ui_data = entry; + field->ui_data = create_string_field(field, &screenname); } else if (type == PURPLE_REQUEST_FIELD_INTEGER) { - char str[256]; - int val = purple_request_field_int_get_default_value(field); - GntWidget *entry; - - snprintf(str, sizeof(str), "%d", val); - entry = gnt_entry_new(str); - gnt_entry_set_flag(GNT_ENTRY(entry), GNT_ENTRY_FLAG_INT); - gnt_box_add_widget(GNT_BOX(hbox), entry); - field->ui_data = entry; + field->ui_data = create_integer_field(field); } else if (type == PURPLE_REQUEST_FIELD_CHOICE) { - int id; - GList *list; - GntWidget *combo = gnt_combo_box_new(); - gnt_box_add_widget(GNT_BOX(hbox), combo); - field->ui_data = combo; - - list = purple_request_field_choice_get_labels(field); - for (id = 1; list; list = list->next, id++) - { - gnt_combo_box_add_data(GNT_COMBO_BOX(combo), - GINT_TO_POINTER(id), list->data); - } - gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), - GINT_TO_POINTER(purple_request_field_choice_get_default_value(field))); + field->ui_data = create_choice_field(field); } else if (type == PURPLE_REQUEST_FIELD_LIST) { - GList *list; - gboolean multi = purple_request_field_list_get_multi_select(field); - if (multi) - { - GntWidget *tree = gnt_tree_new(); - gnt_box_add_widget(GNT_BOX(hbox), tree); - field->ui_data = tree; - - list = purple_request_field_list_get_items(field); - for (; list; list = list->next) - { - const char *text = list->data; - gpointer key = purple_request_field_list_get_data(field, text); - gnt_tree_add_choice(GNT_TREE(tree), key, - gnt_tree_create_row(GNT_TREE(tree), text), NULL, NULL); - if (purple_request_field_list_is_selected(field, text)) - gnt_tree_set_choice(GNT_TREE(tree), key, TRUE); - } - } - else - { - GntWidget *combo = gnt_combo_box_new(); - gnt_box_set_alignment(GNT_BOX(hbox), GNT_ALIGN_MID); - gnt_box_add_widget(GNT_BOX(hbox), combo); - field->ui_data = combo; - - list = purple_request_field_list_get_items(field); - for (; list; list = list->next) - { - const char *text = list->data; - gpointer key = purple_request_field_list_get_data(field, text); - gnt_combo_box_add_data(GNT_COMBO_BOX(combo), key, text); - if (purple_request_field_list_is_selected(field, text)) - gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), key); - } - } + field->ui_data = create_list_field(field); } else if (type == PURPLE_REQUEST_FIELD_ACCOUNT) { - gboolean all; - PurpleAccount *def; - GList *list; - GntWidget *combo = gnt_combo_box_new(); - gnt_box_set_alignment(GNT_BOX(hbox), GNT_ALIGN_MID); - gnt_box_add_widget(GNT_BOX(hbox), combo); - field->ui_data = combo; - - all = purple_request_field_account_get_show_all(field); - def = purple_request_field_account_get_value(field); - if (!def) - def = purple_request_field_account_get_default_value(field); - - if (all) - list = purple_accounts_get_all(); - else - list = purple_connections_get_all(); - - for (; list; list = list->next) - { - PurpleAccount *account; - char *text; - - if (all) - account = list->data; - else - account = purple_connection_get_account(list->data); - - text = g_strdup_printf("%s (%s)", - purple_account_get_username(account), - purple_account_get_protocol_name(account)); - gnt_combo_box_add_data(GNT_COMBO_BOX(combo), account, text); - g_free(text); - if (account == def) - gnt_combo_box_set_selected(GNT_COMBO_BOX(combo), account); - } - gnt_widget_set_size(combo, 20, 3); /* ew */ - accountlist = combo; + accountlist = field->ui_data = create_account_field(field); } else { - gnt_box_add_widget(GNT_BOX(hbox), - gnt_label_new_with_format(_("Not implemented yet."), - GNT_TEXT_FLAG_BOLD)); + field->ui_data = gnt_label_new_with_format(_("Not implemented yet."), + GNT_TEXT_FLAG_BOLD); } + gnt_box_set_alignment(GNT_BOX(hbox), GNT_ALIGN_MID); + gnt_box_add_widget(GNT_BOX(hbox), GNT_WIDGET(field->ui_data)); } if (grlist->next) gnt_box_add_widget(GNT_BOX(box), gnt_hline_new()); @@ -610,7 +641,7 @@ static void file_cancel_cb(gpointer fq, GntWidget *wid) { - PurpleGntFileRequest *data = fq; + FinchFileRequest *data = fq; if (data->cbs[1] != NULL) ((PurpleRequestFileCb)data->cbs[1])(data->user_data, NULL); @@ -620,7 +651,7 @@ static void file_ok_cb(gpointer fq, GntWidget *widget) { - PurpleGntFileRequest *data = fq; + FinchFileRequest *data = fq; char *file = gnt_file_sel_get_selected_file(GNT_FILE_SEL(data->dialog)); char *dir = g_path_get_dirname(file); if (data->cbs[0] != NULL) @@ -634,38 +665,30 @@ } static void -file_request_destroy(PurpleGntFileRequest *data) +file_request_destroy(FinchFileRequest *data) { g_free(data->cbs); g_free(data); } -static void * -finch_request_file(const char *title, const char *filename, - gboolean savedialog, +static FinchFileRequest * +finch_file_request_window(const char *title, const char *path, GCallback ok_cb, GCallback cancel_cb, - PurpleAccount *account, const char *who, PurpleConversation *conv, void *user_data) { GntWidget *window = gnt_file_sel_new(); GntFileSel *sel = GNT_FILE_SEL(window); - PurpleGntFileRequest *data = g_new0(PurpleGntFileRequest, 1); - const char *path; + FinchFileRequest *data = g_new0(FinchFileRequest, 1); data->user_data = user_data; data->cbs = g_new0(GCallback, 2); data->cbs[0] = ok_cb; data->cbs[1] = cancel_cb; data->dialog = window; - data->save = savedialog; - gnt_box_set_title(GNT_BOX(window), title ? title : (savedialog ? _("Save File...") : _("Open File..."))); + gnt_box_set_title(GNT_BOX(window), title); - path = purple_prefs_get_path(savedialog ? "/finch/filelocations/last_save_folder" : "/finch/filelocations/last_open_folder"); gnt_file_sel_set_current_location(sel, (path && *path) ? path : purple_home_dir()); - if (savedialog) - gnt_file_sel_set_suggested_filename(sel, filename); - g_signal_connect(G_OBJECT(sel->cancel), "activate", G_CALLBACK(action_performed), window); g_signal_connect(G_OBJECT(sel->select), "activate", @@ -678,9 +701,45 @@ setup_default_callback(window, file_cancel_cb, data); g_object_set_data_full(G_OBJECT(window), "filerequestdata", data, (GDestroyNotify)file_request_destroy); - gnt_widget_show(window); + + return data; +} + +static void * +finch_request_file(const char *title, const char *filename, + gboolean savedialog, + GCallback ok_cb, GCallback cancel_cb, + PurpleAccount *account, const char *who, PurpleConversation *conv, + void *user_data) +{ + FinchFileRequest *data; + const char *path; - return window; + path = purple_prefs_get_path(savedialog ? "/finch/filelocations/last_save_folder" : "/finch/filelocations/last_open_folder"); + data = finch_file_request_window(title ? title : (savedialog ? _("Save File...") : _("Open File...")), path, + ok_cb, cancel_cb, user_data); + data->save = savedialog; + if (savedialog) + gnt_file_sel_set_suggested_filename(GNT_FILE_SEL(data->dialog), filename); + + gnt_widget_show(data->dialog); + return data->dialog; +} + +static void * +finch_request_folder(const char *title, const char *dirname, GCallback ok_cb, + GCallback cancel_cb, PurpleAccount *account, const char *who, PurpleConversation *conv, + void *user_data) +{ + FinchFileRequest *data; + + data = finch_file_request_window(title ? title : _("Choose Location..."), dirname, + ok_cb, cancel_cb, user_data); + data->save = TRUE; + gnt_file_sel_set_dirs_only(GNT_FILE_SEL(data->dialog), TRUE); + + gnt_widget_show(data->dialog); + return data->dialog; } static PurpleRequestUiOps uiops = @@ -691,7 +750,7 @@ finch_request_fields, finch_request_file, finch_close_request, - NULL, /* No plans for request_folder */ + finch_request_folder, NULL, NULL, NULL,
--- a/finch/libgnt/Makefile.am Sat Dec 22 17:54:30 2007 +0000 +++ b/finch/libgnt/Makefile.am Sat Dec 22 23:18:08 2007 +0000 @@ -84,10 +84,12 @@ libgnt_la_LIBADD = \ $(GLIB_LIBS) \ $(GNT_LIBS) \ - $(LIBXML_LIBS) + $(LIBXML_LIBS) \ + $(PY_LIBS) AM_CPPFLAGS = \ $(GLIB_CFLAGS) \ $(GNT_CFLAGS) \ $(DEBUG_CFLAGS) \ - $(LIBXML_CFLAGS) + $(LIBXML_CFLAGS) \ + $(PY_CFLAGS)
--- a/finch/libgnt/configure.ac Sat Dec 22 17:54:30 2007 +0000 +++ b/finch/libgnt/configure.ac Sat Dec 22 23:18:08 2007 +0000 @@ -24,10 +24,10 @@ # Make sure to update ../../configure.ac with libgnt version changes. # -m4_define([gnt_lt_current], [3]) +m4_define([gnt_lt_current], [4]) m4_define([gnt_major_version], [2]) -m4_define([gnt_minor_version], [3]) -m4_define([gnt_micro_version], [1]) +m4_define([gnt_minor_version], [4]) +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]) @@ -302,6 +302,34 @@ *** You need ncursesw or ncurses and its header files.]) fi +dnl Check for Python headers (currently useful only for libgnt) +dnl (Thanks to XChat) +AC_PATH_PROG(pythonpath, python) +if test "_$pythonpath" != _ ; then + AC_MSG_CHECKING(for Python compile flags) + PY_PREFIX=`$pythonpath -c 'import sys ; print sys.prefix'` + PY_EXEC_PREFIX=`$pythonpath -c 'import sys ; print sys.exec_prefix'` + changequote(<<, >>)dnl + PY_VERSION=`$pythonpath -c 'import sys ; print sys.version[0:3]'` + PY_MAJOR=`$pythonpath -c 'import sys ; print sys.version[0:2]'` + changequote([, ])dnl + if test -f $PY_PREFIX/include/python$PY_VERSION/Python.h -a "$PY_MAJOR" = "2."; then + AC_CHECK_LIB(pthread, pthread_create, ) + AC_CHECK_LIB(util, openpty, ) + AC_CHECK_LIB(db, dbopen, ) + PY_LIBS="-lpython$PY_VERSION -L$PY_EXEC_PREFIX/lib/python$PY_VERSION/config" + PY_CFLAGS="-I$PY_PREFIX/include/python$PY_VERSION" + AC_DEFINE(USE_PYTHON, [1], [Define if python headers are available.]) + AC_MSG_RESULT(ok) + else + AC_MSG_RESULT([Can't find Python.h]) + PY_LIBS="" + PY_CFLAGS="" + fi +fi +AC_SUBST(PY_CFLAGS) +AC_SUBST(PY_LIBS) + dnl Check for libxml have_libxml=yes PKG_CHECK_MODULES(LIBXML, [libxml-2.0], , [
--- a/finch/libgnt/gntcolors.h Sat Dec 22 17:54:30 2007 +0000 +++ b/finch/libgnt/gntcolors.h Sat Dec 22 23:18:08 2007 +0000 @@ -93,7 +93,7 @@ * * @return A color * - * @since 2.3.1 (gnt), 2.3.1 (pidgin) + * @since 2.4.0 */ int gnt_colors_get_color(char *key); #endif @@ -119,7 +119,7 @@ * * @return A color pair * - * @since 2.3.1 + * @since 2.4.0 */ int gnt_color_add_pair(int fg, int bg); #endif
--- a/finch/libgnt/gntstyle.h Sat Dec 22 17:54:30 2007 +0000 +++ b/finch/libgnt/gntstyle.h Sat Dec 22 23:18:08 2007 +0000 @@ -74,7 +74,7 @@ * * @return NULL terminated string array. The array should be freed with g_strfreev(). * - * @since 2.3.2 + * @since 2.4.0 */ char **gnt_style_get_string_list(const char *group, const char *key, gsize *length); @@ -87,7 +87,7 @@ * * @return The value of the color as an int, or 0 on error. * - * @since 2.3.2 + * @since 2.4.0 */ int gnt_style_get_color(char *group, char *key);
--- a/finch/libgnt/gnttree.h Sat Dec 22 17:54:30 2007 +0000 +++ b/finch/libgnt/gnttree.h Sat Dec 22 23:18:08 2007 +0000 @@ -330,6 +330,7 @@ * @param tree The tree * @param key The key for the row * @param color The color + * @since 2.4.0 */ void gnt_tree_set_row_color(GntTree *, void *, int);
--- a/finch/libgnt/gntwm.c Sat Dec 22 17:54:30 2007 +0000 +++ b/finch/libgnt/gntwm.c Sat Dec 22 23:18:08 2007 +0000 @@ -20,12 +20,16 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ +#include "config.h" + +#ifdef USE_PYTHON +#include <Python.h> +#else #define _GNU_SOURCE #if (defined(__APPLE__) || defined(__unix__)) && !defined(__FreeBSD__) #define _XOPEN_SOURCE_EXTENDED #endif - -#include "config.h" +#endif #include <glib.h> #include <glib/gstdio.h> @@ -1198,12 +1202,50 @@ return ignore_keys ? !(ignore_keys = FALSE) : FALSE; } +#ifdef USE_PYTHON +static void +python_script_selected(GntFileSel *fs, const char *path, const char *f, gpointer n) +{ + char *dir = g_path_get_dirname(path); + FILE *file = fopen(path, "r"); + PyObject *pp = PySys_GetObject("path"), *dirobj = PyString_FromString(dir); + + PyList_Insert(pp, 0, dirobj); + Py_DECREF(dirobj); + PyRun_SimpleFile(file, path); + fclose(file); + + if (PyErr_Occurred()) { + PyErr_Print(); + } + g_free(dir); + + gnt_widget_destroy(GNT_WIDGET(fs)); +} + +static gboolean +run_python(GntBindable *bindable, GList *n) +{ + GntWidget *window = gnt_file_sel_new(); + GntFileSel *sel = GNT_FILE_SEL(window); + + g_object_set(G_OBJECT(window), "vertical", TRUE, NULL); + gnt_box_add_widget(GNT_BOX(window), gnt_label_new("Please select the python script you want to run.")); + gnt_box_set_title(GNT_BOX(window), "Select Python Script..."); + + g_signal_connect(G_OBJECT(sel), "file_selected", G_CALLBACK(python_script_selected), NULL); + g_signal_connect_swapped(G_OBJECT(sel->cancel), "activate", G_CALLBACK(gnt_widget_destroy), sel); + gnt_widget_show(window); + return TRUE; +} +#endif /* USE_PYTHON */ + static gboolean help_for_bindable(GntWM *wm, GntBindable *bindable) { gboolean ret = TRUE; GntBindableClass *klass = GNT_BINDABLE_GET_CLASS(bindable); - + if (klass->help_window) { gnt_wm_raise_window(wm, GNT_WIDGET(klass->help_window)); } else { @@ -1271,6 +1313,9 @@ g_object_unref(wm->workspaces->data); wm->workspaces = g_list_delete_link(wm->workspaces, wm->workspaces); } +#ifdef USE_PYTHON + Py_Finalize(); +#endif } static void @@ -1441,6 +1486,12 @@ GNT_KEY_CTRL_G, NULL); gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "ignore-keys-end", ignore_keys_end, "\033" GNT_KEY_CTRL_G, NULL); +#ifdef USE_PYTHON + gnt_bindable_class_register_action(GNT_BINDABLE_CLASS(klass), "run-python", run_python, + GNT_KEY_F3, NULL); + Py_SetProgramName("gnt"); + Py_Initialize(); +#endif gnt_style_read_actions(G_OBJECT_CLASS_TYPE(klass), GNT_BINDABLE_CLASS(klass));
--- a/libpurple/Makefile.am Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/Makefile.am Sat Dec 22 23:18:08 2007 +0000 @@ -135,8 +135,6 @@ purple_builtheaders = purple.h version.h -BUILT_SOURCES = $(purple_builtheaders) - if ENABLE_DBUS CLEANFILES = \ @@ -211,6 +209,17 @@ bin_SCRIPTS = purple-remote purple-send purple-send-async purple-url-handler +BUILT_SOURCES = $(purple_builtheaders) \ + dbus-types.c \ + dbus-types.h \ + dbus-bindings.c \ + purple-client-bindings.c \ + purple-client-bindings.h + +else + +BUILT_SOURCES = $(purple_builtheaders) + endif lib_LTLIBRARIES = libpurple.la $(libpurple_client_lib)
--- a/libpurple/account.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/account.c Sat Dec 22 23:18:08 2007 +0000 @@ -331,13 +331,24 @@ if(err == NULL) return node; + /* It doesn't make sense to have transient errors persist across a + * restart. + */ + if(!purple_connection_error_is_fatal (err->type)) + return node; + child = xmlnode_new_child(node, "type"); snprintf(type_str, sizeof(type_str), "%u", err->type); xmlnode_insert_data(child, type_str, -1); child = xmlnode_new_child(node, "description"); - if(err->description) - xmlnode_insert_data(child, err->description, -1); + if(err->description) { + char *utf8ized = purple_utf8_try_convert(err->description); + if(utf8ized == NULL) + utf8ized = purple_utf8_salvage(err->description); + xmlnode_insert_data(child, utf8ized, -1); + g_free(utf8ized); + } return node; } @@ -2529,22 +2540,19 @@ g_return_val_if_fail(name != NULL, NULL); - who = g_strdup(purple_normalize(NULL, name)); - for (l = purple_accounts_get_all(); l != NULL; l = l->next) { account = (PurpleAccount *)l->data; - if (!strcmp(purple_normalize(NULL, purple_account_get_username(account)), who) && + who = g_strdup(purple_normalize(account, name)); + if (!strcmp(purple_normalize(account, purple_account_get_username(account)), who) && (!protocol_id || !strcmp(account->protocol_id, protocol_id))) { - + g_free(who); break; } - + g_free(who); account = NULL; } - g_free(who); - return account; }
--- a/libpurple/account.h Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/account.h Sat Dec 22 23:18:08 2007 +0000 @@ -256,7 +256,7 @@ * @param remote_user The name of the user that added this account. * @param id The optional ID of the local account. Rarely used. * @param alias The optional alias of the remote user. - * @param message The optional message sent from the uer requesting you + * @param message The optional message sent by the user wanting to add you. * @param on_list Is the remote user already on the buddy list? * @param auth_cb The callback called when the local user accepts * @param deny_cb The callback called when the local user rejects
--- a/libpurple/buddyicon.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/buddyicon.c Sat Dec 22 23:18:08 2007 +0000 @@ -98,8 +98,7 @@ { const char *dirname; char *path; - FILE *file = NULL; - + g_return_if_fail(img != NULL); if (!purple_buddy_icons_is_caching()) @@ -120,24 +119,12 @@ } } - if ((file = g_fopen(path, "wb")) != NULL) - { - if (!fwrite(purple_imgstore_get_data(img), purple_imgstore_get_size(img), 1, file)) - { - purple_debug_error("buddyicon", "Error writing %s: %s\n", - path, g_strerror(errno)); - } - else - purple_debug_info("buddyicon", "Wrote cache file: %s\n", path); - - fclose(file); - } - else - { + if (!g_file_test(path, G_FILE_TEST_EXISTS)) { + purple_util_write_data_to_file_absolute(path, purple_imgstore_get_data(img), + purple_imgstore_get_size(img)); + } else { purple_debug_error("buddyicon", "Unable to create file %s: %s\n", path, g_strerror(errno)); - g_free(path); - return; } g_free(path); }
--- a/libpurple/certificate.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/certificate.c Sat Dec 22 23:18:08 2007 +0000 @@ -627,7 +627,7 @@ /** System directory to probe for CA certificates */ /* This is set in the lazy_init function */ -static const gchar *x509_ca_syspath = NULL; +static GList *x509_ca_paths = NULL; /** A list of loaded CAs, populated from the above path whenever the lazy_init happens. Contains pointers to x509_ca_elements */ @@ -674,6 +674,7 @@ GDir *certdir; const gchar *entry; GPatternSpec *pempat; + GList *iter = NULL; if (x509_ca_initialized) return TRUE; @@ -687,54 +688,48 @@ return FALSE; } - /* Attempt to point at the appropriate system path */ - if (NULL == x509_ca_syspath) { -#ifdef _WIN32 - x509_ca_syspath = g_build_filename(DATADIR, - "ca-certs", NULL); -#else - x509_ca_syspath = g_build_filename(DATADIR, - "purple", "ca-certs", NULL); -#endif - } - - /* Populate the certificates pool from the system path */ - certdir = g_dir_open(x509_ca_syspath, 0, NULL); - g_return_val_if_fail(certdir, FALSE); - /* Use a glob to only read .pem files */ pempat = g_pattern_spec_new("*.pem"); - - while ( (entry = g_dir_read_name(certdir)) ) { - gchar *fullpath; - PurpleCertificate *crt; - if ( !g_pattern_match_string(pempat, entry) ) { + /* Populate the certificates pool from the search path(s) */ + for (iter = x509_ca_paths; iter; iter = iter->next) { + certdir = g_dir_open(iter->data, 0, NULL); + if (!certdir) { + purple_debug_error("certificate/x509/ca", "Couldn't open location '%s'\n", iter->data); continue; } - fullpath = g_build_filename(x509_ca_syspath, entry, NULL); - - /* TODO: Respond to a failure in the following? */ - crt = purple_certificate_import(x509, fullpath); + while ( (entry = g_dir_read_name(certdir)) ) { + gchar *fullpath; + PurpleCertificate *crt; + + if ( !g_pattern_match_string(pempat, entry) ) { + continue; + } + + fullpath = g_build_filename(iter->data, entry, NULL); + + /* TODO: Respond to a failure in the following? */ + crt = purple_certificate_import(x509, fullpath); - if (x509_ca_quiet_put_cert(crt)) { - purple_debug_info("certificate/x509/ca", - "Loaded %s\n", - fullpath); - } else { - purple_debug_error("certificate/x509/ca", - "Failed to load %s\n", - fullpath); + if (x509_ca_quiet_put_cert(crt)) { + purple_debug_info("certificate/x509/ca", + "Loaded %s\n", + fullpath); + } else { + purple_debug_error("certificate/x509/ca", + "Failed to load %s\n", + fullpath); + } + + purple_certificate_destroy(crt); + g_free(fullpath); } - - purple_certificate_destroy(crt); - g_free(fullpath); + g_dir_close(certdir); } g_pattern_spec_free(pempat); - g_dir_close(certdir); - + purple_debug_info("certificate/x509/ca", "Lazy init completed.\n"); x509_ca_initialized = TRUE; @@ -744,6 +739,17 @@ static gboolean x509_ca_init(void) { + /* Attempt to point at the appropriate system path */ + if (NULL == x509_ca_paths) { +#ifdef _WIN32 + x509_ca_paths = g_list_append(NULL, g_build_filename(DATADIR, + "ca-certs", NULL)); +#else + x509_ca_paths = g_list_append(NULL, g_build_filename(DATADIR, + "purple", "ca-certs", NULL)); +#endif + } + /* Attempt to initialize now, but if it doesn't work, that's OK; it will get done later */ if ( ! x509_ca_lazy_init()) { @@ -752,7 +758,7 @@ "dependency is not yet registered. " "It has been deferred to later.\n"); } - + return TRUE; } @@ -768,6 +774,9 @@ g_list_free(x509_ca_certs); x509_ca_certs = NULL; x509_ca_initialized = FALSE; + g_list_foreach(x509_ca_paths, (GFunc)g_free, NULL); + g_list_free(x509_ca_paths); + x509_ca_paths = NULL; } /** Look up a ca_element by dn */ @@ -1901,3 +1910,10 @@ g_byte_array_free(sha_bin, TRUE); } +void purple_certificate_add_ca_search_path(const char *path) +{ + if (g_list_find_custom(x509_ca_paths, path, (GCompareFunc)strcmp)) + return; + x509_ca_paths = g_list_append(x509_ca_paths, g_strdup(path)); +} +
--- a/libpurple/certificate.h Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/certificate.h Sat Dec 22 23:18:08 2007 +0000 @@ -786,6 +786,12 @@ void purple_certificate_display_x509(PurpleCertificate *crt); +/** + * Add a search path for certificates. + * + * @param path Path to search for certificates. + */ +void purple_certificate_add_ca_search_path(const char *path); #ifdef __cplusplus }
--- a/libpurple/cipher.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/cipher.c Sat Dec 22 23:18:08 2007 +0000 @@ -64,6 +64,8 @@ /******************************************************************************* * MD5 ******************************************************************************/ +#define MD5_HMAC_BLOCK_SIZE 64 + struct MD5Context { guint32 total[2]; guint32 state[4]; @@ -325,6 +327,13 @@ return TRUE; } +static size_t +md5_get_block_size(PurpleCipherContext *context) +{ + /* This does not change (in this case) */ + return MD5_HMAC_BLOCK_SIZE; +} + static PurpleCipherOps MD5Ops = { NULL, /* Set option */ NULL, /* Get option */ @@ -340,12 +349,10 @@ NULL, /* get salt size */ NULL, /* set key */ NULL, /* get key size */ - - /* padding */ - NULL, - NULL, - NULL, - NULL + NULL, /* set batch mode */ + NULL, /* get batch mode */ + md5_get_block_size, /* get block size */ + NULL /* set key with len */ }; /******************************************************************************* @@ -580,6 +587,13 @@ md4_context = NULL; } +static size_t +md4_get_block_size(PurpleCipherContext *context) +{ + /* This does not change (in this case) */ + return MD4_HMAC_BLOCK_SIZE; +} + static PurpleCipherOps MD4Ops = { NULL, /* Set option */ NULL, /* Get option */ @@ -595,12 +609,202 @@ NULL, /* get salt size */ NULL, /* set key */ NULL, /* get key size */ - - /* padding */ - NULL, - NULL, - NULL, - NULL + NULL, /* set batch mode */ + NULL, /* get batch mode */ + md4_get_block_size, /* get block size */ + NULL /* set key with len */ +}; + +/******************************************************************************* + * HMAC + ******************************************************************************/ + +struct HMAC_Context { + PurpleCipherContext *hash; + char *name; + int blocksize; + guchar *opad; +}; + +static void +hmac_init(PurpleCipherContext *context, gpointer extra) +{ + struct HMAC_Context *hctx; + hctx = g_new0(struct HMAC_Context, 1); + purple_cipher_context_set_data(context, hctx); + purple_cipher_context_reset(context, extra); +} + +static void +hmac_reset(PurpleCipherContext *context, gpointer extra) +{ + struct HMAC_Context *hctx; + + hctx = purple_cipher_context_get_data(context); + + g_free(hctx->name); + hctx->name = NULL; + if (hctx->hash) + purple_cipher_context_destroy(hctx->hash); + hctx->hash = NULL; + hctx->blocksize = 0; + g_free(hctx->opad); + hctx->opad = NULL; +} + +static void +hmac_set_opt(PurpleCipherContext *context, const gchar *name, void *value) +{ + struct HMAC_Context *hctx; + + hctx = purple_cipher_context_get_data(context); + + if (!strcmp(name, "hash")) { + g_free(hctx->name); + if (hctx->hash) + purple_cipher_context_destroy(hctx->hash); + hctx->name = g_strdup((char*)value); + hctx->hash = purple_cipher_context_new_by_name((char *)value, NULL); + hctx->blocksize = purple_cipher_context_get_block_size(hctx->hash); + } +} + +static void * +hmac_get_opt(PurpleCipherContext *context, const gchar *name) +{ + struct HMAC_Context *hctx; + + hctx = purple_cipher_context_get_data(context); + + if (!strcmp(name, "hash")) { + return hctx->name; + } + + return NULL; +} + +static void +hmac_append(PurpleCipherContext *context, const guchar *data, size_t len) +{ + struct HMAC_Context *hctx = purple_cipher_context_get_data(context); + + g_return_if_fail(hctx->hash != NULL); + + purple_cipher_context_append(hctx->hash, data, len); +} + +static gboolean +hmac_digest(PurpleCipherContext *context, size_t in_len, guchar *out, size_t *out_len) +{ + struct HMAC_Context *hctx = purple_cipher_context_get_data(context); + PurpleCipherContext *hash = hctx->hash; + guchar *inner_hash; + size_t hash_len; + gboolean result; + + g_return_val_if_fail(hash != NULL, FALSE); + + inner_hash = g_malloc(100); /* TODO: Should be enough for now... */ + result = purple_cipher_context_digest(hash, 100, inner_hash, &hash_len); + + purple_cipher_context_reset(hash, NULL); + + purple_cipher_context_append(hash, hctx->opad, hctx->blocksize); + purple_cipher_context_append(hash, inner_hash, hash_len); + + g_free(inner_hash); + + result = result && purple_cipher_context_digest(hash, in_len, out, out_len); + + return result; +} + +static void +hmac_uninit(PurpleCipherContext *context) +{ + struct HMAC_Context *hctx; + + purple_cipher_context_reset(context, NULL); + + hctx = purple_cipher_context_get_data(context); + + g_free(hctx); +} + +static void +hmac_set_key_with_len(PurpleCipherContext *context, const guchar * key, size_t key_len) +{ + struct HMAC_Context *hctx = purple_cipher_context_get_data(context); + int blocksize, i; + guchar *ipad; + guchar *full_key; + + g_return_if_fail(hctx->hash != NULL); + + g_free(hctx->opad); + + blocksize = hctx->blocksize; + ipad = g_malloc(blocksize); + hctx->opad = g_malloc(blocksize); + + if (key_len > blocksize) { + purple_cipher_context_reset(hctx->hash, NULL); + purple_cipher_context_append(hctx->hash, key, key_len); + full_key = g_malloc(100); /* TODO: Should be enough for now... */ + purple_cipher_context_digest(hctx->hash, 100, full_key, &key_len); + } else + full_key = g_memdup(key, key_len); + + if (key_len < blocksize) { + full_key = g_realloc(full_key, blocksize); + memset(full_key + key_len, 0, blocksize - key_len); + } + + for(i = 0; i < blocksize; i++) { + ipad[i] = 0x36 ^ full_key[i]; + hctx->opad[i] = 0x5c ^ full_key[i]; + } + + g_free(full_key); + + purple_cipher_context_reset(hctx->hash, NULL); + purple_cipher_context_append(hctx->hash, ipad, blocksize); + g_free(ipad); +} + +static void +hmac_set_key(PurpleCipherContext *context, const guchar * key) +{ + hmac_set_key_with_len(context, key, strlen(key)); +} + +static size_t +hmac_get_block_size(PurpleCipherContext *context) +{ + struct HMAC_Context *hctx = purple_cipher_context_get_data(context); + + return hctx->blocksize; +} + +static PurpleCipherOps HMACOps = { + hmac_set_opt, /* Set option */ + hmac_get_opt, /* Get option */ + hmac_init, /* init */ + hmac_reset, /* reset */ + hmac_uninit, /* uninit */ + NULL, /* set iv */ + hmac_append, /* append */ + hmac_digest, /* digest */ + NULL, /* encrypt */ + NULL, /* decrypt */ + NULL, /* set salt */ + NULL, /* get salt size */ + hmac_set_key, /* set key */ + NULL, /* get key size */ + NULL, /* set batch mode */ + NULL, /* get batch mode */ + hmac_get_block_size, /* get block size */ + hmac_set_key_with_len /* set key with len */ }; /****************************************************************************** @@ -986,6 +1190,36 @@ return 0; } +static gint +des_decrypt(PurpleCipherContext *context, const guchar data[], + size_t len, guchar output[], size_t *outlen) { + int offset = 0; + int i = 0; + int tmp; + guint8 buf[8] = {0,0,0,0,0,0,0,0}; + while(offset+8<=len) { + des_ecb_crypt(purple_cipher_context_get_data(context), + data+offset, + output+offset, + 1); + offset+=8; + } + *outlen = len; + if(offset<len) { + *outlen += len - offset; + tmp = offset; + while(tmp<len) { + buf[i++] = data[tmp]; + tmp++; + } + des_ecb_crypt(purple_cipher_context_get_data(context), + buf, + output+offset, + 1); + } + return 0; +} + static void des_init(PurpleCipherContext *context, gpointer extra) { struct _des_ctx *mctx; @@ -1005,32 +1239,380 @@ } static PurpleCipherOps DESOps = { - NULL, /* Set option */ - NULL, /* Get option */ - des_init, /* init */ + NULL, /* Set option */ + NULL, /* Get option */ + des_init, /* init */ + NULL, /* reset */ + des_uninit, /* uninit */ + NULL, /* set iv */ + NULL, /* append */ + NULL, /* digest */ + des_encrypt, /* encrypt */ + des_decrypt, /* decrypt */ + NULL, /* set salt */ + NULL, /* get salt size */ + des_set_key, /* set key */ + NULL, /* get key size */ + NULL, /* set batch mode */ + NULL, /* get batch mode */ + NULL, /* get block size */ + NULL /* set key with len */ +}; + +/****************************************************************************** + * Triple-DES + *****************************************************************************/ + +typedef struct _des3_ctx +{ + PurpleCipherBatchMode mode; + guchar iv[8]; + /* First key for encryption */ + struct _des_ctx key1; + /* Second key for decryption */ + struct _des_ctx key2; + /* Third key for encryption */ + struct _des_ctx key3; +} des3_ctx[1]; + +/* + * Fill a DES3 context with subkeys calculated from 3 64bit key. + * Does not check parity bits, but simply ignore them. + * Does not check for weak keys. + **/ +static void +des3_set_key(PurpleCipherContext *context, const guchar * key) +{ + struct _des3_ctx *ctx = purple_cipher_context_get_data(context); + int i; + + des_key_schedule (key + 0, ctx->key1.encrypt_subkeys); + des_key_schedule (key + 8, ctx->key2.encrypt_subkeys); + des_key_schedule (key + 16, ctx->key3.encrypt_subkeys); + + for (i = 0; i < 32; i += 2) + { + ctx->key1.decrypt_subkeys[i] = ctx->key1.encrypt_subkeys[30-i]; + ctx->key1.decrypt_subkeys[i+1] = ctx->key1.encrypt_subkeys[31-i]; + ctx->key2.decrypt_subkeys[i] = ctx->key2.encrypt_subkeys[30-i]; + ctx->key2.decrypt_subkeys[i+1] = ctx->key2.encrypt_subkeys[31-i]; + ctx->key3.decrypt_subkeys[i] = ctx->key3.encrypt_subkeys[30-i]; + ctx->key3.decrypt_subkeys[i+1] = ctx->key3.encrypt_subkeys[31-i]; + } +} + +static gint +des3_ecb_encrypt(struct _des3_ctx *ctx, const guchar data[], + size_t len, guchar output[], size_t *outlen) +{ + int offset = 0; + int i = 0; + int tmp; + guint8 buf[8] = {0,0,0,0,0,0,0,0}; + while (offset + 8 <= len) { + des_ecb_crypt(&ctx->key1, + data+offset, + output+offset, + 0); + des_ecb_crypt(&ctx->key2, + output+offset, + buf, + 1); + des_ecb_crypt(&ctx->key3, + buf, + output+offset, + 0); + offset += 8; + } + *outlen = len; + if (offset < len) { + *outlen += len - offset; + tmp = offset; + memset(buf, 0, 8); + while (tmp < len) { + buf[i++] = data[tmp]; + tmp++; + } + des_ecb_crypt(&ctx->key1, + buf, + output+offset, + 0); + des_ecb_crypt(&ctx->key2, + output+offset, + buf, + 1); + des_ecb_crypt(&ctx->key3, + buf, + output+offset, + 0); + } + return 0; +} + +static gint +des3_cbc_encrypt(struct _des3_ctx *ctx, const guchar data[], + size_t len, guchar output[], size_t *outlen) +{ + int offset = 0; + int i = 0; + int tmp; + guint8 buf[8]; + memcpy(buf, ctx->iv, 8); + while (offset + 8 <= len) { + for (i = 0; i < 8; i++) + buf[i] ^= data[offset + i]; + des_ecb_crypt(&ctx->key1, + buf, + output+offset, + 0); + des_ecb_crypt(&ctx->key2, + output+offset, + buf, + 1); + des_ecb_crypt(&ctx->key3, + buf, + output+offset, + 0); + memcpy(buf, output+offset, 8); + offset += 8; + } + *outlen = len; + if (offset < len) { + *outlen += len - offset; + tmp = offset; + i = 0; + while (tmp < len) { + buf[i++] ^= data[tmp]; + tmp++; + } + des_ecb_crypt(&ctx->key1, + buf, + output+offset, + 0); + des_ecb_crypt(&ctx->key2, + output+offset, + buf, + 1); + des_ecb_crypt(&ctx->key3, + buf, + output+offset, + 0); + } + return 0; +} + +static gint +des3_encrypt(PurpleCipherContext *context, const guchar data[], + size_t len, guchar output[], size_t *outlen) +{ + struct _des3_ctx *ctx = purple_cipher_context_get_data(context); + + if (ctx->mode == PURPLE_CIPHER_BATCH_MODE_ECB) { + return des3_ecb_encrypt(ctx, data, len, output, outlen); + } else if (ctx->mode == PURPLE_CIPHER_BATCH_MODE_CBC) { + return des3_cbc_encrypt(ctx, data, len, output, outlen); + } else { + g_return_val_if_reached(0); + } + + return 0; +} + +static gint +des3_ecb_decrypt(struct _des3_ctx *ctx, const guchar data[], + size_t len, guchar output[], size_t *outlen) +{ + int offset = 0; + int i = 0; + int tmp; + guint8 buf[8] = {0,0,0,0,0,0,0,0}; + while (offset + 8 <= len) { + /* NOTE: Apply key in reverse */ + des_ecb_crypt(&ctx->key3, + data+offset, + output+offset, + 1); + des_ecb_crypt(&ctx->key2, + output+offset, + buf, + 0); + des_ecb_crypt(&ctx->key1, + buf, + output+offset, + 1); + offset+=8; + } + *outlen = len; + if (offset < len) { + *outlen += len - offset; + tmp = offset; + memset(buf, 0, 8); + while (tmp < len) { + buf[i++] = data[tmp]; + tmp++; + } + des_ecb_crypt(&ctx->key3, + buf, + output+offset, + 1); + des_ecb_crypt(&ctx->key2, + output+offset, + buf, + 0); + des_ecb_crypt(&ctx->key1, + buf, + output+offset, + 1); + } + return 0; +} + +static gint +des3_cbc_decrypt(struct _des3_ctx *ctx, const guchar data[], + size_t len, guchar output[], size_t *outlen) +{ + int offset = 0; + int i = 0; + int tmp; + guint8 buf[8] = {0,0,0,0,0,0,0,0}; + guint8 link[8]; + memcpy(link, ctx->iv, 8); + while (offset + 8 <= len) { + des_ecb_crypt(&ctx->key3, + data+offset, + output+offset, + 1); + des_ecb_crypt(&ctx->key2, + output+offset, + buf, + 0); + des_ecb_crypt(&ctx->key1, + buf, + output+offset, + 1); + for (i = 0; i < 8; i++) + output[offset + i] ^= link[i]; + memcpy(link, data + offset, 8); + offset+=8; + } + *outlen = len; + if(offset<len) { + *outlen += len - offset; + tmp = offset; + memset(buf, 0, 8); + i = 0; + while(tmp<len) { + buf[i++] = data[tmp]; + tmp++; + } + des_ecb_crypt(&ctx->key3, + buf, + output+offset, + 1); + des_ecb_crypt(&ctx->key2, + output+offset, + buf, + 0); + des_ecb_crypt(&ctx->key1, + buf, + output+offset, + 1); + for (i = 0; i < 8; i++) + output[offset + i] ^= link[i]; + } + return 0; +} + +static gint +des3_decrypt(PurpleCipherContext *context, const guchar data[], + size_t len, guchar output[], size_t *outlen) +{ + struct _des3_ctx *ctx = purple_cipher_context_get_data(context); + + if (ctx->mode == PURPLE_CIPHER_BATCH_MODE_ECB) { + return des3_ecb_decrypt(ctx, data, len, output, outlen); + } else if (ctx->mode == PURPLE_CIPHER_BATCH_MODE_CBC) { + return des3_cbc_decrypt(ctx, data, len, output, outlen); + } else { + g_return_val_if_reached(0); + } + + return 0; +} + +static void +des3_set_batch(PurpleCipherContext *context, PurpleCipherBatchMode mode) +{ + struct _des3_ctx *ctx = purple_cipher_context_get_data(context); + + ctx->mode = mode; +} + +static PurpleCipherBatchMode +des3_get_batch(PurpleCipherContext *context) +{ + struct _des3_ctx *ctx = purple_cipher_context_get_data(context); + + return ctx->mode; +} + +static void +des3_set_iv(PurpleCipherContext *context, guchar *iv, size_t len) +{ + struct _des3_ctx *ctx; + + g_return_if_fail(len == 8); + + ctx = purple_cipher_context_get_data(context); + + memcpy(ctx->iv, iv, len); +} + +static void +des3_init(PurpleCipherContext *context, gpointer extra) +{ + struct _des3_ctx *mctx; + mctx = g_new0(struct _des3_ctx, 1); + purple_cipher_context_set_data(context, mctx); +} + +static void +des3_uninit(PurpleCipherContext *context) +{ + struct _des3_ctx *des3_context; + + des3_context = purple_cipher_context_get_data(context); + memset(des3_context, 0, sizeof(des3_context)); + + g_free(des3_context); + des3_context = NULL; +} + +static PurpleCipherOps DES3Ops = { + NULL, /* Set option */ + NULL, /* Get option */ + des3_init, /* init */ NULL, /* reset */ - des_uninit, /* uninit */ - NULL, /* set iv */ - NULL, /* append */ - NULL, /* digest */ - des_encrypt, /* encrypt */ - NULL, /* decrypt */ - NULL, /* set salt */ - NULL, /* get salt size */ - des_set_key, /* set key */ - NULL, /* get key size */ - - /* padding */ - NULL, - NULL, - NULL, - NULL + des3_uninit, /* uninit */ + des3_set_iv, /* set iv */ + NULL, /* append */ + NULL, /* digest */ + des3_encrypt, /* encrypt */ + des3_decrypt, /* decrypt */ + NULL, /* set salt */ + NULL, /* get salt size */ + des3_set_key, /* set key */ + NULL, /* get key size */ + des3_set_batch, /* set batch mode */ + des3_get_batch, /* get batch mode */ + NULL, /* get block size */ + NULL /* set key with len */ }; - /******************************************************************************* * SHA-1 ******************************************************************************/ +#define SHA1_HMAC_BLOCK_SIZE 64 #define SHA1_ROTL(X,n) ((((X) << (n)) | ((X) >> (32-(n)))) & 0xFFFFFFFF) struct SHA1Context { @@ -1251,6 +1833,13 @@ return TRUE; } +static size_t +sha1_get_block_size(PurpleCipherContext *context) +{ + /* This does not change (in this case) */ + return SHA1_HMAC_BLOCK_SIZE; +} + static PurpleCipherOps SHA1Ops = { sha1_set_opt, /* Set Option */ sha1_get_opt, /* Get Option */ @@ -1266,12 +1855,10 @@ NULL, /* get salt size */ NULL, /* set key */ NULL, /* get key size */ - - /* padding */ - NULL, - NULL, - NULL, - NULL + NULL, /* set batch mode */ + NULL, /* get batch mode */ + sha1_get_block_size, /* get block size */ + NULL /* set key with len */ }; /******************************************************************************* @@ -1435,12 +2022,10 @@ NULL, /* get salt size */ rc4_set_key, /* set key */ rc4_get_key_size, /* get key size */ - - /* padding */ - NULL, - NULL, - NULL, - NULL + NULL, /* set batch mode */ + NULL, /* get batch mode */ + NULL, /* get block size */ + NULL /* set key with len */ }; /******************************************************************************* @@ -1510,6 +2095,14 @@ caps |= PURPLE_CIPHER_CAPS_SET_KEY; if(ops->get_key_size) caps |= PURPLE_CIPHER_CAPS_GET_KEY_SIZE; + if(ops->set_batch_mode) + caps |= PURPLE_CIPHER_CAPS_SET_BATCH_MODE; + if(ops->get_batch_mode) + caps |= PURPLE_CIPHER_CAPS_GET_BATCH_MODE; + if(ops->get_block_size) + caps |= PURPLE_CIPHER_CAPS_GET_BLOCK_SIZE; + if(ops->set_key_with_len) + caps |= PURPLE_CIPHER_CAPS_SET_KEY_WITH_LEN; return caps; } @@ -1636,7 +2229,9 @@ purple_ciphers_register_cipher("md5", &MD5Ops); purple_ciphers_register_cipher("sha1", &SHA1Ops); purple_ciphers_register_cipher("md4", &MD4Ops); + purple_ciphers_register_cipher("hmac", &HMACOps); purple_ciphers_register_cipher("des", &DESOps); + purple_ciphers_register_cipher("des3", &DES3Ops); purple_ciphers_register_cipher("rc4", &RC4Ops); } @@ -1967,6 +2562,80 @@ } void +purple_cipher_context_set_batch_mode(PurpleCipherContext *context, + PurpleCipherBatchMode mode) +{ + PurpleCipher *cipher = NULL; + + g_return_if_fail(context); + + cipher = context->cipher; + g_return_if_fail(cipher); + + if(cipher->ops && cipher->ops->set_batch_mode) + cipher->ops->set_batch_mode(context, mode); + else + purple_debug_info("cipher", "The %s cipher does not support the " + "set_batch_mode operation\n", cipher->name); +} + +PurpleCipherBatchMode +purple_cipher_context_get_batch_mode(PurpleCipherContext *context) +{ + PurpleCipher *cipher = NULL; + + g_return_val_if_fail(context, -1); + + cipher = context->cipher; + g_return_val_if_fail(cipher, -1); + + if(cipher->ops && cipher->ops->get_batch_mode) + return cipher->ops->get_batch_mode(context); + else { + purple_debug_info("cipher", "The %s cipher does not support the " + "get_batch_mode operation\n", cipher->name); + return -1; + } +} + +size_t +purple_cipher_context_get_block_size(PurpleCipherContext *context) +{ + PurpleCipher *cipher = NULL; + + g_return_val_if_fail(context, -1); + + cipher = context->cipher; + g_return_val_if_fail(cipher, -1); + + if(cipher->ops && cipher->ops->get_block_size) + return cipher->ops->get_block_size(context); + else { + purple_debug_info("cipher", "The %s cipher does not support the " + "get_block_size operation\n", cipher->name); + return -1; + } +} + +void +purple_cipher_context_set_key_with_len(PurpleCipherContext *context, + const guchar *key, size_t len) +{ + PurpleCipher *cipher = NULL; + + g_return_if_fail(context); + + cipher = context->cipher; + g_return_if_fail(cipher); + + if(cipher->ops && cipher->ops->set_key_with_len) + cipher->ops->set_key_with_len(context, key, len); + else + purple_debug_info("cipher", "The %s cipher does not support the " + "set_key_with_len operation\n", cipher->name); +} + +void purple_cipher_context_set_data(PurpleCipherContext *context, gpointer data) { g_return_if_fail(context);
--- a/libpurple/cipher.h Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/cipher.h Sat Dec 22 23:18:08 2007 +0000 @@ -37,26 +37,37 @@ typedef struct _PurpleCipherOps PurpleCipherOps; /**< Ops for a PurpleCipher */ typedef struct _PurpleCipherContext PurpleCipherContext; /**< A context for a PurpleCipher */ +/** + * Modes for batch encrypters + */ +typedef enum _PurpleCipherBatchMode { + PURPLE_CIPHER_BATCH_MODE_ECB, + PURPLE_CIPHER_BATCH_MODE_CBC +} PurpleCipherBatchMode; /** * The operation flags for a cipher */ typedef enum _PurpleCipherCaps { - PURPLE_CIPHER_CAPS_SET_OPT = 1 << 1, /**< Set option flag */ - PURPLE_CIPHER_CAPS_GET_OPT = 1 << 2, /**< Get option flag */ - PURPLE_CIPHER_CAPS_INIT = 1 << 3, /**< Init flag */ - PURPLE_CIPHER_CAPS_RESET = 1 << 4, /**< Reset flag */ - PURPLE_CIPHER_CAPS_UNINIT = 1 << 5, /**< Uninit flag */ - PURPLE_CIPHER_CAPS_SET_IV = 1 << 6, /**< Set IV flag */ - PURPLE_CIPHER_CAPS_APPEND = 1 << 7, /**< Append flag */ - PURPLE_CIPHER_CAPS_DIGEST = 1 << 8, /**< Digest flag */ - PURPLE_CIPHER_CAPS_ENCRYPT = 1 << 9, /**< Encrypt flag */ - PURPLE_CIPHER_CAPS_DECRYPT = 1 << 10, /**< Decrypt flag */ - PURPLE_CIPHER_CAPS_SET_SALT = 1 << 11, /**< Set salt flag */ - PURPLE_CIPHER_CAPS_GET_SALT_SIZE = 1 << 12, /**< Get salt size flag */ - PURPLE_CIPHER_CAPS_SET_KEY = 1 << 13, /**< Set key flag */ - PURPLE_CIPHER_CAPS_GET_KEY_SIZE = 1 << 14, /**< Get key size flag */ - PURPLE_CIPHER_CAPS_UNKNOWN = 1 << 16 /**< Unknown */ + PURPLE_CIPHER_CAPS_SET_OPT = 1 << 1, /**< Set option flag */ + PURPLE_CIPHER_CAPS_GET_OPT = 1 << 2, /**< Get option flag */ + PURPLE_CIPHER_CAPS_INIT = 1 << 3, /**< Init flag */ + PURPLE_CIPHER_CAPS_RESET = 1 << 4, /**< Reset flag */ + PURPLE_CIPHER_CAPS_UNINIT = 1 << 5, /**< Uninit flag */ + PURPLE_CIPHER_CAPS_SET_IV = 1 << 6, /**< Set IV flag */ + PURPLE_CIPHER_CAPS_APPEND = 1 << 7, /**< Append flag */ + PURPLE_CIPHER_CAPS_DIGEST = 1 << 8, /**< Digest flag */ + PURPLE_CIPHER_CAPS_ENCRYPT = 1 << 9, /**< Encrypt flag */ + PURPLE_CIPHER_CAPS_DECRYPT = 1 << 10, /**< Decrypt flag */ + PURPLE_CIPHER_CAPS_SET_SALT = 1 << 11, /**< Set salt flag */ + PURPLE_CIPHER_CAPS_GET_SALT_SIZE = 1 << 12, /**< Get salt size flag */ + PURPLE_CIPHER_CAPS_SET_KEY = 1 << 13, /**< Set key flag */ + PURPLE_CIPHER_CAPS_GET_KEY_SIZE = 1 << 14, /**< Get key size flag */ + PURPLE_CIPHER_CAPS_SET_BATCH_MODE = 1 << 15, /**< Set batch mode flag */ + PURPLE_CIPHER_CAPS_GET_BATCH_MODE = 1 << 16, /**< Get batch mode flag */ + PURPLE_CIPHER_CAPS_GET_BLOCK_SIZE = 1 << 17, /**< The get block size flag */ + PURPLE_CIPHER_CAPS_SET_KEY_WITH_LEN = 1 << 18, /**< The set key with length flag */ + PURPLE_CIPHER_CAPS_UNKNOWN = 1 << 19 /**< Unknown */ } PurpleCipherCaps; /** @@ -105,10 +116,17 @@ /** The get key size function */ size_t (*get_key_size)(PurpleCipherContext *context); - void (*_purple_reserved1)(void); - void (*_purple_reserved2)(void); - void (*_purple_reserved3)(void); - void (*_purple_reserved4)(void); + /** The set batch mode function */ + void (*set_batch_mode)(PurpleCipherContext *context, PurpleCipherBatchMode mode); + + /** The get batch mode function */ + PurpleCipherBatchMode (*get_batch_mode)(PurpleCipherContext *context); + + /** The get block size function */ + size_t (*get_block_size)(PurpleCipherContext *context); + + /** The set key with length function */ + void (*set_key_with_len)(PurpleCipherContext *context, const guchar *key, size_t len); }; #ifdef __cplusplus @@ -345,7 +363,7 @@ /** * Sets the salt on a context * - * @param context The context who's salt to set + * @param context The context whose salt to set * @param salt The salt */ void purple_cipher_context_set_salt(PurpleCipherContext *context, guchar *salt); @@ -353,7 +371,7 @@ /** * Gets the size of the salt if the cipher supports it * - * @param context The context who's salt size to get + * @param context The context whose salt size to get * * @return The size of the salt */ @@ -362,7 +380,7 @@ /** * Sets the key on a context * - * @param context The context who's key to set + * @param context The context whose key to set * @param key The key */ void purple_cipher_context_set_key(PurpleCipherContext *context, const guchar *key); @@ -370,16 +388,53 @@ /** * Gets the key size for a context * - * @param context The context who's key size to get + * @param context The context whose key size to get * * @return The size of the key */ size_t purple_cipher_context_get_key_size(PurpleCipherContext *context); /** + * Sets the batch mode of a context + * + * @param context The context whose batch mode to set + * @param mode The batch mode under which the cipher should operate + * + */ +void purple_cipher_context_set_batch_mode(PurpleCipherContext *context, PurpleCipherBatchMode mode); + +/** + * Gets the batch mode of a context + * + * @param context The context whose batch mode to get + * + * @return The batch mode under which the cipher is operating + */ +PurpleCipherBatchMode purple_cipher_context_get_batch_mode(PurpleCipherContext *context); + +/** + * Gets the block size of a context + * + * @param context The context whose block size to get + * + * @return The block size of the context + */ +size_t purple_cipher_context_get_block_size(PurpleCipherContext *context); + +/** + * Sets the key with a given length on a context + * + * @param context The context whose key to set + * @param key The key + * @param len The length of the key + * + */ +void purple_cipher_context_set_key_with_len(PurpleCipherContext *context, const guchar *key, size_t len); + +/** * Sets the cipher data for a context * - * @param context The context who's cipher data to set + * @param context The context whose cipher data to set * @param data The cipher data to set */ void purple_cipher_context_set_data(PurpleCipherContext *context, gpointer data); @@ -387,7 +442,7 @@ /** * Gets the cipher data for a context * - * @param context The context who's cipher data to get + * @param context The context whose cipher data to get * * @return The cipher data */
--- a/libpurple/dnsquery.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/dnsquery.c Sat Dec 22 23:18:08 2007 +0000 @@ -547,7 +547,7 @@ { #ifdef HAVE_GETADDRINFO g_snprintf(message, sizeof(message), _("Error resolving %s:\n%s"), - query_data->hostname, gai_strerror(err)); + query_data->hostname, purple_gai_strerror(err)); #else g_snprintf(message, sizeof(message), _("Error resolving %s: %d"), query_data->hostname, err); @@ -695,7 +695,7 @@ } freeaddrinfo(tmp); } else { - query_data->error_message = g_strdup_printf(_("Error resolving %s:\n%s"), query_data->hostname, gai_strerror(rc)); + query_data->error_message = g_strdup_printf(_("Error resolving %s:\n%s"), query_data->hostname, purple_gai_strerror(rc)); } #else if ((hp = gethostbyname(query_data->hostname))) {
--- a/libpurple/ft.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/ft.c Sat Dec 22 23:18:08 2007 +0000 @@ -479,10 +479,11 @@ /* Sending a file */ /* Check the filename. */ #ifdef _WIN32 - if (g_strrstr(filename, "../") || g_strrstr(filename, "..\\")) { + if (g_strrstr(filename, "../") || g_strrstr(filename, "..\\")) #else - if (g_strrstr(filename, "../")) { + if (g_strrstr(filename, "../")) #endif + { char *utf8 = g_filename_to_utf8(filename, -1, NULL, NULL, NULL); msg = g_strdup_printf(_("%s is not a valid filename.\n"), utf8);
--- a/libpurple/log.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/log.c Sat Dec 22 23:18:08 2007 +0000 @@ -348,7 +348,6 @@ void(*get_log_sets)(PurpleLogSetCallback cb, GHashTable *sets), gboolean(*remove)(PurpleLog *log), gboolean(*is_deletable)(PurpleLog *log)) -{ #endif PurpleLogLogger *logger; va_list args;
--- a/libpurple/network.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/network.c Sat Dec 22 23:18:08 2007 +0000 @@ -289,7 +289,7 @@ errnum = getaddrinfo(NULL /* any IP */, serv, &hints, &res); if (errnum != 0) { #ifndef _WIN32 - purple_debug_warning("network", "getaddrinfo: %s\n", gai_strerror(errnum)); + purple_debug_warning("network", "getaddrinfo: %s\n", purple_gai_strerror(errnum)); if (errnum == EAI_SYSTEM) purple_debug_warning("network", "getaddrinfo: system error: %s\n", g_strerror(errno)); #else
--- a/libpurple/plugins/perl/common/Prpl.xs Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/plugins/perl/common/Prpl.xs Sat Dec 22 23:18:08 2007 +0000 @@ -54,3 +54,20 @@ Purple::Account account const char *name time_t login_time + +int +purple_prpl_send_raw(gc, str) + Purple::Connection gc + const char *str +PREINIT: + PurplePluginProtocolInfo *prpl_info; +CODE: + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); + if (prpl_info && prpl_info->send_raw != NULL) { + RETVAL = prpl_info->send_raw(gc, str, strlen(str)); + } else { + RETVAL = 0; + } +OUTPUT: + RETVAL +
--- a/libpurple/pounce.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/pounce.c Sat Dec 22 23:18:08 2007 +0000 @@ -181,7 +181,8 @@ child = xmlnode_new_child(node, "account"); xmlnode_set_attrib(child, "protocol", pouncer->protocol_id); - xmlnode_insert_data(child, purple_account_get_username(pouncer), -1); + xmlnode_insert_data(child, + purple_normalize(pouncer, purple_account_get_username(pouncer)), -1); child = xmlnode_new_child(node, "pouncee"); xmlnode_insert_data(child, purple_pounce_get_pouncee(pounce), -1);
--- a/libpurple/prefs.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/prefs.c Sat Dec 22 23:18:08 2007 +0000 @@ -914,7 +914,7 @@ if(pref) { if(pref->type != PURPLE_PREF_PATH) { purple_debug_error("prefs", - "purple_prefs_set_path: %s not a string pref\n", name); + "purple_prefs_set_path: %s not a path pref\n", name); return; }
--- a/libpurple/protocols/bonjour/Makefile.am Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/bonjour/Makefile.am Sat Dec 22 23:18:08 2007 +0000 @@ -13,6 +13,7 @@ buddy.h \ jabber.c \ jabber.h \ + mdns_avahi.c \ mdns_common.c \ mdns_common.h \ mdns_interface.h \ @@ -22,14 +23,6 @@ bonjour_ft.c \ bonjour_ft.h -if MDNS_AVAHI - BONJOURSOURCES += mdns_avahi.c -else -if MDNS_HOWL - BONJOURSOURCES += mdns_howl.c -endif -endif - AM_CFLAGS = $(st) libbonjour_la_LDFLAGS = -module -avoid-version @@ -40,29 +33,14 @@ noinst_LIBRARIES = libbonjour.a libbonjour_a_SOURCES = $(BONJOURSOURCES) libbonjour_a_CFLAGS = $(AM_CFLAGS) -libbonjour_a_LIBADD = - -if MDNS_AVAHI - libbonjour_a_LIBADD += $(AVAHI_LIBS) -else -if MDNS_HOWL - libbonjour_a_LIBADD += $(HOWL_LIBS) -endif -endif +libbonjour_a_LIBADD = $(AVAHI_LIBS) else st = pkg_LTLIBRARIES = libbonjour.la libbonjour_la_SOURCES = $(BONJOURSOURCES) -libbonjour_la_LIBADD = $(GLIB_LIBS) $(LIBXML_LIBS) -if MDNS_AVAHI - libbonjour_la_LIBADD += $(AVAHI_LIBS) -else -if MDNS_HOWL - libbonjour_la_LIBADD += $(HOWL_LIBS) -endif -endif +libbonjour_la_LIBADD = $(GLIB_LIBS) $(LIBXML_LIBS) $(AVAHI_LIBS) endif @@ -72,13 +50,6 @@ -I$(top_builddir)/libpurple \ $(GLIB_CFLAGS) \ $(DEBUG_CFLAGS) \ - $(LIBXML_CFLAGS) + $(LIBXML_CFLAGS) \ + $(AVAHI_CFLAGS) -if MDNS_AVAHI - AM_CPPFLAGS += $(AVAHI_CFLAGS) -else -if MDNS_HOWL - AM_CPPFLAGS += $(HOWL_CFLAGS) -endif -endif -
--- a/libpurple/protocols/bonjour/bonjour.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/bonjour/bonjour.c Sat Dec 22 23:18:08 2007 +0000 @@ -109,7 +109,7 @@ gc->proto_data = bd = g_new0(BonjourData, 1); /* Start waiting for jabber connections (iChat style) */ - bd->jabber_data = g_new(BonjourJabber, 1); + bd->jabber_data = g_new0(BonjourJabber, 1); bd->jabber_data->port = BONJOUR_DEFAULT_PORT_INT; bd->jabber_data->account = account;
--- a/libpurple/protocols/bonjour/bonjour.h Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/bonjour/bonjour.h Sat Dec 22 23:18:08 2007 +0000 @@ -44,7 +44,7 @@ { BonjourDnsSd *dns_sd_data; BonjourJabber *jabber_data; - GList *xfer_lists; + GSList *xfer_lists; } BonjourData; #endif /* _BONJOUR_H_ */
--- a/libpurple/protocols/bonjour/bonjour_ft.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/bonjour/bonjour_ft.c Sat Dec 22 23:18:08 2007 +0000 @@ -149,7 +149,7 @@ static PurpleXfer* bonjour_si_xfer_find(BonjourData *bd, const char *sid, const char *from) { - GList *xfers = NULL; + GSList *xfers = NULL; PurpleXfer *xfer = NULL; XepXfer *xf = NULL; @@ -309,7 +309,7 @@ if(xf != NULL) { bd = (BonjourData*)xf->data; if(bd != NULL) { - bd->xfer_lists = g_list_remove(bd->xfer_lists, xfer); + bd->xfer_lists = g_slist_remove(bd->xfer_lists, xfer); purple_debug_info("bonjour", "B free xfer from lists(%p).\n", bd->xfer_lists); } if (xf->proxy_connection != NULL) @@ -359,7 +359,7 @@ purple_xfer_set_cancel_send_fnc(xfer, bonjour_xfer_cancel_send); purple_xfer_set_end_fnc(xfer, bonjour_xfer_end); - bd->xfer_lists = g_list_append(bd->xfer_lists, xfer); + bd->xfer_lists = g_slist_append(bd->xfer_lists, xfer); return xfer; } @@ -387,7 +387,7 @@ bonjour_xfer_init(PurpleXfer *xfer) { PurpleBuddy *buddy = NULL; - BonjourBuddy *bd = NULL; + BonjourBuddy *bb = NULL; XepXfer *xf = NULL; xf = (XepXfer*)xfer->data; @@ -401,8 +401,10 @@ if (buddy == NULL) return; - bd = (BonjourBuddy *)buddy->proto_data; - xf->buddy_ip = g_strdup(bd->ip); + bb = (BonjourBuddy *)buddy->proto_data; + /* Assume it is the first IP. We could do something like keep track of which one is in use or something. */ + if (bb->ips) + xf->buddy_ip = g_strdup(bb->ips->data); if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) { /* initiate file transfer, send SI offer. */ purple_debug_info("bonjour", "Bonjour xfer type is PURPLE_XFER_SEND.\n"); @@ -603,7 +605,7 @@ purple_xfer_set_cancel_recv_fnc(xfer, bonjour_xfer_cancel_recv); purple_xfer_set_end_fnc(xfer, bonjour_xfer_end); - bd->xfer_lists = g_list_append(bd->xfer_lists, xfer); + bd->xfer_lists = g_slist_append(bd->xfer_lists, xfer); purple_xfer_request(xfer); }
--- a/libpurple/protocols/bonjour/buddy.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/bonjour/buddy.c Sat Dec 22 23:18:08 2007 +0000 @@ -237,7 +237,10 @@ bonjour_buddy_delete(BonjourBuddy *buddy) { g_free(buddy->name); - g_free(buddy->ip); + while (buddy->ips != NULL) { + g_free(buddy->ips->data); + buddy->ips = g_slist_delete_link(buddy->ips, buddy->ips); + } g_free(buddy->first); g_free(buddy->phsh); g_free(buddy->status);
--- a/libpurple/protocols/bonjour/buddy.h Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/bonjour/buddy.h Sat Dec 22 23:18:08 2007 +0000 @@ -27,8 +27,7 @@ PurpleAccount *account; gchar *name; - /* TODO: Remove and just use the hostname */ - gchar *ip; + GSList *ips; gint port_p2pj; gchar *first;
--- a/libpurple/protocols/bonjour/jabber.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/bonjour/jabber.c Sat Dec 22 23:18:08 2007 +0000 @@ -32,6 +32,12 @@ #include "libc_interface.h" #endif #include <sys/types.h> + +/* Solaris */ +#if defined (__SVR4) && defined (__sun) +#include <sys/sockio.h> +#endif + #include <glib.h> #include <unistd.h> #include <fcntl.h> @@ -71,33 +77,8 @@ static void xep_iq_parse(xmlnode *packet, PurpleConnection *connection, PurpleBuddy *pb); -#if 0 /* this isn't used anywhere... */ -static const char * -_font_size_purple_to_ichat(int size) -{ - switch (size) { - case 1: - return "8"; - case 2: - return "10"; - case 3: - return "12"; - case 4: - return "14"; - case 5: - return "17"; - case 6: - return "21"; - case 7: - return "24"; - } - - return "12"; -} -#endif - static BonjourJabberConversation * -bonjour_jabber_conv_new(PurpleBuddy *pb) { +bonjour_jabber_conv_new(PurpleBuddy *pb, PurpleAccount *account, const char *ip) { BonjourJabberConversation *bconv = g_new0(BonjourJabberConversation, 1); bconv->socket = -1; @@ -105,13 +86,14 @@ bconv->tx_handler = 0; bconv->rx_handler = 0; bconv->pb = pb; + bconv->account = account; + bconv->ip = g_strdup(ip); bonjour_parser_setup(bconv); return bconv; } - static const char * _font_size_ichat_to_purple(int size) { @@ -137,34 +119,48 @@ { xmlnode *body_node, *html_node, *events_node; PurpleConnection *gc = pb->account->gc; - char *body, *html_body = NULL; - const char *ichat_balloon_color = NULL; - const char *ichat_text_color = NULL; - const char *font_face = NULL; - const char *font_size = NULL; - const char *font_color = NULL; + gchar *body = NULL; gboolean composing_event = FALSE; body_node = xmlnode_get_child(message_node, "body"); - if (body_node == NULL) + html_node = xmlnode_get_child(message_node, "html"); + + if (body_node == NULL && html_node == NULL) { + purple_debug_error("bonjour", "No body or html node found, discarding message.\n"); return; - body = xmlnode_get_data(body_node); + } - html_node = xmlnode_get_child(message_node, "html"); - if (html_node != NULL) - { + 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 */ + /* TODO: Deal with typing notification */ + return; + } + } + + if (html_node != NULL) { xmlnode *html_body_node; html_body_node = xmlnode_get_child(html_node, "body"); - if (html_body_node != NULL) - { + if (html_body_node != NULL) { xmlnode *html_body_font_node; + const char *ichat_balloon_color = NULL; + const char *ichat_text_color = NULL; + const char *font_face = NULL; + const char *font_size = NULL; + const char *font_color = NULL; 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"); - if (html_body_font_node != NULL) - { /* Types of messages sent by iChat */ + + /* Types of messages sent by iChat */ + if (html_body_font_node != NULL) { + gchar *html_body; + font_face = xmlnode_get_attrib(html_body_font_node, "face"); /* The absolute iChat font sizes should be converted to 1..7 range */ font_size = xmlnode_get_attrib(html_body_font_node, "ABSZ"); @@ -172,74 +168,74 @@ 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, NULL); + + if (html_body != NULL) { + /* Use some sane defaults */ + 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_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); + + g_free(html_body); + } } } } - 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 */ - /* TODO: Deal with typing notification */ - g_free(body); - g_free(html_body); - return; - } - } + /* Compose the message */ + if (body == NULL && body_node != NULL) + body = xmlnode_get_data(body_node); - /* Compose the message */ - 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_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); + if (body == NULL) { + purple_debug_error("bonjour", "No html body or regular body found.\n"); + return; } - /* TODO: Should we do something with "composing_event" here? */ - /* Send the message to the UI */ serv_got_im(gc, pb->name, body, 0, time(NULL)); g_free(body); - g_free(html_body); } -struct _check_buddy_by_address_t { +struct _match_buddies_by_address_t { const char *address; - PurpleBuddy **pb; - BonjourJabber *bj; + GSList *matched_buddies; + BonjourJabber *jdata; }; static void -_check_buddy_by_address(gpointer key, gpointer value, gpointer data) +_match_buddies_by_address(gpointer key, gpointer value, gpointer data) { PurpleBuddy *pb = value; - BonjourBuddy *bb; - struct _check_buddy_by_address_t *cbba = data; + struct _match_buddies_by_address_t *mbba = 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. + * whether one of the buddies IPs matches the target IP. */ - if (cbba->bj->account == pb->account) + if (mbba->jdata->account == pb->account && pb->proto_data != NULL) { - bb = pb->proto_data; - if ((bb != NULL) && (g_ascii_strcasecmp(bb->ip, cbba->address) == 0)) - *(cbba->pb) = pb; + const char *ip; + BonjourBuddy *bb = pb->proto_data; + GSList *tmp = bb->ips; + + while(tmp) { + ip = tmp->data; + if (ip != NULL && g_ascii_strcasecmp(ip, mbba->address) == 0) { + mbba->matched_buddies = g_slist_prepend(mbba->matched_buddies, pb); + break; + } + tmp = tmp->next; + } } } @@ -251,8 +247,6 @@ 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) { @@ -328,7 +322,7 @@ if (ret < len) { /* Don't interfere with the stream starting */ - if (bconv->tx_handler == 0) + if (bconv->sent_stream_start == FULLY_SENT && bconv->recv_stream_start && bconv->tx_handler == 0) 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); @@ -340,6 +334,7 @@ void bonjour_jabber_process_packet(PurpleBuddy *pb, xmlnode *packet) { g_return_if_fail(packet != NULL); + g_return_if_fail(pb != NULL); if (!strcmp(packet->name, "message")) _jabber_parse_and_write_message_to_ui(packet, pb); @@ -353,31 +348,31 @@ static void _client_socket_handler(gpointer data, gint socket, PurpleInputCondition condition) { - PurpleBuddy *pb = data; + BonjourJabberConversation *bconv = data; gint len, message_length; static char message[4096]; - /*TODO: use a static buffer */ - /* Read the data from the socket */ if ((len = recv(socket, message, sizeof(message) - 1, 0)) == -1) { /* There have been an error reading from the socket */ if (errno != EAGAIN) { - BonjourBuddy *bb = pb->proto_data; const char *err = g_strerror(errno); purple_debug_warning("bonjour", "receive error: %s\n", err ? err : "(null)"); - bonjour_jabber_close_conversation(bb->conversation); - bb->conversation = NULL; + bonjour_jabber_close_conversation(bconv); + if (bconv->pb != NULL) { + BonjourBuddy *bb = bconv->pb->proto_data; + 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 (len == 0) { /* The other end has closed the socket */ - purple_debug_warning("bonjour", "Connection closed (without stream end) by %s.\n", pb->name ? pb->name : "(null)"); - bonjour_jabber_stream_ended(pb); + purple_debug_warning("bonjour", "Connection closed (without stream end) by %s.\n", (bconv->pb && bconv->pb->name) ? bconv->pb->name : "(unknown)"); + bonjour_jabber_stream_ended(bconv); return; } else { message_length = len; @@ -391,30 +386,34 @@ purple_debug_info("bonjour", "Receive: -%s- %d bytes\n", message, len); - bonjour_parser_process(pb, message, message_length); + bonjour_parser_process(bconv, message, message_length); } -void bonjour_jabber_stream_ended(PurpleBuddy *pb) { - BonjourBuddy *bb = pb->proto_data; +void bonjour_jabber_stream_ended(BonjourJabberConversation *bconv) { - purple_debug_info("bonjour", "Recieved conversation close notification from %s.\n", pb->name); - - g_return_if_fail(bb != NULL); + purple_debug_info("bonjour", "Recieved conversation close notification from %s.\n", bconv->pb ? bconv->pb->name : "(unknown)"); /* Inform the user that the conversation has been closed */ - if (bb->conversation != NULL) { + if (bconv != NULL) { + BonjourBuddy *bb = NULL; + + if(bconv->pb != NULL) + bb = bconv->pb->proto_data; #if 0 - PurpleConversation *conv; - conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, pb->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); + if(bconv->pb != NULL) { + PurpleConversation *conv; + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bconv->pb->name, bconv->pb->account); + if (conv != NULL) { + char *tmp = g_strdup_printf(_("%s has closed the conversation."), bconv->pb->name); + purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL)); + g_free(tmp); + } } #endif /* Close the socket, clear the watcher and free memory */ - bonjour_jabber_close_conversation(bb->conversation); - bb->conversation = NULL; + bonjour_jabber_close_conversation(bconv); + if(bb) + bb->conversation = NULL; } } @@ -427,9 +426,7 @@ static void _start_stream(gpointer data, gint source, PurpleInputCondition condition) { - PurpleBuddy *pb = data; - BonjourBuddy *bb = pb->proto_data; - BonjourJabberConversation *bconv = bb->conversation; + BonjourJabberConversation *bconv = data; struct _stream_start_data *ss = bconv->stream_data; int len, ret; @@ -443,18 +440,26 @@ else if (ret <= 0) { const char *err = g_strerror(errno); PurpleConversation *conv; + const char *bname = bconv->buddy_name; + BonjourBuddy *bb = NULL; - 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)"); + if(bconv->pb) { + bb = bconv->pb->proto_data; + bname = purple_buddy_get_name(bconv->pb); + } - conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account); + purple_debug_error("bonjour", "Error starting stream with buddy %s at %s error: %s\n", + bname ? bname : "(unknown)", bconv->ip, err ? err : "(null)"); + + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bname, bconv->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(bconv); - bb->conversation = NULL; + if(bb != NULL) + bb->conversation = NULL; return; } @@ -476,20 +481,27 @@ bconv->tx_handler = 0; bconv->sent_stream_start = FULLY_SENT; - bonjour_jabber_stream_started(pb); + bonjour_jabber_stream_started(bconv); } -static gboolean bonjour_jabber_send_stream_init(PurpleBuddy *pb, int client_socket) +static gboolean bonjour_jabber_send_stream_init(BonjourJabberConversation *bconv, int client_socket) { int ret, len; char *stream_start; - BonjourBuddy *bb = pb->proto_data; + const char *bname = bconv->buddy_name; + + if (bconv->pb != NULL) + bname = purple_buddy_get_name(bconv->pb); - stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account), - purple_buddy_get_name(pb)); + /* If we have no idea who "to" is, use an empty string. + * If we don't know now, it is because the other side isn't playing nice, so they can't complain. */ + if (bname == NULL) + bname = ""; + + stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(bconv->account), bname); len = strlen(stream_start); - bb->conversation->sent_stream_start = PARTIALLY_SENT; + bconv->sent_stream_start = PARTIALLY_SENT; /* Start the stream */ ret = send(client_socket, stream_start, len, 0); @@ -499,8 +511,17 @@ else if (ret <= 0) { const char *err = g_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)"); + purple_debug_error("bonjour", "Error starting stream with buddy %s at %s error: %s\n", + (*bname) ? bname : "(unknown)", bconv->ip, err ? err : "(null)"); + + if (bconv->pb) { + PurpleConversation *conv; + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bname, bconv->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(client_socket); g_free(stream_start); @@ -512,58 +533,61 @@ if (ret < len) { struct _stream_start_data *ss = g_new(struct _stream_start_data, 1); ss->msg = g_strdup(stream_start + ret); - bb->conversation->stream_data = ss; + bconv->stream_data = ss; /* Finish sending the stream start */ - bb->conversation->tx_handler = purple_input_add(client_socket, - PURPLE_INPUT_WRITE, _start_stream, pb); + bconv->tx_handler = purple_input_add(client_socket, + PURPLE_INPUT_WRITE, _start_stream, bconv); } else - bb->conversation->sent_stream_start = FULLY_SENT; + bconv->sent_stream_start = FULLY_SENT; g_free(stream_start); return TRUE; } -static gboolean -_async_bonjour_jabber_close_conversation(gpointer data) { - BonjourJabberConversation *bconv = data; - bonjour_jabber_close_conversation(bconv); - return FALSE; -} +/* This gets called when we've successfully sent our <stream:stream /> + * AND when we've recieved a <stream:stream /> */ +void bonjour_jabber_stream_started(BonjourJabberConversation *bconv) { -void bonjour_jabber_stream_started(PurpleBuddy *pb) { - BonjourBuddy *bb = pb->proto_data; - BonjourJabberConversation *bconv = bb->conversation; - - if (bconv->sent_stream_start == NOT_SENT && !bonjour_jabber_send_stream_init(pb, bconv->socket)) { + if (bconv->sent_stream_start == NOT_SENT && !bonjour_jabber_send_stream_init(bconv, bconv->socket)) { const char *err = g_strerror(errno); - PurpleConversation *conv; + const char *bname = bconv->buddy_name; - 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)"); + if (bconv->pb) + bname = purple_buddy_get_name(bconv->pb); + + purple_debug_error("bonjour", "Error starting stream with buddy %s at %s error: %s\n", + bname ? bname : "(unknown)", bconv->ip, 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)); + if (bconv->pb) { + PurpleConversation *conv; + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bname, bconv->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)); + } + /* We don't want to recieve anything else */ close(bconv->socket); + bconv->socket = -1; + /* This must be asynchronous because it destroys the parser and we * may be in the middle of parsing. */ - purple_timeout_add(0, _async_bonjour_jabber_close_conversation, bb->conversation); - bb->conversation = NULL; + async_bonjour_jabber_close_conversation(bconv); return; } - /* If the stream has been completely started, we can start doing stuff */ - if (bconv->sent_stream_start == FULLY_SENT && bconv->recv_stream_start && purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) { + /* If the stream has been completely started and we know who we're talking to, we can start doing stuff. */ + /* I don't think the circ_buffer can actually contain anything without a buddy being associated, but lets be explicit. */ + if (bconv->sent_stream_start == FULLY_SENT && bconv->recv_stream_start + && bconv->pb && purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) { /* Watch for when we can write the buffered messages */ bconv->tx_handler = purple_input_add(bconv->socket, PURPLE_INPUT_WRITE, - _send_data_write_cb, pb); + _send_data_write_cb, bconv->pb); /* We can probably write the data right now. */ - _send_data_write_cb(pb, bconv->socket, PURPLE_INPUT_WRITE); + _send_data_write_cb(bconv->pb, bconv->socket, PURPLE_INPUT_WRITE); } } @@ -571,15 +595,14 @@ static void _server_socket_handler(gpointer data, int server_socket, PurpleInputCondition condition) { - PurpleBuddy *pb = NULL; + BonjourJabber *jdata = data; struct sockaddr_in their_addr; /* connector's address information */ socklen_t sin_size = sizeof(struct sockaddr); int client_socket; int flags; - BonjourBuddy *bb; char *address_text = NULL; - PurpleBuddyList *bl = purple_get_blist(); - struct _check_buddy_by_address_t *cbba; + struct _match_buddies_by_address_t *mbba; + BonjourJabberConversation *bconv; /* Check that it is a read condition */ if (condition != PURPLE_INPUT_READ) @@ -594,39 +617,35 @@ /* Look for the buddy that has opened the conversation and fill information */ address_text = inet_ntoa(their_addr.sin_addr); purple_debug_info("bonjour", "Received incoming connection from %s.\n", address_text); - cbba = g_new0(struct _check_buddy_by_address_t, 1); - cbba->address = address_text; - cbba->pb = &pb; - cbba->bj = data; - g_hash_table_foreach(bl->buddies, _check_buddy_by_address, cbba); - g_free(cbba); - if (pb == NULL) - { + mbba = g_new0(struct _match_buddies_by_address_t, 1); + mbba->address = address_text; + mbba->jdata = jdata; + g_hash_table_foreach(purple_get_blist()->buddies, _match_buddies_by_address, mbba); + + if (mbba->matched_buddies == NULL) { purple_debug_info("bonjour", "We don't like invisible buddies, this is not a superheros comic\n"); + g_slist_free(mbba->matched_buddies); + g_free(mbba); close(client_socket); return; } - bb = pb->proto_data; - /* Check if the conversation has been previously started */ - /* This really shouldn't ever happen unless something weird is going on */ - if (bb->conversation == NULL) - { - bb->conversation = bonjour_jabber_conv_new(pb); + g_slist_free(mbba->matched_buddies); + g_free(mbba); - /* We wait for the stream start before doing anything else */ - bb->conversation->socket = client_socket; - bb->conversation->rx_handler = purple_input_add(client_socket, - PURPLE_INPUT_READ, _client_socket_handler, pb); + /* We've established that this *could* be from one of our buddies. + * Wait for the stream open to see if that matches too before assigning it. + */ + bconv = bonjour_jabber_conv_new(NULL, jdata->account, address_text); - } else { - purple_debug_warning("bonjour", "Ignoring incoming connection because an existing connection exists.\n"); - close(client_socket); - } + /* We wait for the stream start before doing anything else */ + bconv->socket = client_socket; + bconv->rx_handler = purple_input_add(client_socket, PURPLE_INPUT_READ, _client_socket_handler, bconv); + } gint -bonjour_jabber_start(BonjourJabber *data) +bonjour_jabber_start(BonjourJabber *jdata) { struct sockaddr_in my_addr; int yes = 1; @@ -634,20 +653,20 @@ gboolean bind_successful; /* Open a listening socket for incoming conversations */ - if ((data->socket = socket(PF_INET, SOCK_STREAM, 0)) < 0) + if ((jdata->socket = socket(PF_INET, SOCK_STREAM, 0)) < 0) { purple_debug_error("bonjour", "Cannot open socket: %s\n", g_strerror(errno)); - purple_connection_error_reason (data->account->gc, + purple_connection_error_reason (jdata->account->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Cannot open socket")); return -1; } /* Make the socket reusable */ - if (setsockopt(data->socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) != 0) + if (setsockopt(jdata->socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) != 0) { purple_debug_error("bonjour", "Error setting socket options: %s\n", g_strerror(errno)); - purple_connection_error_reason (data->account->gc, + purple_connection_error_reason (jdata->account->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Error setting socket options")); return -1; @@ -660,30 +679,30 @@ bind_successful = FALSE; for (i = 0; i < 10; i++) { - my_addr.sin_port = htons(data->port); - if (bind(data->socket, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) == 0) + my_addr.sin_port = htons(jdata->port); + if (bind(jdata->socket, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) == 0) { bind_successful = TRUE; break; } - data->port++; + jdata->port++; } /* On no! We tried 10 ports and could not bind to ANY of them */ if (!bind_successful) { purple_debug_error("bonjour", "Cannot bind socket: %s\n", g_strerror(errno)); - purple_connection_error_reason (data->account->gc, + purple_connection_error_reason (jdata->account->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Could not bind socket to port")); return -1; } /* Attempt to listen on the bound socket */ - if (listen(data->socket, 10) != 0) + if (listen(jdata->socket, 10) != 0) { purple_debug_error("bonjour", "Cannot listen on socket: %s\n", g_strerror(errno)); - purple_connection_error_reason (data->account->gc, + purple_connection_error_reason (jdata->account->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Could not listen on socket")); return -1; @@ -691,18 +710,18 @@ #if 0 /* TODO: Why isn't this being used? */ - data->socket = purple_network_listen(data->port, SOCK_STREAM); + data->socket = purple_network_listen(jdata->port, SOCK_STREAM); - if (data->socket == -1) + if (jdata->socket == -1) { purple_debug_error("bonjour", "No se ha podido crear el socket\n"); } #endif /* Open a watcher in the socket we have just opened */ - data->watcher_id = purple_input_add(data->socket, PURPLE_INPUT_READ, _server_socket_handler, data); + jdata->watcher_id = purple_input_add(jdata->socket, PURPLE_INPUT_READ, _server_socket_handler, jdata); - return data->port; + return jdata->port; } static void @@ -717,7 +736,7 @@ 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)"); + purple_buddy_get_name(pb), bb->conversation->ip, bb->port_p2pj, error ? error : "(null)"); conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account); if (conv != NULL) @@ -730,12 +749,12 @@ return; } - if (!bonjour_jabber_send_stream_init(pb, source)) { + if (!bonjour_jabber_send_stream_init(bb->conversation, source)) { const char *err = g_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)"); + purple_buddy_get_name(pb), bb->conversation->ip, bb->port_p2pj, err ? err : "(null)"); conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account); if (conv != NULL) @@ -752,19 +771,116 @@ /* Start listening for the stream acknowledgement */ bb->conversation->socket = source; bb->conversation->rx_handler = purple_input_add(source, - PURPLE_INPUT_READ, _client_socket_handler, pb); + PURPLE_INPUT_READ, _client_socket_handler, bb->conversation); +} + +void +bonjour_jabber_conv_match_by_name(BonjourJabberConversation *bconv) { + PurpleBuddy *pb; + + g_return_if_fail(bconv->ip != NULL); + g_return_if_fail(bconv->pb == NULL); + + pb = purple_find_buddy(bconv->account, bconv->buddy_name); + if (pb && pb->proto_data) { + BonjourBuddy *bb = pb->proto_data; + const char *ip; + GSList *tmp = bb->ips; + + purple_debug_info("bonjour", "Found buddy %s for incoming conversation \"from\" attrib.\n", + purple_buddy_get_name(pb)); + + /* Check that one of the buddy's IPs matches */ + while(tmp) { + ip = tmp->data; + if (ip != NULL && g_ascii_strcasecmp(ip, bconv->ip) == 0) { + BonjourJabber *jdata = ((BonjourData*) bconv->account->gc->proto_data)->jabber_data; + + purple_debug_info("bonjour", "Matched buddy %s to incoming conversation \"from\" attrib and IP (%s)\n", + purple_buddy_get_name(pb), bconv->ip); + + /* Attach conv. to buddy and remove from pending list */ + jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv); + + /* Check if the buddy already has a conversation and, if so, replace it */ + if(bb->conversation != NULL && bb->conversation != bconv) + bonjour_jabber_close_conversation(bb->conversation); + + bconv->pb = pb; + bb->conversation = bconv; + + break; + } + tmp = tmp->next; + } + } + + /* We've failed to match a buddy - give up */ + if (bconv->pb == NULL) { + /* This must be asynchronous because it destroys the parser and we + * may be in the middle of parsing. + */ + async_bonjour_jabber_close_conversation(bconv); + } +} + + +void +bonjour_jabber_conv_match_by_ip(BonjourJabberConversation *bconv) { + BonjourJabber *jdata = ((BonjourData*) bconv->account->gc->proto_data)->jabber_data; + struct _match_buddies_by_address_t *mbba; + + mbba = g_new0(struct _match_buddies_by_address_t, 1); + mbba->address = bconv->ip; + mbba->jdata = jdata; + g_hash_table_foreach(purple_get_blist()->buddies, _match_buddies_by_address, mbba); + + /* If there is exactly one match, use it */ + if(mbba->matched_buddies != NULL) { + if(mbba->matched_buddies->next != NULL) + purple_debug_error("bonjour", "More than one buddy matched for ip %s.\n", bconv->ip); + else { + PurpleBuddy *pb = mbba->matched_buddies->data; + BonjourBuddy *bb = pb->proto_data; + + purple_debug_info("bonjour", "Matched buddy %s to incoming conversation using IP (%s)\n", + purple_buddy_get_name(pb), bconv->ip); + + /* Attach conv. to buddy and remove from pending list */ + jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv); + + /* Check if the buddy already has a conversation and, if so, replace it */ + if (bb->conversation != NULL && bb->conversation != bconv) + bonjour_jabber_close_conversation(bb->conversation); + + bconv->pb = pb; + bb->conversation = bconv; + } + } else + purple_debug_error("bonjour", "No buddies matched for ip %s.\n", bconv->ip); + + /* We've failed to match a buddy - give up */ + if (bconv->pb == NULL) { + /* This must be asynchronous because it destroys the parser and we + * may be in the middle of parsing. + */ + async_bonjour_jabber_close_conversation(bconv); + } + + g_slist_free(mbba->matched_buddies); + g_free(mbba); } static PurpleBuddy * -_find_or_start_conversation(BonjourJabber *data, const gchar *to) +_find_or_start_conversation(BonjourJabber *jdata, const gchar *to) { PurpleBuddy *pb = NULL; BonjourBuddy *bb = NULL; - g_return_val_if_fail(data != NULL, NULL); + g_return_val_if_fail(jdata != NULL, NULL); g_return_val_if_fail(to != NULL, NULL); - pb = purple_find_buddy(data->account, to); + pb = purple_find_buddy(jdata->account, to); if (pb == NULL || pb->proto_data == NULL) /* You can not send a message to an offline buddy */ return NULL; @@ -776,27 +892,29 @@ { PurpleProxyConnectData *connect_data; PurpleProxyInfo *proxy_info; + /* For better or worse, use the first IP*/ + const char *ip = bb->ips->data; purple_debug_info("bonjour", "Starting conversation with %s\n", to); /* 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); + proxy_info = purple_account_get_proxy_info(jdata->account); if (proxy_info == NULL) { proxy_info = purple_proxy_info_new(); - purple_account_set_proxy_info(data->account, proxy_info); + purple_account_set_proxy_info(jdata->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); + connect_data = purple_proxy_connect(NULL, jdata->account, + 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 NULL; } - bb->conversation = bonjour_jabber_conv_new(pb); + bb->conversation = bonjour_jabber_conv_new(pb, jdata->account, ip); 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. */ @@ -806,7 +924,7 @@ } int -bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gchar *body) +bonjour_jabber_send_message(BonjourJabber *jdata, const gchar *to, const gchar *body) { xmlnode *message_node, *node, *node2; gchar *message; @@ -814,7 +932,7 @@ BonjourBuddy *bb; int ret; - pb = _find_or_start_conversation(data, to); + pb = _find_or_start_conversation(jdata, 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 */ @@ -825,7 +943,7 @@ 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, "from", purple_account_get_username(jdata->account)); xmlnode_set_attrib(message_node, "type", "chat"); /* Enclose the message from the UI within a "font" node */ @@ -857,26 +975,57 @@ return ret; } +static gboolean +_async_bonjour_jabber_close_conversation_cb(gpointer data) { + BonjourJabberConversation *bconv = data; + bonjour_jabber_close_conversation(bconv); + return FALSE; +} + +void +async_bonjour_jabber_close_conversation(BonjourJabberConversation *bconv) { + BonjourJabber *jdata = ((BonjourData*) bconv->account->gc->proto_data)->jabber_data; + + jdata->pending_conversations = g_slist_remove(jdata->pending_conversations, bconv); + + /* Disconnect this conv. from the buddy here so it can't be disposed of twice.*/ + if(bconv->pb != NULL) { + BonjourBuddy *bb = bconv->pb->proto_data; + if (bb->conversation == bconv) + bb->conversation = NULL; + } + + purple_timeout_add(0, _async_bonjour_jabber_close_conversation_cb, bconv); +} + void bonjour_jabber_close_conversation(BonjourJabberConversation *bconv) { if (bconv != NULL) { - GList *xfers, *tmp_next; - BonjourData *bd = bconv->pb->account->gc->proto_data; + BonjourData *bd = NULL; + + if(PURPLE_CONNECTION_IS_VALID(bconv->account->gc)) { + bd = bconv->account->gc->proto_data; + bd->jabber_data->pending_conversations = g_slist_remove(bd->jabber_data->pending_conversations, bconv); + } /* Cancel any file transfers that are waiting to begin */ - xfers = bd->xfer_lists; - while(xfers != NULL) { - PurpleXfer *xfer = xfers->data; - tmp_next = xfers->next; - /* We only need to cancel this if it hasn't actually started transferring. */ - /* This will change if we ever support IBB transfers. */ - if (strcmp(xfer->who, bconv->pb->name) == 0 - && (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_NOT_STARTED - || purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_UNKNOWN)) { - purple_xfer_cancel_remote(xfer); + /* There wont be any transfers if it hasn't been attached to a buddy */ + if (bconv->pb != NULL && bd != NULL) { + GSList *xfers, *tmp_next; + xfers = bd->xfer_lists; + while(xfers != NULL) { + PurpleXfer *xfer = xfers->data; + tmp_next = xfers->next; + /* We only need to cancel this if it hasn't actually started transferring. */ + /* This will change if we ever support IBB transfers. */ + if (strcmp(xfer->who, bconv->pb->name) == 0 + && (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_NOT_STARTED + || purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_UNKNOWN)) { + purple_xfer_cancel_remote(xfer); + } + xfers = tmp_next; } - xfers = tmp_next; } /* Close the socket and remove the watcher */ @@ -905,25 +1054,26 @@ if (bconv->context != NULL) bonjour_parser_setup(bconv); + g_free(bconv->buddy_name); + g_free(bconv->ip); g_free(bconv); } } void -bonjour_jabber_stop(BonjourJabber *data) +bonjour_jabber_stop(BonjourJabber *jdata) { /* 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); + if (jdata->socket >= 0) + close(jdata->socket); + if (jdata->watcher_id > 0) + purple_input_remove(jdata->watcher_id); /* Close all the conversation sockets and remove all the watchers after sending end streams */ - if (data->account->gc != NULL) - { + if (jdata->account->gc != NULL) { GSList *buddies, *l; - buddies = purple_find_buddies(data->account, purple_account_get_username(data->account)); + buddies = purple_find_buddies(jdata->account, NULL); for (l = buddies; l; l = l->next) { BonjourBuddy *bb = ((PurpleBuddy*) l->data)->proto_data; bonjour_jabber_close_conversation(bb->conversation); @@ -932,6 +1082,11 @@ g_slist_free(buddies); } + + while (jdata->pending_conversations != NULL) { + bonjour_jabber_close_conversation(jdata->pending_conversations->data); + jdata->pending_conversations = g_slist_delete_link(jdata->pending_conversations, jdata->pending_conversations); + } } XepIq * @@ -1028,7 +1183,7 @@ PurpleBuddy *pb = NULL; /* start the talk, reuse the message socket */ - pb = _find_or_start_conversation ((BonjourJabber*)iq->data, iq->to); + pb = _find_or_start_conversation((BonjourJabber*) iq->data, iq->to); /* Send the message */ if (pb != NULL) { /* Convert xml node into stream */
--- a/libpurple/protocols/bonjour/jabber.h Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/bonjour/jabber.h Sat Dec 22 23:18:08 2007 +0000 @@ -38,7 +38,8 @@ gint port; gint socket; gint watcher_id; - PurpleAccount* account; + PurpleAccount *account; + GSList *pending_conversations; } BonjourJabber; typedef struct _BonjourJabberConversation @@ -54,6 +55,11 @@ xmlParserCtxt *context; xmlnode *current; PurpleBuddy *pb; + PurpleAccount *account; + + /* The following are only needed before attaching to a PurpleBuddy */ + gchar *buddy_name; + gchar *ip; } BonjourJabberConversation; /** @@ -68,14 +74,20 @@ void bonjour_jabber_close_conversation(BonjourJabberConversation *bconv); -void bonjour_jabber_stream_started(PurpleBuddy *pb); +void async_bonjour_jabber_close_conversation(BonjourJabberConversation *bconv); -void bonjour_jabber_stream_ended(PurpleBuddy *pb); +void bonjour_jabber_stream_started(BonjourJabberConversation *bconv); + +void bonjour_jabber_stream_ended(BonjourJabberConversation *bconv); void bonjour_jabber_process_packet(PurpleBuddy *pb, xmlnode *packet); void bonjour_jabber_stop(BonjourJabber *data); +void bonjour_jabber_conv_match_by_ip(BonjourJabberConversation *bconv); + +void bonjour_jabber_conv_match_by_name(BonjourJabberConversation *bconv); + typedef enum { XEP_IQ_SET, XEP_IQ_GET,
--- a/libpurple/protocols/bonjour/mdns_avahi.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_avahi.c Sat Dec 22 23:18:08 2007 +0000 @@ -47,11 +47,62 @@ AvahiEntryGroup *buddy_icon_group; } AvahiSessionImplData; +typedef struct _avahi_buddy_service_resolver_data { + AvahiServiceResolver *resolver; + AvahiIfIndex interface; + AvahiProtocol protocol; + gchar *name; + gchar *type; + gchar *domain; + /* This is a reference to the entry in BonjourBuddy->ips */ + const char *ip; +} AvahiSvcResolverData; + typedef struct _avahi_buddy_impl_data { - AvahiServiceResolver *resolver; + GSList *resolvers; AvahiRecordBrowser *buddy_icon_rec_browser; } AvahiBuddyImplData; +static gint +_find_resolver_data(gconstpointer a, gconstpointer b) { + const AvahiSvcResolverData *rd_a = a; + const AvahiSvcResolverData *rd_b = b; + gint ret = 1; + + if(rd_a->interface == rd_b->interface + && rd_a->protocol == rd_b->protocol + && !strcmp(rd_a->name, rd_b->name) + && !strcmp(rd_a->type, rd_b->type) + && !strcmp(rd_a->domain, rd_b->domain)) { + ret = 0; + } + + return ret; +} + +static gint +_find_resolver_data_by_resolver(gconstpointer a, gconstpointer b) { + const AvahiSvcResolverData *rd_a = a; + const AvahiServiceResolver *resolver = b; + gint ret = 1; + + if(rd_a->resolver == resolver) + ret = 0; + + return ret; +} + +static void +_cleanup_resolver_data(AvahiSvcResolverData *rd) { + if (rd->resolver) + avahi_service_resolver_free(rd->resolver); + g_free(rd->name); + g_free(rd->type); + g_free(rd->domain); + g_free(rd); +} + + static void _resolver_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, @@ -65,6 +116,10 @@ size_t size; char *key, *value; int ret; + char ip[AVAHI_ADDRESS_STR_MAX]; + AvahiBuddyImplData *b_impl; + AvahiSvcResolverData *rd; + GSList *res; g_return_if_fail(r != NULL); @@ -78,30 +133,59 @@ avahi_service_resolver_free(r); if (bb != NULL) { - /* We've already freed the resolver */ - if (r == ((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver) - ((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver = NULL; - purple_account_remove_buddy(account, pb, NULL); - purple_blist_remove_buddy(pb); + b_impl = bb->mdns_impl_data; + res = g_slist_find_custom(b_impl->resolvers, r, _find_resolver_data_by_resolver); + if (res != NULL) { + rd = res->data; + b_impl->resolvers = g_slist_remove_link(b_impl->resolvers, res); + + /* We've already freed the resolver */ + rd->resolver = NULL; + _cleanup_resolver_data(rd); + + /* If this was the last resolver, remove the buddy */ + if (b_impl->resolvers == NULL) { + purple_account_remove_buddy(account, pb, NULL); + purple_blist_remove_buddy(pb); + } + } } break; case AVAHI_RESOLVER_FOUND: /* create a buddy record */ if (bb == NULL) bb = bonjour_buddy_new(name, account); + b_impl = bb->mdns_impl_data; - /* If we're reusing an existing buddy, make sure if it is a different resolver to clean up the old one. - * I don't think this should ever happen, but I'm afraid we might get events out of sequence. */ - if (((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver != NULL - && ((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver != r) { - avahi_service_resolver_free(((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver); + /* If we're reusing an existing buddy, it may be a new resolver or an existing one. */ + res = g_slist_find_custom(b_impl->resolvers, r, _find_resolver_data_by_resolver); + if (res != NULL) + rd = res->data; + else { + rd = g_new0(AvahiSvcResolverData, 1); + rd->resolver = r; + rd->interface = interface; + rd->protocol = protocol; + rd->name = g_strdup(name); + rd->type = g_strdup(type); + rd->domain = g_strdup(domain); + + b_impl->resolvers = g_slist_prepend(b_impl->resolvers, rd); } - ((AvahiBuddyImplData *)bb->mdns_impl_data)->resolver = r; + + + /* Get the ip as a string */ + avahi_address_snprint(ip, AVAHI_ADDRESS_STR_MAX, a); - g_free(bb->ip); - /* Get the ip as a string */ - bb->ip = g_malloc(AVAHI_ADDRESS_STR_MAX); - avahi_address_snprint(bb->ip, AVAHI_ADDRESS_STR_MAX, a); + if (rd->ip == NULL || strcmp(rd->ip, ip) != 0) { + /* We store duplicates in bb->ips, so we always remove the one */ + if (rd->ip != NULL) { + bb->ips = g_slist_remove(bb->ips, rd->ip); + g_free((gchar *) rd->ip); + } + bb->ips = g_slist_prepend(bb->ips, g_strdup(ip)); + rd->ip = bb->ips->data; + } bb->port_p2pj = port; @@ -118,11 +202,16 @@ } if (!bonjour_buddy_check(bb)) { - if (pb != NULL) { - purple_account_remove_buddy(account, pb, NULL); - purple_blist_remove_buddy(pb); - } else - bonjour_buddy_delete(bb); + _cleanup_resolver_data(rd); + b_impl->resolvers = g_slist_remove(b_impl->resolvers, rd); + /* If this was the last resolver, remove the buddy */ + if (b_impl->resolvers == NULL) { + if (pb != NULL) { + purple_account_remove_buddy(account, pb, NULL); + purple_blist_remove_buddy(pb); + } else + bonjour_buddy_delete(bb); + } } else /* Add or update the buddy in our buddy list */ bonjour_buddy_add_to_purple(bb, pb); @@ -166,8 +255,44 @@ purple_debug_info("bonjour", "_browser_callback - Remove service\n"); pb = purple_find_buddy(account, name); if (pb != NULL) { - purple_account_remove_buddy(account, pb, NULL); - purple_blist_remove_buddy(pb); + BonjourBuddy *bb = pb->proto_data; + AvahiBuddyImplData *b_impl; + GSList *l; + AvahiSvcResolverData *rd_search; + + g_return_if_fail(bb != NULL); + + b_impl = bb->mdns_impl_data; + + /* There may be multiple presences, we should only get rid of this one */ + + rd_search = g_new0(AvahiSvcResolverData, 1); + rd_search->interface = interface; + rd_search->protocol = protocol; + rd_search->name = (gchar *) name; + rd_search->type = (gchar *) type; + rd_search->domain = (gchar *) domain; + + l = g_slist_find_custom(b_impl->resolvers, rd_search, _find_resolver_data); + + g_free(rd_search); + + if (l != NULL) { + AvahiSvcResolverData *rd = l->data; + b_impl->resolvers = g_slist_remove(b_impl->resolvers, rd); + /* This IP is no longer available */ + if (rd->ip != NULL) { + bb->ips = g_slist_remove(bb->ips, rd->ip); + g_free((gchar *) rd->ip); + } + _cleanup_resolver_data(rd); + + /* If this was the last resolver, remove the buddy */ + if (b_impl->resolvers == NULL) { + purple_account_remove_buddy(account, pb, NULL); + purple_blist_remove_buddy(pb); + } + } } break; case AVAHI_BROWSER_ALL_FOR_NOW: @@ -188,6 +313,7 @@ switch(state) { case AVAHI_ENTRY_GROUP_ESTABLISHED: purple_debug_info("bonjour", "Successfully registered buddy icon data.\n"); + break; case AVAHI_ENTRY_GROUP_COLLISION: purple_debug_error("bonjour", "Collision registering buddy icon data.\n"); break; @@ -249,8 +375,10 @@ } /* Stop listening */ - avahi_record_browser_free(idata->buddy_icon_rec_browser); - idata->buddy_icon_rec_browser = NULL; + avahi_record_browser_free(b); + if (idata->buddy_icon_rec_browser == b) { + idata->buddy_icon_rec_browser = NULL; + } } /**************************** @@ -462,8 +590,11 @@ if (idata->buddy_icon_rec_browser != NULL) avahi_record_browser_free(idata->buddy_icon_rec_browser); - if (idata->resolver != NULL) - avahi_service_resolver_free(idata->resolver); + while(idata->resolvers != NULL) { + AvahiSvcResolverData *rd = idata->resolvers->data; + _cleanup_resolver_data(rd); + idata->resolvers = g_slist_delete_link(idata->resolvers, idata->resolvers); + } g_free(idata);
--- a/libpurple/protocols/bonjour/mdns_howl.c Sat Dec 22 17:54:30 2007 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,288 +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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA. - */ - -#include "internal.h" - -#include "mdns_interface.h" -#include "debug.h" -#include "buddy.h" - -#include <howl.h> - -/* data used by howl bonjour implementation */ -typedef struct _howl_impl_data { - sw_discovery session; - sw_discovery_oid session_id; - guint session_handler; -} HowlSessionImplData; - -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; - 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; - - /* TODO: We want to keep listening for updates*/ - 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')) - { - clear_bonjour_buddy_values(buddy); - sw_text_record_iterator_init(&iterator, text_record, text_record_len); - while (sw_text_record_iterator_next(iterator, key, (sw_octet *)value, &value_length) == SW_OKAY) - set_bonjour_buddy_value(buddy, key, value, value_length); - - 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, NULL); - - 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 *pb = 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"); - pb = purple_find_buddy(account, name); - if (pb != NULL) { - purple_account_remove_buddy(account, pb, NULL); - purple_blist_remove_buddy(pb); - } - break; - case SW_DISCOVERY_BROWSE_RESOLVED: - purple_debug_info("bonjour", "_browse_reply --> Resolved\n"); - break; - default: - break; - } - - return SW_OKAY; -} - -static void -_mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition) -{ - sw_discovery_read_socket((sw_discovery)data); -} - -/**************************** - * mdns_interface functions * - ****************************/ - -gboolean _mdns_init_session(BonjourDnsSd *data) { - HowlSessionImplData *idata = g_new0(HowlSessionImplData, 1); - - if (sw_discovery_init(&idata->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 */ - idata->session = NULL; - - g_free(idata); - - return FALSE; - } - - data->mdns_impl_data = idata; - - return TRUE; -} - - -gboolean _mdns_publish(BonjourDnsSd *data, PublishType type, GSList *records) { - sw_text_record dns_data; - sw_result publish_result = SW_OKAY; - HowlSessionImplData *idata = data->mdns_impl_data; - - g_return_val_if_fail(idata != NULL, FALSE); - - /* 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 FALSE; - } - - while (records) { - PurpleKeyValuePair *kvp = records->data; - sw_text_record_add_key_and_string_value(dns_data, kvp->key, kvp->value); - records = records->next; - } - - /* Publish the service */ - switch (type) { - case PUBLISH_START: - publish_result = sw_discovery_publish(idata->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, &idata->session_id); - break; - case PUBLISH_UPDATE: - publish_result = sw_discovery_publish_update(idata->session, idata->session_id, - sw_text_record_bytes(dns_data), sw_text_record_len(dns_data)); - break; - } - - /* Free the memory used by temp data */ - sw_text_record_fina(dns_data); - - if (publish_result != SW_OKAY) { - purple_debug_error("bonjour", "Unable to publish or change the status of the " ICHAT_SERVICE " service.\n"); - return FALSE; - } - - return TRUE; -} - -gboolean _mdns_browse(BonjourDnsSd *data) { - HowlSessionImplData *idata = data->mdns_impl_data; - /* TODO: don't we need to hang onto this to cancel later? */ - sw_discovery_oid session_id; - - g_return_val_if_fail(idata != NULL, FALSE); - - if (sw_discovery_browse(idata->session, 0, ICHAT_SERVICE, NULL, _browser_reply, - data->account, &session_id) == SW_OKAY) { - idata->session_handler = purple_input_add(sw_discovery_socket(idata->session), - PURPLE_INPUT_READ, _mdns_handle_event, idata->session); - return TRUE; - } - - return FALSE; -} - -gboolean _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len) { - return FALSE; -} - -void _mdns_stop(BonjourDnsSd *data) { - HowlSessionImplData *idata = data->mdns_impl_data; - - if (idata == NULL || idata->session == NULL) - return; - - sw_discovery_cancel(idata->session, idata->session_id); - - purple_input_remove(idata->session_handler); - - /* TODO: should this really be g_free()'d ??? */ - g_free(idata->session); - - g_free(idata); - - data->mdns_impl_data = NULL; -} - -void _mdns_init_buddy(BonjourBuddy *buddy) { -} - -void _mdns_delete_buddy(BonjourBuddy *buddy) { -} - -void _mdns_retrieve_buddy_icon(BonjourBuddy* buddy) { -} - -
--- a/libpurple/protocols/bonjour/mdns_win32.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_win32.c Sat Dec 22 23:18:08 2007 +0000 @@ -28,17 +28,7 @@ #include "dnsquery.h" #include "mdns_common.h" - -/* data structure for the resolve callback */ -typedef struct _ResolveCallbackArgs { - DNSServiceRef resolver; - guint resolver_handler; - gchar *full_service_name; - - PurpleDnsQueryData *query; - - BonjourBuddy* buddy; -} ResolveCallbackArgs; +static GSList *pending_buddies = NULL; /* data used by win32 bonjour implementation */ typedef struct _win32_session_impl_data { @@ -50,20 +40,69 @@ guint browser_handler; } Win32SessionImplData; -typedef struct _win32_buddy_impl_data { +typedef struct _win32_buddy_service_resolver_data { DNSServiceRef txt_query; guint txt_query_handler; + uint32_t if_idx; + gchar *name; + gchar *type; + gchar *domain; + /* This is a reference to the entry in BonjourBuddy->ips */ + const char *ip; +} Win32SvcResolverData; + +typedef struct _win32_buddy_impl_data { + GSList *resolvers; DNSServiceRef null_query; guint null_query_handler; } Win32BuddyImplData; +/* data structure for the resolve callback */ +typedef struct _ResolveCallbackArgs { + DNSServiceRef resolver; + guint resolver_handler; + PurpleAccount *account; + BonjourBuddy *bb; + Win32SvcResolverData *res_data; + gchar *full_service_name; + PurpleDnsQueryData *query; +} ResolveCallbackArgs; + +static gint +_find_resolver_data(gconstpointer a, gconstpointer b) { + const Win32SvcResolverData *rd_a = a; + const Win32SvcResolverData *rd_b = b; + gint ret = 1; + + if(rd_a->if_idx == rd_b->if_idx + && !strcmp(rd_a->name, rd_b->name) + && !strcmp(rd_a->type, rd_b->type) + && !strcmp(rd_a->domain, rd_b->domain)) { + ret = 0; + } + + return ret; +} + +static void +_cleanup_resolver_data(Win32SvcResolverData *rd) { + if (rd->txt_query != NULL) { + purple_input_remove(rd->txt_query_handler); + DNSServiceRefDeallocate(rd->txt_query); + } + g_free(rd->name); + g_free(rd->type); + g_free(rd->domain); + g_free(rd); +} + static void _mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition) { DNSServiceProcessResult((DNSServiceRef) data); } static void -_mdns_parse_text_record(BonjourBuddy* buddy, const char* record, uint16_t record_len) +_mdns_parse_text_record(BonjourBuddy *buddy, const char *record, uint16_t record_len) { const char *txt_entry; uint8_t txt_len; @@ -114,35 +153,44 @@ static void _mdns_resolve_host_callback(GSList *hosts, gpointer data, const char *error_message) { - ResolveCallbackArgs* args = (ResolveCallbackArgs*)data; - BonjourBuddy* bb = args->buddy; + ResolveCallbackArgs* args = (ResolveCallbackArgs*) data; + Win32BuddyImplData *idata = args->bb->mdns_impl_data; + gboolean delete_buddy = FALSE; + PurpleBuddy *pb; + + if ((pb = purple_find_buddy(args->account, args->bb->name))) + if (pb->proto_data != args->bb) + purple_debug_error("bonjour", "Found purple buddy for %s not matching bonjour buddy record. " + "This is going to be ugly!.\n", args->bb->name); if (!hosts || !hosts->data) { purple_debug_error("bonjour", "host resolution - callback error.\n"); - bonjour_buddy_delete(bb); + delete_buddy = TRUE; } else { - struct sockaddr_in *addr = (struct sockaddr_in*)g_slist_nth_data(hosts, 1); - Win32BuddyImplData *idata = bb->mdns_impl_data; - - g_return_if_fail(idata != NULL); - - g_free(bb->ip); - bb->ip = g_strdup(inet_ntoa(addr->sin_addr)); + struct sockaddr_in *addr = g_slist_nth_data(hosts, 1); /* finally, set up the continuous txt record watcher, and add the buddy to purple */ - if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->txt_query, kDNSServiceFlagsLongLivedQuery, + if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&args->res_data->txt_query, kDNSServiceFlagsLongLivedQuery, kDNSServiceInterfaceIndexAny, args->full_service_name, kDNSServiceType_TXT, - kDNSServiceClass_IN, _mdns_record_query_callback, bb)) { + kDNSServiceClass_IN, _mdns_record_query_callback, args->bb)) { - purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", bb->name, bb->ip, bb->port_p2pj); + const char *ip = inet_ntoa(addr->sin_addr); + + purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", args->bb->name, ip, args->bb->port_p2pj); + - idata->txt_query_handler = purple_input_add(DNSServiceRefSockFD(idata->txt_query), - PURPLE_INPUT_READ, _mdns_handle_event, idata->txt_query); + args->bb->ips = g_slist_prepend(args->bb->ips, g_strdup(ip)); + args->res_data->ip = args->bb->ips->data; + + args->res_data->txt_query_handler = purple_input_add(DNSServiceRefSockFD(args->res_data->txt_query), + PURPLE_INPUT_READ, _mdns_handle_event, args->res_data->txt_query); - bonjour_buddy_add_to_purple(bb, NULL); - } else - bonjour_buddy_delete(bb); + bonjour_buddy_add_to_purple(args->bb, NULL); + } else { + purple_debug_error("bonjour", "Unable to set up record watcher for buddy %s\n", args->bb->name); + delete_buddy = TRUE; + } } @@ -153,6 +201,26 @@ hosts = g_slist_remove(hosts, hosts->data); } + if (delete_buddy) { + idata->resolvers = g_slist_remove(idata->resolvers, args->res_data); + _cleanup_resolver_data(args->res_data); + + /* If this was the last resolver, remove the buddy */ + if (idata->resolvers == NULL) { + if (pb) { + purple_account_remove_buddy(args->account, pb, NULL); + purple_blist_remove_buddy(pb); + } else + bonjour_buddy_delete(args->bb); + + /* Remove from the pending list */ + pending_buddies = g_slist_remove(pending_buddies, args->bb); + } + } else { + /* Remove from the pending list */ + pending_buddies = g_slist_remove(pending_buddies, args->bb); + } + /* free the remaining args memory */ g_free(args->full_service_name); g_free(args); @@ -163,37 +231,54 @@ const char *fullname, const char *hosttarget, uint16_t port, uint16_t txtLen, const char *txtRecord, void *context) { ResolveCallbackArgs *args = (ResolveCallbackArgs*)context; + Win32BuddyImplData *idata = args->bb->mdns_impl_data; /* remove the input fd and destroy the service ref */ purple_input_remove(args->resolver_handler); + args->resolver_handler = 0; DNSServiceRefDeallocate(args->resolver); + args->resolver = NULL; 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); + else { + /* set more arguments, and start the host resolver */ + + if ((args->query = + purple_dnsquery_a(hosttarget, port, _mdns_resolve_host_callback, args)) != NULL) { + + args->full_service_name = g_strdup(fullname); + + /* TODO: Should this be per resolver? */ + args->bb->port_p2pj = ntohs(port); - /* parse the text record */ - _mdns_parse_text_record(args->buddy, txtRecord, txtLen); + /* We don't want to hit the cleanup code */ + return; + } else + purple_debug_error("bonjour", "service resolver - host resolution failed.\n"); + } - /* set more arguments, and start the host resolver */ - args->full_service_name = g_strdup(fullname); + /* If we get this far, clean up */ + + idata->resolvers = g_slist_remove(idata->resolvers, args->res_data); + _cleanup_resolver_data(args->res_data); - if (!(args->query = - purple_dnsquery_a(hosttarget, port, _mdns_resolve_host_callback, args))) - { - purple_debug_error("bonjour", "service resolver - host resolution failed.\n"); - bonjour_buddy_delete(args->buddy); - g_free(args->full_service_name); - g_free(args); + /* If this was the last resolver, remove the buddy */ + if (idata->resolvers == NULL) { + PurpleBuddy *pb; + /* See if this is now attached to a PurpleBuddy */ + if ((pb = purple_find_buddy(args->account, args->bb->name))) { + purple_account_remove_buddy(args->account, pb, NULL); + purple_blist_remove_buddy(pb); + } else { + /* Remove from the pending list */ + pending_buddies = g_slist_remove(pending_buddies, args->bb); + bonjour_buddy_delete(args->bb); } } + g_free(args); + } static void DNSSD_API @@ -212,7 +297,6 @@ DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context) { PurpleAccount *account = (PurpleAccount*)context; - PurpleBuddy *pb = NULL; if (kDNSServiceErr_NoError != errorCode) purple_debug_error("bonjour", "service browser - callback error\n"); @@ -221,28 +305,118 @@ if (purple_utf8_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); + + purple_debug_info("bonjour", "Received new record for '%s' on iface %u (%s, %s)\n", + serviceName, interfaceIndex, regtype ? regtype : "", + replyDomain ? replyDomain : ""); + + if (kDNSServiceErr_NoError == DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype, + replyDomain, _mdns_service_resolve_callback, args)) { + GSList *tmp = pending_buddies; + PurpleBuddy *pb; + BonjourBuddy* bb = NULL; + Win32SvcResolverData *rd; + Win32BuddyImplData *idata; + gint fd; + + /* Is there an existing buddy? */ + if ((pb = purple_find_buddy(account, serviceName))) + bb = pb->proto_data; + /* Is there a pending buddy? */ + else { + while (tmp) { + BonjourBuddy *bb_tmp = tmp->data; + if (!strcmp(bb_tmp->name, serviceName)) { + bb = bb_tmp; + break; + } + tmp = tmp->next; + } + } - if (kDNSServiceErr_NoError != DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype, - replyDomain, _mdns_service_resolve_callback, args)) { - bonjour_buddy_delete(args->buddy); + if (bb == NULL) { + bb = bonjour_buddy_new(serviceName, account); + + /* This is only necessary for the wacky case where someone previously manually added a buddy. */ + if (pb == NULL) + pending_buddies = g_slist_prepend(pending_buddies, bb); + else + pb->proto_data = bb; + } + + + rd = g_new0(Win32SvcResolverData, 1); + rd->if_idx = interfaceIndex; + rd->name = g_strdup(serviceName); + rd->type = g_strdup(regtype); + rd->domain = g_strdup(replyDomain); + + idata = bb->mdns_impl_data; + idata->resolvers = g_slist_prepend(idata->resolvers, rd); + + args->bb = bb; + args->res_data = rd; + args->account = account; + + /* get a file descriptor for this service ref, and add it to the input list */ + fd = DNSServiceRefSockFD(args->resolver); + args->resolver_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, args->resolver); + } else { + purple_debug_error("bonjour", "service browser - failed to resolve service.\n"); 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 fd = DNSServiceRefSockFD(args->resolver); - args->resolver_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, args->resolver); } } } else { + PurpleBuddy *pb = NULL; + /* A peer has sent a goodbye packet, remove them from the buddy list */ - purple_debug_info("bonjour", "service browser - remove notification\n"); + purple_debug_info("bonjour", "Received remove notification for '%s' on iface %u (%s, %s)\n", + serviceName, interfaceIndex, regtype ? regtype : "", + replyDomain ? replyDomain : ""); + pb = purple_find_buddy(account, serviceName); if (pb != NULL) { - purple_account_remove_buddy(account, pb, NULL); - purple_blist_remove_buddy(pb); - } else + GSList *l; + /* There may be multiple presences, we should only get rid of this one */ + Win32SvcResolverData *rd_search; + BonjourBuddy *bb = pb->proto_data; + Win32BuddyImplData *idata; + + g_return_if_fail(bb != NULL); + + idata = bb->mdns_impl_data; + + rd_search = g_new0(Win32SvcResolverData, 1); + rd_search->if_idx = interfaceIndex; + rd_search->name = (gchar *) serviceName; + rd_search->type = (gchar *) regtype; + rd_search->domain = (gchar *) replyDomain; + + l = g_slist_find_custom(idata->resolvers, rd_search, _find_resolver_data); + + g_free(rd_search); + + if (l != NULL) { + Win32SvcResolverData *rd = l->data; + idata->resolvers = g_slist_delete_link(idata->resolvers, l); + /* This IP is no longer available */ + if (rd->ip != NULL) { + bb->ips = g_slist_remove(bb->ips, rd->ip); + g_free((gchar *) rd->ip); + } + _cleanup_resolver_data(rd); + + /* If this was the last resolver, remove the buddy */ + if (idata->resolvers == NULL) { + purple_debug_info("bonjour", "Removed last presence for buddy '%s'; removing buddy.\n", serviceName); + purple_account_remove_buddy(account, pb, NULL); + purple_blist_remove_buddy(pb); + } + } + } else { purple_debug_warning("bonjour", "Unable to find buddy (%s) to remove\n", serviceName ? serviceName : "(null)"); + /* TODO: Should we look in the pending buddies list? */ + } } } @@ -385,9 +559,10 @@ g_return_if_fail(idata != NULL); - if (idata->txt_query != NULL) { - purple_input_remove(idata->txt_query_handler); - DNSServiceRefDeallocate(idata->txt_query); + while (idata->resolvers) { + Win32SvcResolverData *rd = idata->resolvers->data; + _cleanup_resolver_data(rd); + idata->resolvers = g_slist_delete_link(idata->resolvers, idata->resolvers); } if (idata->null_query != NULL) {
--- a/libpurple/protocols/bonjour/parser.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/bonjour/parser.c Sat Dec 22 23:18:08 2007 +0000 @@ -31,26 +31,59 @@ #include "util.h" #include "xmlnode.h" +static gboolean +parse_from_attrib_and_find_buddy(BonjourJabberConversation *bconv, int nb_attributes, const xmlChar **attributes) { + int i; + + /* If the "from" attribute is specified, attach it to the conversation. */ + for(i=0; i < nb_attributes * 5; i+=5) { + if(!xmlStrcmp(attributes[i], (xmlChar*) "from")) { + int len = attributes[i+4] - attributes[i+3]; + bconv->buddy_name = g_strndup(attributes[i+3], len); + bonjour_jabber_conv_match_by_name(bconv); + + return (bconv->pb != NULL); + } + } + + return FALSE; +} + static void bonjour_parser_element_start_libxml(void *user_data, const xmlChar *element_name, const xmlChar *prefix, const xmlChar *namespace, int nb_namespaces, const xmlChar **namespaces, int nb_attributes, int nb_defaulted, const xmlChar **attributes) { - PurpleBuddy *pb = user_data; - BonjourBuddy *bb = pb->proto_data; - BonjourJabberConversation *bconv = bb->conversation; + BonjourJabberConversation *bconv = user_data; xmlnode *node; int i; - if(!element_name) { - return; - } else if(!xmlStrcmp(element_name, (xmlChar*) "stream")) { - bconv->recv_stream_start = TRUE; - bonjour_jabber_stream_started(pb); + g_return_if_fail(element_name != NULL); + + if(!xmlStrcmp(element_name, (xmlChar*) "stream")) { + if(!bconv->recv_stream_start) { + bconv->recv_stream_start = TRUE; + + if (bconv->pb == NULL) + parse_from_attrib_and_find_buddy(bconv, nb_attributes, attributes); + + bonjour_jabber_stream_started(bconv); + } } else { + /* If we haven't yet attached a buddy and this isn't "<stream:features />", + * try to get a "from" attribute as a last resort to match our buddy. */ + if(bconv->pb == NULL + && !(prefix && !xmlStrcmp(prefix, (xmlChar*) "stream") + && !xmlStrcmp(element_name, (xmlChar*) "features")) + && !parse_from_attrib_and_find_buddy(bconv, nb_attributes, attributes)) + /* We've run out of options for finding who the conversation is from + using explicitly specified stuff; see if we can make a good match + by using the IP */ + bonjour_jabber_conv_match_by_ip(bconv); + if(bconv->current) node = xmlnode_new_child(bconv->current, (const char*) element_name); else @@ -82,27 +115,19 @@ } } -static gboolean _async_bonjour_jabber_stream_ended_cb(gpointer data) { - bonjour_jabber_stream_ended((PurpleBuddy *) data); - return FALSE; -} - static void bonjour_parser_element_end_libxml(void *user_data, const xmlChar *element_name, const xmlChar *prefix, const xmlChar *namespace) { - PurpleBuddy *pb = user_data; - BonjourBuddy *bb = pb->proto_data; - BonjourJabberConversation *bconv = bb->conversation; + BonjourJabberConversation *bconv = user_data; if(!bconv->current) { /* We don't keep a reference to the start stream xmlnode, * so we have to check for it here to close the conversation */ - if(!xmlStrcmp(element_name, (xmlChar*) "stream")) { + if(!xmlStrcmp(element_name, (xmlChar*) "stream")) /* Asynchronously close the conversation to prevent bonjour_parser_setup() * being called from within this context */ - purple_timeout_add(0, _async_bonjour_jabber_stream_ended_cb, pb); - } + async_bonjour_jabber_close_conversation(bconv); return; } @@ -112,7 +137,7 @@ } else { xmlnode *packet = bconv->current; bconv->current = NULL; - bonjour_jabber_process_packet(pb, packet); + bonjour_jabber_process_packet(bconv->pb, packet); xmlnode_free(packet); } } @@ -120,9 +145,7 @@ static void bonjour_parser_element_text_libxml(void *user_data, const xmlChar *text, int text_len) { - PurpleBuddy *pb = user_data; - BonjourBuddy *bb = pb->proto_data; - BonjourJabberConversation *bconv = bb->conversation; + BonjourJabberConversation *bconv = user_data; if(!bconv->current) return; @@ -184,21 +207,17 @@ } -void bonjour_parser_process(PurpleBuddy *pb, const char *buf, int len) +void bonjour_parser_process(BonjourJabberConversation *bconv, const char *buf, int len) { - BonjourBuddy *bb = pb->proto_data; - g_return_if_fail(bb != NULL); - g_return_if_fail(bb->conversation != NULL); - - if (bb->conversation->context == NULL) { + if (bconv->context == NULL) { /* libxml inconsistently starts parsing on creating the * parser, so do a ParseChunk right afterwards to force it. */ - bb->conversation->context = xmlCreatePushParserCtxt(&bonjour_parser_libxml, pb, buf, len, NULL); - xmlParseChunk(bb->conversation->context, "", 0, 0); - } else if (xmlParseChunk(bb->conversation->context, buf, len, 0) < 0) { + bconv->context = xmlCreatePushParserCtxt(&bonjour_parser_libxml, bconv, buf, len, NULL); + xmlParseChunk(bconv->context, "", 0, 0); + } else if (xmlParseChunk(bconv->context, buf, len, 0) < 0) /* TODO: What should we do here - I assume we should display an error or something (maybe just print something to the conv?) */ purple_debug_error("bonjour", "Error parsing xml.\n"); - } + }
--- a/libpurple/protocols/bonjour/parser.h Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/bonjour/parser.h Sat Dec 22 23:18:08 2007 +0000 @@ -28,6 +28,6 @@ #include "jabber.h" void bonjour_parser_setup(BonjourJabberConversation *bconv); -void bonjour_parser_process(PurpleBuddy *pb, const char *buf, int len); +void bonjour_parser_process(BonjourJabberConversation *bconv, const char *buf, int len); #endif /* _PURPLE_BONJOUR_PARSER_H_ */
--- a/libpurple/protocols/jabber/auth.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/jabber/auth.c Sat Dec 22 23:18:08 2007 +0000 @@ -330,14 +330,21 @@ disallow_plaintext_auth); g_free(msg); return; - /* Everything else has failed, so fail the - * connection. Should probably have a better - * error here. - */ + } else { - purple_connection_error_reason (js->gc, - PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, - _("Server does not use any supported authentication method")); + /* We have no mechs which can work. + * Try falling back on the old jabber:iq:auth method. We get here if the server supports + * one or more sasl mechs, we are compiled with cyrus-sasl support, but we support or can connect with none of + * the offerred mechs. jabberd 2.0 w/ SASL and Apple's iChat Server 10.5 both handle and expect + * jabber:iq:auth in this situation. iChat Server in particular offers SASL GSSAPI by default, which is often + * not configured on the client side, and expects a fallback to jabber:iq:auth when it (predictably) fails. + * + * Note: xep-0078 points out that using jabber:iq:auth after a sasl failure is wrong. However, + * I believe this refers to actual authentication failure, not a simple lack of concordant mechanisms. + * Doing otherwise means that simply compiling with SASL support renders the client unable to connect to servers + * which would connect without issue otherwise. -evands + */ + jabber_auth_start_old(js); return; } /* not reached */ @@ -389,6 +396,24 @@ g_free(enc_out); } } + + if (mech && (strcmp(mech, "DIGEST-MD5") == 0)) { + /* CYRUS-SASL's DIGEST-MD5 and Java's DIGEST-MD5 are mutually incompatible because of different interpretations of RFC2831. + * This means that if we are using SASL and connecting to a Java-based server such as OpenFire, we will receive an authentication + * failure if that server offers DIGEST-MD5 in such a way that SASL chooses it as the best mechanism for us. + * + * However, we implement our own DIGEST-MD5 for use when we're compiled without SASL support, and that implementation + * works correctly. Therefore, if SASL chooses DIGEST-MD5, we switch over to our own implementation. + * jabber_auth_handle_challenge() will note the auth_type and take it from there. + * + * SASL would change state to SASL_OK after when handling the challenge; we do so immediately to avoid an error later. + */ + js->auth_type = JABBER_AUTH_DIGEST_MD5; + js->sasl_state = SASL_OK; + sasl_dispose(&js->sasl); + js->sasl = NULL; + } + jabber_send(js, auth); xmlnode_free(auth); } else { @@ -563,6 +588,75 @@ } } +/*! + * @brief Given the server challenge (message) and the key (password), calculate the HMAC-MD5 digest + * + * This is the crammd5 response. Inspired by cyrus-sasl's _sasl_hmac_md5() + */ +static void +auth_hmac_md5(const char *challenge, size_t challenge_len, const char *key, size_t key_len, guchar *digest) +{ + PurpleCipher *cipher; + PurpleCipherContext *context; + int i; + /* inner padding - key XORd with ipad */ + unsigned char k_ipad[65]; + /* outer padding - key XORd with opad */ + unsigned char k_opad[65]; + + cipher = purple_ciphers_find_cipher("md5"); + + /* if key is longer than 64 bytes reset it to key=MD5(key) */ + if (strlen(key) > 64) { + guchar keydigest[16]; + + context = purple_cipher_context_new(cipher, NULL); + purple_cipher_context_append(context, (const guchar *)key, strlen(key)); + purple_cipher_context_digest(context, 16, keydigest, NULL); + purple_cipher_context_destroy(context); + + key = (char *)keydigest; + key_len = 16; + } + + /* + * the HMAC_MD5 transform looks like: + * + * MD5(K XOR opad, MD5(K XOR ipad, text)) + * + * where K is an n byte key + * ipad is the byte 0x36 repeated 64 times + * opad is the byte 0x5c repeated 64 times + * and text is the data being protected + */ + + /* start out by storing key in pads */ + memset(k_ipad, '\0', sizeof k_ipad); + memset(k_opad, '\0', sizeof k_opad); + memcpy(k_ipad, (void *)key, key_len); + memcpy(k_opad, (void *)key, key_len); + + /* XOR key with ipad and opad values */ + for (i=0; i<64; i++) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + + /* perform inner MD5 */ + context = purple_cipher_context_new(cipher, NULL); + purple_cipher_context_append(context, k_ipad, 64); /* start with inner pad */ + purple_cipher_context_append(context, (const guchar *)challenge, challenge_len); /* then text of datagram */ + purple_cipher_context_digest(context, 16, digest, NULL); /* finish up 1st pass */ + purple_cipher_context_destroy(context); + + /* perform outer MD5 */ + context = purple_cipher_context_new(cipher, NULL); + purple_cipher_context_append(context, k_opad, 64); /* start with outer pad */ + purple_cipher_context_append(context, digest, 16); /* then results of 1st hash */ + purple_cipher_context_digest(context, 16, digest, NULL); /* finish up 2nd pass */ + purple_cipher_context_destroy(context); +} + static void auth_old_cb(JabberStream *js, xmlnode *packet, gpointer data) { JabberIq *iq; @@ -608,6 +702,35 @@ jabber_iq_set_callback(iq, auth_old_result_cb, NULL); jabber_iq_send(iq); + } else if(js->stream_id && xmlnode_get_child(query, "crammd5")) { + const char *challenge; + guchar digest[16]; + char h[17], *p; + int i; + + challenge = xmlnode_get_attrib(xmlnode_get_child(query, "crammd5"), "challenge"); + auth_hmac_md5(challenge, strlen(challenge), pw, strlen(pw), digest); + + /* Create the response query */ + iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth"); + query = xmlnode_get_child(iq->node, "query"); + + x = xmlnode_new_child(query, "username"); + xmlnode_insert_data(x, js->user->node, -1); + x = xmlnode_new_child(query, "resource"); + xmlnode_insert_data(x, js->user->resource, -1); + + x = xmlnode_new_child(query, "crammd5"); + + /* Translate the digest to a hexadecimal notation */ + p = h; + for(i=0; i<16; i++, p+=2) + snprintf(p, 3, "%02x", digest[i]); + xmlnode_insert_data(x, h, -1); + + jabber_iq_set_callback(iq, auth_old_result_cb, NULL); + jabber_iq_send(iq); + } else if(xmlnode_get_child(query, "password")) { if(js->gsc == NULL && !purple_account_get_bool(js->gc->account, "auth_plain_in_clear", FALSE)) {
--- a/libpurple/protocols/jabber/buddy.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/jabber/buddy.c Sat Dec 22 23:18:08 2007 +0000 @@ -1153,8 +1153,10 @@ void jabber_vcard_fetch_mine(JabberStream *js) { - JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_GET, "vcard-temp"); - + JabberIq *iq = jabber_iq_new(js, JABBER_IQ_GET); + + xmlnode *vcard = xmlnode_new_child(iq->node, "vCard"); + xmlnode_set_namespace(vcard, "vcard-temp"); jabber_iq_set_callback(iq, jabber_vcard_save_mine, NULL); jabber_iq_send(iq);
--- a/libpurple/protocols/jabber/jabber.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.c Sat Dec 22 23:18:08 2007 +0000 @@ -388,9 +388,29 @@ g_free(txt); } +static void jabber_pong_cb(JabberStream *js, xmlnode *packet, gpointer timeout) +{ + g_source_remove(GPOINTER_TO_INT(timeout)); +} + +static gboolean jabber_pong_timeout(PurpleConnection *gc) +{ + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Ping timeout")); + return FALSE; +} + void jabber_keepalive(PurpleConnection *gc) { - jabber_send_raw(gc->proto_data, "\t", -1); + JabberIq *iq = jabber_iq_new(gc->proto_data, JABBER_IQ_GET); + guint timeout; + + xmlnode *ping = xmlnode_new_child(iq->node, "ping"); + xmlnode_set_namespace(ping, "urn:xmpp:ping"); + + timeout = purple_timeout_add_seconds(20, (GSourceFunc)(jabber_pong_timeout), gc); + jabber_iq_set_callback(iq, jabber_pong_cb, GINT_TO_POINTER(timeout)); + jabber_iq_send(iq); } static void @@ -536,12 +556,13 @@ purple_input_remove(js->gc->inpa); js->gc->inpa = 0; js->gsc = purple_ssl_connect_with_host_fd(js->gc->account, js->fd, - jabber_login_callback_ssl, jabber_ssl_connect_failure, js->serverFQDN, js->gc); + jabber_login_callback_ssl, jabber_ssl_connect_failure, js->host, js->gc); } static void jabber_login_connect(JabberStream *js, const char *fqdn, const char *host, int port) { js->serverFQDN = g_strdup(fqdn); + js->host = g_strdup(host); if (purple_proxy_connect(js->gc, js->gc->account, host, port, jabber_login_callback, js->gc) == NULL) @@ -1279,6 +1300,7 @@ js->commands = g_list_delete_link(js->commands, js->commands); } g_free(js->server_name); + g_free(js->host); g_free(js->gmail_last_time); g_free(js->gmail_last_tid); g_free(js->old_msg);
--- a/libpurple/protocols/jabber/jabber.h Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.h Sat Dec 22 23:18:08 2007 +0000 @@ -191,6 +191,8 @@ char *old_uri; int old_length; char *old_track; + + char *host; }; typedef gboolean (JabberFeatureEnabled)(JabberStream *js, const gchar *shortname, const gchar *namespace);
--- a/libpurple/protocols/jabber/libxmpp.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Sat Dec 22 23:18:08 2007 +0000 @@ -53,7 +53,7 @@ OPT_PROTO_SLASH_COMMANDS_NATIVE, NULL, /* user_splits */ NULL, /* protocol_options */ - {"png", 32, 32, 96, 96, 8191, PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */ + {"png", 32, 32, 96, 96, 0, PURPLE_ICON_SCALE_SEND | PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */ jabber_list_icon, /* list_icon */ jabber_list_emblem, /* list_emblems */ jabber_status_text, /* status_text */
--- a/libpurple/protocols/msn/contact.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/msn/contact.c Sat Dec 22 23:18:08 2007 +0000 @@ -1289,16 +1289,16 @@ purple_debug_info("MSNCL", "Adding group %s with guid = %s to the userlist\n", state->new_group_name, guid); msn_group_new(session->userlist, guid, state->new_group_name); - g_free(guid); - if (state->action & MSN_ADD_BUDDY) { msn_userlist_add_buddy(session->userlist, state->who, state->new_group_name); } else if (state->action & MSN_MOVE_BUDDY) { msn_add_contact_to_group(session->contact, state, state->who, guid); + g_free(guid); return; } + g_free(guid); } else { purple_debug_info("MSNCL", "Adding group %s failed\n", state->new_group_name);
--- a/libpurple/protocols/msn/notification.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/msn/notification.c Sat Dec 22 23:18:08 2007 +0000 @@ -1579,6 +1579,7 @@ { xmlnode * root; gchar * buf; + int xmllen; g_return_if_fail(cmd->payload != NULL); @@ -1588,10 +1589,10 @@ return; } - buf = xmlnode_to_formatted_str(root, NULL); + buf = xmlnode_to_formatted_str(root, &xmllen); /* get the payload content */ - purple_debug_info("MSNP14","GCF command payload:\n%s\n",buf); + purple_debug_info("MSNP14","GCF command payload:\n%.*s\n", xmllen, buf); g_free(buf); xmlnode_free(root);
--- a/libpurple/protocols/msn/session.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/msn/session.c Sat Dec 22 23:18:08 2007 +0000 @@ -74,6 +74,7 @@ msn_userlist_destroy(session->userlist); + g_free(session->psm); g_free(session->passport_info.t); g_free(session->passport_info.p); g_free(session->passport_info.kv);
--- a/libpurple/protocols/msn/soap.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/msn/soap.c Sat Dec 22 23:18:08 2007 +0000 @@ -813,10 +813,8 @@ purple_debug_info("MSN SOAP", "Currently processing another SOAP request\n"); } else { purple_debug_info("MSN SOAP", "No requests left to dispatch\n"); +#endif } -#else - } -#endif }
--- a/libpurple/protocols/msn/state.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/msn/state.c Sat Dec 22 23:18:08 2007 +0000 @@ -238,13 +238,15 @@ media = create_media_string(presence); g_free(session->psm); session->psm = msn_build_psm(statusline_stripped, media, NULL); - g_free(statusline_stripped); payload = session->psm; purple_debug_misc("MSNP14","Sending UUX command with payload: %s\n",payload); trans = msn_transaction_new(cmdproc, "UUX", "%d", strlen(payload)); msn_transaction_set_payload(trans, payload, strlen(payload)); msn_cmdproc_send_trans(cmdproc, trans); + + g_free(statusline_stripped); + g_free(media); } void
--- a/libpurple/protocols/msn/user.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/msn/user.c Sat Dec 22 23:18:08 2007 +0000 @@ -83,6 +83,7 @@ g_free(user->media.artist); g_free(user->media.title); g_free(user->media.album); + g_free(user->statusline); g_free(user); }
--- a/libpurple/protocols/sametime/sametime.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/sametime/sametime.c Sat Dec 22 23:18:08 2007 +0000 @@ -3749,7 +3749,7 @@ client = purple_account_get_int(account, MW_KEY_CLIENT, mwLogin_BINARY); major = purple_account_get_int(account, MW_KEY_MAJOR, 0x001e); - minor = purple_account_get_int(account, MW_KEY_MINOR, 0x001d); + minor = purple_account_get_int(account, MW_KEY_MINOR, 0x196f); DEBUG_INFO("client id: 0x%04x\n", client); DEBUG_INFO("client major: 0x%04x\n", major);
--- a/libpurple/protocols/simple/sipmsg.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/simple/sipmsg.c Sat Dec 22 23:18:08 2007 +0000 @@ -45,7 +45,10 @@ line = g_strndup(msg, tmp - msg); smsg = sipmsg_parse_header(line); - smsg->body = g_strdup(tmp + 4); + if(smsg != NULL) + smsg->body = g_strdup(tmp + 4); + else + purple_debug_error("SIMPLE", "No header parsed from line: %s\n", line); g_free(line); return smsg;
--- a/libpurple/protocols/yahoo/util.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/yahoo/util.c Sat Dec 22 23:18:08 2007 +0000 @@ -715,12 +715,15 @@ } else if (((len - i) >= 4) && !strncmp(&src[i], ">", 4)) { g_string_append_c(dest, '>'); i += 3; - } else if (((len - i) >= 5) && !strncmp(&src[i], "&", 4)) { + } else if (((len - i) >= 5) && !strncmp(&src[i], "&", 5)) { g_string_append_c(dest, '&'); i += 4; - } else if (((len - i) >= 6) && !strncmp(&src[i], """, 4)) { + } else if (((len - i) >= 6) && !strncmp(&src[i], """, 6)) { g_string_append_c(dest, '"'); i += 5; + } else if (((len - i) >= 6) && !strncmp(&src[i], "'", 6)) { + g_string_append_c(dest, '\''); + i += 5; } else { g_string_append_c(dest, src[i]); }
--- a/libpurple/protocols/yahoo/yahoo.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo.c Sat Dec 22 23:18:08 2007 +0000 @@ -493,13 +493,14 @@ static void yahoo_process_cookie(struct yahoo_data *yd, char *c) { if (c[0] == 'Y') { - if (yd->cookie_y) - g_free(yd->cookie_y); + g_free(yd->cookie_y); yd->cookie_y = _getcookie(c); } else if (c[0] == 'T') { - if (yd->cookie_t) - g_free(yd->cookie_t); + g_free(yd->cookie_t); yd->cookie_t = _getcookie(c); + } else if (c[0] == 'C') { + g_free(yd->cookie_c); + yd->cookie_c = _getcookie(c); } else purple_debug_info("yahoo", "Ignoring unrecognized cookie '%c'\n", c[0]); } @@ -899,7 +900,6 @@ purple_util_chrreplace(m, '\r', '\n'); if (!strcmp(m, "<ding>")) { - PurpleBuddy *buddy; PurpleAccount *account; PurpleConversation *c; char *username; @@ -909,13 +909,8 @@ if (c == NULL) c = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, im->from); - if ((buddy = purple_find_buddy(account, im->from)) != NULL) - username = g_markup_escape_text(purple_buddy_get_alias(buddy), -1); - else - username = g_markup_escape_text(im->from, -1); - + username = g_markup_escape_text(im->from, -1); serv_got_attention(gc, username, YAHOO_BUZZ); - g_free(username); g_free(m); g_free(im); @@ -2439,6 +2434,12 @@ case YAHOO_SERVICE_AUDIBLE: yahoo_process_audible(gc, pkt); break; + case YAHOO_SERVICE_Y7_FILETRANSFER: + yahoo_process_y7_filetransfer(gc, pkt); + break; + case YAHOO_SERVICE_Y7_FILETRANSFER_INFO: + yahoo_process_y7_filetransfer_info(gc, pkt); + break; default: purple_debug(PURPLE_DEBUG_ERROR, "yahoo", "Unhandled service 0x%02x\n", pkt->service); @@ -3018,6 +3019,7 @@ g_free(yd->cookie_y); g_free(yd->cookie_t); + g_free(yd->cookie_c); if (yd->txhandler) purple_input_remove(yd->txhandler);
--- a/libpurple/protocols/yahoo/yahoo.h Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo.h Sat Dec 22 23:18:08 2007 +0000 @@ -134,6 +134,7 @@ gsize auth_written; char *cookie_y; char *cookie_t; + char *cookie_c; int session_id; gboolean jp; gboolean wm; /* connected w/ web messenger method */
--- a/libpurple/protocols/yahoo/yahoo_filexfer.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo_filexfer.c Sat Dec 22 23:18:08 2007 +0000 @@ -46,6 +46,10 @@ guint tx_handler; gchar *rxqueue; guint rxlen; + + gboolean y7; /* true for Y7 transfers (receive only for now) */ + gchar *token; + gchar *tid; }; static void yahoo_xfer_data_free(struct yahoo_xfer_data *xd) @@ -53,11 +57,70 @@ g_free(xd->host); g_free(xd->path); g_free(xd->txbuf); + g_free(xd->token); + g_free(xd->tid); if (xd->tx_handler) purple_input_remove(xd->tx_handler); g_free(xd); } + +static void yahoo_xfer_y7_request_next_file(PurpleXfer *xfer) +{ + struct yahoo_packet *pack; + struct yahoo_xfer_data *xd = xfer->data; + PurpleConnection *gc = xd->gc; + struct yahoo_data *yd = gc->proto_data; + + g_return_if_fail(xd->y7); + + pack = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER_ACCEPT, YAHOO_STATUS_AVAILABLE, 0); + yahoo_packet_hash(pack, "sssi", + 1, purple_connection_get_display_name(xd->gc), + 5, xfer->who, + 265, xd->tid, + 271, 1); + yahoo_packet_send_and_free(pack, yd); +} + +static void yahoo_xfer_y7_cancel_receive(PurpleXfer *xfer) +{ + struct yahoo_packet *pack; + struct yahoo_xfer_data *xd = xfer->data; + PurpleConnection *gc = xd->gc; + struct yahoo_data *yd = gc->proto_data; + + g_return_if_fail(xd->y7); + + pack = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER_ACCEPT, -1, 0); + yahoo_packet_hash(pack, "sssi", + 1, purple_connection_get_display_name(gc), + 5, xfer->who, + 265, xd->tid, + 66, -1); + yahoo_packet_send_and_free(pack, yd); +} + +static void yahoo_xfer_y7_accept_file(PurpleXfer *xfer) +{ + struct yahoo_packet *pack; + struct yahoo_xfer_data *xd = xfer->data; + PurpleConnection *gc = xd->gc; + struct yahoo_data *yd = gc->proto_data; + + g_return_if_fail(xd->y7); + + pack = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER_ACCEPT, YAHOO_STATUS_AVAILABLE, 0); + yahoo_packet_hash(pack, "ssssis", + 1, purple_connection_get_display_name(gc), + 5, xfer->who, /* XXX this needs an accessor */ + 265, xd->tid, + 27, purple_xfer_get_filename(xfer), /* XXX this might be of incorrect encoding */ + 249, 3, + 251, xd->token); + yahoo_packet_send_and_free(pack, yd); +} + static void yahoo_receivefile_send_cb(gpointer data, gint source, PurpleInputCondition condition) { PurpleXfer *xfer; @@ -97,6 +160,7 @@ { PurpleXfer *xfer; struct yahoo_xfer_data *xd; + struct yahoo_data *yd; purple_debug(PURPLE_DEBUG_INFO, "yahoo", "AAA - in yahoo_receivefile_connected\n"); @@ -112,11 +176,22 @@ } xfer->fd = source; + yd = xd->gc->proto_data; /* The first time we get here, assemble the tx buffer */ if (xd->txbuflen == 0) { - xd->txbuf = g_strdup_printf("GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n", - xd->path, xd->host); + if (!xd->y7) + xd->txbuf = g_strdup_printf("GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n", + xd->path, xd->host); + else + xd->txbuf = g_strdup_printf("GET /%s HTTP/1.0\r\n" + "Connection: close\r\n" + "Accept: */*\r\n" + "Host: %s\r\n" + "Cookie: Y=%s; T=%s\r\n" + "\r\n", + xd->path, xd->host, yd->cookie_y, yd->cookie_t); + purple_debug(PURPLE_DEBUG_INFO, "yahoo_filexfer", "HTTP request: [%s]\n", xd->txbuf); xd->txbuflen = strlen(xd->txbuf); xd->txbuf_written = 0; } @@ -281,6 +356,9 @@ } } } else { + if (xfer_data->y7) + yahoo_xfer_y7_accept_file(xfer); + xfer->fd = -1; if (purple_proxy_connect(NULL, account, xfer_data->host, xfer_data->port, yahoo_receivefile_connected, xfer) == NULL) { @@ -340,6 +418,8 @@ if ((purple_xfer_get_size(xfer) > 0) && (purple_xfer_get_bytes_sent(xfer) >= purple_xfer_get_size(xfer))) { purple_xfer_set_completed(xfer, TRUE); + if (xd->y7) + yahoo_xfer_y7_request_next_file(xfer); return 0; } else return -1; @@ -430,11 +510,24 @@ xfer_data = xfer->data; - if (xfer_data) + if (xfer_data) { + if (xfer_data->y7) + yahoo_xfer_y7_cancel_receive(xfer); yahoo_xfer_data_free(xfer_data); + } xfer->data = NULL; } +static void yahoo_xfer_request_denied(PurpleXfer *xfer) +{ + struct yahoo_xfer_data *xfer_data; + + xfer_data = xfer->data; + + if (xfer_data->y7) + yahoo_xfer_y7_cancel_receive(xfer); +} + void yahoo_process_p2pfilexfer(PurpleConnection *gc, struct yahoo_packet *pkt) { GSList *l = pkt->hash; @@ -628,6 +721,165 @@ } } +void yahoo_process_y7_filetransfer(PurpleConnection *gc, struct yahoo_packet *pkt) +{ + struct yahoo_data *yd = gc->proto_data; + char *who = NULL, *name = NULL; + int ttype = 0; + char *tid = NULL; + GSList *l = pkt->hash; + + while (l) { + struct yahoo_pair *pair = l->data; + + switch (pair->key) { + case 4: + /* them */ + who = pair->value; + break; + case 5: + /* us */ + name = pair->value; + break; + case 222: + /* 1=send, 2=cancel, 3=accept, 4=reject */ + if(pair->value) + ttype = atoi(pair->value); + break; + case 265: + /* transfer ID */ + tid = pair->value; + break; + case 266: + /* number of files */ + break; + case 27: + /* filename */ + break; + case 28: + /* filesize */ + break; + } + + l = l->next; + } + if (ttype == 1 && tid) { + /* We auto-accept all offers here, and ask the user about each individual + * file in yahoo_process_y7_filetransfer_info. This works fine for receiving + * a single file; when receiving multiple canceling one in the middle + * will also cancel the rest of them. + * Maybe TODO: UI and API allowing transfer of multiple files as a package. */ + struct yahoo_packet *pack; + pack = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER, YAHOO_STATUS_AVAILABLE, 0); + yahoo_packet_hash(pack, "sssi", 1, name, 5, who, 265, tid, 222, 3); + yahoo_packet_send_and_free(pack, yd); + } +} + +void yahoo_process_y7_filetransfer_info(PurpleConnection *gc, struct yahoo_packet *pkt) +{ + struct yahoo_data *yd = gc->proto_data; + char *who = NULL, *name = NULL; + int medium = 0; + char *tid = NULL, *server_host = NULL, *server_token = NULL, *filename = NULL; + GSList *l = pkt->hash; + struct yahoo_packet *pack; + PurpleXfer *xfer; + struct yahoo_xfer_data *xfer_data; + + while (l) { + struct yahoo_pair *pair = l->data; + + switch (pair->key) { + case 4: + /* them */ + who = pair->value; + break; + case 5: + /* us */ + name = pair->value; + break; + case 249: + /* 1=p2p, 3=reflection server */ + if(pair->value) + medium = atoi(pair->value); + break; + case 265: + /* transfer ID */ + tid = pair->value; + break; + case 27: + filename = pair->value; + break; + case 250: + server_host = pair->value; + break; + case 251: + server_token = pair->value; + break; + } + + l = l->next; + } + if (medium == 1) { + /* reject P2P transfers */ + pack = yahoo_packet_new(YAHOO_SERVICE_Y7_FILETRANSFER_ACCEPT, YAHOO_STATUS_AVAILABLE, 0); + yahoo_packet_hash(pack, "sssi", 1, name, 5, who, 265, tid, 66, -3); + yahoo_packet_send_and_free(pack, yd); + return; + } + + if (medium != 3) { + purple_debug_error("yahoo", "Unexpected medium %d.\n", medium); + /* weird */ + return; + } + + /* Setup the Yahoo-specific file transfer data */ + xfer_data = g_new0(struct yahoo_xfer_data, 1); + xfer_data->gc = gc; + xfer_data->host = g_strdup(server_host); + xfer_data->token = g_strdup(server_token); + xfer_data->tid = g_strdup(tid); + xfer_data->port = 80; + xfer_data->y7 = TRUE; + + /* TODO: full urlencode here */ + server_token = purple_strreplace(server_token, "\002", "%02"); + xfer_data->path = g_strdup_printf("relay?token=%s&sender=%s&recver=%s", + server_token, who, name); + g_free(server_token); + + purple_debug_misc("yahoo_filexfer", "Host is %s, port is %d, path is %s.\n", + xfer_data->host, xfer_data->port, xfer_data->path); + + /* Build the file transfer handle. */ + xfer = purple_xfer_new(gc->account, PURPLE_XFER_RECEIVE, who); + xfer->data = xfer_data; + + /* Set the info about the incoming file. */ + { + char *utf8_filename = yahoo_string_decode(gc, filename, TRUE); + purple_xfer_set_filename(xfer, utf8_filename); + g_free(utf8_filename); + } + + /* purple_xfer_set_size(xfer, filesize); */ + + /* Setup our I/O op functions */ + purple_xfer_set_init_fnc(xfer, yahoo_xfer_init); + purple_xfer_set_start_fnc(xfer, yahoo_xfer_start); + purple_xfer_set_end_fnc(xfer, yahoo_xfer_end); + purple_xfer_set_cancel_send_fnc(xfer, yahoo_xfer_cancel_send); + purple_xfer_set_cancel_recv_fnc(xfer, yahoo_xfer_cancel_recv); + purple_xfer_set_read_fnc(xfer, yahoo_xfer_read); + purple_xfer_set_write_fnc(xfer, yahoo_xfer_write); + purple_xfer_set_request_denied_fnc(xfer, yahoo_xfer_request_denied); + + /* Now perform the request */ + purple_xfer_request(xfer); +} + PurpleXfer *yahoo_new_xfer(PurpleConnection *gc, const char *who) { PurpleXfer *xfer;
--- a/libpurple/protocols/yahoo/yahoo_filexfer.h Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo_filexfer.h Sat Dec 22 23:18:08 2007 +0000 @@ -30,6 +30,16 @@ void yahoo_process_p2pfilexfer( PurpleConnection *gc, struct yahoo_packet *pkt ); /** + * Process ymsg version 7 file receive invites. + */ +void yahoo_process_y7_filetransfer(PurpleConnection *gc, struct yahoo_packet *pkt); + +/** + * Process ymsg version 7 file receive connection setups. + */ +void yahoo_process_y7_filetransfer_info(PurpleConnection *gc, struct yahoo_packet *pkt); + +/** * Process ymsg file receive invites. */ void yahoo_process_filetransfer(PurpleConnection *gc, struct yahoo_packet *pkt);
--- a/libpurple/protocols/yahoo/yahoo_packet.h Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo_packet.h Sat Dec 22 23:18:08 2007 +0000 @@ -99,9 +99,12 @@ YAHOO_SERVICE_VERIFY_ID_EXISTS = 0xc8, YAHOO_SERVICE_AUDIBLE = 0xd0, YAHOO_SERVICE_AUTH_REQ_15 = 0xd6, + YAHOO_SERVICE_Y7_FILETRANSFER = 0xdc, + YAHOO_SERVICE_Y7_FILETRANSFER_INFO = 0xdd, + YAHOO_SERVICE_Y7_FILETRANSFER_ACCEPT = 0xde, YAHOO_SERVICE_CHGRP_15 = 0xe7, YAHOO_SERVICE_STATUS_15 = 0xf0, - YAHOO_SERVICE_LIST_15 = 0Xf1, + YAHOO_SERVICE_LIST_15 = 0xf1, YAHOO_SERVICE_WEBLOGIN = 0x0226, YAHOO_SERVICE_SMS_MSG = 0x02ea };
--- a/libpurple/purple.h.in Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/purple.h.in Sat Dec 22 23:18:08 2007 +0000 @@ -2,10 +2,11 @@ * @file purple.h Header files and defines * This file contains all the necessary preprocessor directives to include * libpurple's headers and other preprocessor directives required for plugins - * or UIs to build. Inlcuding this file eliminates the need to directly + * or UIs to build. Including this file eliminates the need to directly * include any other libpurple files. * * @ingroup core libpurple + * @since 2.3.0 */ /* purple
--- a/libpurple/request.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/request.c Sat Dec 22 23:18:08 2007 +0000 @@ -1208,6 +1208,7 @@ g_return_val_if_fail(ok_text != NULL, NULL); g_return_val_if_fail(ok_cb != NULL, NULL); + g_return_val_if_fail(cancel_text != NULL, NULL); ops = purple_request_get_ui_ops(); @@ -1296,6 +1297,7 @@ g_return_val_if_fail(fields != NULL, NULL); g_return_val_if_fail(ok_text != NULL, NULL); g_return_val_if_fail(ok_cb != NULL, NULL); + g_return_val_if_fail(cancel_text != NULL, NULL); ops = purple_request_get_ui_ops();
--- a/libpurple/request.h Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/request.h Sat Dec 22 23:18:08 2007 +0000 @@ -183,39 +183,53 @@ */ typedef struct { + /** @see purple_request_input(). */ void *(*request_input)(const char *title, const char *primary, - const char *secondary, const char *default_value, - gboolean multiline, gboolean masked, gchar *hint, - const char *ok_text, GCallback ok_cb, - const char *cancel_text, GCallback cancel_cb, - PurpleAccount *account, const char *who, PurpleConversation *conv, - void *user_data); + const char *secondary, const char *default_value, + gboolean multiline, gboolean masked, gchar *hint, + const char *ok_text, GCallback ok_cb, + const char *cancel_text, GCallback cancel_cb, + PurpleAccount *account, const char *who, + PurpleConversation *conv, void *user_data); + + /** @see purple_request_choice_varg(). */ void *(*request_choice)(const char *title, const char *primary, - const char *secondary, int default_value, - const char *ok_text, GCallback ok_cb, - const char *cancel_text, GCallback cancel_cb, - PurpleAccount *account, const char *who, PurpleConversation *conv, - void *user_data, va_list choices); + const char *secondary, int default_value, + const char *ok_text, GCallback ok_cb, + const char *cancel_text, GCallback cancel_cb, + PurpleAccount *account, const char *who, + PurpleConversation *conv, void *user_data, + va_list choices); + + /** @see purple_request_action_varg(). */ void *(*request_action)(const char *title, const char *primary, - const char *secondary, int default_action, - PurpleAccount *account, const char *who, PurpleConversation *conv, - void *user_data, size_t action_count, - va_list actions); + const char *secondary, int default_action, + PurpleAccount *account, const char *who, + PurpleConversation *conv, void *user_data, + size_t action_count, va_list actions); + + /** @see purple_request_fields(). */ void *(*request_fields)(const char *title, const char *primary, - const char *secondary, PurpleRequestFields *fields, - const char *ok_text, GCallback ok_cb, - const char *cancel_text, GCallback cancel_cb, - PurpleAccount *account, const char *who, PurpleConversation *conv, - void *user_data); + const char *secondary, PurpleRequestFields *fields, + const char *ok_text, GCallback ok_cb, + const char *cancel_text, GCallback cancel_cb, + PurpleAccount *account, const char *who, + PurpleConversation *conv, void *user_data); + + /** @see purple_request_file(). */ void *(*request_file)(const char *title, const char *filename, - gboolean savedialog, GCallback ok_cb, GCallback cancel_cb, - PurpleAccount *account, const char *who, PurpleConversation *conv, - void *user_data); + gboolean savedialog, GCallback ok_cb, + GCallback cancel_cb, PurpleAccount *account, + const char *who, PurpleConversation *conv, + void *user_data); + void (*close_request)(PurpleRequestType type, void *ui_handle); + + /** @see purple_request_folder(). */ void *(*request_folder)(const char *title, const char *dirname, - GCallback ok_cb, GCallback cancel_cb, - PurpleAccount *account, const char *who, PurpleConversation *conv, - void *user_data); + GCallback ok_cb, GCallback cancel_cb, + PurpleAccount *account, const char *who, + PurpleConversation *conv, void *user_data); void (*_purple_reserved1)(void); void (*_purple_reserved2)(void); @@ -1159,198 +1173,247 @@ * Prompts the user for text input. * * @param handle The plugin or connection handle. For some - * things this is EXTREMELY important. The - * handle is used to programmatically close - * the request dialog when it is no longer - * needed. For PRPLs this is often a pointer - * to the PurpleConnection instance. For plugins - * this should be a similar, unique memory - * location. This value is important because - * it allows a request to be closed, say, when - * you sign offline. If the request is NOT - * closed it is VERY likely to cause a crash - * whenever the callback handler functions are - * triggered. - * @param title The title of the message. - * @param primary The main point of the message. - * @param secondary The secondary information. + * things this is <em>extremely</em> important. The + * handle is used to programmatically close the request + * dialog when it is no longer needed. For PRPLs this + * is often a pointer to the #PurpleConnection + * instance. For plugins this should be a similar, + * unique memory location. This value is important + * because it allows a request to be closed with + * purple_request_close_with_handle() when, for + * example, you sign offline. If the request is + * <em>not</em> closed it is <strong>very</strong> + * likely to cause a crash whenever the callback + * handler functions are triggered. + * @param title The title of the message, or @c NULL if it should have + * no title. + * @param primary The main point of the message, or @c NULL if you're + * feeling enigmatic. + * @param secondary Secondary information, or @c NULL if there is none. * @param default_value The default value. - * @param multiline TRUE if the inputted text can span multiple lines. - * @param masked TRUE if the inputted text should be masked in some way. + * @param multiline @c TRUE if the inputted text can span multiple lines. + * @param masked @c TRUE if the inputted text should be masked in some + * way (such as by displaying characters as stars). This + * might be because the input is some kind of password. * @param hint Optionally suggest how the input box should appear. - * Use "html," for example, to allow the user to enter + * Use "html", for example, to allow the user to enter * HTML. - * @param ok_text The text for the @c OK button. - * @param ok_cb The callback for the @c OK button. - * @param cancel_text The text for the @c Cancel button. - * @param cancel_cb The callback for the @c Cancel button. - * @param account The PurpleAccount associated with this request, or NULL if none is - * @param who The username of the buddy assocaited with this request, or NULL if none is - * @param conv The PurpleConversation associated with this request, or NULL if none is + * @param ok_text The text for the @c OK button, which may not be @c NULL. + * @param ok_cb The callback for the @c OK button, which may not be @c + * NULL. + * @param cancel_text The text for the @c Cancel button, which may not be @c + * NULL. + * @param cancel_cb The callback for the @c Cancel button, which may be + * @c NULL. + * @param account The #PurpleAccount associated with this request, or @c + * NULL if none is. + * @param who The username of the buddy associated with this request, + * or @c NULL if none is. + * @param conv The #PurpleConversation associated with this request, or + * @c NULL if none is. * @param user_data The data to pass to the callback. * * @return A UI-specific handle. */ -void *purple_request_input(void *handle, const char *title, - const char *primary, const char *secondary, - const char *default_value, - gboolean multiline, gboolean masked, gchar *hint, - 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 *purple_request_input(void *handle, const char *title, const char *primary, + const char *secondary, const char *default_value, gboolean multiline, + gboolean masked, gchar *hint, + const char *ok_text, GCallback ok_cb, + const char *cancel_text, GCallback cancel_cb, + PurpleAccount *account, const char *who, PurpleConversation *conv, + void *user_data); /** * Prompts the user for multiple-choice input. * - * @param handle The plugin or connection handle. For some - * things this is EXTREMELY important. See - * the comments on purple_request_input. - * @param title The title of the message. - * @param primary The main point of the message. - * @param secondary The secondary information. - * @param default_value The default value. - * @param ok_text The text for the @c OK button. - * @param ok_cb The callback for the @c OK button. - * @param cancel_text The text for the @c Cancel button. - * @param cancel_cb The callback for the @c Cancel button. - * @param account The PurpleAccount associated with this request, or NULL if none is - * @param who The username of the buddy assocaited with this request, or NULL if none is - * @param conv The PurpleConversation associated with this request, or NULL if none is + * @param handle The plugin or connection handle. For some things this + * is <em>extremely</em> important. See the comments on + * purple_request_input(). + * @param title The title of the message, or @c NULL if it should have + * no title. + * @param primary The main point of the message, or @c NULL if you're + * feeling enigmatic. + * @param secondary Secondary information, or @c NULL if there is none. + * @param default_value The default choice; this should be one of the values + * listed in the varargs. + * @param ok_text The text for the @c OK button, which may not be @c NULL. + * @param ok_cb The callback for the @c OK button, which may not be @c + * NULL. + * @param cancel_text The text for the @c Cancel button, which may not be @c + * NULL. + * @param cancel_cb The callback for the @c Cancel button, or @c NULL to + * do nothing. + * @param account The #PurpleAccount associated with this request, or @c + * NULL if none is. + * @param who The username of the buddy associated with this request, + * or @c NULL if none is. + * @param conv The #PurpleConversation associated with this request, or + * @c NULL if none is. * @param user_data The data to pass to the callback. - * @param ... The choices. This argument list should be - * terminated with a NULL parameter. + * @param ... The choices, which should be pairs of <tt>char *</tt> + * descriptions and <tt>int</tt> values, terminated with a + * @c NULL parameter. * * @return A UI-specific handle. */ -void *purple_request_choice(void *handle, const char *title, - const char *primary, const char *secondary, - int default_value, - const char *ok_text, GCallback ok_cb, - const char *cancel_text, GCallback cancel_cb, - PurpleAccount *account, const char *who, PurpleConversation *conv, - void *user_data, ...) G_GNUC_NULL_TERMINATED; +void *purple_request_choice(void *handle, const char *title, const char *primary, + const char *secondary, int default_value, + const char *ok_text, GCallback ok_cb, + const char *cancel_text, GCallback cancel_cb, + PurpleAccount *account, const char *who, PurpleConversation *conv, + void *user_data, ...) G_GNUC_NULL_TERMINATED; /** * Prompts the user for multiple-choice input. * - * @param handle The plugin or connection handle. For some - * things this is EXTREMELY important. See - * the comments on purple_request_input. - * @param title The title of the message. - * @param primary The main point of the message. - * @param secondary The secondary information. - * @param default_value The default value. - * @param ok_text The text for the @c OK button. - * @param ok_cb The callback for the @c OK button. - * @param cancel_text The text for the @c Cancel button. - * @param cancel_cb The callback for the @c Cancel button. - * @param account The PurpleAccount associated with this request, or NULL if none is - * @param who The username of the buddy assocaited with this request, or NULL if none is - * @param conv The PurpleConversation associated with this request, or NULL if none is + * @param handle The plugin or connection handle. For some things this + * is <em>extremely</em> important. See the comments on + * purple_request_input(). + * @param title The title of the message, or @c NULL if it should have + * no title. + * @param primary The main point of the message, or @c NULL if you're + * feeling enigmatic. + * @param secondary Secondary information, or @c NULL if there is none. + * @param default_value The default choice; this should be one of the values + * listed in the varargs. + * @param ok_text The text for the @c OK button, which may not be @c NULL. + * @param ok_cb The callback for the @c OK button, which may not be @c + * NULL. + * @param cancel_text The text for the @c Cancel button, which may not be @c + * NULL. + * @param cancel_cb The callback for the @c Cancel button, or @c NULL to do + * nothing. + * @param account The #PurpleAccount associated with this request, or @c + * NULL if none is + * @param who The username of the buddy associated with this request, + * or @c NULL if none is + * @param conv The #PurpleConversation associated with this request, or + * @c NULL if none is * @param user_data The data to pass to the callback. - * @param choices The choices. This argument list should be - * terminated with a @c NULL parameter. + * @param choices The choices, which should be pairs of <tt>char *</tt> + * descriptions and <tt>int</tt> values, terminated with a + * @c NULL parameter. * * @return A UI-specific handle. */ void *purple_request_choice_varg(void *handle, const char *title, - const char *primary, const char *secondary, - int default_value, - const char *ok_text, GCallback ok_cb, - const char *cancel_text, GCallback cancel_cb, - PurpleAccount *account, const char *who, PurpleConversation *conv, - void *user_data, va_list choices); + const char *primary, const char *secondary, int default_value, + const char *ok_text, GCallback ok_cb, + const char *cancel_text, GCallback cancel_cb, + PurpleAccount *account, const char *who, PurpleConversation *conv, + void *user_data, va_list choices); /** * Prompts the user for an action. * * This is often represented as a dialog with a button for each action. * - * @param handle The plugin or connection handle. For some - * things this is EXTREMELY important. See - * the comments on purple_request_input. - * @param title The title of the message. - * @param primary The main point of the message. - * @param secondary The secondary information. - * @param default_action The default value. - * @param account The PurpleAccount associated with this request, or NULL if none is - * @param who The username of the buddy assocaited with this request, or NULL if none is - * @param conv The PurpleConversation associated with this request, or NULL if none is + * @param handle The plugin or connection handle. For some things this + * is <em>extremely</em> important. See the comments on + * purple_request_input(). + * @param title The title of the message, or @c NULL if it should have + * no title. + * @param primary The main point of the message, or @c NULL if you're + * feeling enigmatic. + * @param secondary Secondary information, or @c NULL if there is none. + * @param default_action The default action, zero-indexed; if the third action + * supplied should be the default, supply <tt>2</tt>. + * @param account The #PurpleAccount associated with this request, or @c + * NULL if none is. + * @param who The username of the buddy associated with this request, + * or @c NULL if none is. + * @param conv The #PurpleConversation associated with this request, or + * @c NULL if none is. * @param user_data The data to pass to the callback. * @param action_count The number of actions. * @param ... A list of actions. These are pairs of * arguments. The first of each pair is the - * string that appears on the button. It should + * <tt>char *</tt> that appears on the button. It should * have an underscore before the letter you want * to use as the accelerator key for the button. - * The second of each pair is the callback + * The second of each pair is the <tt>GCallback</tt> * function to use when the button is clicked. * * @return A UI-specific handle. */ -void *purple_request_action(void *handle, const char *title, - const char *primary, const char *secondary, - int default_action, - PurpleAccount *account, const char *who, PurpleConversation *conv, - void *user_data, size_t action_count, ...); +void *purple_request_action(void *handle, const char *title, const char *primary, + const char *secondary, int default_action, PurpleAccount *account, + const char *who, PurpleConversation *conv, void *user_data, + size_t action_count, ...); /** * Prompts the user for an action. * * This is often represented as a dialog with a button for each action. * - * @param handle The plugin or connection handle. For some - * things this is EXTREMELY important. See - * the comments on purple_request_input. - * @param title The title of the message. - * @param primary The main point of the message. - * @param secondary The secondary information. - * @param default_action The default value. - * @param account The PurpleAccount associated with this request, or NULL if none is - * @param who The username of the buddy assocaited with this request, or NULL if none is - * @param conv The PurpleConversation associated with this request, or NULL if none is + * @param handle The plugin or connection handle. For some things this + * is <em>extremely</em> important. See the comments on + * purple_request_input(). + * @param title The title of the message, or @c NULL if it should have + * no title. + * @param primary The main point of the message, or @c NULL if you're + * feeling enigmatic. + * @param secondary Secondary information, or @c NULL if there is none. + * @param default_action The default action, zero-indexed; if the third action + * supplied should be the default, supply <tt>2</tt>. + * @param account The #PurpleAccount associated with this request, or @c + * NULL if none is. + * @param who The username of the buddy associated with this request, + * or @c NULL if none is. + * @param conv The #PurpleConversation associated with this request, or + * @c NULL if none is. * @param user_data The data to pass to the callback. * @param action_count The number of actions. - * @param actions A list of actions and callbacks. + * @param actions A list of actions. These are pairs of + * arguments. The first of each pair is the + * <tt>char *</tt> that appears on the button. It should + * have an underscore before the letter you want + * to use as the accelerator key for the button. + * The second of each pair is the <tt>GCallback</tt> + * function to use when the button is clicked. * * @return A UI-specific handle. */ void *purple_request_action_varg(void *handle, const char *title, - const char *primary, const char *secondary, - int default_action, - PurpleAccount *account, const char *who, PurpleConversation *conv, - void *user_data, size_t action_count, - va_list actions); + const char *primary, const char *secondary, int default_action, + PurpleAccount *account, const char *who, PurpleConversation *conv, + void *user_data, size_t action_count, va_list actions); /** * Displays groups of fields for the user to fill in. * - * @param handle The plugin or connection handle. For some - * things this is EXTREMELY important. See - * the comments on purple_request_input. - * @param title The title of the message. - * @param primary The main point of the message. - * @param secondary The secondary information. + * @param handle The plugin or connection handle. For some things this + * is <em>extremely</em> important. See the comments on + * purple_request_input(). + * @param title The title of the message, or @c NULL if it should have + * no title. + * @param primary The main point of the message, or @c NULL if you're + * feeling enigmatic. + * @param secondary Secondary information, or @c NULL if there is none. * @param fields The list of fields. - * @param ok_text The text for the @c OK button. - * @param ok_cb The callback for the @c OK button. - * @param cancel_text The text for the @c Cancel button. - * @param cancel_cb The callback for the @c Cancel button. - * @param account The PurpleAccount associated with this request, or NULL if none is - * @param who The username of the buddy associated with this request, or NULL if none is - * @param conv The PurpleConversation associated with this request, or NULL if none is + * @param ok_text The text for the @c OK button, which may not be @c NULL. + * @param ok_cb The callback for the @c OK button, which may not be @c + * NULL. + * @param cancel_text The text for the @c Cancel button, which may not be @c + * NULL. + * @param cancel_cb The callback for the @c Cancel button, which may be + * @c NULL. + * @param account The #PurpleAccount associated with this request, or @c + * NULL if none is + * @param who The username of the buddy associated with this request, + * or @c NULL if none is + * @param conv The #PurpleConversation associated with this request, or + * @c NULL if none is * @param user_data The data to pass to the callback. * * @return A UI-specific handle. */ -void *purple_request_fields(void *handle, const char *title, - const char *primary, const char *secondary, - PurpleRequestFields *fields, - 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 *purple_request_fields(void *handle, const char *title, const char *primary, + const char *secondary, PurpleRequestFields *fields, + const char *ok_text, GCallback ok_cb, + const char *cancel_text, GCallback cancel_cb, + PurpleAccount *account, const char *who, PurpleConversation *conv, + void *user_data); /** * Closes a request. @@ -1363,7 +1426,10 @@ /** * Closes all requests registered with the specified handle. * - * @param handle The handle. + * @param handle The handle, as supplied as the @a handle parameter to one of the + * <tt>purple_request_*</tt> functions. + * + * @see purple_request_input(). */ void purple_request_close_with_handle(void *handle); @@ -1401,50 +1467,57 @@ * Displays a file selector request dialog. Returns the selected filename to * the callback. Can be used for either opening a file or saving a file. * - * @param handle The plugin or connection handle. For some - * things this is EXTREMELY important. See - * the comments on purple_request_input. - * @param title The title for the dialog (may be @c NULL) + * @param handle The plugin or connection handle. For some things this + * is <em>extremely</em> important. See the comments on + * purple_request_input(). + * @param title The title of the message, or @c NULL if it should have + * no title. * @param filename The default filename (may be @c NULL) * @param savedialog True if this dialog is being used to save a file. * False if it is being used to open a file. * @param ok_cb The callback for the @c OK button. - * @param cancel_cb The callback for the @c Cancel button. - * @param account The PurpleAccount associated with this request, or NULL if none is - * @param who The username of the buddy assocaited with this request, or NULL if none is - * @param conv The PurpleConversation associated with this request, or NULL if none is + * @param cancel_cb The callback for the @c Cancel button, which may be @c NULL. + * @param account The #PurpleAccount associated with this request, or @c + * NULL if none is + * @param who The username of the buddy associated with this request, + * or @c NULL if none is + * @param conv The #PurpleConversation associated with this request, or + * @c NULL if none is * @param user_data The data to pass to the callback. * * @return A UI-specific handle. */ void *purple_request_file(void *handle, const char *title, const char *filename, - gboolean savedialog, - GCallback ok_cb, GCallback cancel_cb, - PurpleAccount *account, const char *who, PurpleConversation *conv, - void *user_data); + gboolean savedialog, GCallback ok_cb, GCallback cancel_cb, + PurpleAccount *account, const char *who, PurpleConversation *conv, + void *user_data); /** * Displays a folder select dialog. Returns the selected filename to * the callback. * - * @param handle The plugin or connection handle. For some - * things this is EXTREMELY important. See - * the comments on purple_request_input. - * @param title The title for the dialog (may be @c NULL) + * @param handle The plugin or connection handle. For some things this + * is <em>extremely</em> important. See the comments on + * purple_request_input(). + * @param title The title of the message, or @c NULL if it should have + * no title. * @param dirname The default directory name (may be @c NULL) * @param ok_cb The callback for the @c OK button. - * @param cancel_cb The callback for the @c Cancel button. - * @param account The PurpleAccount associated with this request, or NULL if none is - * @param who The username of the buddy assocaited with this request, or NULL if none is - * @param conv The PurpleConversation associated with this request, or NULL if none is + * @param cancel_cb The callback for the @c Cancel button, which may be @c NULL. + * @param account The #PurpleAccount associated with this request, or @c + * NULL if none is + * @param who The username of the buddy associated with this request, + * or @c NULL if none is + * @param conv The #PurpleConversation associated with this request, or + * @c NULL if none is * @param user_data The data to pass to the callback. * * @return A UI-specific handle. */ void *purple_request_folder(void *handle, const char *title, const char *dirname, - GCallback ok_cb, GCallback cancel_cb, - PurpleAccount *account, const char *who, PurpleConversation *conv, - void *user_data); + GCallback ok_cb, GCallback cancel_cb, + PurpleAccount *account, const char *who, PurpleConversation *conv, + void *user_data); /*@}*/
--- a/libpurple/savedstatuses.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/savedstatuses.c Sat Dec 22 23:18:08 2007 +0000 @@ -243,7 +243,9 @@ child = xmlnode_new_child(node, "account"); xmlnode_set_attrib(child, "protocol", purple_account_get_protocol_id(substatus->account)); - xmlnode_insert_data(child, purple_account_get_username(substatus->account), -1); + xmlnode_insert_data(child, + purple_normalize(substatus->account, + purple_account_get_username(substatus->account)), -1); child = xmlnode_new_child(node, "state"); xmlnode_insert_data(child, purple_status_type_get_id(substatus->type), -1);
--- a/libpurple/status.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/status.c Sat Dec 22 23:18:08 2007 +0000 @@ -607,13 +607,10 @@ if (old_status != NULL) { - tmp = g_strdup_printf(_("%s changed status from %s to %s"), buddy_alias, + tmp = g_strdup_printf(_("%s (%s) changed status from %s to %s"), buddy_alias, buddy->name, purple_status_get_name(old_status), purple_status_get_name(new_status)); - logtmp = g_strdup_printf(_("%s (%s) changed status from %s to %s"), buddy_alias, buddy->name, - purple_status_get_name(old_status), - purple_status_get_name(new_status)); - + logtmp = g_markup_escape_text(tmp, -1); } else { @@ -621,18 +618,15 @@ if (purple_status_is_active(new_status)) { - tmp = g_strdup_printf(_("%s is now %s"), buddy_alias, + tmp = g_strdup_printf(_("%s (%s) is now %s"), buddy_alias, buddy->name, purple_status_get_name(new_status)); - logtmp = g_strdup_printf(_("%s (%s) is now %s"), buddy_alias, buddy->name, - purple_status_get_name(new_status)); - + logtmp = g_markup_escape_text(tmp, -1); } else { - tmp = g_strdup_printf(_("%s is no longer %s"), buddy_alias, + tmp = g_strdup_printf(_("%s (%s) is no longer %s"), buddy_alias, buddy->name, purple_status_get_name(new_status)); - logtmp = g_strdup_printf(_("%s (%s) is no longer %s"), buddy_alias, buddy->name, - purple_status_get_name(new_status)); + logtmp = g_markup_escape_text(tmp, -1); } } @@ -1244,12 +1238,15 @@ if (log != NULL) { - char *tmp = g_strdup_printf(_("%s became idle"), + char *tmp, *tmp2; + tmp = g_strdup_printf(_("%s became idle"), purple_buddy_get_alias(buddy)); + tmp2 = g_markup_escape_text(tmp, -1); + g_free(tmp); purple_log_write(log, PURPLE_MESSAGE_SYSTEM, - purple_buddy_get_alias(buddy), current_time, tmp); - g_free(tmp); + purple_buddy_get_alias(buddy), current_time, tmp2); + g_free(tmp2); } } } @@ -1261,12 +1258,15 @@ if (log != NULL) { - char *tmp = g_strdup_printf(_("%s became unidle"), + char *tmp, *tmp2; + tmp = g_strdup_printf(_("%s became unidle"), purple_buddy_get_alias(buddy)); + tmp2 = g_markup_escape_text(tmp, -1); + g_free(tmp); purple_log_write(log, PURPLE_MESSAGE_SYSTEM, - purple_buddy_get_alias(buddy), current_time, tmp); - g_free(tmp); + purple_buddy_get_alias(buddy), current_time, tmp2); + g_free(tmp2); } } } @@ -1321,13 +1321,15 @@ if (log != NULL) { - char *msg; + char *msg, *tmp; if (idle) - msg = g_strdup_printf(_("+++ %s became idle"), purple_account_get_username(account)); + tmp = g_strdup_printf(_("+++ %s became idle"), purple_account_get_username(account)); else - msg = g_strdup_printf(_("+++ %s became unidle"), purple_account_get_username(account)); + tmp = g_strdup_printf(_("+++ %s became unidle"), purple_account_get_username(account)); + msg = g_markup_escape_text(tmp, -1); + g_free(tmp); purple_log_write(log, PURPLE_MESSAGE_SYSTEM, purple_account_get_username(account), (idle ? idle_time : current_time), msg);
--- a/libpurple/tests/check_libpurple.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/tests/check_libpurple.c Sat Dec 22 23:18:08 2007 +0000 @@ -1,11 +1,12 @@ #include <glib.h> #include <stdlib.h> +#include "tests.h" + #include "../core.h" #include "../eventloop.h" #include "../util.h" -#include "tests.h" /****************************************************************************** * libpurple goodies
--- a/libpurple/tests/test_cipher.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/tests/test_cipher.c Sat Dec 22 23:18:08 2007 +0000 @@ -190,6 +190,511 @@ END_TEST /****************************************************************************** + * DES Tests + *****************************************************************************/ +#define DES_TEST(in, keyz, out, len) { \ + PurpleCipher *cipher = NULL; \ + PurpleCipherContext *context = NULL; \ + guchar answer[len+1]; \ + gint ret = 0; \ + guchar decrypt[len+1] = in; \ + guchar key[8+1] = keyz;\ + guchar encrypt[len+1] = out;\ + size_t outlen; \ + \ + cipher = purple_ciphers_find_cipher("des"); \ + context = purple_cipher_context_new(cipher, NULL); \ + purple_cipher_context_set_key(context, key); \ + \ + ret = purple_cipher_context_encrypt(context, decrypt, len, answer, &outlen); \ + fail_unless(ret == 0, NULL); \ + fail_unless(outlen == (len), NULL); \ + fail_unless(memcmp(encrypt, answer, len) == 0, NULL); \ + \ + ret = purple_cipher_context_decrypt(context, encrypt, len, answer, &outlen); \ + fail_unless(ret == 0, NULL); \ + fail_unless(outlen == (len), NULL); \ + fail_unless(memcmp(decrypt, answer, len) == 0, NULL); \ + \ + purple_cipher_context_destroy(context); \ +} + +START_TEST(test_des_12345678) { + DES_TEST("12345678", + "\x3b\x38\x98\x37\x15\x20\xf7\x5e", + "\x06\x22\x05\xac\x6a\x0d\x55\xdd", + 8); +} +END_TEST + +START_TEST(test_des_abcdefgh) { + DES_TEST("abcdefgh", + "\x3b\x38\x98\x37\x15\x20\xf7\x5e", + "\x62\xe0\xc6\x8c\x48\xe4\x75\xed", + 8); +} +END_TEST + +/****************************************************************************** + * DES3 Tests + * See http://csrc.nist.gov/groups/ST/toolkit/examples.html + * and some NULL things I made up + *****************************************************************************/ + +#define DES3_TEST(in, key, iv, out, len, mode) { \ + PurpleCipher *cipher = NULL; \ + PurpleCipherContext *context = NULL; \ + guchar answer[len+1]; \ + guchar decrypt[len+1] = in; \ + guchar encrypt[len+1] = out; \ + size_t outlen; \ + gint ret = 0; \ + \ + cipher = purple_ciphers_find_cipher("des3"); \ + context = purple_cipher_context_new(cipher, NULL); \ + purple_cipher_context_set_key(context, (guchar *)key); \ + purple_cipher_context_set_batch_mode(context, (mode)); \ + purple_cipher_context_set_iv(context, (guchar *)iv, 8); \ + \ + ret = purple_cipher_context_encrypt(context, decrypt, len, answer, &outlen); \ + fail_unless(ret == 0, NULL); \ + fail_unless(outlen == (len), NULL); \ + fail_unless(memcmp(encrypt, answer, len) == 0, NULL); \ + \ + ret = purple_cipher_context_decrypt(context, encrypt, len, answer, &outlen); \ + fail_unless(ret == 0, NULL); \ + fail_unless(outlen == (len), NULL); \ + fail_unless(memcmp(decrypt, answer, len) == 0, NULL); \ + \ + purple_cipher_context_destroy(context); \ +} + +START_TEST(test_des3_ecb_nist1) { + DES3_TEST( + "\x6B\xC1\xBE\xE2\x2E\x40\x9F\x96\xE9\x3D\x7E\x11\x73\x93\x17\x2A" + "\xAE\x2D\x8A\x57\x1E\x03\xAC\x9C\x9E\xB7\x6F\xAC\x45\xAF\x8E\x51", + "\x01\x23\x45\x67\x89\xAB\xCD\xEF" + "\x23\x45\x67\x89\xAB\xCD\xEF\x01" + "\x45\x67\x89\xAB\xCD\xEF\x01\x23", + "00000000", /* ignored */ + "\x71\x47\x72\xF3\x39\x84\x1D\x34\x26\x7F\xCC\x4B\xD2\x94\x9C\xC3" + "\xEE\x11\xC2\x2A\x57\x6A\x30\x38\x76\x18\x3F\x99\xC0\xB6\xDE\x87", + 32, + PURPLE_CIPHER_BATCH_MODE_ECB); +} +END_TEST + +START_TEST(test_des3_ecb_nist2) { + DES3_TEST( + "\x6B\xC1\xBE\xE2\x2E\x40\x9F\x96\xE9\x3D\x7E\x11\x73\x93\x17\x2A" + "\xAE\x2D\x8A\x57\x1E\x03\xAC\x9C\x9E\xB7\x6F\xAC\x45\xAF\x8E\x51", + "\x01\x23\x45\x67\x89\xAB\xCD\xEF" + "\x23\x45\x67\x89\xAB\xCD\xEF\x01" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF", + "00000000", /* ignored */ + "\x06\xED\xE3\xD8\x28\x84\x09\x0A\xFF\x32\x2C\x19\xF0\x51\x84\x86" + "\x73\x05\x76\x97\x2A\x66\x6E\x58\xB6\xC8\x8C\xF1\x07\x34\x0D\x3D", + 32, + PURPLE_CIPHER_BATCH_MODE_ECB); +} +END_TEST + +START_TEST(test_des3_ecb_null_key) { + DES3_TEST( + "\x16\xf4\xb3\x77\xfd\x4b\x9e\xca", + "\x38\x00\x88\x6a\xef\xcb\x00\xad" + "\x5d\xe5\x29\x00\x7d\x98\x64\x4c" + "\x86\x00\x7b\xd3\xc7\x00\x7b\x32", + "00000000", /* ignored */ + "\xc0\x60\x30\xa1\xb7\x25\x42\x44", + 8, + PURPLE_CIPHER_BATCH_MODE_ECB); +} +END_TEST + +START_TEST(test_des3_ecb_null_text) { + DES3_TEST( + "\x65\x73\x34\xc1\x19\x00\x79\x65", + "\x32\x64\xda\x10\x13\x6a\xfe\x1e" + "\x37\x54\xd1\x2c\x41\x04\x10\x40" + "\xaf\x1c\x75\x2b\x51\x3a\x03\xf5", + "00000000", /* ignored */ + "\xe5\x80\xf6\x12\xf8\x4e\xd9\x6c", + 8, + PURPLE_CIPHER_BATCH_MODE_ECB); +} +END_TEST + +START_TEST(test_des3_ecb_null_key_and_text) { + DES3_TEST( + "\xdf\x7f\x00\x92\xe7\xc1\x49\xd2", + "\x0e\x41\x00\xc4\x8b\xf0\x6e\xa1" + "\x66\x49\x42\x63\x22\x00\xf0\x99" + "\x6b\x22\xc1\x37\x9c\x00\xe4\x8f", + "00000000", /* ignored */ + "\x73\xd8\x1f\x1f\x50\x01\xe4\x79", + 8, + PURPLE_CIPHER_BATCH_MODE_ECB); +} +END_TEST + +START_TEST(test_des3_cbc_nist1) { + DES3_TEST( + "\x6B\xC1\xBE\xE2\x2E\x40\x9F\x96\xE9\x3D\x7E\x11\x73\x93\x17\x2A" + "\xAE\x2D\x8A\x57\x1E\x03\xAC\x9C\x9E\xB7\x6F\xAC\x45\xAF\x8E\x51", + "\x01\x23\x45\x67\x89\xAB\xCD\xEF" + "\x23\x45\x67\x89\xAB\xCD\xEF\x01" + "\x45\x67\x89\xAB\xCD\xEF\x01\x23", + "\xF6\x9F\x24\x45\xDF\x4F\x9B\x17", + "\x20\x79\xC3\xD5\x3A\xA7\x63\xE1\x93\xB7\x9E\x25\x69\xAB\x52\x62" + "\x51\x65\x70\x48\x1F\x25\xB5\x0F\x73\xC0\xBD\xA8\x5C\x8E\x0D\xA7", + 32, + PURPLE_CIPHER_BATCH_MODE_CBC); +} +END_TEST + +START_TEST(test_des3_cbc_nist2) { + DES3_TEST( + "\x6B\xC1\xBE\xE2\x2E\x40\x9F\x96\xE9\x3D\x7E\x11\x73\x93\x17\x2A" + "\xAE\x2D\x8A\x57\x1E\x03\xAC\x9C\x9E\xB7\x6F\xAC\x45\xAF\x8E\x51", + "\x01\x23\x45\x67\x89\xAB\xCD\xEF" + "\x23\x45\x67\x89\xAB\xCD\xEF\x01" + "\x01\x23\x45\x67\x89\xAB\xCD\xEF", + "\xF6\x9F\x24\x45\xDF\x4F\x9B\x17", + "\x74\x01\xCE\x1E\xAB\x6D\x00\x3C\xAF\xF8\x4B\xF4\x7B\x36\xCC\x21" + "\x54\xF0\x23\x8F\x9F\xFE\xCD\x8F\x6A\xCF\x11\x83\x92\xB4\x55\x81", + 32, + PURPLE_CIPHER_BATCH_MODE_CBC); +} +END_TEST + +START_TEST(test_des3_cbc_null_key) { + DES3_TEST( + "\x16\xf4\xb3\x77\xfd\x4b\x9e\xca", + "\x38\x00\x88\x6a\xef\xcb\x00\xad" + "\x5d\xe5\x29\x00\x7d\x98\x64\x4c" + "\x86\x00\x7b\xd3\xc7\x00\x7b\x32", + "\x31\x32\x33\x34\x35\x36\x37\x38", + "\x52\xe7\xde\x96\x39\x87\x87\xdb", + 8, + PURPLE_CIPHER_BATCH_MODE_CBC); +} +END_TEST + +START_TEST(test_des3_cbc_null_text) { + DES3_TEST( + "\x65\x73\x34\xc1\x19\x00\x79\x65", + "\x32\x64\xda\x10\x13\x6a\xfe\x1e" + "\x37\x54\xd1\x2c\x41\x04\x10\x40" + "\xaf\x1c\x75\x2b\x51\x3a\x03\xf5", + "\x7C\xAF\x0D\x57\x1E\x57\x10\xDA", + "\x40\x12\x0e\x00\x85\xff\x6c\xc2", + 8, + PURPLE_CIPHER_BATCH_MODE_CBC); +} +END_TEST + +START_TEST(test_des3_cbc_null_key_and_text) { + DES3_TEST( + "\xdf\x7f\x00\x92\xe7\xc1\x49\xd2", + "\x0e\x41\x00\xc4\x8b\xf0\x6e\xa1" + "\x66\x49\x42\x63\x22\x00\xf0\x99" + "\x6b\x22\xc1\x37\x9c\x00\xe4\x8f", + "\x01\x19\x0D\x2c\x40\x67\x89\x67", + "\xa7\xc1\x10\xbe\x9b\xd5\x8a\x67", + 8, + PURPLE_CIPHER_BATCH_MODE_CBC); +} +END_TEST + +/****************************************************************************** + * HMAC Tests + * See RFC2202 and some other NULL tests I made up + *****************************************************************************/ + +#define HMAC_TEST(data, data_len, key, key_len, type, digest) { \ + PurpleCipher *cipher = NULL; \ + PurpleCipherContext *context = NULL; \ + gchar cdigest[41]; \ + gboolean ret = FALSE; \ + \ + cipher = purple_ciphers_find_cipher("hmac"); \ + context = purple_cipher_context_new(cipher, NULL); \ + purple_cipher_context_set_option(context, "hash", type); \ + purple_cipher_context_set_key_with_len(context, (guchar *)key, (key_len)); \ + \ + purple_cipher_context_append(context, (guchar *)(data), (data_len)); \ + ret = purple_cipher_context_digest_to_str(context, sizeof(cdigest), cdigest, \ + NULL); \ + \ + fail_unless(ret == TRUE, NULL); \ + fail_unless(strcmp((digest), cdigest) == 0, NULL); \ + \ + purple_cipher_context_destroy(context); \ +} + +/* HMAC MD5 */ + +START_TEST(test_hmac_md5_Hi) { + HMAC_TEST("Hi There", + 8, + "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b", + 16, + "md5", + "9294727a3638bb1c13f48ef8158bfc9d"); +} +END_TEST + +START_TEST(test_hmac_md5_what) { + HMAC_TEST("what do ya want for nothing?", + 28, + "Jefe", + 4, + "md5", + "750c783e6ab0b503eaa86e310a5db738"); +} +END_TEST + +START_TEST(test_hmac_md5_dd) { + HMAC_TEST("\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd" + "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd" + "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd" + "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd" + "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd", + 50, + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa", + 16, + "md5", + "56be34521d144c88dbb8c733f0e8b3f6"); +} +END_TEST + +START_TEST(test_hmac_md5_cd) { + HMAC_TEST("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd", + 50, + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a" + "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14" + "\x15\x16\x17\x18\x19", + 25, + "md5", + "697eaf0aca3a3aea3a75164746ffaa79"); +} +END_TEST + +START_TEST(test_hmac_md5_truncation) { + HMAC_TEST("Test With Truncation", + 20, + "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c", + 16, + "md5", + "56461ef2342edc00f9bab995690efd4c"); +} +END_TEST + +START_TEST(test_hmac_md5_large_key) { + HMAC_TEST("Test Using Larger Than Block-Size Key - Hash Key First", + 54, + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa", + 80, + "md5", + "6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd"); +} +END_TEST + +START_TEST(test_hmac_md5_large_key_and_data) { + HMAC_TEST("Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data", + 73, + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa", + 80, + "md5", + "6f630fad67cda0ee1fb1f562db3aa53e"); +} +END_TEST + +START_TEST(test_hmac_md5_null_key) { + HMAC_TEST("Hi There", + 8, + "\x0a\x0b\x00\x0d\x0e\x0f\x1a\x2f\x0b\x0b" + "\x0b\x00\x00\x0b\x0b\x49\x5f\x6e\x0b\x0b", + 20, + "md5", + "597bfd644b797a985561eeb03a169e59"); +} +END_TEST + +START_TEST(test_hmac_md5_null_text) { + HMAC_TEST("Hi\x00There", + 8, + "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b" + "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b", + 20, + "md5", + "70be8e1b7b50dfcc335d6cd7992c564f"); +} +END_TEST + +START_TEST(test_hmac_md5_null_key_and_text) { + HMAC_TEST("Hi\x00Th\x00re", + 8, + "\x0c\x0d\x00\x0f\x10\x1a\x3a\x3a\xe6\x34" + "\x0b\x00\x00\x0b\x0b\x49\x5f\x6e\x0b\x0b", + 20, + "md5", + "b31bcbba35a33a067cbba9131cba4889"); +} +END_TEST + +/* HMAC SHA1 */ + +START_TEST(test_hmac_sha1_Hi) { + HMAC_TEST("Hi There", + 8, + "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b" + "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b", + 20, + "sha1", + "b617318655057264e28bc0b6fb378c8ef146be00"); +} +END_TEST + +START_TEST(test_hmac_sha1_what) { + HMAC_TEST("what do ya want for nothing?", + 28, + "Jefe", + 4, + "sha1", + "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79"); +} +END_TEST + +START_TEST(test_hmac_sha1_dd) { + HMAC_TEST("\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd" + "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd" + "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd" + "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd" + "\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd", + 50, + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa", + 20, + "sha1", + "125d7342b9ac11cd91a39af48aa17b4f63f175d3"); +} +END_TEST + +START_TEST(test_hmac_sha1_cd) { + HMAC_TEST("\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd" + "\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd\xcd", + 50, + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a" + "\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14" + "\x15\x16\x17\x18\x19", + 25, + "sha1", + "4c9007f4026250c6bc8414f9bf50c86c2d7235da"); +} +END_TEST + +START_TEST(test_hmac_sha1_truncation) { + HMAC_TEST("Test With Truncation", + 20, + "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c" + "\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c", + 20, + "sha1", + "4c1a03424b55e07fe7f27be1d58bb9324a9a5a04"); +} +END_TEST + +START_TEST(test_hmac_sha1_large_key) { + HMAC_TEST("Test Using Larger Than Block-Size Key - Hash Key First", + 54, + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa", + 80, + "sha1", + "aa4ae5e15272d00e95705637ce8a3b55ed402112"); +} +END_TEST + +START_TEST(test_hmac_sha1_large_key_and_data) { + HMAC_TEST("Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data", + 73, + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa", + 80, + "sha1", + "e8e99d0f45237d786d6bbaa7965c7808bbff1a91"); +} +END_TEST + +START_TEST(test_hmac_sha1_null_key) { + HMAC_TEST("Hi There", + 8, + "\x0a\x0b\x00\x0d\x0e\x0f\x1a\x2f\x0b\x0b" + "\x0b\x00\x00\x0b\x0b\x49\x5f\x6e\x0b\x0b", + 20, + "sha1", + "eb62a2e0e33d300be669c52aab3f591bc960aac5"); +} +END_TEST + +START_TEST(test_hmac_sha1_null_text) { + HMAC_TEST("Hi\x00There", + 8, + "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b" + "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b", + 20, + "sha1", + "31ca58d849e971e418e3439de2c6f83144b6abb7"); +} +END_TEST + +START_TEST(test_hmac_sha1_null_key_and_text) { + HMAC_TEST("Hi\x00Th\x00re", + 8, + "\x0c\x0d\x00\x0f\x10\x1a\x3a\x3a\xe6\x34" + "\x0b\x00\x00\x0b\x0b\x49\x5f\x6e\x0b\x0b", + 20, + "sha1", + "e6b8e2fede87aa09dcb13e554df1435e056eae36"); +} +END_TEST + +/****************************************************************************** * Suite *****************************************************************************/ Suite * @@ -227,6 +732,53 @@ tcase_add_test(tc, test_sha1_1000_as_1000_times); suite_add_tcase(s, tc); + /* des tests */ + tc = tcase_create("DES"); + tcase_add_test(tc, test_des_12345678); + tcase_add_test(tc, test_des_abcdefgh); + suite_add_tcase(s, tc); + + /* des3 ecb tests */ + tc = tcase_create("DES3 ECB"); + tcase_add_test(tc, test_des3_ecb_nist1); + tcase_add_test(tc, test_des3_ecb_nist2); + tcase_add_test(tc, test_des3_ecb_null_key); + tcase_add_test(tc, test_des3_ecb_null_text); + tcase_add_test(tc, test_des3_ecb_null_key_and_text); + suite_add_tcase(s, tc); + /* des3 cbc tests */ + tc = tcase_create("DES3 CBC"); + tcase_add_test(tc, test_des3_cbc_nist1); + tcase_add_test(tc, test_des3_cbc_nist2); + tcase_add_test(tc, test_des3_cbc_null_key); + tcase_add_test(tc, test_des3_cbc_null_text); + tcase_add_test(tc, test_des3_cbc_null_key_and_text); + suite_add_tcase(s, tc); + + /* hmac tests */ + tc = tcase_create("HMAC"); + tcase_add_test(tc, test_hmac_md5_Hi); + tcase_add_test(tc, test_hmac_md5_what); + tcase_add_test(tc, test_hmac_md5_dd); + tcase_add_test(tc, test_hmac_md5_cd); + tcase_add_test(tc, test_hmac_md5_truncation); + tcase_add_test(tc, test_hmac_md5_large_key); + tcase_add_test(tc, test_hmac_md5_large_key_and_data); + tcase_add_test(tc, test_hmac_md5_null_key); + tcase_add_test(tc, test_hmac_md5_null_text); + tcase_add_test(tc, test_hmac_md5_null_key_and_text); + tcase_add_test(tc, test_hmac_sha1_Hi); + tcase_add_test(tc, test_hmac_sha1_what); + tcase_add_test(tc, test_hmac_sha1_dd); + tcase_add_test(tc, test_hmac_sha1_cd); + tcase_add_test(tc, test_hmac_sha1_truncation); + tcase_add_test(tc, test_hmac_sha1_large_key); + tcase_add_test(tc, test_hmac_sha1_large_key_and_data); + tcase_add_test(tc, test_hmac_sha1_null_key); + tcase_add_test(tc, test_hmac_sha1_null_text); + tcase_add_test(tc, test_hmac_sha1_null_key_and_text); + suite_add_tcase(s, tc); + return s; }
--- a/libpurple/tests/tests.h Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/tests/tests.h Sat Dec 22 23:18:08 2007 +0000 @@ -1,7 +1,8 @@ #ifndef TESTS_H # define TESTS_H -#include <glib.h> +#include "../purple.h" + #include <check.h> /* define the test suites here */
--- a/libpurple/util.c Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/util.c Sat Dec 22 23:18:08 2007 +0000 @@ -921,6 +921,7 @@ { const char *pln; int len, pound; + char temp[2]; if (!text || *text != '&') return NULL; @@ -943,8 +944,9 @@ pln = "\302\256"; /* or use g_unichar_to_utf8(0xae); */ else if(IS_ENTITY("'")) pln = "\'"; - else if(*(text+1) == '#' && (sscanf(text, "&#%u;", £) == 1) && - pound != 0 && *(text+3+(gint)log10(pound)) == ';') { + else if(*(text+1) == '#' && + (sscanf(text, "&#%u%1[;]", £, temp) == 2 || sscanf(text, "&#x%x%1[;]", £, temp) == 2) && + pound != 0) { static char buf[7]; int buflen = g_unichar_to_utf8((gunichar)pound, buf); buf[buflen] = '\0'; @@ -4255,6 +4257,53 @@ return g_string_free(workstr, FALSE); } +/* + * This function is copied from g_strerror() but changed to use + * gai_strerror(). + */ +G_CONST_RETURN gchar * +purple_gai_strerror(gint errnum) +{ + static GStaticPrivate msg_private = G_STATIC_PRIVATE_INIT; + char *msg; + int saved_errno = errno; + + const char *msg_locale; + + msg_locale = gai_strerror(errnum); + if (g_get_charset(NULL)) + { + /* This string is already UTF-8--great! */ + errno = saved_errno; + return msg_locale; + } + else + { + gchar *msg_utf8 = g_locale_to_utf8(msg_locale, -1, NULL, NULL, NULL); + if (msg_utf8) + { + /* Stick in the quark table so that we can return a static result */ + GQuark msg_quark = g_quark_from_string(msg_utf8); + g_free(msg_utf8); + + msg_utf8 = (gchar *)g_quark_to_string(msg_quark); + errno = saved_errno; + return msg_utf8; + } + } + + msg = g_static_private_get(&msg_private); + if (!msg) + { + msg = g_new(gchar, 64); + g_static_private_set(&msg_private, msg, g_free); + } + + sprintf(msg, "unknown error (%d)", errnum); + + errno = saved_errno; + return msg; +} char * purple_utf8_ncr_encode(const char *str)
--- a/libpurple/util.h Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/util.h Sat Dec 22 23:18:08 2007 +0000 @@ -1110,6 +1110,18 @@ gchar *purple_utf8_salvage(const char *str); /** + * Return the UTF-8 version of gai_strerror(). It calls gai_strerror() + * then converts the result to UTF-8. This function is analogous to + * g_strerror(). + * + * @param errnum The error code. + * + * @return The UTF-8 error message. + * @since 2.4.0 + */ +G_CONST_RETURN gchar *purple_gai_strerror(gint errnum); + +/** * Compares two UTF-8 strings case-insensitively. This string is * more expensive than a simple g_utf8_collate() comparison because * it calls g_utf8_casefold() on each string, which allocates new
--- a/libpurple/version.h.in Sat Dec 22 17:54:30 2007 +0000 +++ b/libpurple/version.h.in Sat Dec 22 23:18:08 2007 +0000 @@ -49,6 +49,34 @@ */ const char *purple_version_check(guint required_major, guint required_minor, guint required_micro); +/** + * The major version of the running libpurple. Contrast with + * #PURPLE_MAJOR_VERSION, which expands at compile time to the major version of + * libpurple being compiled against. + * + * @since 2.4.0 + */ +extern const guint purple_major_version; + +/** + * The minor version of the running libpurple. Contrast with + * #PURPLE_MINOR_VERSION, which expands at compile time to the minor version of + * libpurple being compiled against. + * + * @since 2.4.0 + */ +extern const guint purple_minor_version; + +/** + * + * The micro version of the running libpurple. Contrast with + * #PURPLE_MICRO_VERSION, which expands at compile time to the micro version of + * libpurple being compiled against. + * + * @since 2.4.0 + */ +extern const guint purple_micro_version; + #ifdef __cplusplus } #endif
--- a/pidgin/Makefile.am Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/Makefile.am Sat Dec 22 23:18:08 2007 +0000 @@ -119,7 +119,8 @@ gtkthemes.c \ gtkutils.c \ gtkwhiteboard.c \ - minidialog.c + minidialog.c \ + pidgintooltip.c pidgin_headers = \ eggtrayicon.h \ @@ -172,6 +173,7 @@ gtkutils.h \ gtkwhiteboard.h \ minidialog.h \ + pidgintooltip.h \ pidgin.h pidginincludedir=$(includedir)/pidgin
--- a/pidgin/Makefile.mingw Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/Makefile.mingw Sat Dec 22 23:18:08 2007 +0000 @@ -95,6 +95,7 @@ gtkwhiteboard.c \ minidialog.c \ pidginstock.c \ + pidgintooltip.c \ win32/MinimizeToTray.c \ win32/gtkdocklet-win32.c \ win32/gtkwin32dep.c \
--- a/pidgin/gtkaccount.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkaccount.c Sat Dec 22 23:18:08 2007 +0000 @@ -55,7 +55,6 @@ COLUMN_ENABLED, COLUMN_PROTOCOL, COLUMN_DATA, - COLUMN_PULSE_DATA, NUM_COLUMNS }; @@ -139,18 +138,6 @@ } AccountPrefsDialog; -typedef struct -{ - GdkPixbuf *online_pixbuf; - gboolean pulse_to_grey; - float pulse_value; - int timeout; - PurpleAccount *account; - GtkTreeModel *model; - -} PidginPulseData; - - static AccountsWindow *accounts_window = NULL; static GHashTable *account_pref_wins; @@ -1121,7 +1108,7 @@ G_CALLBACK(proxy_type_changed_cb), dialog); } -static void +static gboolean account_win_destroy_cb(GtkWidget *w, GdkEvent *event, AccountPrefsDialog *dialog) { @@ -1142,6 +1129,7 @@ purple_signals_disconnect_by_handle(dialog); g_free(dialog); + return FALSE; } static void @@ -1437,7 +1425,6 @@ GtkWidget *win; GtkWidget *main_vbox; GtkWidget *vbox; - GtkWidget *bbox; GtkWidget *dbox; GtkWidget *notebook; GtkWidget *button; @@ -1475,16 +1462,14 @@ if ((dialog->plugin = purple_find_prpl(dialog->protocol_id)) != NULL) dialog->prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(dialog->plugin); - dialog->window = win = pidgin_create_window((type == PIDGIN_ADD_ACCOUNT_DIALOG) ? _("Add Account") : _("Modify Account"), + dialog->window = win = pidgin_create_dialog((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); /* Setup the vbox */ - main_vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(win), main_vbox); - gtk_widget_show(main_vbox); + main_vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER); notebook = gtk_notebook_new(); gtk_box_pack_start(GTK_BOX(main_vbox), notebook, FALSE, FALSE, 0); @@ -1511,8 +1496,6 @@ if (!dialog->prpl_info || !dialog->prpl_info->register_user) gtk_widget_hide(button); - - /* Setup the page with 'Advanced'. */ dialog->bottom_vbox = dbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); gtk_container_set_border_width(GTK_CONTAINER(dbox), PIDGIN_HIG_BORDER); @@ -1524,30 +1507,13 @@ add_protocol_options(dialog, dbox); add_proxy_options(dialog, dbox); - /* Setup the button box */ - bbox = gtk_hbutton_box_new(); - gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_end(GTK_BOX(main_vbox), bbox, FALSE, TRUE, 0); - gtk_widget_show(bbox); - /* Cancel button */ - button = gtk_button_new_from_stock(GTK_STOCK_CANCEL); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(cancel_account_prefs_cb), dialog); + pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CANCEL, G_CALLBACK(cancel_account_prefs_cb), dialog); /* Save button */ - button = gtk_button_new_from_stock(GTK_STOCK_SAVE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - + button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_SAVE, G_CALLBACK(ok_account_prefs_cb), dialog); if (dialog->account == NULL) gtk_widget_set_sensitive(button, FALSE); - - gtk_widget_show(button); - dialog->ok_button = button; /* Set up DND */ @@ -1561,9 +1527,6 @@ g_signal_connect(G_OBJECT(dialog->window), "drag_data_received", G_CALLBACK(account_dnd_recv), dialog); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(ok_account_prefs_cb), dialog); - /* Show the window. */ gtk_widget_show(win); } @@ -1575,7 +1538,6 @@ signed_on_off_cb(PurpleConnection *gc, gpointer user_data) { PurpleAccount *account; - PidginPulseData *pulse_data; GtkTreeModel *model; GtkTreeIter iter; GdkPixbuf *pixbuf; @@ -1591,29 +1553,14 @@ if (gtk_tree_model_iter_nth_child(model, &iter, NULL, index)) { - gtk_tree_model_get(GTK_TREE_MODEL(accounts_window->model), &iter, - COLUMN_PULSE_DATA, &pulse_data, -1); - - if (pulse_data != NULL) - { - if (pulse_data->timeout > 0) - g_source_remove(pulse_data->timeout); - - g_object_unref(G_OBJECT(pulse_data->online_pixbuf)); - - g_free(pulse_data); - } - pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_MEDIUM); if ((pixbuf != NULL) && purple_account_is_disconnected(account)) gdk_pixbuf_saturate_and_pixelate(pixbuf, pixbuf, 0.0, FALSE); gtk_list_store_set(accounts_window->model, &iter, COLUMN_ICON, pixbuf, - COLUMN_PULSE_DATA, NULL, -1); - if (pixbuf != NULL) g_object_unref(G_OBJECT(pixbuf)); } @@ -2313,7 +2260,6 @@ AccountsWindow *dialog; GtkWidget *win; GtkWidget *vbox; - GtkWidget *bbox; GtkWidget *sw; GtkWidget *button; int width, height; @@ -2328,7 +2274,7 @@ 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 = pidgin_create_window(_("Accounts"), PIDGIN_HIG_BORDER, "accounts", TRUE); + dialog->window = win = pidgin_create_dialog(_("Accounts"), PIDGIN_HIG_BORDER, "accounts", TRUE); gtk_window_set_default_size(GTK_WINDOW(win), width, height); g_signal_connect(G_OBJECT(win), "delete_event", @@ -2337,57 +2283,28 @@ G_CALLBACK(configure_cb), accounts_window); /* Setup the vbox */ - vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(win), vbox); - gtk_widget_show(vbox); + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER); /* Setup the scrolled window that will contain the list of accounts. */ sw = create_accounts_list(dialog); gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0); gtk_widget_show(sw); - /* Button box. */ - bbox = gtk_hbutton_box_new(); - gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0); - gtk_widget_show(bbox); - /* Add button */ - button = gtk_button_new_from_stock(GTK_STOCK_ADD); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(add_account_cb), dialog); + pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_ADD, G_CALLBACK(add_account_cb), dialog); /* Modify button */ - button = gtk_button_new_from_stock(PIDGIN_STOCK_MODIFY); + button = pidgin_dialog_add_button(GTK_DIALOG(win), PIDGIN_STOCK_MODIFY, G_CALLBACK(modify_account_cb), dialog); dialog->modify_button = button; - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); gtk_widget_set_sensitive(button, FALSE); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(modify_account_cb), dialog); /* Delete button */ - button = gtk_button_new_from_stock(GTK_STOCK_DELETE); + button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_DELETE, G_CALLBACK(ask_delete_account_cb), dialog); dialog->delete_button = button; - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); gtk_widget_set_sensitive(button, FALSE); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(ask_delete_account_cb), dialog); /* Close button */ - button = gtk_button_new_from_stock(GTK_STOCK_CLOSE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(close_accounts_cb), dialog); + pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE, G_CALLBACK(close_accounts_cb), dialog); purple_signal_connect(pidgin_account_get_handle(), "account-modified", accounts_window,
--- a/pidgin/gtkblist.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkblist.c Sat Dec 22 23:18:08 2007 +0000 @@ -59,6 +59,7 @@ #include "gtkscrollbook.h" #include "gtkutils.h" #include "pidgin/minidialog.h" +#include "pidgin/pidgintooltip.h" #include <gdk/gdkkeysyms.h> #include <gtk/gtk.h> @@ -153,6 +154,7 @@ static char *pidgin_get_tooltip_text(PurpleBlistNode *node, gboolean full); static const char *item_factory_translate_func (const char *path, gpointer func_data); static gboolean get_iter_from_node(PurpleBlistNode *node, GtkTreeIter *iter); +static gboolean buddy_is_displayable(PurpleBuddy *buddy); static void redo_buddy_list(PurpleBuddyList *list, gboolean remove, gboolean rerender); static void pidgin_blist_collapse_contact_cb(GtkWidget *w, PurpleBlistNode *node); static char *pidgin_get_group_title(PurpleBlistNode *gnode, gboolean expanded); @@ -2630,7 +2632,8 @@ return td; } -static void pidgin_blist_paint_tip(GtkWidget *widget, GdkEventExpose *event, PurpleBlistNode *node) +static gboolean +pidgin_blist_paint_tip(GtkWidget *widget, gpointer null) { GtkStyle *style; int current_height, max_width; @@ -2638,10 +2641,10 @@ int max_avatar_width; GList *l; int prpl_col = 0; - GtkTextDirection dir = gtk_widget_get_direction(widget); + GtkTextDirection dir = gtk_widget_get_direction(widget); if(gtkblist->tooltipdata == NULL) - return; + return FALSE; style = gtkblist->tipwindow->style; gtk_paint_flat_box(style, gtkblist->tipwindow->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT, @@ -2740,10 +2743,11 @@ current_height += MAX(td->name_height + td->height, td->avatar_height) + TOOLTIP_BORDER; } -} - - -void pidgin_blist_tooltip_destroy() + return FALSE; +} + +static void +pidgin_blist_destroy_tooltip_data() { while(gtkblist->tooltipdata) { struct tooltip_data *td = gtkblist->tooltipdata->data; @@ -2759,12 +2763,62 @@ g_free(td); gtkblist->tooltipdata = g_list_delete_link(gtkblist->tooltipdata, gtkblist->tooltipdata); } - - if (gtkblist->tipwindow == NULL) - return; - - gtk_widget_destroy(gtkblist->tipwindow); - gtkblist->tipwindow = NULL; +} + +void pidgin_blist_tooltip_destroy() +{ + pidgin_blist_destroy_tooltip_data(); + pidgin_tooltip_destroy(); +} + +static gboolean +pidgin_blist_create_tooltip_for_node(GtkWidget *widget, gpointer data, int *w, int *h) +{ + PurpleBlistNode *node = data; + int width, height; + + gtkblist->tipwindow = widget; + if(PURPLE_BLIST_NODE_IS_CHAT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node)) { + struct tooltip_data *td = create_tip_for_node(node, TRUE); + gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td); + width = TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE + + MAX(td->width, td->name_width) + SMALL_SPACE + td->avatar_width + TOOLTIP_BORDER; + height = TOOLTIP_BORDER + MAX(td->height + td->name_height, MAX(STATUS_SIZE, td->avatar_height)) + + TOOLTIP_BORDER; + } else if(PURPLE_BLIST_NODE_IS_CONTACT(node)) { + PurpleBlistNode *child; + PurpleBuddy *b = purple_contact_get_priority_buddy((PurpleContact *)node); + int max_text_width = 0; + int max_avatar_width = 0; + width = height = 0; + + for(child = node->child; child; child = child->next) + { + if(PURPLE_BLIST_NODE_IS_BUDDY(child) && buddy_is_displayable((PurpleBuddy*)child)) { + struct tooltip_data *td = create_tip_for_node(child, (b == (PurpleBuddy*)child)); + if (b == (PurpleBuddy *)child) { + gtkblist->tooltipdata = g_list_prepend(gtkblist->tooltipdata, td); + } else { + gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td); + } + max_text_width = MAX(max_text_width, MAX(td->width, td->name_width)); + max_avatar_width = MAX(max_avatar_width, td->avatar_width); + height += MAX(TOOLTIP_BORDER + MAX(STATUS_SIZE,td->avatar_height), + TOOLTIP_BORDER + td->height + td->name_height); + } + } + height += TOOLTIP_BORDER; + width = TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER; + } else { + return FALSE; + } + + if (w) + *w = width; + if (h) + *h = height; + + return TRUE; } static gboolean pidgin_blist_expand_timeout(GtkWidget *tv) @@ -2826,164 +2880,9 @@ purple_blist_node_get_bool((PurpleBlistNode*)buddy, "show_offline"))); } -static gboolean pidgin_blist_tooltip_timeout(GtkWidget *tv) -{ - GtkTreePath *path; - GtkTreeIter iter; - PurpleBlistNode *node; - GValue val; - gboolean editable = FALSE; - - /* If we're editing a cell (e.g. alias editing), don't show the tooltip */ - g_object_get(G_OBJECT(gtkblist->text_rend), "editable", &editable, NULL); - if (editable) - return FALSE; - - if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), gtkblist->tip_rect.x, gtkblist->tip_rect.y + (gtkblist->tip_rect.height/2), - &path, NULL, NULL, NULL)) - return FALSE; - gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path); - val.g_type = 0; - gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val); - node = g_value_get_pointer(&val); - - pidgin_blist_draw_tooltip(node, gtkblist->window); - - gtk_tree_path_free(path); - return FALSE; -} - void pidgin_blist_draw_tooltip(PurpleBlistNode *node, GtkWidget *widget) { - int scr_w, scr_h, w, h, x, y; -#if GTK_CHECK_VERSION(2,2,0) - int mon_num; - GdkScreen *screen = NULL; -#endif - gboolean tooltip_top = FALSE; - struct _pidgin_blist_node *gtknode; - GdkRectangle mon_size; - int sig; - const char *name; - - if (node == NULL) - return; - - /* - * Attempt to free the previous tooltip. I have a feeling - * this is never needed... but just in case. - */ - pidgin_blist_tooltip_destroy(); - - gtkblist->tipwindow = gtk_window_new(GTK_WINDOW_POPUP); - - if(PURPLE_BLIST_NODE_IS_CHAT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node)) { - struct tooltip_data *td = create_tip_for_node(node, TRUE); - gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td); - w = TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE + - MAX(td->width, td->name_width) + SMALL_SPACE + td->avatar_width + TOOLTIP_BORDER; - h = TOOLTIP_BORDER + MAX(td->height + td->name_height, MAX(STATUS_SIZE, td->avatar_height)) - + TOOLTIP_BORDER; - } else if(PURPLE_BLIST_NODE_IS_CONTACT(node)) { - PurpleBlistNode *child; - PurpleBuddy *b = purple_contact_get_priority_buddy((PurpleContact *)node); - int max_text_width = 0; - int max_avatar_width = 0; - w = h = 0; - - for(child = node->child; child; child = child->next) - { - if(PURPLE_BLIST_NODE_IS_BUDDY(child) && buddy_is_displayable((PurpleBuddy*)child)) { - struct tooltip_data *td = create_tip_for_node(child, (b == (PurpleBuddy*)child)); - if (b == (PurpleBuddy *)child) { - gtkblist->tooltipdata = g_list_prepend(gtkblist->tooltipdata, td); - } else { - gtkblist->tooltipdata = g_list_append(gtkblist->tooltipdata, td); - } - max_text_width = MAX(max_text_width, MAX(td->width, td->name_width)); - max_avatar_width = MAX(max_avatar_width, td->avatar_width); - h += MAX(TOOLTIP_BORDER + MAX(STATUS_SIZE,td->avatar_height), - TOOLTIP_BORDER + td->height + td->name_height); - } - } - h += TOOLTIP_BORDER; - w = TOOLTIP_BORDER + STATUS_SIZE + SMALL_SPACE + max_text_width + SMALL_SPACE + max_avatar_width + TOOLTIP_BORDER; - } else { - gtk_widget_destroy(gtkblist->tipwindow); - gtkblist->tipwindow = NULL; - return; - } - - if (gtkblist->tooltipdata == NULL) { - gtk_widget_destroy(gtkblist->tipwindow); - gtkblist->tipwindow = NULL; - return; - } - - gtknode = node->ui_data; - - name = gtk_window_get_title(GTK_WINDOW(gtk_widget_get_toplevel(widget))); - gtk_widget_set_app_paintable(gtkblist->tipwindow, TRUE); - gtk_window_set_title(GTK_WINDOW(gtkblist->tipwindow), name ? name : _("Buddy List")); - gtk_window_set_resizable(GTK_WINDOW(gtkblist->tipwindow), FALSE); - gtk_widget_set_name(gtkblist->tipwindow, "gtk-tooltips"); - g_signal_connect(G_OBJECT(gtkblist->tipwindow), "expose_event", - G_CALLBACK(pidgin_blist_paint_tip), NULL); - gtk_widget_ensure_style (gtkblist->tipwindow); - -#if GTK_CHECK_VERSION(2,2,0) - gdk_display_get_pointer(gdk_display_get_default(), &screen, &x, &y, NULL); - mon_num = gdk_screen_get_monitor_at_point(screen, x, y); - gdk_screen_get_monitor_geometry(screen, mon_num, &mon_size); - - scr_w = mon_size.width + mon_size.x; - scr_h = mon_size.height + mon_size.y; -#else - scr_w = gdk_screen_width(); - scr_h = gdk_screen_height(); - gdk_window_get_pointer(NULL, &x, &y, NULL); - mon_size.x = 0; - mon_size.y = 0; -#endif - -#if GTK_CHECK_VERSION(2,2,0) - if (w > mon_size.width) - w = mon_size.width - 10; - - if (h > mon_size.height) - h = mon_size.height - 10; -#endif - - x -= ((w >> 1) + 4); - - if ((y + h + 4) > scr_h || tooltip_top) - y = y - h - 5; - else - y = y + 6; - - if (y < mon_size.y) - y = mon_size.y; - - if (y != mon_size.y) { - if ((x + w) > scr_w) - x -= (x + w + 5) - scr_w; - else if (x < mon_size.x) - x = mon_size.x; - } else { - x -= (w / 2 + 10); - if (x < mon_size.x) - x = mon_size.x; - } - - gtk_widget_set_size_request(gtkblist->tipwindow, w, h); - gtk_window_move(GTK_WINDOW(gtkblist->tipwindow), x, y); - gtk_widget_show(gtkblist->tipwindow); - - /* Hide the tooltip when the widget is destroyed */ - sig = g_signal_connect(G_OBJECT(widget), "destroy", G_CALLBACK(pidgin_blist_tooltip_destroy), NULL); - g_signal_connect_swapped(G_OBJECT(gtkblist->tipwindow), "destroy", G_CALLBACK(g_source_remove), GINT_TO_POINTER(sig)); - - return; + pidgin_tooltip_show(widget, node, pidgin_blist_create_tooltip_for_node, pidgin_blist_paint_tip); } static gboolean pidgin_blist_drag_motion_cb(GtkWidget *tv, GdkDragContext *drag_context, @@ -3033,31 +2932,34 @@ return FALSE; } -static gboolean pidgin_blist_motion_cb (GtkWidget *tv, GdkEventMotion *event, gpointer null) -{ - GtkTreePath *path; - int delay; - - delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay"); - - if (delay == 0) +static gboolean +pidgin_blist_create_tooltip(GtkWidget *widget, GtkTreePath *path, + gpointer null, int *w, int *h) +{ + GtkTreeIter iter; + PurpleBlistNode *node; + GValue val; + gboolean editable = FALSE; + + /* If we're editing a cell (e.g. alias editing), don't show the tooltip */ + g_object_get(G_OBJECT(gtkblist->text_rend), "editable", &editable, NULL); + if (editable) return FALSE; - if (gtkblist->timeout) { - if ((event->y > gtkblist->tip_rect.y) && ((event->y - gtkblist->tip_rect.height) < gtkblist->tip_rect.y)) - return FALSE; - /* We've left the cell. Remove the timeout and create a new one below */ - pidgin_blist_tooltip_destroy(); - g_source_remove(gtkblist->timeout); - } - - gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL); - gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, >kblist->tip_rect); - - if (path) - gtk_tree_path_free(path); - gtkblist->timeout = g_timeout_add(delay, (GSourceFunc)pidgin_blist_tooltip_timeout, tv); - + if (gtkblist->tooltipdata) { + gtkblist->tipwindow = NULL; + pidgin_blist_destroy_tooltip_data(); + } + + gtk_tree_model_get_iter(GTK_TREE_MODEL(gtkblist->treemodel), &iter, path); + val.g_type = 0; + gtk_tree_model_get_value (GTK_TREE_MODEL(gtkblist->treemodel), &iter, NODE_COLUMN, &val); + node = g_value_get_pointer(&val); + return pidgin_blist_create_tooltip_for_node(widget, node, w, h); +} + +static gboolean pidgin_blist_motion_cb (GtkWidget *tv, GdkEventMotion *event, gpointer null) +{ if (gtkblist->mouseover_contact) { if ((event->y < gtkblist->contact_rect.y) || ((event->y - gtkblist->contact_rect.height) > gtkblist->contact_rect.y)) { pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact); @@ -3068,9 +2970,8 @@ return FALSE; } -static void pidgin_blist_leave_cb (GtkWidget *w, GdkEventCrossing *e, gpointer n) -{ - +static gboolean pidgin_blist_leave_cb (GtkWidget *w, GdkEventCrossing *e, gpointer n) +{ if (gtkblist->timeout) { g_source_remove(gtkblist->timeout); gtkblist->timeout = 0; @@ -3081,14 +2982,13 @@ gtkblist->drag_timeout = 0; } - pidgin_blist_tooltip_destroy(); - if (gtkblist->mouseover_contact && !((e->x > gtkblist->contact_rect.x) && (e->x < (gtkblist->contact_rect.x + gtkblist->contact_rect.width)) && (e->y > gtkblist->contact_rect.y) && (e->y < (gtkblist->contact_rect.y + gtkblist->contact_rect.height)))) { pidgin_blist_collapse_contact_cb(NULL, gtkblist->mouseover_contact); gtkblist->mouseover_contact = NULL; } + return FALSE; } static void @@ -4643,6 +4543,13 @@ } static void +clear_elsewhere_errors(PidginMiniDialog *mini_dialog, + gpointer unused) +{ + elsewhere_foreach_account(mini_dialog, purple_account_clear_current_error); +} + +static void ensure_signed_on_elsewhere_minidialog(PidginBuddyList *gtkblist) { PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist); @@ -4657,6 +4564,12 @@ pidgin_mini_dialog_add_button(mini_dialog, _("Re-enable"), reconnect_elsewhere_accounts, NULL); + /* Make dismissing the dialog clear the errors. The "destroy" signal + * does not appear to fire at quit, which is fortunate! + */ + g_signal_connect(G_OBJECT(mini_dialog), "destroy", + (GCallback) clear_elsewhere_errors, NULL); + add_error_dialog(gtkblist, GTK_WIDGET(mini_dialog)); /* Set priv->signed_on_elsewhere to NULL when the dialog is destroyed */ @@ -5152,12 +5065,14 @@ #ifdef _WIN32 g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-begin", G_CALLBACK(pidgin_blist_drag_begin), NULL); #endif - g_signal_connect(G_OBJECT(gtkblist->treeview), "drag-motion", G_CALLBACK(pidgin_blist_drag_motion_cb), NULL); + g_signal_connect(G_OBJECT(gtkblist->treeview), "motion-notify-event", G_CALLBACK(pidgin_blist_motion_cb), NULL); + g_signal_connect(G_OBJECT(gtkblist->treeview), "leave-notify-event", G_CALLBACK(pidgin_blist_leave_cb), NULL); /* Tooltips */ - g_signal_connect(G_OBJECT(gtkblist->treeview), "motion-notify-event", G_CALLBACK(pidgin_blist_motion_cb), NULL); - g_signal_connect(G_OBJECT(gtkblist->treeview), "leave-notify-event", G_CALLBACK(pidgin_blist_leave_cb), NULL); + pidgin_tooltip_setup_for_treeview(gtkblist->treeview, NULL, + pidgin_blist_create_tooltip, + pidgin_blist_paint_tip); gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(gtkblist->treeview), FALSE);
--- a/pidgin/gtkcertmgr.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkcertmgr.c Sat Dec 22 23:18:08 2007 +0000 @@ -545,12 +545,13 @@ So if it is set, don't open another one! */ CertMgrDialog *certmgr_dialog = NULL; -static void +static gboolean certmgr_close_cb(GtkWidget *w, CertMgrDialog *dlg) { /* TODO: Ignoring the arguments to this function may not be ideal, but there *should* only be "one dialog to rule them all" at a time*/ pidgin_certmgr_hide(); + return FALSE; } void @@ -559,7 +560,6 @@ CertMgrDialog *dlg; GtkWidget *win; GtkWidget *vbox; - GtkWidget *bbox; /* Enumerate all the certificates on file */ { @@ -599,7 +599,7 @@ dlg = certmgr_dialog = g_new0(CertMgrDialog, 1); win = dlg->window = - pidgin_create_window(_("Certificate Manager"),/* Title */ + pidgin_create_dialog(_("Certificate Manager"),/* Title */ PIDGIN_HIG_BORDER, /*Window border*/ "certmgr", /* Role */ TRUE); /* Allow resizing */ @@ -611,9 +611,7 @@ gtk_window_set_default_size(GTK_WINDOW(win), 400, 400); /* Main vbox */ - vbox = gtk_vbox_new( FALSE, PIDGIN_HIG_BORDER ); - gtk_container_add(GTK_CONTAINER(win), vbox); - gtk_widget_show(vbox); + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER); /* Notebook of various certificate managers */ dlg->notebook = gtk_notebook_new(); @@ -622,19 +620,9 @@ 0); gtk_widget_show(dlg->notebook); - /* Box for the close button */ - bbox = gtk_hbutton_box_new(); - gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0); - gtk_widget_show(bbox); - /* Close button */ - dlg->closebutton = gtk_button_new_from_stock(GTK_STOCK_CLOSE); - gtk_box_pack_start(GTK_BOX(bbox), dlg->closebutton, FALSE, FALSE, 0); - gtk_widget_show(dlg->closebutton); - g_signal_connect(G_OBJECT(dlg->closebutton), "clicked", - G_CALLBACK(certmgr_close_cb), dlg); + dlg->closebutton = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE, + G_CALLBACK(certmgr_close_cb), dlg); /* Add the defined certificate managers */ /* TODO: Find a way of determining whether each is shown or not */
--- a/pidgin/gtkconn.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkconn.c Sat Dec 22 23:18:08 2007 +0000 @@ -184,36 +184,39 @@ static void pidgin_connection_network_connected () { - GList *list = purple_accounts_get_all_active(); + GList *list, *l; PidginBuddyList *gtkblist = pidgin_blist_get_default_gtk_blist(); if(gtkblist) pidgin_status_box_set_network_available(PIDGIN_STATUS_BOX(gtkblist->statusbox), TRUE); - while (list) { - PurpleAccount *account = (PurpleAccount*)list->data; + l = list = purple_accounts_get_all_active(); + while (l) { + PurpleAccount *account = (PurpleAccount*)l->data; g_hash_table_remove(auto_reconns, account); if (purple_account_is_disconnected(account)) do_signon(account); - list = list->next; + l = l->next; } + g_list_free(list); } static void pidgin_connection_network_disconnected () { - GList *l = purple_accounts_get_all_active(); + GList *list, *l; PidginBuddyList *gtkblist = pidgin_blist_get_default_gtk_blist(); PurplePluginProtocolInfo *prpl_info = NULL; PurpleConnection *gc = NULL; - + if(gtkblist) pidgin_status_box_set_network_available(PIDGIN_STATUS_BOX(gtkblist->statusbox), FALSE); + l = list = purple_accounts_get_all_active(); while (l) { PurpleAccount *a = (PurpleAccount*)l->data; if (!purple_account_is_disconnected(a)) { gc = purple_account_get_connection(a); - if (gc && gc->prpl) + if (gc && gc->prpl) prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl); if (prpl_info) { if (prpl_info->keepalive) @@ -224,6 +227,7 @@ } l = l->next; } + g_list_free(list); } static void pidgin_connection_notice(PurpleConnection *gc, const char *text)
--- a/pidgin/gtkconv.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkconv.c Sat Dec 22 23:18:08 2007 +0000 @@ -95,9 +95,10 @@ #define PIDGIN_CONV_ALL ((1 << 7) - 1) -#define SEND_COLOR "#204a87" -#define RECV_COLOR "#cc0000" -#define HIGHLIGHT_COLOR "#AF7F00" +#define DEFAULT_SEND_COLOR "#204a87" +#define DEFAULT_RECV_COLOR "#cc0000" +#define DEFAULT_HIGHLIGHT_COLOR "#AF7F00" +#define DEFAULT_ACTION_COLOR "#062585" /* Undef this to turn off "custom-smiley" debug messages */ #define DEBUG_CUSTOM_SMILEY @@ -152,6 +153,7 @@ static void conv_set_unseen(PurpleConversation *gtkconv, PidginUnseenState state); static void gtkconv_set_unseen(PidginConversation *gtkconv, PidginUnseenState state); static void update_typing_icon(PidginConversation *gtkconv); +static void update_typing_message(PidginConversation *gtkconv, const char *message); static const char *item_factory_translate_func (const char *path, gpointer func_data); gboolean pidgin_conv_has_focus(PurpleConversation *conv); static void pidgin_conv_custom_smiley_allocated(GdkPixbufLoader *loader, gpointer user_data); @@ -163,7 +165,7 @@ static void pidgin_conv_tab_pack(PidginWindow *win, PidginConversation *gtkconv); static gboolean infopane_press_cb(GtkWidget *widget, GdkEventButton *e, PidginConversation *conv); static gboolean pidgin_userlist_motion_cb (GtkWidget *w, GdkEventMotion *event, PidginConversation *gtkconv); -static void pidgin_conv_leave_cb (GtkWidget *w, GdkEventCrossing *e, PidginConversation *gtkconv); +static gboolean pidgin_conv_leave_cb (GtkWidget *w, GdkEventCrossing *e, PidginConversation *gtkconv); static void hide_conv(PidginConversation *gtkconv, gboolean closetimer); static void pidgin_conv_set_position_size(PidginWindow *win, int x, int y, @@ -3376,6 +3378,34 @@ } static void +update_typing_message(PidginConversation *gtkconv, const char *message) +{ + GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(gtkconv->imhtml)); + GtkTextMark *stmark, *enmark; + + stmark = gtk_text_buffer_get_mark(buffer, "typing-notification-start"); + enmark = gtk_text_buffer_get_mark(buffer, "typing-notification-end"); + if (stmark && enmark) { + GtkTextIter start, end; + gtk_text_buffer_get_iter_at_mark(buffer, &start, stmark); + gtk_text_buffer_get_iter_at_mark(buffer, &end, enmark); + gtk_text_buffer_delete_mark(buffer, stmark); + gtk_text_buffer_delete_mark(buffer, enmark); + gtk_text_buffer_delete(buffer, &start, &end); + } else if (message && *message == '\n' && !*(message + 1)) + message = NULL; + + if (message) { + GtkTextIter iter; + gtk_text_buffer_get_end_iter(buffer, &iter); + gtk_text_buffer_create_mark(buffer, "typing-notification-start", &iter, TRUE); + gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, message, -1, "TYPING-NOTIFICATION", NULL); + gtk_text_buffer_get_end_iter(buffer, &iter); + gtk_text_buffer_create_mark(buffer, "typing-notification-end", &iter, TRUE); + } +} + +static void update_typing_icon(PidginConversation *gtkconv) { PidginWindow *gtkwin; @@ -3383,6 +3413,7 @@ PurpleConversation *conv = gtkconv->active_conv; char *stock_id; const char *tooltip; + char *message = NULL; gtkwin = gtkconv->win; @@ -3401,6 +3432,7 @@ g_source_remove(gtkconv->u.im->typing_timer); gtkconv->u.im->typing_timer = 0; } + update_typing_message(gtkconv, "\n"); return; } @@ -3410,9 +3442,11 @@ } stock_id = PIDGIN_STOCK_ANIMATION_TYPING1; tooltip = _("User is typing..."); + message = g_strdup_printf(_("\n%s is typing..."), purple_conversation_get_title(conv)); } else { stock_id = PIDGIN_STOCK_ANIMATION_TYPING5; tooltip = _("User has typed something and stopped"); + message = g_strdup_printf(_("\n%s has typed something and stopped"), purple_conversation_get_title(conv)); if (gtkconv->u.im->typing_timer != 0) { g_source_remove(gtkconv->u.im->typing_timer); gtkconv->u.im->typing_timer = 0; @@ -3435,6 +3469,8 @@ } gtk_widget_show(gtkwin->menu.typing_icon); + update_typing_message(gtkconv, message); + g_free(message); } static gboolean @@ -3790,7 +3826,7 @@ if (is_me) { GdkColor send_color; - gdk_color_parse(SEND_COLOR, &send_color); + gdk_color_parse(DEFAULT_SEND_COLOR, &send_color); #if GTK_CHECK_VERSION(2,6,0) gtk_list_store_insert_with_values(ls, &iter, @@ -4343,14 +4379,15 @@ /* Show a maximum of 4 lines */ lines = MIN(lines, 4); - wrapped_lines = MIN(wrapped_lines, 4); + wrapped_lines = MIN(MAX(wrapped_lines, 2), 4); pad_top = gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(gtkconv->entry)); pad_bottom = gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(gtkconv->entry)); pad_inside = gtk_text_view_get_pixels_inside_wrap(GTK_TEXT_VIEW(gtkconv->entry)); - height = (oneline.height + pad_top + pad_bottom) * (lines + 1); - height += (oneline.height + pad_inside) * (wrapped_lines - lines); + height = (oneline.height + pad_top + pad_bottom) * lines; + if (wrapped_lines > lines) + height += (oneline.height + pad_inside) * (wrapped_lines - lines); gtkconv->auto_resize = TRUE; g_idle_add(reset_auto_resize_cb, gtkconv); @@ -4375,7 +4412,7 @@ { 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); @@ -4524,20 +4561,30 @@ conv = gtkconv->active_conv; if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) { node = (PurpleBlistNode*)(purple_blist_find_chat(conv->account, conv->name)); +#if 0 + /* Using the transient blist nodes to show the tooltip doesn't quite work yet. */ + if (!node) + node = g_object_get_data(G_OBJECT(gtkconv->imhtml), "transient_chat"); +#endif } else { node = (PurpleBlistNode*)(purple_find_buddy(conv->account, conv->name)); - } - - if (node) +#if 0 + if (!node) + node = g_object_get_data(G_OBJECT(gtkconv->imhtml), "transient_buddy"); +#endif + } + + if (node) pidgin_blist_draw_tooltip(node, gtkconv->infopane); return FALSE; } -static void +static gboolean pidgin_conv_leave_cb (GtkWidget *w, GdkEventCrossing *e, PidginConversation *gtkconv) { pidgin_blist_tooltip_destroy(); reset_tooltip(); + return FALSE; } static gboolean @@ -5020,6 +5067,13 @@ g_signal_connect(G_OBJECT(gtkconv->entry), "drag_data_received", G_CALLBACK(conv_dnd_recv), gtkconv); + gtk_text_buffer_create_tag(GTK_IMHTML(gtkconv->imhtml)->text_buffer, "TYPING-NOTIFICATION", + "foreground", "#888888", + "justification", GTK_JUSTIFY_LEFT, /* XXX: RTL'ify */ + "weight", PANGO_WEIGHT_BOLD, + "scale", PANGO_SCALE_SMALL, + NULL); + /* Setup the container for the tab. */ gtkconv->tab_cont = tab_cont = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); g_object_set_data(G_OBJECT(tab_cont), "PidginConversation", gtkconv); @@ -5592,6 +5646,7 @@ } else { if (purple_message_meify(new_message, -1)) { + GdkColor *col; str = g_malloc(1024); if (flags & PURPLE_MESSAGE_AUTO_RESP) { @@ -5604,9 +5659,20 @@ } if (flags & PURPLE_MESSAGE_NICK) - strcpy(color, HIGHLIGHT_COLOR); + gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "highlight-name-color", &col, NULL); else - strcpy(color, "#062585"); + gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "action-name-color", &col, NULL); + + if(col) { + g_snprintf(color, sizeof(color), "#%02X%02X%02X", + col->red >> 8, col->green >> 8, col->blue >> 8); + } + else { + if (flags & PURPLE_MESSAGE_NICK) + strcpy(color, DEFAULT_HIGHLIGHT_COLOR); + else + strcpy(color, DEFAULT_ACTION_COLOR); + } } else { str = g_malloc(1024); @@ -5618,19 +5684,46 @@ g_snprintf(str, 1024, "%s:", alias_escaped); tag_end_offset = 1; } - if (flags & PURPLE_MESSAGE_NICK) - strcpy(color, HIGHLIGHT_COLOR); + if (flags & PURPLE_MESSAGE_NICK) { + GdkColor *col; + gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "highlight-name-color", &col, NULL); + if(col) { + g_snprintf(color, sizeof(color), "#%02X%02X%02X", + col->red >> 8, col->green >> 8, col->blue >> 8); + } + else { + strcpy(color, DEFAULT_HIGHLIGHT_COLOR); + } + } else if (flags & PURPLE_MESSAGE_RECV) { if (type == PURPLE_CONV_TYPE_CHAT) { GdkColor *col = get_nick_color(gtkconv, name); g_snprintf(color, sizeof(color), "#%02X%02X%02X", col->red >> 8, col->green >> 8, col->blue >> 8); - } else - strcpy(color, RECV_COLOR); + } else { + GdkColor *col; + gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "receive-name-color", &col, NULL); + if(col) { + g_snprintf(color, sizeof(color), "#%02X%02X%02X", + col->red >> 8, col->green >> 8, col->blue >> 8); + } + else { + strcpy(color, DEFAULT_RECV_COLOR); + } + } } - else if (flags & PURPLE_MESSAGE_SEND) - strcpy(color, SEND_COLOR); + else if (flags & PURPLE_MESSAGE_SEND) { + GdkColor *col; + gtk_widget_style_get(GTK_WIDGET(gtkconv->imhtml), "send-name-color", &col, NULL); + if(col) { + g_snprintf(color, sizeof(color), "#%02X%02X%02X", + col->red >> 8, col->green >> 8, col->blue >> 8); + } + else { + strcpy(color, DEFAULT_SEND_COLOR); + } + } else { purple_debug_error("gtkconv", "message missing flags\n"); strcpy(color, "#000000"); @@ -5751,6 +5844,7 @@ (type == PURPLE_CONV_TYPE_IM ? "displayed-im-msg" : "displayed-chat-msg"), account, name, displaying, conv, flags); g_free(displaying); + update_typing_message(gtkconv, NULL); } static void @@ -7477,10 +7571,13 @@ purple_conversation_set_data(conv, "unseen-count", NULL); purple_conversation_set_data(conv, "unseen-state", NULL); purple_conversation_set_ui_ops(conv, pidgin_conversations_get_conv_ui_ops()); - private_gtkconv_new(conv, FALSE); + if (!PIDGIN_CONVERSATION(conv)) + private_gtkconv_new(conv, FALSE); timer = GPOINTER_TO_INT(purple_conversation_get_data(conv, "close-timer")); - if (timer) + if (timer) { purple_timeout_remove(timer); + purple_conversation_set_data(conv, "close-timer", NULL); + } } gboolean pidgin_conv_attach_to_conversation(PurpleConversation *conv) @@ -7489,6 +7586,7 @@ PidginConversation *gtkconv; if (PIDGIN_IS_PIDGIN_CONVERSATION(conv)) { + /* This is pretty much always the case now. */ gtkconv = PIDGIN_CONVERSATION(conv); if (gtkconv->win != hidden_convwin) return FALSE; @@ -7496,6 +7594,11 @@ pidgin_conv_placement_place(gtkconv); purple_signal_emit(pidgin_conversations_get_handle(), "conversation-displayed", gtkconv); + list = gtkconv->convs; + while (list) { + pidgin_conv_attach(list->data); + list = list->next; + } return TRUE; } @@ -7814,6 +7917,7 @@ /* Set default tab colors */ GString *str = g_string_new(NULL); GtkSettings *settings = gtk_settings_get_default(); + GtkStyle *parent = gtk_rc_get_style_by_paths(settings, "tab-container.tab-label*", NULL, G_TYPE_NONE), *now; struct { const char *stylename; const char *labelname; @@ -7828,8 +7932,9 @@ }; int iter; for (iter = 0; styles[iter].stylename; iter++) { - if (!gtk_rc_get_style_by_paths(settings, styles[iter].labelname, NULL, G_TYPE_NONE)) - /* Apparently both ACTIVE and NORMAL are required */ + now = gtk_rc_get_style_by_paths(settings, styles[iter].labelname, NULL, G_TYPE_NONE); + if (parent == now || + (parent && now && parent->rc_style == now->rc_style)) { g_string_append_printf(str, "style \"%s\" {\n" "fg[ACTIVE] = \"%s\"\n" "}\n" @@ -7837,6 +7942,7 @@ styles[iter].stylename, styles[iter].color, styles[iter].labelname, styles[iter].stylename); + } } gtk_rc_parse_string(str->str); g_string_free(str, TRUE); @@ -8259,7 +8365,7 @@ } if (e->button == 3) { - /* Right click was pressed. Popup the Send To menu. */ + /* Right click was pressed. Popup the context menu. */ GtkWidget *menu = gtk_menu_new(), *sub; gboolean populated = populate_menu_with_options(menu, gtkconv, TRUE); sub = gtk_menu_item_get_submenu(GTK_MENU_ITEM(gtkconv->win->menu.send_to)); @@ -9237,6 +9343,7 @@ gtkconv->tabby = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); else gtkconv->tabby = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); + gtk_widget_set_name(gtkconv->tabby, "tab-container"); /* select the correct ordering for verticle tabs */ if (angle == 90) { @@ -9904,8 +10011,8 @@ GdkColor send_color; time_t breakout_time; - gdk_color_parse(HIGHLIGHT_COLOR, &nick_highlight); - gdk_color_parse(SEND_COLOR, &send_color); + gdk_color_parse(DEFAULT_HIGHLIGHT_COLOR, &nick_highlight); + gdk_color_parse(DEFAULT_SEND_COLOR, &send_color); srand(background.red + background.green + background.blue + 1);
--- a/pidgin/gtkdebug.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkdebug.c Sat Dec 22 23:18:08 2007 +0000 @@ -985,6 +985,9 @@ REGISTER_G_LOG_HANDLER("GModule"); REGISTER_G_LOG_HANDLER("GLib-GObject"); REGISTER_G_LOG_HANDLER("GThread"); +#ifdef USE_GSTREAMER + REGISTER_G_LOG_HANDLER("GStreamer"); +#endif #ifdef _WIN32 if (!purple_debug_is_enabled())
--- a/pidgin/gtkdialogs.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkdialogs.c Sat Dec 22 23:18:08 2007 +0000 @@ -89,6 +89,7 @@ {"Megan 'Cae' Schneider", N_("support/QA"), NULL}, {"Evan Schoenberg", N_("developer"), NULL}, {"Kevin 'SimGuy' Stange", N_("developer & webmaster"), NULL}, + {"Will 'resiak' Thompson", N_("developer"), NULL}, {"Stu 'nosnilmot' Tomlinson", N_("developer"), NULL}, {"Nathan 'faceprint' Walp", N_("developer"), NULL}, {NULL, NULL, NULL} @@ -98,8 +99,8 @@ static const struct developer patch_writers[] = { {"Dennis 'EvilDennisR' Ristuccia", N_("Senior Contributor/QA"), NULL}, {"Peter 'Fmoo' Ruibal", NULL, NULL}, + {"Elliott 'QuLogic' Sales de Andrade", NULL, NULL}, {"Gabriel 'Nix' Schulhof", NULL, NULL}, - {"Will 'resiak' Thompson", NULL, NULL}, {NULL, NULL, NULL} };
--- a/pidgin/gtkdocklet-x11.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkdocklet-x11.c Sat Dec 22 23:18:08 2007 +0000 @@ -79,13 +79,14 @@ g_idle_add(docklet_x11_recreate_cb, NULL); } -static void +static gboolean docklet_x11_clicked_cb(GtkWidget *button, GdkEventButton *event, void *data) { if (event->type != GDK_BUTTON_RELEASE) - return; + return FALSE; pidgin_docklet_clicked(event->button); + return TRUE; } static void
--- a/pidgin/gtkft.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkft.c Sat Dec 22 23:18:08 2007 +0000 @@ -745,7 +745,6 @@ PidginXferDialog *dialog; GtkWidget *window; GtkWidget *vbox1, *vbox2; - GtkWidget *bbox; GtkWidget *sw; GtkWidget *button; GtkWidget *expander; @@ -759,15 +758,13 @@ purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/filetransfer/clear_finished"); /* Create the window. */ - dialog->window = window = pidgin_create_window(_("File Transfers"), PIDGIN_HIG_BORDER, "file transfer", TRUE); + dialog->window = window = pidgin_create_dialog(_("File Transfers"), PIDGIN_HIG_BORDER, "file transfer", TRUE); g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_win_cb), dialog); /* Create the parent vbox for everything. */ - vbox1 = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(window), vbox1); - gtk_widget_show(vbox1); + vbox1 = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(window), FALSE, PIDGIN_HIG_BORDER); /* Create the main vbox for top half of the window. */ vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); @@ -812,71 +809,35 @@ gtk_container_add(GTK_CONTAINER(expander), table); gtk_widget_show(table); - /* Now the button box for the buttons */ - bbox = gtk_hbutton_box_new(); - gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_end(GTK_BOX(vbox1), bbox, FALSE, TRUE, 0); - gtk_widget_show(bbox); - /* Open button */ - button = gtk_button_new_from_stock(GTK_STOCK_OPEN); + button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_OPEN, G_CALLBACK(open_button_cb), dialog); gtk_widget_set_sensitive(button, FALSE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); dialog->open_button = button; - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(open_button_cb), dialog); - /* Pause button */ - button = gtk_button_new_with_mnemonic(_("_Pause")); + button = pidgin_dialog_add_button(GTK_DIALOG(window), _("_Pause"), G_CALLBACK(pause_button_cb), dialog); gtk_widget_set_sensitive(button, FALSE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); dialog->pause_button = button; - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(pause_button_cb), dialog); - /* Resume button */ - button = gtk_button_new_with_mnemonic(_("_Resume")); + button = pidgin_dialog_add_button(GTK_DIALOG(window), _("_Resume"), G_CALLBACK(resume_button_cb), dialog); gtk_widget_set_sensitive(button, FALSE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); dialog->resume_button = button; - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(resume_button_cb), dialog); - /* Remove button */ - button = gtk_button_new_from_stock(GTK_STOCK_REMOVE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_REMOVE, G_CALLBACK(remove_button_cb), dialog); gtk_widget_hide(button); dialog->remove_button = button; - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(remove_button_cb), dialog); - /* Stop button */ - button = gtk_button_new_from_stock(GTK_STOCK_STOP); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); + button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_STOP, G_CALLBACK(stop_button_cb), dialog); gtk_widget_set_sensitive(button, FALSE); dialog->stop_button = button; - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(stop_button_cb), dialog); - /* Close button */ - button = gtk_button_new_from_stock(GTK_STOCK_CLOSE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); + button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, G_CALLBACK(close_button_cb), dialog); dialog->close_button = button; - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(close_button_cb), dialog); - #ifdef _WIN32 g_signal_connect(G_OBJECT(dialog->window), "show", G_CALLBACK(winpidgin_ensure_onscreen), dialog->window);
--- a/pidgin/gtkimhtml.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkimhtml.c Sat Dec 22 23:18:08 2007 +0000 @@ -812,7 +812,6 @@ static void hijack_menu_cb(GtkIMHtml *imhtml, GtkMenu *menu, gpointer data) { GtkWidget *menuitem; - GtkWidget *mi, *img; menuitem = gtk_menu_item_new_with_mnemonic(_("Paste as Plain _Text")); gtk_widget_show(menuitem); @@ -1012,7 +1011,7 @@ static void imhtml_paste_insert(GtkIMHtml *imhtml, const char *text, gboolean plaintext) { GtkTextIter iter; - GtkIMHtmlOptions flags = plaintext ? 0 : (GTK_IMHTML_NO_NEWLINE | GTK_IMHTML_NO_COMMENTS); + GtkIMHtmlOptions flags = plaintext ? GTK_IMHTML_NO_SMILEY : (GTK_IMHTML_NO_NEWLINE | GTK_IMHTML_NO_COMMENTS); if (gtk_text_buffer_get_selection_bounds(imhtml->text_buffer, NULL, NULL)) gtk_text_buffer_delete_selection(imhtml->text_buffer, TRUE, TRUE); @@ -1136,14 +1135,13 @@ #ifdef _WIN32 /* If we're on windows, let's see if we can get data from the HTML Format clipboard before we try to paste from the GTK buffer */ - if (!clipboard_paste_html_win32(imhtml)) { + if (!clipboard_paste_html_win32(imhtml)) #endif + { GtkClipboard *clipboard = gtk_widget_get_clipboard(GTK_WIDGET(imhtml), GDK_SELECTION_CLIPBOARD); gtk_clipboard_request_contents(clipboard, gdk_atom_intern("text/html", FALSE), paste_received_cb, imhtml); -#ifdef _WIN32 } -#endif g_signal_stop_emission_by_name(imhtml, "paste-clipboard"); } @@ -1397,6 +1395,22 @@ _("Hyperlink prelight color"), _("Color to draw hyperlinks when mouse is over them."), GDK_TYPE_COLOR, G_PARAM_READABLE)); + gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("send-name-color", + _("Sent Message Name Color"), + _("Color to draw the name of a message you sent."), + GDK_TYPE_COLOR, G_PARAM_READABLE)); + gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("receive-name-color", + _("Received Message Name Color"), + _("Color to draw the name of a message you received."), + GDK_TYPE_COLOR, G_PARAM_READABLE)); + gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("highlight-name-color", + _("\"Attention\" Name Color"), + _("Color to draw the name of a message you received containing your name."), + GDK_TYPE_COLOR, G_PARAM_READABLE)); + gtk_widget_class_install_style_property(widget_class, g_param_spec_boxed("action-name-color", + _("Action Message Name Color"), + _("Color to draw the name of an action message."), + GDK_TYPE_COLOR, G_PARAM_READABLE)); binding_set = gtk_binding_set_by_class (parent_class); gtk_binding_entry_add_signal (binding_set, GDK_b, GDK_CONTROL_MASK, "format_function_toggle", 1, G_TYPE_INT, GTK_IMHTML_BOLD); @@ -2982,6 +2996,7 @@ pos += tlen; g_free(tag); /* This was allocated back in VALID_TAG() */ } else if (imhtml->edit.link == NULL && + !(options & GTK_IMHTML_NO_SMILEY) && gtk_imhtml_is_smiley(imhtml, fonts, c, &smilelen)) { GtkIMHtmlFontDetail *fd; gchar *sml = NULL; @@ -3467,6 +3482,9 @@ return; } #endif /* FILECHOOSER */ +#if 0 /* mismatched curly braces */ + } +#endif /* * XXX - We should probably prompt the user to determine if they really
--- a/pidgin/gtkimhtml.h Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkimhtml.h Sat Dec 22 23:18:08 2007 +0000 @@ -225,7 +225,8 @@ GTK_IMHTML_RETURN_LOG = 1 << 7, GTK_IMHTML_USE_POINTSIZE = 1 << 8, GTK_IMHTML_NO_FORMATTING = 1 << 9, - GTK_IMHTML_USE_SMOOTHSCROLLING = 1 << 10 + GTK_IMHTML_USE_SMOOTHSCROLLING = 1 << 10, + GTK_IMHTML_NO_SMILEY = 1 << 11, } GtkIMHtmlOptions; enum {
--- a/pidgin/gtkimhtmltoolbar.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkimhtmltoolbar.c Sat Dec 22 23:18:08 2007 +0000 @@ -99,7 +99,7 @@ gtk_widget_grab_focus(toolbar->imhtml); } -static void +static gboolean destroy_toolbar_font(GtkWidget *widget, GdkEvent *event, GtkIMHtmlToolbar *toolbar) { @@ -111,6 +111,7 @@ gtk_widget_destroy(toolbar->font_dialog); toolbar->font_dialog = NULL; } + return FALSE; } static void @@ -191,7 +192,7 @@ gtk_widget_grab_focus(toolbar->imhtml); } -static void +static gboolean destroy_toolbar_fgcolor(GtkWidget *widget, GdkEvent *event, GtkIMHtmlToolbar *toolbar) { @@ -203,6 +204,7 @@ gtk_widget_destroy(toolbar->fgcolor_dialog); toolbar->fgcolor_dialog = NULL; } + return FALSE; } static void cancel_toolbar_fgcolor(GtkWidget *widget, @@ -263,7 +265,7 @@ gtk_widget_grab_focus(toolbar->imhtml); } -static void +static gboolean destroy_toolbar_bgcolor(GtkWidget *widget, GdkEvent *event, GtkIMHtmlToolbar *toolbar) { @@ -279,6 +281,7 @@ gtk_widget_destroy(toolbar->bgcolor_dialog); toolbar->bgcolor_dialog = NULL; } + return FALSE; } static void @@ -467,10 +470,11 @@ GtkTextMark *ins; #if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */ - if (response != GTK_RESPONSE_ACCEPT) { + if (response != GTK_RESPONSE_ACCEPT) #else /* FILECHOOSER */ - if (response != GTK_RESPONSE_OK) { + if (response != GTK_RESPONSE_OK) #endif /* FILECHOOSER */ + { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar->image), FALSE); return; } @@ -574,11 +578,12 @@ } } -static void +static gboolean close_smiley_dialog(GtkWidget *widget, GdkEvent *event, GtkIMHtmlToolbar *toolbar) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toolbar->smiley), FALSE); + return FALSE; } @@ -1096,8 +1101,8 @@ {PIDGIN_STOCK_TOOLBAR_TEXT_SMALLER, do_small, &toolbar->smaller_size, _("Decrease Font Size")}, {"", NULL, NULL, NULL}, {PIDGIN_STOCK_TOOLBAR_FONT_FACE, toggle_font, &toolbar->font, _("Font Face")}, - {PIDGIN_STOCK_TOOLBAR_FGCOLOR, toggle_bg_color, &toolbar->bgcolor, _("Background Color")}, - {PIDGIN_STOCK_TOOLBAR_BGCOLOR, toggle_fg_color, &toolbar->fgcolor, _("Foreground Color")}, + {PIDGIN_STOCK_TOOLBAR_BGCOLOR, toggle_bg_color, &toolbar->bgcolor, _("Background Color")}, + {PIDGIN_STOCK_TOOLBAR_FGCOLOR, toggle_fg_color, &toolbar->fgcolor, _("Foreground Color")}, {"", NULL, NULL, NULL}, {PIDGIN_STOCK_CLEAR, clear_formatting_cb, &toolbar->clear, _("Reset Formatting")}, {"", NULL, NULL, NULL},
--- a/pidgin/gtkmain.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkmain.c Sat Dec 22 23:18:08 2007 +0000 @@ -397,6 +397,7 @@ if (terse) { text = g_strdup_printf(_("%s %s. Try `%s -h' for more information.\n"), PIDGIN_NAME, DISPLAY_VERSION, name); } else { +#ifndef WIN32 text = g_strdup_printf(_("%s %s\n" "Usage: %s [OPTION]...\n\n" " -c, --config=DIR use DIR for config files\n" @@ -406,10 +407,20 @@ " -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" -#ifndef WIN32 " --display=DISPLAY X display to use\n" + " -v, --version display the current version and exit\n"), PIDGIN_NAME, DISPLAY_VERSION, name); +#else + text = g_strdup_printf(_("%s %s\n" + "Usage: %s [OPTION]...\n\n" + " -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" + " -v, --version display the current version and exit\n"), PIDGIN_NAME, DISPLAY_VERSION, name); #endif - " -v, --version display the current version and exit\n"), PIDGIN_NAME, DISPLAY_VERSION, name); } purple_print_utf8_to_console(stdout, text);
--- a/pidgin/gtknotify.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtknotify.c Sat Dec 22 23:18:08 2007 +0000 @@ -166,16 +166,18 @@ mail_dialog = NULL; } -static void +static gboolean formatted_close_cb(GtkWidget *win, GdkEvent *event, void *user_data) { purple_notify_close(PURPLE_NOTIFY_FORMATTED, win); + return FALSE; } -static void +static gboolean searchresults_close_cb(PidginNotifySearchResultsData *data, GdkEvent *event, gpointer user_data) { purple_notify_close(PURPLE_NOTIFY_SEARCHRESULTS, data); + return FALSE; } static void @@ -284,6 +286,8 @@ gtk_misc_set_alignment(GTK_MISC(label), 0, 0); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + pidgin_auto_parent_window(dialog); + gtk_widget_show_all(dialog); return dialog; @@ -684,6 +688,8 @@ g_object_set_data(G_OBJECT(window), "info-widget", imhtml); /* Show the window */ + pidgin_auto_parent_window(window); + gtk_widget_show(window); return window; @@ -894,6 +900,8 @@ pidgin_notify_searchresults_new_rows(gc, results, data); /* Show the window */ + pidgin_auto_parent_window(window); + gtk_widget_show(window); return data; }
--- a/pidgin/gtkpounce.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkpounce.c Sat Dec 22 23:18:08 2007 +0000 @@ -1317,7 +1317,6 @@ pidgin_pounces_manager_show(void) { PouncesManager *dialog; - GtkWidget *bbox; GtkWidget *button; GtkWidget *list; GtkWidget *vbox; @@ -1334,7 +1333,7 @@ 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 = pidgin_create_window(_("Buddy Pounces"), PIDGIN_HIG_BORDER, "pounces", TRUE); + dialog->window = win = pidgin_create_dialog(_("Buddy Pounces"), PIDGIN_HIG_BORDER, "pounces", TRUE); gtk_window_set_default_size(GTK_WINDOW(win), width, height); g_signal_connect(G_OBJECT(win), "delete_event", @@ -1343,61 +1342,33 @@ G_CALLBACK(pounces_manager_configure_cb), dialog); /* Setup the vbox */ - vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(win), vbox); - gtk_widget_show(vbox); + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER); /* List of saved buddy pounces */ list = create_pounces_list(dialog); gtk_box_pack_start(GTK_BOX(vbox), list, TRUE, TRUE, 0); - /* Button box. */ - bbox = gtk_hbutton_box_new(); - gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0); - gtk_widget_show(bbox); + /* Add button */ + button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_ADD, G_CALLBACK(pounces_manager_add_cb), dialog); + gtk_widget_set_sensitive(button, (purple_accounts_get_all() != NULL)); - /* Add button */ - button = gtk_button_new_from_stock(GTK_STOCK_ADD); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_set_sensitive(button, (purple_accounts_get_all() != NULL)); purple_signal_connect(purple_connections_get_handle(), "signed-on", pounces_manager, PURPLE_CALLBACK(pounces_manager_connection_cb), button); purple_signal_connect(purple_connections_get_handle(), "signed-off", pounces_manager, PURPLE_CALLBACK(pounces_manager_connection_cb), button); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(pounces_manager_add_cb), dialog); /* Modify button */ - button = gtk_button_new_from_stock(PIDGIN_STOCK_MODIFY); - dialog->modify_button = button; - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + button = pidgin_dialog_add_button(GTK_DIALOG(win), PIDGIN_STOCK_MODIFY, G_CALLBACK(pounces_manager_modify_cb), dialog); gtk_widget_set_sensitive(button, FALSE); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(pounces_manager_modify_cb), dialog); + dialog->modify_button = button; /* Delete button */ - button = gtk_button_new_from_stock(GTK_STOCK_DELETE); - dialog->delete_button = button; - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_DELETE, G_CALLBACK(pounces_manager_delete_cb), dialog); gtk_widget_set_sensitive(button, FALSE); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(pounces_manager_delete_cb), dialog); + dialog->delete_button = button; /* Close button */ - button = gtk_button_new_from_stock(GTK_STOCK_CLOSE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(pounces_manager_close_cb), dialog); + pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE, G_CALLBACK(pounces_manager_close_cb), dialog); gtk_widget_show(win); }
--- a/pidgin/gtkprefs.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkprefs.c Sat Dec 22 23:18:08 2007 +0000 @@ -2150,7 +2150,6 @@ void pidgin_prefs_show(void) { GtkWidget *vbox; - GtkWidget *bbox; GtkWidget *notebook; GtkWidget *button; @@ -2166,31 +2165,20 @@ /* Back to instant-apply! I win! BU-HAHAHA! */ /* Create the window */ - prefs = pidgin_create_window(_("Preferences"), PIDGIN_HIG_BORDER, "preferences", FALSE); + prefs = pidgin_create_dialog(_("Preferences"), PIDGIN_HIG_BORDER, "preferences", FALSE); g_signal_connect(G_OBJECT(prefs), "destroy", G_CALLBACK(delete_prefs), NULL); - vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(prefs), vbox); - gtk_widget_show(vbox); + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(prefs), FALSE, PIDGIN_HIG_BORDER); /* The notebook */ prefsnotebook = notebook = gtk_notebook_new (); gtk_box_pack_start (GTK_BOX (vbox), notebook, FALSE, FALSE, 0); gtk_widget_show(prefsnotebook); - /* The buttons to press! */ - bbox = gtk_hbutton_box_new(); - gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0); - gtk_widget_show (bbox); - - button = gtk_button_new_from_stock (GTK_STOCK_CLOSE); + button = pidgin_dialog_add_button(GTK_DIALOG(prefs), GTK_STOCK_CLOSE, NULL, NULL); g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(gtk_widget_destroy), prefs); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); prefs_notebook_init();
--- a/pidgin/gtkprivacy.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkprivacy.c Sat Dec 22 23:18:08 2007 +0000 @@ -45,6 +45,7 @@ GtkWidget *add_button; GtkWidget *remove_button; GtkWidget *clear_button; + GtkWidget *close_button; GtkWidget *button_box; GtkWidget *allow_widget; @@ -259,19 +260,22 @@ gtk_widget_hide(dialog->allow_widget); gtk_widget_hide(dialog->block_widget); - gtk_widget_hide(dialog->button_box); + gtk_widget_hide_all(dialog->button_box); if (new_type == PURPLE_PRIVACY_ALLOW_USERS) { gtk_widget_show(dialog->allow_widget); - gtk_widget_show(dialog->button_box); + gtk_widget_show_all(dialog->button_box); dialog->in_allow_list = TRUE; } else if (new_type == PURPLE_PRIVACY_DENY_USERS) { gtk_widget_show(dialog->block_widget); - gtk_widget_show(dialog->button_box); + gtk_widget_show_all(dialog->button_box); dialog->in_allow_list = FALSE; } + gtk_widget_show_all(dialog->close_button); + gtk_widget_show(dialog->button_box); + purple_blist_schedule_save(); pidgin_blist_refresh(purple_get_blist()); } @@ -355,7 +359,6 @@ privacy_dialog_new(void) { PidginPrivacyDialog *dialog; - GtkWidget *bbox; GtkWidget *hbox; GtkWidget *vbox; GtkWidget *button; @@ -367,15 +370,13 @@ dialog = g_new0(PidginPrivacyDialog, 1); - dialog->win = pidgin_create_window(_("Privacy"), PIDGIN_HIG_BORDER, "privacy", TRUE); + dialog->win = pidgin_create_dialog(_("Privacy"), PIDGIN_HIG_BORDER, "privacy", TRUE); g_signal_connect(G_OBJECT(dialog->win), "delete_event", G_CALLBACK(destroy_cb), dialog); /* Main vbox */ - vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(dialog->win), vbox); - gtk_widget_show(vbox); + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(dialog->win), FALSE, PIDGIN_HIG_BORDER); /* Description label */ label = gtk_label_new( @@ -433,52 +434,27 @@ gtk_box_pack_start(GTK_BOX(vbox), dialog->block_widget, TRUE, TRUE, 0); /* Add the button box for Add, Remove, Clear */ - dialog->button_box = bbox = gtk_hbutton_box_new(); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_SPREAD); - gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0); + dialog->button_box = pidgin_dialog_get_action_area(GTK_DIALOG(dialog->win)); /* Add button */ - button = gtk_button_new_from_stock(GTK_STOCK_ADD); + button = pidgin_dialog_add_button(GTK_DIALOG(dialog->win), GTK_STOCK_ADD, G_CALLBACK(add_cb), dialog); dialog->add_button = button; - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(add_cb), dialog); /* Remove button */ - button = gtk_button_new_from_stock(GTK_STOCK_REMOVE); + button = pidgin_dialog_add_button(GTK_DIALOG(dialog->win), GTK_STOCK_REMOVE, G_CALLBACK(remove_cb), dialog); dialog->remove_button = button; gtk_widget_set_sensitive(button, FALSE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(remove_cb), dialog); /* Clear button */ - button = gtk_button_new_from_stock(GTK_STOCK_CLEAR); + button = pidgin_dialog_add_button(GTK_DIALOG(dialog->win), GTK_STOCK_CLEAR, G_CALLBACK(clear_cb), dialog); dialog->clear_button = button; - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(clear_cb), dialog); - - /* Another button box. */ - bbox = gtk_hbutton_box_new(); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0); - gtk_widget_show(bbox); /* Close button */ - button = gtk_button_new_from_stock(GTK_STOCK_CLOSE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); + button = pidgin_dialog_add_button(GTK_DIALOG(dialog->win), GTK_STOCK_CLOSE, G_CALLBACK(close_cb), dialog); + dialog->close_button = button; - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(close_cb), dialog); - + type_changed_cb(GTK_OPTION_MENU(dialog->type_menu), dialog); +#if 0 if (dialog->account->perm_deny == PURPLE_PRIVACY_ALLOW_USERS) { gtk_widget_show(dialog->allow_widget); gtk_widget_show(dialog->button_box); @@ -489,7 +465,7 @@ gtk_widget_show(dialog->button_box); dialog->in_allow_list = FALSE; } - +#endif return dialog; }
--- a/pidgin/gtkrequest.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkrequest.c Sat Dec 22 23:18:08 2007 +0000 @@ -251,11 +251,12 @@ purple_request_close(PURPLE_REQUEST_FIELDS, data); } -static void +static gboolean destroy_multifield_cb(GtkWidget *dialog, GdkEvent *event, PidginRequestData *data) { multifield_cancel_cb(NULL, data); + return FALSE; } @@ -439,6 +440,8 @@ pidgin_set_accessible_label (entry, label); data->u.input.entry = entry; + pidgin_auto_parent_window(dialog); + /* Show everything. */ gtk_widget_show(dialog); @@ -546,6 +549,8 @@ g_object_set_data(G_OBJECT(dialog), "radio", radio); /* Show everything. */ + pidgin_auto_parent_window(dialog); + gtk_widget_show_all(dialog); return data; @@ -661,6 +666,8 @@ gtk_dialog_set_default_response(GTK_DIALOG(dialog), default_action); /* Show everything. */ + pidgin_auto_parent_window(dialog); + gtk_widget_show_all(dialog); return data; @@ -1059,7 +1066,6 @@ GtkWidget *vbox; GtkWidget *vbox2; GtkWidget *hbox; - GtkWidget *bbox; GtkWidget *frame; GtkWidget *label; GtkWidget *table; @@ -1089,9 +1095,9 @@ #ifdef _WIN32 - data->dialog = win = pidgin_create_window(PIDGIN_ALERT_TITLE, PIDGIN_HIG_BORDER, "multifield", TRUE) ; + data->dialog = win = pidgin_create_dialog(PIDGIN_ALERT_TITLE, PIDGIN_HIG_BORDER, "multifield", TRUE) ; #else /* !_WIN32 */ - data->dialog = win = pidgin_create_window(title, PIDGIN_HIG_BORDER, "multifield", TRUE) ; + data->dialog = win = pidgin_create_dialog(title, PIDGIN_HIG_BORDER, "multifield", TRUE) ; #endif /* _WIN32 */ g_signal_connect(G_OBJECT(win), "delete_event", @@ -1099,7 +1105,7 @@ /* Setup the main horizontal box */ hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(win), hbox); + gtk_container_add(GTK_CONTAINER(pidgin_dialog_get_vbox(GTK_DIALOG(win))), hbox); gtk_widget_show(hbox); /* Dialog icon. */ @@ -1382,39 +1388,21 @@ g_object_unref(sg); - /* Button box. */ - bbox = gtk_hbutton_box_new(); - gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0); - gtk_widget_show(bbox); - /* Cancel button */ - button = gtk_button_new_from_stock(text_to_stock(cancel_text)); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(multifield_cancel_cb), data); - + button = pidgin_dialog_add_button(GTK_DIALOG(win), text_to_stock(cancel_text), G_CALLBACK(multifield_cancel_cb), data); GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); /* OK button */ - button = gtk_button_new_from_stock(text_to_stock(ok_text)); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_show(button); - + button = pidgin_dialog_add_button(GTK_DIALOG(win), text_to_stock(ok_text), G_CALLBACK(multifield_ok_cb), data); data->ok_button = button; - GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); gtk_window_set_default(GTK_WINDOW(win), button); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(multifield_ok_cb), data); - if (!purple_request_fields_all_required_filled(fields)) gtk_widget_set_sensitive(button, FALSE); + pidgin_auto_parent_window(win); + gtk_widget_show(win); return data; @@ -1502,7 +1490,9 @@ } #endif /* FILECHOOSER */ - +#if 0 /* mismatched curly braces */ + } +#endif if ((data->u.file.savedialog == TRUE) && (g_file_test(data->u.file.name, G_FILE_TEST_EXISTS))) { purple_request_action(data, NULL, _("That file already exists"), @@ -1516,7 +1506,7 @@ } #if !GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */ -static void +static gboolean file_cancel_cb(PidginRequestData *data) { generic_response_start(data); @@ -1525,6 +1515,7 @@ ((PurpleRequestFileCb)data->cbs[0])(data->user_data, NULL); purple_request_close(data->type, data); + return FALSE; } #endif /* FILECHOOSER */ @@ -1622,6 +1613,8 @@ G_CALLBACK(file_ok_check_if_exists_cb), data); #endif /* FILECHOOSER */ + pidgin_auto_parent_window(filesel); + data->dialog = filesel; gtk_widget_show(filesel); @@ -1673,6 +1666,8 @@ #endif data->dialog = dirsel; + pidgin_auto_parent_window(dirsel); + gtk_widget_show(dirsel); return (void *)data;
--- a/pidgin/gtkroomlist.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkroomlist.c Sat Dec 22 23:18:08 2007 +0000 @@ -28,6 +28,8 @@ #include "pidgin.h" #include "gtkutils.h" #include "pidginstock.h" +#include "pidgintooltip.h" + #include "debug.h" #include "account.h" #include "connection.h" @@ -340,33 +342,14 @@ } } -static void pidgin_roomlist_tooltip_destroy(PidginRoomlist *grl) -{ - if ((grl == NULL) || (grl->tipwindow == NULL)) - return; - - gtk_widget_destroy(grl->tipwindow); - grl->tipwindow = NULL; -} - -static void pidgin_roomlist_tooltip_destroy_cb(GObject *object, PidginRoomlist *grl) -{ - if ((grl == NULL) || (grl->tipwindow == NULL)) - return; - - if (grl->timeout) - g_source_remove(grl->timeout); - grl->timeout = 0; - - pidgin_roomlist_tooltip_destroy(grl); -} - #define SMALL_SPACE 6 #define TOOLTIP_BORDER 12 -static void pidgin_roomlist_paint_tip(GtkWidget *widget, GdkEventExpose *event, gpointer user_data) +static gboolean +pidgin_roomlist_paint_tooltip(GtkWidget *widget, gpointer user_data) { - PidginRoomlist *grl = (PidginRoomlist *)user_data; + PurpleRoomlist *list = user_data; + PidginRoomlist *grl = list->ui_data; GtkStyle *style; int current_height, max_width; int max_text_width; @@ -404,15 +387,13 @@ current_height + grl->tip_name_height, grl->tip_layout); } - + return FALSE; } -static gboolean pidgin_roomlist_create_tip(PurpleRoomlist *list) +static gboolean pidgin_roomlist_create_tip(PurpleRoomlist *list, GtkTreePath *path) { PidginRoomlist *grl = list->ui_data; - GtkWidget *tv = grl->tree; PurpleRoomlistRoom *room; - GtkTreePath *path; GtkTreeIter iter; GValue val; gchar *name, *tmp, *node_name; @@ -421,10 +402,11 @@ gint j; gboolean first = TRUE; +#if 0 if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), grl->tip_rect.x, grl->tip_rect.y + (grl->tip_rect.height/2), &path, NULL, NULL, NULL)) return FALSE; - +#endif gtk_tree_model_get_iter(GTK_TREE_MODEL(grl->model), &iter, path); val.g_type = 0; @@ -460,8 +442,6 @@ g_free(label); } - gtk_tree_path_free(path); - grl->tip_layout = gtk_widget_create_pango_layout(grl->tipwindow, NULL); grl->tip_name_layout = gtk_widget_create_pango_layout(grl->tipwindow, NULL); @@ -492,156 +472,22 @@ return TRUE; } -static void pidgin_roomlist_draw_tooltip(PurpleRoomlist *list, GtkWidget *widget) +static gboolean +pidgin_roomlist_create_tooltip(GtkWidget *widget, GtkTreePath *path, + gpointer data, int *w, int *h) { + PurpleRoomlist *list = data; PidginRoomlist *grl = list->ui_data; - int scr_w, scr_h, w, h, x, y; -#if GTK_CHECK_VERSION(2,2,0) - int mon_num; - GdkScreen *screen = NULL; -#endif - GdkRectangle mon_size; - int sig; - const char *name; - - pidgin_roomlist_tooltip_destroy(grl); - grl->tipwindow = gtk_window_new(GTK_WINDOW_POPUP); - gtk_widget_ensure_style (grl->tipwindow); - - if (!pidgin_roomlist_create_tip(list)) { - pidgin_roomlist_tooltip_destroy(grl); - return; - } - - name = gtk_window_get_title(GTK_WINDOW(gtk_widget_get_toplevel(widget))); - gtk_widget_set_app_paintable(grl->tipwindow, TRUE); - gtk_window_set_title(GTK_WINDOW(grl->tipwindow), name ? name : _("Room List")); - gtk_window_set_resizable(GTK_WINDOW(grl->tipwindow), FALSE); - gtk_widget_set_name(grl->tipwindow, "gtk-tooltips"); - g_signal_connect(G_OBJECT(grl->tipwindow), "expose_event", - G_CALLBACK(pidgin_roomlist_paint_tip), grl); - - w = TOOLTIP_BORDER + SMALL_SPACE + - MAX(grl->tip_width, grl->tip_name_width) + TOOLTIP_BORDER; - h = TOOLTIP_BORDER + grl->tip_height + grl->tip_name_height - + TOOLTIP_BORDER; - -#if GTK_CHECK_VERSION(2,2,0) - gdk_display_get_pointer(gdk_display_get_default(), &screen, &x, &y, NULL); - mon_num = gdk_screen_get_monitor_at_point(screen, x, y); - gdk_screen_get_monitor_geometry(screen, mon_num, &mon_size); - - scr_w = mon_size.width + mon_size.x; - scr_h = mon_size.height + mon_size.y; -#else - scr_w = gdk_screen_width(); - scr_h = gdk_screen_height(); - gdk_window_get_pointer(NULL, &x, &y, NULL); - mon_size.x = 0; - mon_size.y = 0; -#endif - -#if GTK_CHECK_VERSION(2,2,0) - if (w > mon_size.width) - w = mon_size.width - 10; - - if (h > mon_size.height) - h = mon_size.height - 10; -#endif - x -= ((w >> 1) + 4); - - if ((y + h + 4) > scr_h) - y = y - h - 5; - else - y = y + 6; - - if (y < mon_size.y) - y = mon_size.y; - - if (y != mon_size.y) { - if ((x + w) > scr_w) - x -= (x + w + 5) - scr_w; - else if (x < mon_size.x) - x = mon_size.x; - } else { - x -= (w / 2 + 10); - if (x < mon_size.x) - x = mon_size.x; - } - - gtk_widget_set_size_request(grl->tipwindow, w, h); - gtk_window_move(GTK_WINDOW(grl->tipwindow), x, y); - gtk_widget_show(grl->tipwindow); - - /* Hide the tooltip when the widget is destroyed */ - sig = g_signal_connect(G_OBJECT(widget), "destroy", G_CALLBACK(pidgin_roomlist_tooltip_destroy_cb), grl); - g_signal_connect_swapped(G_OBJECT(grl->tipwindow), "destroy", G_CALLBACK(g_source_remove), GINT_TO_POINTER(sig)); -} - -static gboolean pidgin_roomlist_tooltip_timeout(PurpleRoomlist *list) -{ - PidginRoomlist *grl = list->ui_data; - GtkWidget *tv = grl->tree; - GtkTreePath *path; - - pidgin_roomlist_tooltip_destroy(grl); - - if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), grl->tip_rect.x, grl->tip_rect.y + (grl->tip_rect.height/2), - &path, NULL, NULL, NULL)) + grl->tipwindow = widget; + if (!pidgin_roomlist_create_tip(data, path)) return FALSE; - - pidgin_roomlist_draw_tooltip(list, GTK_WIDGET(grl->tree)); - - return FALSE; -} - -static gboolean row_motion_cb(GtkWidget *tv, GdkEventMotion *event, gpointer user_data) -{ - PurpleRoomlist *list = user_data; - PidginRoomlist *grl = list->ui_data; - GtkTreePath *path; - int delay; - - /* XXX: should this be using the blist delay pref? */ - delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay"); - - if (delay == 0) - return FALSE; - - if (grl->timeout) { - if ((event->y > grl->tip_rect.y) && ((event->y - grl->tip_rect.height) < grl->tip_rect.y)) - return FALSE; - /* We've left the cell. Remove the timeout and create a new one below */ - pidgin_roomlist_tooltip_destroy(grl); - } - - gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL); - - if (path == NULL) { - pidgin_roomlist_tooltip_destroy(grl); - return FALSE; - } - - gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &grl->tip_rect); - - if (path) - gtk_tree_path_free(path); - grl->timeout = g_timeout_add(delay, (GSourceFunc)pidgin_roomlist_tooltip_timeout, list); - - return FALSE; -} - -static void row_leave_cb(GtkWidget *tv, GdkEventCrossing *e, gpointer user_data) -{ - PurpleRoomlist *list = user_data; - PidginRoomlist *grl = list->ui_data; - - if (grl->timeout) { - g_source_remove(grl->timeout); - grl->timeout = 0; - } - - pidgin_roomlist_tooltip_destroy(grl); + if (w) + *w = TOOLTIP_BORDER + SMALL_SPACE + + MAX(grl->tip_width, grl->tip_name_width) + TOOLTIP_BORDER; + if (h) + *h = TOOLTIP_BORDER + grl->tip_height + grl->tip_name_height + + TOOLTIP_BORDER; + return TRUE; } static gboolean account_filter_func(PurpleAccount *account) @@ -685,15 +531,13 @@ dialog->account = account; /* Create the window. */ - dialog->window = window = pidgin_create_window(_("Room List"), PIDGIN_HIG_BORDER, "room list", TRUE); + dialog->window = window = pidgin_create_dialog(_("Room List"), PIDGIN_HIG_BORDER, "room list", TRUE); g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(delete_win_cb), dialog); /* Create the parent vbox for everything. */ - vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(window), vbox); - gtk_widget_show(vbox); + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(window), FALSE, PIDGIN_HIG_BORDER); vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); gtk_container_add(GTK_CONTAINER(vbox), vbox2); @@ -738,19 +582,14 @@ gtk_widget_show(dialog->progress); /* button box */ - bbox = gtk_hbutton_box_new(); + bbox = pidgin_dialog_get_action_area(GTK_DIALOG(window)); gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, TRUE, 0); - gtk_widget_show(bbox); /* stop button */ - dialog->stop_button = gtk_button_new_from_stock(GTK_STOCK_STOP); - gtk_box_pack_start(GTK_BOX(bbox), dialog->stop_button, FALSE, FALSE, 0); - g_signal_connect(G_OBJECT(dialog->stop_button), "clicked", + dialog->stop_button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_STOP, G_CALLBACK(stop_button_cb), dialog); gtk_widget_set_sensitive(dialog->stop_button, FALSE); - gtk_widget_show(dialog->stop_button); /* list button */ dialog->list_button = pidgin_pixbuf_button_from_stock(_("_Get List"), GTK_STOCK_REFRESH, @@ -779,11 +618,8 @@ gtk_widget_show(dialog->join_button); /* close button */ - dialog->close_button = gtk_button_new_from_stock(GTK_STOCK_CLOSE); - gtk_box_pack_start(GTK_BOX(bbox), dialog->close_button, FALSE, FALSE, 0); - g_signal_connect(G_OBJECT(dialog->close_button), "clicked", + dialog->close_button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, G_CALLBACK(close_button_cb), dialog); - gtk_widget_show(dialog->close_button); /* show the dialog window and return the dialog */ gtk_widget_show(dialog->window); @@ -967,6 +803,9 @@ g_signal_connect(G_OBJECT(tree), "motion-notify-event", G_CALLBACK(row_motion_cb), list); g_signal_connect(G_OBJECT(tree), "leave-notify-event", G_CALLBACK(row_leave_cb), list); #endif + pidgin_tooltip_setup_for_treeview(tree, list, + pidgin_roomlist_create_tooltip, + pidgin_roomlist_paint_tooltip); /* Enable CTRL+F searching */ gtk_tree_view_set_search_column(GTK_TREE_VIEW(tree), NAME_COLUMN);
--- a/pidgin/gtksavedstatuses.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtksavedstatuses.c Sat Dec 22 23:18:08 2007 +0000 @@ -594,7 +594,7 @@ 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 = pidgin_create_window(_("Saved Statuses"), PIDGIN_HIG_BORDER, "statuses", TRUE); + dialog->window = win = pidgin_create_dialog(_("Saved Statuses"), PIDGIN_HIG_BORDER, "statuses", TRUE); gtk_window_set_default_size(GTK_WINDOW(win), width, height); g_signal_connect(G_OBJECT(win), "delete_event", @@ -603,18 +603,14 @@ G_CALLBACK(configure_cb), dialog); /* Setup the vbox */ - vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(win), vbox); + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER); /* List of saved status states */ list = create_saved_status_list(dialog); gtk_box_pack_start(GTK_BOX(vbox), list, TRUE, TRUE, 0); /* Button box. */ - bbox = gtk_hbutton_box_new(); - gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0); + bbox = pidgin_dialog_get_action_area(GTK_DIALOG(win)); /* Use button */ button = pidgin_pixbuf_button_from_stock(_("_Use"), GTK_STOCK_EXECUTE, @@ -627,36 +623,23 @@ G_CALLBACK(status_window_use_cb), dialog); /* Add button */ - button = gtk_button_new_from_stock(GTK_STOCK_ADD); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(status_window_add_cb), dialog); + pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_ADD, + G_CALLBACK(status_window_add_cb), dialog); /* Modify button */ - button = gtk_button_new_from_stock(PIDGIN_STOCK_MODIFY); + button = pidgin_dialog_add_button(GTK_DIALOG(win), PIDGIN_STOCK_MODIFY, + G_CALLBACK(status_window_modify_cb), dialog); dialog->modify_button = button; - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + + /* Delete button */ + button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_DELETE, + G_CALLBACK(status_window_delete_cb), dialog); + dialog->delete_button = button; gtk_widget_set_sensitive(button, FALSE); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(status_window_modify_cb), dialog); - - /* Delete button */ - button = gtk_button_new_from_stock(GTK_STOCK_DELETE); - dialog->delete_button = button; - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - gtk_widget_set_sensitive(button, FALSE); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(status_window_delete_cb), dialog); - /* Close button */ - button = gtk_button_new_from_stock(GTK_STOCK_CLOSE); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(status_window_close_cb), dialog); + pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CLOSE, + G_CALLBACK(status_window_close_cb), dialog); purple_signal_connect(purple_savedstatuses_get_handle(), "savedstatus-changed", status_window, @@ -1147,14 +1130,13 @@ if (edit) dialog->original_title = g_strdup(purple_savedstatus_get_title(saved_status)); - dialog->window = win = pidgin_create_window(_("Status"), PIDGIN_HIG_BORDER, "status", TRUE); + dialog->window = win = pidgin_create_dialog(_("Status"), PIDGIN_HIG_BORDER, "status", TRUE); g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(status_editor_destroy_cb), dialog); /* Setup the vbox */ - vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(win), vbox); + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER); sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); @@ -1257,23 +1239,18 @@ (saved_status != NULL) && purple_savedstatus_has_substatuses(saved_status)); /* Button box */ - bbox = gtk_hbutton_box_new(); + bbox = pidgin_dialog_get_action_area(GTK_DIALOG(win)); gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0); /* Cancel button */ - button = gtk_button_new_from_stock(GTK_STOCK_CANCEL); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(status_editor_cancel_cb), dialog); + pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CANCEL, + G_CALLBACK(status_editor_cancel_cb), dialog); /* Use button */ button = pidgin_pixbuf_button_from_stock(_("_Use"), GTK_STOCK_EXECUTE, PIDGIN_BUTTON_HORIZONTAL); gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(status_editor_ok_cb), dialog); @@ -1284,19 +1261,15 @@ gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); if (dialog->original_title == NULL) gtk_widget_set_sensitive(button, FALSE); - g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(status_editor_ok_cb), dialog); /* Save button */ - button = gtk_button_new_from_stock(GTK_STOCK_SAVE); - dialog->save_button = GTK_BUTTON(button); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + button = pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_SAVE, + G_CALLBACK(status_editor_ok_cb), dialog); if (dialog->original_title == NULL) gtk_widget_set_sensitive(button, FALSE); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(status_editor_ok_cb), dialog); + dialog->save_button = GTK_BUTTON(button); gtk_widget_show_all(win); g_object_unref(sg); @@ -1447,8 +1420,6 @@ char *tmp; SubStatusEditor *dialog; GtkSizeGroup *sg; - GtkWidget *bbox; - GtkWidget *button; GtkWidget *combo; GtkWidget *hbox; GtkWidget *frame; @@ -1486,15 +1457,14 @@ dialog->account = account; tmp = g_strdup_printf(_("Status for %s"), purple_account_get_username(account)); - dialog->window = win = pidgin_create_window(tmp, PIDGIN_HIG_BORDER, "substatus", TRUE); + dialog->window = win = pidgin_create_dialog(tmp, PIDGIN_HIG_BORDER, "substatus", TRUE); g_free(tmp); g_signal_connect(G_OBJECT(win), "delete_event", G_CALLBACK(substatus_editor_destroy_cb), dialog); /* Setup the vbox */ - vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); - gtk_container_add(GTK_CONTAINER(win), vbox); + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(win), FALSE, PIDGIN_HIG_BORDER); sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); @@ -1543,25 +1513,13 @@ dialog->toolbar = GTK_IMHTMLTOOLBAR(toolbar); gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0); - /* Button box */ - bbox = gtk_hbutton_box_new(); - gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); - gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); - gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, TRUE, 0); - /* Cancel button */ - button = gtk_button_new_from_stock(GTK_STOCK_CANCEL); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(substatus_editor_cancel_cb), dialog); + pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_CANCEL, + G_CALLBACK(substatus_editor_cancel_cb), dialog); /* OK button */ - button = gtk_button_new_from_stock(GTK_STOCK_OK); - gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); - - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(substatus_editor_ok_cb), dialog); + pidgin_dialog_add_button(GTK_DIALOG(win), GTK_STOCK_OK, + G_CALLBACK(substatus_editor_ok_cb), dialog); /* Seed the input widgets with the current values */
--- a/pidgin/gtkscrollbook.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkscrollbook.c Sat Dec 22 23:18:08 2007 +0000 @@ -64,7 +64,7 @@ return scroll_book_type; } -static void +static gboolean scroll_left_cb(PidginScrollBook *scroll_book) { int index; @@ -72,9 +72,10 @@ if (index > 0) gtk_notebook_set_current_page(GTK_NOTEBOOK(scroll_book->notebook), index - 1); + return TRUE; } -static void +static gboolean scroll_right_cb(PidginScrollBook *scroll_book) { int index, count; @@ -87,6 +88,7 @@ if (index + 1 < count) gtk_notebook_set_current_page(GTK_NOTEBOOK(scroll_book->notebook), index + 1); + return TRUE; } static void @@ -136,10 +138,11 @@ refresh_scroll_box(scroll_book, index, count); } -static void +static gboolean scroll_close_cb(PidginScrollBook *scroll_book) { gtk_widget_destroy(gtk_notebook_get_nth_page(GTK_NOTEBOOK(scroll_book->notebook), gtk_notebook_get_current_page(GTK_NOTEBOOK(scroll_book->notebook)))); + return FALSE; } static void
--- a/pidgin/gtkstatusbox.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkstatusbox.c Sat Dec 22 23:18:08 2007 +0000 @@ -1100,7 +1100,7 @@ return TRUE; } -static int imhtml_remove_focus(GtkWidget *w, GdkEventKey *event, PidginStatusBox *status_box) +static gboolean imhtml_remove_focus(GtkWidget *w, GdkEventKey *event, PidginStatusBox *status_box) { if (event->keyval == GDK_Tab || event->keyval == GDK_KP_Tab) {
--- a/pidgin/gtkutils.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkutils.c Sat Dec 22 23:18:08 2007 +0000 @@ -132,12 +132,9 @@ } } -GtkWidget * -pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable) +static +void pidgin_window_init(GtkWindow *wnd, const char *title, guint border_width, const char *role, gboolean resizable) { - GtkWindow *wnd = NULL; - - wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); if (title) gtk_window_set_title(wnd, title); #ifdef _WIN32 @@ -148,11 +145,63 @@ if (role) gtk_window_set_role(wnd, role); gtk_window_set_resizable(wnd, resizable); +} + +GtkWidget * +pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable) +{ + GtkWindow *wnd = NULL; + + wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); + pidgin_window_init(wnd, title, border_width, role, resizable); + + return GTK_WIDGET(wnd); +} + +GtkWidget * +pidgin_create_dialog(const char *title, guint border_width, const char *role, gboolean resizable) +{ + GtkWindow *wnd = NULL; + + wnd = GTK_WINDOW(gtk_dialog_new()); + pidgin_window_init(wnd, title, border_width, role, resizable); + g_object_set(G_OBJECT(wnd), "has-separator", FALSE, NULL); return GTK_WIDGET(wnd); } GtkWidget * +pidgin_dialog_get_vbox_with_properties(GtkDialog *dialog, gboolean homogeneous, gint spacing) +{ + GtkBox *vbox = GTK_BOX(GTK_DIALOG(dialog)->vbox); + gtk_box_set_homogeneous(vbox, homogeneous); + gtk_box_set_spacing(vbox, spacing); + return GTK_WIDGET(vbox); +} + +GtkWidget *pidgin_dialog_get_vbox(GtkDialog *dialog) +{ + return GTK_DIALOG(dialog)->vbox; +} + +GtkWidget *pidgin_dialog_get_action_area(GtkDialog *dialog) +{ + return GTK_DIALOG(dialog)->action_area; +} + +GtkWidget *pidgin_dialog_add_button(GtkDialog *dialog, const char *label, + GCallback callback, gpointer callbackdata) +{ + GtkWidget *button = gtk_button_new_from_stock(label); + GtkWidget *bbox = pidgin_dialog_get_action_area(dialog); + gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0); + if (callback) + g_signal_connect(G_OBJECT(button), "clicked", callback, callbackdata); + gtk_widget_show(button); + return button; +} + +GtkWidget * pidgin_create_imhtml(gboolean editable, GtkWidget **imhtml_ret, GtkWidget **toolbar_ret, GtkWidget **sw_ret) { GtkWidget *frame; @@ -1399,7 +1448,7 @@ char *str; str = g_strdup_printf(_("The following error has occurred loading %s: %s"), - data->filename, strerror(errno)); + data->filename, g_strerror(errno)); purple_notify_error(NULL, NULL, _("Failed to load image"), str); @@ -2306,6 +2355,9 @@ } #endif /* FILECHOOSER */ +#if 0 /* mismatched curly braces */ + } +#endif if (dialog->callback) dialog->callback(filename, dialog->data); gtk_widget_destroy(dialog->icon_filesel); @@ -3269,3 +3321,107 @@ gtk_entry_set_text(GTK_ENTRY(GTK_BIN((widget))->child), (text)); } +gboolean pidgin_auto_parent_window(GtkWidget *widget) +{ +#if 0 + /* This looks at the most recent window that received focus, and makes + * that the parent window. */ +#ifndef _WIN32 + static GdkAtom _WindowTime = GDK_NONE; + static GdkAtom _Cardinal = GDK_NONE; + GList *windows = NULL; + GtkWidget *parent = NULL; + time_t window_time = 0; + + windows = gtk_window_list_toplevels(); + + if (_WindowTime == GDK_NONE) { + _WindowTime = gdk_x11_xatom_to_atom(gdk_x11_get_xatom_by_name("_NET_WM_USER_TIME")); + } + if (_Cardinal == GDK_NONE) { + _Cardinal = gdk_atom_intern("CARDINAL", FALSE); + } + + while (windows) { + GtkWidget *window = windows->data; + guchar *data = NULL; + int al = 0; + time_t value; + + windows = g_list_delete_link(windows, windows); + + if (window == widget || + !GTK_WIDGET_VISIBLE(window)) + continue; + + if (!gdk_property_get(window->window, _WindowTime, _Cardinal, 0, sizeof(time_t), FALSE, + NULL, NULL, &al, &data)) + continue; + value = *(time_t *)data; + if (window_time < value) { + window_time = value; + parent = window; + } + g_free(data); + } + if (windows) + g_list_free(windows); + if (parent) { + if (!gtk_get_current_event() && gtk_window_has_toplevel_focus(GTK_WINDOW(parent))) { + /* The window is in focus, and the new window was not triggered by a keypress/click + * event. So do not set it transient, to avoid focus stealing and all that. + */ + return FALSE; + } + gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent)); + return TRUE; + } + return FALSE; +#endif +#else + /* This finds the currently active window and makes that the parent window. */ + GList *windows = NULL; + GtkWidget *parent = NULL; + GdkEvent *event = gtk_get_current_event(); + GdkWindow *menu = NULL; + + if (event == NULL) + /* The window was not triggered by a user action. */ + return FALSE; + + /* We need to special case events from a popup menu. */ + if (event->type == GDK_BUTTON_RELEASE) { + /* XXX: Neither of the following works: + menu = event->button.window; + menu = gdk_window_get_parent(event->button.window); + menu = gdk_window_get_toplevel(event->button.window); + */ + } else if (event->type == GDK_KEY_PRESS) + menu = event->key.window; + + windows = gtk_window_list_toplevels(); + while (windows) { + GtkWidget *window = windows->data; + windows = g_list_delete_link(windows, windows); + + if (window == widget || + !GTK_WIDGET_VISIBLE(window)) { + continue; + } + + if (gtk_window_has_toplevel_focus(GTK_WINDOW(window)) || + (menu && menu == window->window)) { + parent = window; + break; + } + } + if (windows) + g_list_free(windows); + if (parent) { + gtk_window_set_transient_for(GTK_WINDOW(widget), GTK_WINDOW(parent)); + return TRUE; + } + return FALSE; +#endif +} +
--- a/pidgin/gtkutils.h Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/gtkutils.h Sat Dec 22 23:18:08 2007 +0000 @@ -121,6 +121,61 @@ GtkWidget *pidgin_create_window(const char *title, guint border_width, const char *role, gboolean resizable); /** + * Creates a new dialog window + * + * @param title The window title, or @c NULL + * @param border_width The window's desired border width + * @param role A string indicating what the window is responsible for doing, or @c NULL + * @param resizable Whether the window should be resizable (@c TRUE) or not (@c FALSE) + * + * @since 2.4.0 + */ +GtkWidget *pidgin_create_dialog(const char *title, guint border_width, const char *role, gboolean resizable); + +/** + * Retrieves the main content box (vbox) from a pidgin dialog window + * + * @param dialog The dialog window + * @param homogeneous TRUE if all children are to be given equal space allotments. + * @param spacing the number of pixels to place by default between children + * + * @since 2.4.0 + */ +GtkWidget *pidgin_dialog_get_vbox_with_properties(GtkDialog *dialog, gboolean homogeneous, gint spacing); + +/** + * Retrieves the main content box (vbox) from a pidgin dialog window + * + * @param dialog The dialog window + * + * @since 2.4.0 + */ +GtkWidget *pidgin_dialog_get_vbox(GtkDialog *dialog); + +/** + * Add a button to a dialog created by #pidgin_create_dialog. + * + * @param dialog The dialog window + * @param label The stock-id or the label for the button + * @param callback The callback function for the button + * @param callbackdata The user data for the callback function + * + * @return The created button. + * @since 2.4.0 + */ +GtkWidget *pidgin_dialog_add_button(GtkDialog *dialog, const char *label, + GCallback callback, gpointer callbackdata); + +/** + * Retrieves the action area (button box) from a pidgin dialog window + * + * @param dialog The dialog window + * + * @since 2.4.0 + */ +GtkWidget *pidgin_dialog_get_action_area(GtkDialog *dialog); + +/** * Toggles the sensitivity of a widget. * * @param widget @c NULL. Used for signal handlers. @@ -725,5 +780,15 @@ */ void pidgin_text_combo_box_entry_set_text(GtkWidget *widget, const char *text); +/** + * Automatically make a window transient to a suitable parent window. + * + * @param window The window to make transient. + * + * @return Whether the window was made transient or not. + * @since 2.4.0 + */ +gboolean pidgin_auto_parent_window(GtkWidget *window); + #endif /* _PIDGINUTILS_H_ */
--- a/pidgin/minidialog.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/minidialog.c Sat Dec 22 23:18:08 2007 +0000 @@ -35,7 +35,40 @@ #include "pidgin/pidgin.h" #include "pidgin/pidginstock.h" -G_DEFINE_TYPE (PidginMiniDialog, pidgin_mini_dialog, GTK_TYPE_VBOX) +static void pidgin_mini_dialog_init (PidginMiniDialog *self); +static void pidgin_mini_dialog_class_init (PidginMiniDialogClass *klass); + +static gpointer pidgin_mini_dialog_parent_class = NULL; + +static void +pidgin_mini_dialog_class_intern_init (gpointer klass) +{ + pidgin_mini_dialog_parent_class = g_type_class_peek_parent (klass); + pidgin_mini_dialog_class_init ((PidginMiniDialogClass*) klass); +} + +GType +pidgin_mini_dialog_get_type (void) +{ + static GType g_define_type_id = 0; + if (G_UNLIKELY (g_define_type_id == 0)) + { + static const GTypeInfo g_define_type_info = { + sizeof (PidginMiniDialogClass), + (GBaseInitFunc) NULL, + (GBaseFinalizeFunc) NULL, + (GClassInitFunc) pidgin_mini_dialog_class_intern_init, + (GClassFinalizeFunc) NULL, + NULL, /* class_data */ + sizeof (PidginMiniDialog), + 0, /* n_preallocs */ + (GInstanceInitFunc) pidgin_mini_dialog_init, + }; + g_define_type_id = g_type_register_static (GTK_TYPE_VBOX, + "PidginMiniDialog", &g_define_type_info, 0); + } + return g_define_type_id; +} enum {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/pidgintooltip.c Sat Dec 22 23:18:08 2007 +0000 @@ -0,0 +1,299 @@ +/** + * @file pidgintooltip.c Pidgin Tooltip API + * @ingroup pidgin + */ + +/* pidgin + * + * Pidgin is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" +#include "prefs.h" +#include "pidgin.h" +#include "pidgintooltip.h" +#include "debug.h" + +struct +{ + GtkWidget *widget; + int timeout; + GdkRectangle tip_rect; + GtkWidget *tipwindow; + PidginTooltipPaint paint_tooltip; +} pidgin_tooltip; + +typedef struct +{ + GtkWidget *widget; + gpointer userdata; + PidginTooltipCreateForTree create_tooltip; + PidginTooltipPaint paint_tooltip; + GtkTreePath *path; +} PidginTooltipData; + +static void +destroy_tooltip_data(PidginTooltipData *data) +{ + gtk_tree_path_free(data->path); + g_free(data); +} + +void pidgin_tooltip_destroy() +{ + if (pidgin_tooltip.timeout > 0) { + g_source_remove(pidgin_tooltip.timeout); + pidgin_tooltip.timeout = 0; + } + if (pidgin_tooltip.tipwindow) { + gtk_widget_destroy(pidgin_tooltip.tipwindow); + pidgin_tooltip.tipwindow = NULL; + } +} + +static gboolean +pidgin_tooltip_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data) +{ + if (pidgin_tooltip.paint_tooltip) + pidgin_tooltip.paint_tooltip(widget, data); + return FALSE; +} + +static GtkWidget* +setup_tooltip_window() +{ + const char *name; + GtkWidget *tipwindow; + + tipwindow = gtk_window_new(GTK_WINDOW_POPUP); + name = gtk_window_get_title(GTK_WINDOW(pidgin_tooltip.widget)); +#if GTK_CHECK_VERSION(2,10,0) + gtk_window_set_type_hint(GTK_WINDOW(tipwindow), GDK_WINDOW_TYPE_HINT_TOOLTIP); +#endif + gtk_widget_set_app_paintable(tipwindow, TRUE); + gtk_window_set_title(GTK_WINDOW(tipwindow), name ? name : _("Pidgin Tooltip")); + gtk_window_set_resizable(GTK_WINDOW(tipwindow), FALSE); + gtk_widget_set_name(tipwindow, "gtk-tooltips"); + gtk_widget_ensure_style(tipwindow); + gtk_widget_realize(tipwindow); + return tipwindow; +} + +static void +setup_tooltip_window_position(gpointer data, int w, int h) +{ + int sig; + int scr_w, scr_h, x, y; +#if GTK_CHECK_VERSION(2,2,0) + int mon_num; + GdkScreen *screen = NULL; +#endif + GdkRectangle mon_size; + GtkWidget *tipwindow = pidgin_tooltip.tipwindow; + +#if GTK_CHECK_VERSION(2,2,0) + gdk_display_get_pointer(gdk_display_get_default(), &screen, &x, &y, NULL); + mon_num = gdk_screen_get_monitor_at_point(screen, x, y); + gdk_screen_get_monitor_geometry(screen, mon_num, &mon_size); + + scr_w = mon_size.width + mon_size.x; + scr_h = mon_size.height + mon_size.y; +#else + scr_w = gdk_screen_width(); + scr_h = gdk_screen_height(); + gdk_window_get_pointer(NULL, &x, &y, NULL); + mon_size.x = 0; + mon_size.y = 0; +#endif + +#if GTK_CHECK_VERSION(2,2,0) + if (w > mon_size.width) + w = mon_size.width - 10; + + if (h > mon_size.height) + h = mon_size.height - 10; +#endif + x -= ((w >> 1) + 4); + + if ((y + h + 4) > scr_h) + y = y - h - 5; + else + y = y + 6; + + if (y < mon_size.y) + y = mon_size.y; + + if (y != mon_size.y) { + if ((x + w) > scr_w) + x -= (x + w + 5) - scr_w; + else if (x < mon_size.x) + x = mon_size.x; + } else { + x -= (w / 2 + 10); + if (x < mon_size.x) + x = mon_size.x; + } + + gtk_widget_set_size_request(tipwindow, w, h); + gtk_window_move(GTK_WINDOW(tipwindow), x, y); + gtk_widget_show(tipwindow); + + g_signal_connect(G_OBJECT(tipwindow), "expose_event", + G_CALLBACK(pidgin_tooltip_expose_event), data); + + /* Hide the tooltip when the widget is destroyed */ + sig = g_signal_connect(G_OBJECT(pidgin_tooltip.widget), "destroy", G_CALLBACK(pidgin_tooltip_destroy), NULL); + g_signal_connect_swapped(G_OBJECT(tipwindow), "destroy", G_CALLBACK(g_source_remove), GINT_TO_POINTER(sig)); +} + +void pidgin_tooltip_show(GtkWidget *widget, gpointer userdata, + PidginTooltipCreate create_tooltip, PidginTooltipPaint paint_tooltip) +{ + GtkWidget *tipwindow; + int w, h; + + pidgin_tooltip_destroy(); + + pidgin_tooltip.widget = gtk_widget_get_toplevel(widget); + pidgin_tooltip.tipwindow = tipwindow = setup_tooltip_window(); + pidgin_tooltip.paint_tooltip = paint_tooltip; + + if (!create_tooltip(tipwindow, userdata, &w, &h)) { + pidgin_tooltip_destroy(); + return; + } + setup_tooltip_window_position(userdata, w, h); +} + +static void +reset_data_treepath(PidginTooltipData *data) +{ + gtk_tree_path_free(data->path); + data->path = NULL; +} + +static void +pidgin_tooltip_draw(PidginTooltipData *data) +{ + GtkWidget *tipwindow; + GtkTreePath *path = NULL; + int w, h; + + if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(data->widget), + pidgin_tooltip.tip_rect.x, + pidgin_tooltip.tip_rect.y + (pidgin_tooltip.tip_rect.height/2), + &path, NULL, NULL, NULL)) { + pidgin_tooltip_destroy(); + return; + } + + if (data->path) { + if (gtk_tree_path_compare(data->path, path) == 0) { + gtk_tree_path_free(path); + return; + } + gtk_tree_path_free(data->path); + data->path = NULL; + } + + pidgin_tooltip_destroy(); + + pidgin_tooltip.widget = gtk_widget_get_toplevel(data->widget); + pidgin_tooltip.tipwindow = tipwindow = setup_tooltip_window(); + pidgin_tooltip.paint_tooltip = data->paint_tooltip; + + if (!data->create_tooltip(tipwindow, path, data->userdata, &w, &h)) { + pidgin_tooltip_destroy(); + gtk_tree_path_free(path); + return; + } + + setup_tooltip_window_position(data->userdata, w, h); + + data->path = path; + g_signal_connect_swapped(G_OBJECT(tipwindow), "destroy", + G_CALLBACK(reset_data_treepath), data); +} + +static gboolean +pidgin_tooltip_timeout(gpointer data) +{ + pidgin_tooltip.timeout = 0; + pidgin_tooltip_draw(data); + return FALSE; +} + +static gboolean +row_motion_cb(GtkWidget *tv, GdkEventMotion *event, gpointer userdata) +{ + GtkTreePath *path; + int delay; + + if (event->window != gtk_tree_view_get_bin_window(GTK_TREE_VIEW(tv))) + return FALSE; /* The cursor is probably on the TreeView's header. */ + + /* XXX: probably use something more generic? */ + delay = purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/tooltip_delay"); + if (delay == 0) + return FALSE; + + if (pidgin_tooltip.timeout) { + if ((event->y >= pidgin_tooltip.tip_rect.y) && ((event->y - pidgin_tooltip.tip_rect.height) <= pidgin_tooltip.tip_rect.y)) + return FALSE; + /* We've left the cell. Remove the timeout and create a new one below */ + pidgin_tooltip_destroy(); + } + + gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL); + + if (path == NULL) { + pidgin_tooltip_destroy(); + return FALSE; + } + + gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &pidgin_tooltip.tip_rect); + gtk_tree_path_free(path); + + pidgin_tooltip.timeout = g_timeout_add(delay, (GSourceFunc)pidgin_tooltip_timeout, userdata); + + return FALSE; +} + +static gboolean +row_leave_cb(GtkWidget *tv, GdkEvent *event, gpointer userdata) +{ + pidgin_tooltip_destroy(); + return FALSE; +} + +gboolean pidgin_tooltip_setup_for_treeview(GtkWidget *tree, gpointer userdata, + PidginTooltipCreateForTree create_tooltip, PidginTooltipPaint paint_tooltip) +{ + PidginTooltipData *tdata = g_new0(PidginTooltipData, 1); + tdata->widget = tree; + tdata->userdata = userdata; + tdata->create_tooltip = create_tooltip; + tdata->paint_tooltip = paint_tooltip; + + g_signal_connect(G_OBJECT(tree), "motion-notify-event", G_CALLBACK(row_motion_cb), tdata); + g_signal_connect(G_OBJECT(tree), "leave-notify-event", G_CALLBACK(row_leave_cb), NULL); + g_signal_connect_swapped(G_OBJECT(tree), "destroy", G_CALLBACK(destroy_tooltip_data), tdata); + return TRUE; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/pidgintooltip.h Sat Dec 22 23:18:08 2007 +0000 @@ -0,0 +1,97 @@ +/** + * @file pidgintooltip.h Pidgin Tooltip API + * @ingroup pidgin + */ + +/* pidgin + * + * Pidgin is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ +#ifndef _PIDGIN_TOOLTIP_H_ +#define _PIDGIN_TOOLTIP_H_ + +#include <gtk/gtk.h> + +/** + * @param tipwindow The window for the tooltip. + * @param path The GtkTreePath representing the row under the cursor. + * @param userdata The userdata set during pidgin_tooltip_setup_for_treeview. + * @param w The value of this should be set to the desired width of the tooltip window. + * @param h The value of this should be set to the desired height of the tooltip window. + * + * @return @c TRUE if the tooltip was created correctly, @c FALSE otherwise. + * @since 2.4.0 + */ +typedef gboolean (*PidginTooltipCreateForTree)(GtkWidget *tipwindow, + GtkTreePath *path, gpointer userdata, int *w, int *h); + +/** + * @param tipwindow The window for the tooltip. + * @param userdata The userdata set during pidgin_tooltip_show. + * @param w The value of this should be set to the desired width of the tooltip window. + * @param h The value of this should be set to the desired height of the tooltip window. + * + * @return @c TRUE if the tooltip was created correctly, @c FALSE otherwise. + * @since 2.4.0 + */ +typedef gboolean (*PidginTooltipCreate)(GtkWidget *tipwindow, + gpointer userdata, int *w, int *h); + +/** + * @param tipwindow The window for the tooltip. + * @param userdata The userdata set during pidgin_tooltip_setup_for_treeview or pidgin_tooltip_show. + * + * @return @c TRUE if the tooltip was painted correctly, @c FALSE otherwise. + * @since 2.4.0 + */ +typedef gboolean (*PidginTooltipPaint)(GtkWidget *tipwindow, gpointer userdata); + +/** + * Setup tooltip drawing functions for a treeview. + * + * @param tree The treeview + * @param userdata The userdata to send to the callback functions + * @param create_cb Callback function to create the tooltip for a GtkTreePath + * @param paint_cb Callback function to paint the tooltip + * + * @return @c TRUE if the tooltip callbacks were setup correctly. + * @since 2.4.0 + */ +gboolean pidgin_tooltip_setup_for_treeview(GtkWidget *tree, gpointer userdata, + PidginTooltipCreateForTree create_cb, PidginTooltipPaint paint_cb); + +/** + * Destroy the tooltip. + * @since 2.4.0 + */ +void pidgin_tooltip_destroy(void); + +/** + * Create and show a tooltip. + * + * @param widget The widget the tooltip is for + * @param userdata The userdata to send to the callback functions + * @param create_cb Callback function to create the tooltip from the GtkTreePath + * @param paint_cb Callback function to paint the tooltip + * + * @since 2.4.0 + */ +void pidgin_tooltip_show(GtkWidget *widget, gpointer userdata, + PidginTooltipCreate create_cb, PidginTooltipPaint paint_cb); +#endif
--- a/pidgin/plugins/crazychat/cc_network.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/plugins/crazychat/cc_network.c Sat Dec 22 23:18:08 2007 +0000 @@ -529,7 +529,7 @@ while (total < len) { n = send(s, buf + total, bytesleft, 0); if (n == -1) { - Debug("ERROR: %s\n", strerror(errno)); + Debug("ERROR: %s\n", g_strerror(errno)); return -1; } total += n;
--- a/pidgin/plugins/pidginrc.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/plugins/pidginrc.c Sat Dec 22 23:18:08 2007 +0000 @@ -30,17 +30,29 @@ static const gchar *color_prefs[] = { "/plugins/gtk/purplerc/color/GtkWidget::cursor-color", "/plugins/gtk/purplerc/color/GtkWidget::secondary-cursor-color", - "/plugins/gtk/purplerc/color/GtkIMHtml::hyperlink-color" + "/plugins/gtk/purplerc/color/GtkIMHtml::hyperlink-color", + "/plugins/gtk/purplerc/color/GtkIMHtml::send-name-color", + "/plugins/gtk/purplerc/color/GtkIMHtml::receive-name-color", + "/plugins/gtk/purplerc/color/GtkIMHtml::highlight-name-color", + "/plugins/gtk/purplerc/color/GtkIMHtml::action-name-color" }; static const gchar *color_prefs_set[] = { "/plugins/gtk/purplerc/set/color/GtkWidget::cursor-color", "/plugins/gtk/purplerc/set/color/GtkWidget::secondary-cursor-color", - "/plugins/gtk/purplerc/set/color/GtkIMHtml::hyperlink-color" + "/plugins/gtk/purplerc/set/color/GtkIMHtml::hyperlink-color", + "/plugins/gtk/purplerc/set/color/GtkIMHtml::send-name-color", + "/plugins/gtk/purplerc/set/color/GtkIMHtml::receive-name-color", + "/plugins/gtk/purplerc/set/color/GtkIMHtml::highlight-name-color", + "/plugins/gtk/purplerc/set/color/GtkIMHtml::action-name-color" }; static const gchar *color_names[] = { N_("Cursor Color"), N_("Secondary Cursor Color"), - N_("Hyperlink Color") + N_("Hyperlink Color"), + N_("Sent Message Name Color"), + N_("Received Message Name Color"), + N_("Highlighted Message Name Color"), + N_("Action Message Name Color") }; static GtkWidget *color_widgets[G_N_ELEMENTS(color_prefs)];
--- a/pidgin/win32/gtkwin32dep.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/win32/gtkwin32dep.c Sat Dec 22 23:18:08 2007 +0000 @@ -40,6 +40,7 @@ #include "debug.h" #include "notify.h" +#include "network.h" #include "resource.h" #include "idletrack.h" @@ -51,6 +52,7 @@ #include "gtkwin32dep.h" #include "win32dep.h" #include "gtkconv.h" +#include "gtkconn.h" #include "util.h" #include "wspell.h" @@ -64,6 +66,7 @@ typedef BOOL (CALLBACK* LPFNFLASHWINDOWEX)(PFLASHWINFO); static LPFNFLASHWINDOWEX MyFlashWindowEx = NULL; +static gboolean pwm_handles_connections = TRUE; /* @@ -202,6 +205,43 @@ #define PIDGIN_WM_FOCUS_REQUEST (WM_APP + 13) #define PIDGIN_WM_PROTOCOL_HANDLE (WM_APP + 14) +static void* +winpidgin_netconfig_changed_cb(void *data) +{ + pwm_handles_connections = FALSE; + + return NULL; +} + +static void* +winpidgin_get_handle(void) +{ + static int handle; + + return &handle; +} + +static gboolean +winpidgin_pwm_reconnect() +{ + purple_signal_disconnect(purple_network_get_handle(), "network-configuration-changed", + winpidgin_get_handle(), PURPLE_CALLBACK(winpidgin_netconfig_changed_cb)); + + if (pwm_handles_connections == TRUE) { + PurpleConnectionUiOps *ui_ops = pidgin_connections_get_ui_ops(); + + purple_debug_info("winpidgin", "Resumed from standby, reconnecting accounts.\n"); + + if (ui_ops != NULL && ui_ops->network_connected != NULL) + ui_ops->network_connected(); + } else { + purple_debug_info("winpidgin", "Resumed from standby, gtkconn will handle reconnecting.\n"); + pwm_handles_connections = TRUE; + } + + return FALSE; +} + static LRESULT CALLBACK message_window_handler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { if (msg == PIDGIN_WM_FOCUS_REQUEST) { @@ -213,6 +253,28 @@ purple_debug_info("winpidgin", "Got protocol handler request: %s\n", proto_msg ? proto_msg : ""); purple_got_protocol_handler_uri(proto_msg); return TRUE; + } else if (msg == WM_POWERBROADCAST) { + if (wparam == PBT_APMQUERYSUSPEND) { + purple_debug_info("winpidgin", "Windows requesting permission to suspend.\n"); + return TRUE; + } else if (wparam == PBT_APMSUSPEND) { + PurpleConnectionUiOps *ui_ops = pidgin_connections_get_ui_ops(); + + purple_debug_info("winpidgin", "Entering system standby, disconnecting accounts.\n"); + + if (ui_ops != NULL && ui_ops->network_disconnected != NULL) + ui_ops->network_disconnected(); + + purple_signal_connect(purple_network_get_handle(), "network-configuration-changed", winpidgin_get_handle(), + PURPLE_CALLBACK(winpidgin_netconfig_changed_cb), NULL); + + return TRUE; + } else if (wparam == PBT_APMRESUMESUSPEND) { + purple_debug_info("winpidgin", "Resuming from system standby.\n"); + /* TODO: It seems like it'd be wise to use the NLA message, if possible, instead of this. */ + purple_timeout_add_seconds(1, winpidgin_pwm_reconnect, NULL); + return TRUE; + } } return DefWindowProc(hwnd, msg, wparam, lparam); @@ -242,7 +304,7 @@ /* Create the window */ if(!(win_hwnd = CreateWindow(wname, TEXT("WinpidginMsgWin"), 0, 0, 0, 0, 0, - HWND_MESSAGE, NULL, winpidgin_exe_hinstance(), 0))) { + NULL, NULL, winpidgin_exe_hinstance(), 0))) { purple_debug_error("winpidgin", "Unable to create message window.\n"); return NULL;
--- a/pidgin/win32/nsis/pidgin-installer.nsi Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/win32/nsis/pidgin-installer.nsi Sat Dec 22 23:18:08 2007 +0000 @@ -1154,11 +1154,21 @@ !macro RunCheckMacro UN Function ${UN}RunCheck Push $R0 - System::Call 'kernel32::OpenMutex(i 2031617, b 0, t "pidgin_is_running") i .R0' - IntCmp $R0 0 done - MessageBox MB_OK|MB_ICONEXCLAMATION $(PIDGIN_IS_RUNNING) /SD IDOK + Push $R1 + + IntOp $R1 0 + 0 + retry_runcheck: + ; Close the Handle (needed if we're retrying) + IntCmp $R1 0 +2 + System::Call 'kernel32::CloseHandle(i $R1) i .R1' + System::Call 'kernel32::CreateMutexA(i 0, i 0, t "pidgin_is_running") i .R1 ?e' + Pop $R0 + IntCmp $R0 0 +3 ;This could check for ERROR_ALREADY_EXISTS(183), but lets just assume + MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION $(PIDGIN_IS_RUNNING) /SD IDCANCEL IDRETRY retry_runcheck Abort + done: + Pop $R1 Pop $R0 FunctionEnd !macroend @@ -1169,10 +1179,16 @@ Push $R0 Push $R1 Push $R2 - System::Call 'kernel32::CreateMutexA(i 0, i 0, t "pidgin_installer_running") i .r1 ?e' + + IntOp $R1 0 + 0 + retry_runcheck: + ; Close the Handle (needed if we're retrying) + IntCmp $R1 0 +2 + System::Call 'kernel32::CloseHandle(i $R1) i .R1' + System::Call 'kernel32::CreateMutexA(i 0, i 0, t "pidgin_installer_running") i .R1 ?e' Pop $R0 - StrCmp $R0 0 +3 - MessageBox MB_OK|MB_ICONEXCLAMATION $(INSTALLER_IS_RUNNING) /SD IDOK + IntCmp $R0 0 +3 ;This could check for ERROR_ALREADY_EXISTS(183), but lets just assume + MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION $(INSTALLER_IS_RUNNING) /SD IDCANCEL IDRETRY retry_runcheck Abort Call RunCheck StrCpy $name "Pidgin ${PIDGIN_VERSION}"
--- a/pidgin/win32/winpidgin.c Sat Dec 22 17:54:30 2007 +0000 +++ b/pidgin/win32/winpidgin.c Sat Dec 22 23:18:08 2007 +0000 @@ -445,11 +445,12 @@ #define PIDGIN_WM_FOCUS_REQUEST (WM_APP + 13) #define PIDGIN_WM_PROTOCOL_HANDLE (WM_APP + 14) -static BOOL winpidgin_set_running() { +static BOOL winpidgin_set_running(BOOL fail_if_running) { HANDLE h; if ((h = CreateMutex(NULL, FALSE, "pidgin_is_running"))) { - if (GetLastError() == ERROR_ALREADY_EXISTS) { + DWORD err = GetLastError(); + if (err == ERROR_ALREADY_EXISTS && fail_if_running) { HWND msg_win; printf("An instance of Pidgin is already running.\n"); @@ -465,7 +466,8 @@ NULL, MB_OK | MB_TOPMOST); return FALSE; - } + } else + printf("Error (%u) accessing \"pidgin_is_running\" mutex.\n", (UINT) err); } return TRUE; } @@ -628,8 +630,8 @@ winpidgin_set_locale(); /* If help, version or multiple flag used, do not check Mutex */ - if (!strstr(lpszCmdLine, "-h") && !strstr(lpszCmdLine, "-v") && !strstr(lpszCmdLine, "-m")) - if (!getenv("PIDGIN_MULTI_INST") && !winpidgin_set_running()) + if (!strstr(lpszCmdLine, "-h") && !strstr(lpszCmdLine, "-v")) + if (!winpidgin_set_running(getenv("PIDGIN_MULTI_INST") == NULL && strstr(lpszCmdLine, "-m") == NULL)) return 0; /* Now we are ready for Pidgin .. */
--- a/po/POTFILES.in Sat Dec 22 17:54:30 2007 +0000 +++ b/po/POTFILES.in Sat Dec 22 23:18:08 2007 +0000 @@ -179,6 +179,7 @@ libpurple/sslconn.c libpurple/status.c libpurple/util.c +libpurple/win32/libc_interface.c pidgin.desktop.in pidgin/eggtrayicon.c pidgin/gtkaccount.c
--- a/po/de.po Sat Dec 22 17:54:30 2007 +0000 +++ b/po/de.po Sat Dec 22 23:18:08 2007 +0000 @@ -11,8 +11,8 @@ msgstr "" "Project-Id-Version: de\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2007-11-30 10:22+0100\n" -"PO-Revision-Date: 2007-11-30 10:21+0100\n" +"POT-Creation-Date: 2007-12-19 10:17+0100\n" +"PO-Revision-Date: 2007-12-19 10:17+0100\n" "Last-Translator: Jochen Kemnade <jochenkemnade@web.de>\n" "Language-Team: Deutsch <de@li.org>\n" "MIME-Version: 1.0\n" @@ -1019,6 +1019,9 @@ msgid "Open File..." msgstr "Datei öffnen..." +msgid "Choose Location..." +msgstr "Wählen Sie einen Ort..." + msgid "Buddy logs in" msgstr "Buddy meldet sich an" @@ -3412,12 +3415,12 @@ msgid "Plaintext Authentication" msgstr "Klartext-Authentifizierung" +msgid "Invalid response from server." +msgstr "Ungültige Serverantwort." + msgid "Server does not use any supported authentication method" msgstr "Der Server benutzt keine der unterstützten Authentifizierungsmethoden" -msgid "Invalid response from server." -msgstr "Ungültige Serverantwort." - msgid "" "This server requires plaintext authentication over an unencrypted " "connection. Allow this and continue authentication?" @@ -3830,6 +3833,9 @@ msgid "Write error" msgstr "Schreibfehler" +msgid "Ping timeout" +msgstr "Ping-Zeitüberschreitung" + msgid "Read Error" msgstr "Fehler beim Lesen" @@ -5766,6 +5772,12 @@ msgid "Server port" msgstr "Server-Port" +msgid "Could not join chat room" +msgstr "Konnte Chatraum nicht betreten" + +msgid "Invalid chat room name" +msgstr "Ungültiger Chatraumname" + msgid "Server closed the connection." msgstr "Der Server hat die Verbindung beendet." @@ -6569,9 +6581,6 @@ msgid "_Exchange:" msgstr "A_ustausch:" -msgid "Invalid chat name specified." -msgstr "Ungültiger Chat-Name angegeben." - msgid "Your IM Image was not sent. You cannot send IM Images in AIM chats." msgstr "" "Ihr IM-Bild wurde nicht gesendet. Sie können keine IM-Bilder in AIM-Chats " @@ -6707,11 +6716,13 @@ msgstr "Anzeigen, wie lange ich untätig war" msgid "" -"Always use ICQ proxy server for file transfers\n" -"(slower, but does not reveal your IP address)" -msgstr "" -"Benutze immer den AIM/ICQ-Proxyserver\n" -"(langsamer, aber zeigt Ihre IP-Adresse nicht)" +"Always use AIM/ICQ proxy server for\n" +"file transfers and direct IM (slower,\n" +"but does not reveal your IP address)" +msgstr "" +"Benutze immer den AIM/ICQ-Proxyserver für\n" +"Dateiübertragungen und Direkt-IM (langsamer,\n" +"aber zeigt Ihre IP-Adresse nicht)" #, c-format msgid "Asking %s to connect to us at %s:%hu for Direct IM." @@ -9508,26 +9519,14 @@ msgstr "Musik hören" #, c-format -msgid "%s changed status from %s to %s" -msgstr "%s hat den Status von %s zu %s geändert" - -#, c-format msgid "%s (%s) changed status from %s to %s" msgstr "%s (%s) hat den Status von %s zu %s geändert" #, c-format -msgid "%s is now %s" -msgstr "%s ist jetzt %s" - -#, c-format msgid "%s (%s) is now %s" msgstr "%s (%s) ist jetzt %s" #, c-format -msgid "%s is no longer %s" -msgstr "%s ist nicht mehr %s" - -#, c-format msgid "%s (%s) is no longer %s" msgstr "%s (%s) ist nicht mehr %s" @@ -9633,6 +9632,28 @@ msgid "Unable to connect to %s: %s" msgstr "Verbindung zu %s nicht möglich: %s" +#. 10053 +#, c-format +msgid "Connection interrupted by other software on your computer." +msgstr "" +"Die Verbindung wurde von einer anderen Software auf ihrem Computer " +"unterbrochen." + +#. 10054 +#, fuzzy, c-format +msgid "Remote host closed connection." +msgstr "Der entfernte Host hat die Verbindung beendet." + +#. 10060 +#, c-format +msgid "Connection timed out." +msgstr "Verbindungsabbruch wegen Zeitüberschreitung." + +#. 10061 +#, c-format +msgid "Connection refused." +msgstr "Verbindung abgelehnt." + msgid "Internet Messenger" msgstr "Internet-Sofortnachrichtendienst" @@ -9995,6 +10016,17 @@ "\n" "<b>Konto:</b> %s" +#, c-format +msgid "" +"\n" +"<b>Topic:</b> %s" +msgstr "" +"\n" +"<b>Thema:</b> %s" + +msgid "(no topic set)" +msgstr "(kein Thema gesetzt)" + msgid "Buddy Alias" msgstr "Buddy-Alias" @@ -10431,6 +10463,14 @@ msgid "User has typed something and stopped" msgstr "Benutzer hat etwas getippt und wartet nun" +#, c-format +msgid "" +"\n" +"%s has typed something and stopped" +msgstr "" +"\n" +"%s hat etwas getippt und wartet nun" + #. Build the Send To menu msgid "S_end To" msgstr "S_enden an" @@ -11055,15 +11095,6 @@ msgid "_Reset formatting" msgstr "Formatierung _zurücksetzen" -msgid "_Smile!" -msgstr "_Lächeln!" - -msgid "_Insert" -msgstr "_Einfügen" - -msgid "_Font" -msgstr "_Schrift" - msgid "Hyperlink color" msgstr "Hyperlink-Farbe" @@ -11077,6 +11108,32 @@ msgstr "" "Farbe zum Darstellen von Hyperlinks, wenn sich die Maus darüber befindet." +#, fuzzy +msgid "Sent Message Name Color" +msgstr "Gesendete Nachrichten" + +msgid "Color to draw the name of a message you sent." +msgstr "" + +#, fuzzy +msgid "Received Message Name Color" +msgstr "Empfangene Nachrichten" + +msgid "Color to draw the name of a message you received." +msgstr "" + +msgid "\"Attention\" Name Color" +msgstr "" + +msgid "Color to draw the name of a message you received containing your name." +msgstr "" + +msgid "Action Message Name Color" +msgstr "" + +msgid "Color to draw the name of an action message." +msgstr "" + msgid "_Copy E-Mail Address" msgstr "Kopiere _E-Mail-Adresse" @@ -11130,6 +11187,15 @@ msgid "_Save Image..." msgstr "Bild _speichern..." +msgid "_Font" +msgstr "_Schrift" + +msgid "_Insert" +msgstr "_Einfügen" + +msgid "S_mile!" +msgstr "_Lächeln!" + msgid "Select Font" msgstr "Schriftart wählen" @@ -11256,6 +11322,9 @@ msgid "_Horizontal rule" msgstr "_Horizontale Linie" +msgid "_Smile!" +msgstr "_Lächeln!" + #, c-format msgid "" "Are you sure you want to permanently delete the log of the conversation with " @@ -11367,6 +11436,33 @@ #, c-format msgid "" +"%s %s\n" +"Usage: %s [OPTION]...\n" +"\n" +" -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" +" -v, --version display the current version and exit\n" +msgstr "" +"%s %s\n" +"Benutzung: %s [OPTION]...\n" +"\n" +" -c, --config=VERZ benutze VERZ als Konfigurationsverzeichnis\n" +" -d, --debug gibt Debugging-Meldungen nach stdout aus\n" +" -h, --help zeigt diese Hilfe und beendet das Programm\n" +" -m, --multiple mehrere Instanzen erlauben\n" +" -n, --nologin nicht automatisch anmelden\n" +" -l, --login[=NAME] automatische Anmeldung (optionales Argument \n" +" NAME bestimmt Konto(n), die benutzt werden\n" +" sollen, getrennt durch Kommata)\n" +" -v, --version zeigt aktuelle Version und beendet das Programm\n" + +#, c-format +msgid "" "%s %s has segfaulted and attempted to dump a core file.\n" "This is a bug in the software and has happened through\n" "no fault of your own.\n" @@ -11675,8 +11771,8 @@ "This is how your outgoing message text will appear when you use protocols " "that support formatting." msgstr "" -"So wird der ausgehende Nachrichtentext aussehen, wenn Sie " -"Protokollebenutzen, die Formatierung unterstützen." +"So wird der ausgehende Nachrichtentext aussehen, wenn Sie Protokolle " +"benutzen, die Formatierung unterstützen." msgid "Cannot start proxy configuration program." msgstr "Kann das Proxy-Konfigurationsprogramm nicht starten." @@ -11699,6 +11795,9 @@ msgid "Ports" msgstr "Ports" +msgid "_Enable automatic router port forwarding" +msgstr "Automatischer _Router-Port-Weiterleitung aktivieren" + msgid "_Manually specify range of ports to listen on" msgstr "Port-Bereich, auf dem gehört werden soll, _manuell bestimmen" @@ -12751,6 +12850,10 @@ msgid "Hyperlink Color" msgstr "Hyperlink-Farbe" +#, fuzzy +msgid "Highlighted Message Name Color" +msgstr "Hervorgehobene Nachrichten" + msgid "GtkTreeView Horizontal Separation" msgstr "GtkTreeview horizontaler Abstand"