Mercurial > pidgin.yaz
changeset 18852:e9fe99a23ab0
merge of 'f5046f52428dc7791ac55340541bd582503b7b8c'
and 'faa15204b2c23cc04fb4a5ac6d7ef2af4c15f28b'
author | Ka-Hing Cheung <khc@hxbc.us> |
---|---|
date | Thu, 09 Aug 2007 02:38:54 +0000 |
parents | f36fc45b7866 (current diff) 9998470a01de (diff) |
children | fab096e7b804 |
files | libpurple/protocols/bonjour/mdns_howl.h libpurple/protocols/bonjour/mdns_win32.h pidgin/gtkconv.c |
diffstat | 66 files changed, 2429 insertions(+), 641 deletions(-) [+] |
line wrap: on
line diff
--- a/ChangeLog Thu Aug 09 02:27:40 2007 +0000 +++ b/ChangeLog Thu Aug 09 02:38:54 2007 +0000 @@ -5,10 +5,14 @@ * Added an account action to open your inbox in the yahoo prpl. * Added support for Unicode status messages in Yahoo. * Server-stored aliases for Yahoo. (John Moody) + * Fixed support for Yahoo! doodling. + * Bonjour plugin uses native Avahi instead of Howl Pidgin: * Show current outgoing conversation formatting on the font label on the toolbar + * Slim new redesign of conversation tabs to maximize number of + conversations that can fit in a window version 2.1.0 (07/28/2007): libpurple:
--- a/Doxyfile.in Thu Aug 09 02:27:40 2007 +0000 +++ b/Doxyfile.in Thu Aug 09 02:38:54 2007 +0000 @@ -300,7 +300,7 @@ # by member name. If set to NO (the default) the members will appear in # declaration order. -SORT_BRIEF_DOCS = YES +SORT_BRIEF_DOCS = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to
--- a/configure.ac Thu Aug 09 02:27:40 2007 +0000 +++ b/configure.ac Thu Aug 09 02:38:54 2007 +0000 @@ -593,6 +593,46 @@ AC_SUBST(MEANWHILE_LIBS) dnl ####################################################################### +dnl # Check for Native Avahi headers (for Bonjour) +dnl ####################################################################### +AC_ARG_WITH(avahi-client-includes, [AC_HELP_STRING([--with-avahi-client-includes=DIR], [compile the Bonjour plugin against the Avahi Client includes in DIR])], [ac_avahi_client_includes="$withval"], [ac_avahi_client_includes="no"]) +AC_ARG_WITH(avahi-client-libs, [AC_HELP_STRING([--with-avahi-client-libs=DIR], [compile the Bonjour plugin against the Avahi Client libs in DIR])], [ac_avahi_client_libs="$withval"], [ac_avahi_client_libs="no"]) +AVAHI_CFLAGS="" +AVAHI_LIBS="" + +dnl Attempt to autodetect Avahi +PKG_CHECK_MODULES(AVAHI, [avahi-client avahi-glib], [ + avahiincludes="yes" + avahilibs="yes" +], [ + AC_MSG_RESULT(no) + avahiincludes="no" + avahilibs="no" +]) + +dnl Override AVAHI_CFLAGS if the user specified an include dir +if test "$ac_avahi_client_includes" != "no"; then + AVAHI_CFLAGS="-I$ac_avahi_client_includes" +fi +CPPFLAGS_save="$CPPFLAGS" +CPPFLAGS="$CPPFLAGS $AVAHI_CFLAGS" +AC_CHECK_HEADER(avahi-client/client.h, [avahiincludes=yes], [avahiincludes=no]) +CPPFLAGS="$CPPFLAGS $AVAHI_CFLAGS $GLIB_CFLAGS" +AC_CHECK_HEADER(avahi-glib/glib-malloc.h, [avahiincludes=yes], [avahiincludes=no]) +CPPFLAGS="$CPPFLAGS_save" + +dnl Override AVAHI_LIBS if the user specified a libs dir +if test "$ac_avahi_client_libs" != "no"; then + AVAHI_LIBS="-L$ac_avahi_client_libs -lavahi-common -lavahi-client -lavahi-glib " +fi +AC_CHECK_LIB(avahi-client, avahi_client_new, [avahilibs=yes], [avahilibs=no], $AVAHI_LIBS) + +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"]) @@ -601,6 +641,7 @@ 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" @@ -640,6 +681,9 @@ 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 dnl ####################################################################### @@ -819,8 +863,10 @@ if test "x$have_meanwhile" != "xyes" ; then STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/sametime//'` fi -if test "x$howlincludes" != "xyes" -o "x$howllibs" != "xyes"; then - STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/bonjour//'` +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 fi if test "x$silcincludes" != "xyes" -o "x$silcclient" != "xyes"; then STATIC_PRPLS=`echo $STATIC_PRPLS | $sedpath 's/silc/silc10/'` @@ -873,7 +919,7 @@ *) echo "Invalid static protocol $i!!" ; exit ;; esac done -AM_CONDITIONAL(STATIC_BONJOUR, test "x$static_bonjour" = "xyes" -a "x$howlincludes" = "xyes" -a "x$howllibs" = "xyes") +AM_CONDITIONAL(STATIC_BONJOUR, test "x$static_bonjour" = "xyes") AM_CONDITIONAL(STATIC_GG, test "x$static_gg" = "xyes") AM_CONDITIONAL(STATIC_IRC, test "x$static_irc" = "xyes") AM_CONDITIONAL(STATIC_JABBER, test "x$static_jabber" = "xyes") @@ -898,8 +944,10 @@ if test "x$have_meanwhile" != "xyes"; then DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/sametime//'` fi -if test "x$howlincludes" != "xyes" -o "x$howllibs" != "xyes"; then - DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/bonjour//'` +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 fi if test "x$silcincludes" != "xyes" -o "x$silcclient" != "xyes"; then DYNAMIC_PRPLS=`echo $DYNAMIC_PRPLS | $sedpath 's/silc/silc10/'` @@ -930,7 +978,7 @@ *) echo "Invalid dynamic protocol $i!!" ; exit ;; esac done -AM_CONDITIONAL(DYNAMIC_BONJOUR, test "x$dynamic_bonjour" = "xyes" -a "x$bonjourincludes" = "xyes" -a "x$bonjourclient" = "xyes") +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_GG, test "x$dynamic_gg" = "xyes") AM_CONDITIONAL(DYNAMIC_IRC, test "x$dynamic_irc" = "xyes") AM_CONDITIONAL(DYNAMIC_JABBER, test "x$dynamic_jabber" = "xyes")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/ui-ops.dox Thu Aug 09 02:38:54 2007 +0000 @@ -0,0 +1,24 @@ +/** @page ui-ops UiOps structures + + When implementing a UI for libpurple, you need to fill in various UiOps + structures: + + - #PurpleAccountUiOps + - #PurpleBlistUiOps + - #PurpleConnectionUiOps + - #PurpleConversationUiOps + - #PurpleCoreUiOps + - #PurpleDebugUiOps + - #PurpleDnsQueryUiOps + - #PurpleEventLoopUiOps + - #PurpleIdleUiOps + - #PurpleNotifyUiOps + - #PurplePrivacyUiOps + - #PurpleRequestUiOps + - #PurpleRoomlistUiOps + - #PurpleSoundUiOps + - #PurpleWhiteboardUiOps + - #PurpleXferUiOps + + */ +// vim: ft=c.doxygen
--- a/finch/finch.c Thu Aug 09 02:27:40 2007 +0000 +++ b/finch/finch.c Thu Aug 09 02:38:54 2007 +0000 @@ -401,7 +401,9 @@ signal(SIGPIPE, SIG_IGN); g_set_prgname("Finch"); +#if GLIB_CHECK_VERSION(2,2,0) g_set_application_name(_("Finch")); +#endif /* Initialize the libpurple stuff */ if (!init_libpurple(argc, argv))
--- a/finch/libgnt/gntbindable.c Thu Aug 09 02:27:40 2007 +0000 +++ b/finch/libgnt/gntbindable.c Thu Aug 09 02:38:54 2007 +0000 @@ -184,7 +184,7 @@ action = g_hash_table_lookup(klass->actions, name); if (!action) { - g_printerr("GntWidget: Invalid action name %s for %s\n", + g_printerr("GntBindable: Invalid action name %s for %s\n", name, g_type_name(G_OBJECT_CLASS_TYPE(klass))); if (list) g_list_free(list);
--- a/finch/libgnt/gntmain.c Thu Aug 09 02:27:40 2007 +0000 +++ b/finch/libgnt/gntmain.c Thu Aug 09 02:38:54 2007 +0000 @@ -517,7 +517,8 @@ void gnt_screen_release(GntWidget *widget) { - gnt_wm_window_close(wm, widget); + if (wm) + gnt_wm_window_close(wm, widget); } void gnt_screen_update(GntWidget *widget) @@ -564,7 +565,9 @@ void gnt_quit() { - g_hash_table_destroy(wm->nodes); /* XXX: */ + g_object_unref(G_OBJECT(wm)); + wm = NULL; + update_panels(); doupdate(); gnt_uninit_colors();
--- a/finch/libgnt/gnttextview.c Thu Aug 09 02:27:40 2007 +0000 +++ b/finch/libgnt/gnttextview.c Thu Aug 09 02:38:54 2007 +0000 @@ -68,17 +68,31 @@ int i = 0; GList *lines; int rows, scrcol; + int comp = 0; /* Used for top-aligned text */ gboolean has_scroll = !(view->flags & GNT_TEXT_VIEW_NO_SCROLL); wbkgd(widget->window, COLOR_PAIR(GNT_COLOR_NORMAL)); werase(widget->window); + if ((view->flags & GNT_TEXT_VIEW_TOP_ALIGN) && + g_list_length(view->list) < widget->priv.height) { + GList *now = view->list; + comp = widget->priv.height - g_list_length(view->list); + view->list = g_list_nth_prev(view->list, comp); + if (!view->list) { + view->list = g_list_first(now); + comp = widget->priv.height - g_list_length(view->list); + } else { + comp = 0; + } + } + for (i = 0, lines = view->list; i < widget->priv.height && lines; i++, lines = lines->next) { GList *iter; GntTextLine *line = lines->data; - wmove(widget->window, widget->priv.height - 1 - i, 0); + wmove(widget->window, widget->priv.height - 1 - i - comp, 0); for (iter = line->segments; iter; iter = iter->next) { @@ -398,7 +412,7 @@ static void gnt_text_view_size_changed(GntWidget *widget, int w, int h) { - if (w != widget->priv.width) { + if (w != widget->priv.width && GNT_WIDGET_IS_FLAG_SET(widget, GNT_WIDGET_MAPPED)) { gnt_text_view_reflow(GNT_TEXT_VIEW(widget)); } } @@ -422,11 +436,16 @@ gnt_text_view_init(GTypeInstance *instance, gpointer class) { GntWidget *widget = GNT_WIDGET(instance); - - GNT_WIDGET_SET_FLAGS(GNT_WIDGET(instance), GNT_WIDGET_GROW_Y | GNT_WIDGET_GROW_X); + GntTextView *view = GNT_TEXT_VIEW(widget); + GntTextLine *line = g_new0(GntTextLine, 1); + GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_BORDER | GNT_WIDGET_NO_SHADOW | + GNT_WIDGET_GROW_Y | GNT_WIDGET_GROW_X); widget->priv.minw = 5; widget->priv.minh = 2; + view->string = g_string_new(NULL); + view->list = g_list_append(view->list, line); + GNTDEBUG; } @@ -464,13 +483,6 @@ GntWidget *gnt_text_view_new() { GntWidget *widget = g_object_new(GNT_TYPE_TEXT_VIEW, NULL); - GntTextView *view = GNT_TEXT_VIEW(widget); - GntTextLine *line = g_new0(GntTextLine, 1); - - GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_BORDER | GNT_WIDGET_NO_SHADOW); - - view->string = g_string_new(NULL); - view->list = g_list_append(view->list, line); return widget; }
--- a/finch/libgnt/gnttextview.h Thu Aug 09 02:27:40 2007 +0000 +++ b/finch/libgnt/gnttextview.h Thu Aug 09 02:38:54 2007 +0000 @@ -47,9 +47,11 @@ typedef struct _GntTextViewPriv GntTextViewPriv; typedef struct _GntTextViewClass GntTextViewClass; -typedef enum _GntTextViewFlag { +typedef enum +{ GNT_TEXT_VIEW_NO_SCROLL = 1 << 0, GNT_TEXT_VIEW_WRAP_CHAR = 1 << 1, + GNT_TEXT_VIEW_TOP_ALIGN = 1 << 2, } GntTextViewFlag; struct _GntTextView
--- a/finch/libgnt/gnttree.c Thu Aug 09 02:27:40 2007 +0000 +++ b/finch/libgnt/gnttree.c Thu Aug 09 02:38:54 2007 +0000 @@ -450,7 +450,7 @@ if (COLUMN_INVISIBLE(tree, i)) { continue; } - mvwaddstr(widget->window, pos, x + 1, tree->columns[i].title); + mvwaddnstr(widget->window, pos, x + (x != pos), tree->columns[i].title, tree->columns[i].width); NEXT_X; } if (pos) @@ -768,6 +768,7 @@ g_string_free(tree->priv->search, TRUE); tree->priv->search = NULL; tree->priv->search_timeout = 0; + GNT_WIDGET_UNSET_FLAGS(GNT_WIDGET(tree), GNT_WIDGET_DISABLE_ACTIONS); } } @@ -1053,7 +1054,9 @@ GntTree *tree = GNT_TREE(widget); tree->show_separator = TRUE; tree->priv = g_new0(GntTreePriv, 1); - GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_GROW_X | GNT_WIDGET_GROW_Y | GNT_WIDGET_CAN_TAKE_FOCUS); + GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_GROW_X | GNT_WIDGET_GROW_Y | + GNT_WIDGET_CAN_TAKE_FOCUS | GNT_WIDGET_NO_SHADOW); + gnt_widget_set_take_focus(widget, TRUE); widget->priv.minw = 4; widget->priv.minh = 1; GNTDEBUG; @@ -1094,7 +1097,7 @@ free_tree_col(gpointer data) { GntTreeCol *col = data; - if (col->isbinary) + if (!col->isbinary) g_free(col->text); g_free(col); } @@ -1605,9 +1608,6 @@ "columns", col, NULL); - GNT_WIDGET_SET_FLAGS(widget, GNT_WIDGET_NO_SHADOW); - gnt_widget_set_take_focus(widget, TRUE); - return widget; }
--- a/finch/libgnt/gntwm.c Thu Aug 09 02:27:40 2007 +0000 +++ b/finch/libgnt/gntwm.c Thu Aug 09 02:38:54 2007 +0000 @@ -684,6 +684,8 @@ {'j', "┘"}, {'a', "▒"}, {'n', "┼"}, + {'w', "┬"}, + {'v', "┴"}, {'\0', NULL} }; @@ -1137,9 +1139,37 @@ } static void +accumulate_windows(gpointer window, gpointer node, gpointer p) +{ + GList *list = *(GList**)p; + list = g_list_prepend(list, window); + *(GList**)p = list; +} + +static void +gnt_wm_destroy(GObject *obj) +{ + GntWM *wm = GNT_WM(obj); + GList *list = NULL; + g_hash_table_foreach(wm->nodes, accumulate_windows, &list); + g_list_foreach(list, (GFunc)gnt_widget_destroy, NULL); + g_list_free(list); + g_hash_table_destroy(wm->nodes); + wm->nodes = NULL; + + while (wm->workspaces) { + g_object_unref(wm->workspaces->data); + wm->workspaces = g_list_delete_link(wm->workspaces, wm->workspaces); + } +} + +static void gnt_wm_class_init(GntWMClass *klass) { int i; + GObjectClass *gclass = G_OBJECT_CLASS(klass); + + gclass->dispose = gnt_wm_destroy; klass->new_window = gnt_wm_new_window_real; klass->decorate_window = NULL;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/libgnt/pygnt/Makefile.am Thu Aug 09 02:38:54 2007 +0000 @@ -0,0 +1,40 @@ +EXTRA_DIST = gendef.sh + +pg_LTLIBRARIES = gnt.la + +pgdir = $(libdir) + +sources = \ + gnt.def \ + gnt.override \ + gntbox.override \ + gntfilesel.override \ + gnttree.override \ + gntwidget.override + +gnt_la_SOURCES = gnt.c common.c common.h gntmodule.c + +gnt_la_LDFLAGS = -module -avoid-version \ + `pkg-config --libs pygobject-2.0` + +gnt_la_LIBADD = \ + $(GLIB_LIBS) \ + ../libgnt.la + +AM_CPPFLAGS = \ + -I../ \ + $(GLIB_CFLAGS) \ + $(GNT_CFLAGS) \ + -I/usr/include/python2.4 \ + `pkg-config --cflags pygobject-2.0` + +CLEANFILES = gnt.def gnt.c gnt.defe + +gnt.def: $(srcdir)/../*.h + $(srcdir)/gendef.sh + +gnt.c: $(sources) + pygtk-codegen-2.0 --prefix gnt \ + --override gnt.override \ + gnt.def > $@ +
--- a/finch/libgnt/pygnt/Makefile.make Thu Aug 09 02:27:40 2007 +0000 +++ b/finch/libgnt/pygnt/Makefile.make Thu Aug 09 02:38:54 2007 +0000 @@ -10,5 +10,7 @@ --override gnt.override \ gnt.def > $@ +#python codegen/codegen.py --prefix gnt \ + clean: @rm *.so *.o gnt.c
--- a/finch/libgnt/pygnt/dbus-gnt Thu Aug 09 02:27:40 2007 +0000 +++ b/finch/libgnt/pygnt/dbus-gnt Thu Aug 09 02:38:54 2007 +0000 @@ -17,13 +17,14 @@ convwins = {} -def buddysignedon(): +def buddysignedon(buddy): pass def conv_closed(conv): key = get_dict_key(conv) stuff = convwins[key] stuff[0].destroy() + # if a conv window is closed, then reopened, this thing crashes convwins[key] = None def wrote_msg(account, who, msg, conv, flags): @@ -31,10 +32,13 @@ tv = stuff[1] tv.append_text_with_flags("\n", 0) tv.append_text_with_flags(strftime("(%X) "), 8) - tv.append_text_with_flags(who + ": ", 1) - tv.append_text_with_flags(msg, 0) + if flags & 3: + tv.append_text_with_flags(who + ": ", 1) + tv.append_text_with_flags(msg, 0) + stuff[0].set_urgent() + else: + tv.append_text_with_flags(msg, 8) tv.scroll(0) - stuff[0].set_urgent() bus = dbus.SessionBus() obj = bus.get_object("im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject") @@ -72,6 +76,9 @@ purple.PurpleConvChatSend(chatdata, entry.get_text()) entry.clear() +def conv_window_destroyed(win, key): + del convwins[key] + def show_conversation(conv): key = get_dict_key(conv) if key in convwins: @@ -91,27 +98,28 @@ tv.clear() win.show() convwins[key] = [win, tv, entry] + win.connect("destroy", conv_window_destroyed, key) return convwins[key] def show_buddylist(): - win = gnt.Window() - tree = gnt.Tree() - tree.set_property("columns", 1) - win.add_widget(tree) - node = purple.PurpleBlistGetRoot() - while node: - if purple.PurpleBlistNodeIsGroup(node): - sys.stderr.write(str(node) + "\n") - tree.add_row_after(str(node), ["asd", ""], None, None) - #tree.add_row_after(node, [str(purple.PurpleGroupGetName(node)), ""], None, None) - #tree.add_row_after(node, ["aasd", ""], None, None) - elif purple.PurpleBlistNodeIsContact(node): - buddy = purple.PurpleContactGetPriorityBuddy(node) - group = purple.PurpleBuddyGetGroup(buddy) - #tree.add_row_after(node, [str(purple.PurpleBuddyGetName(buddy)), ""], group, None) + win = gnt.Window() + tree = gnt.Tree() + tree.set_property("columns", 1) + win.add_widget(tree) + node = purple.PurpleBlistGetRoot() + while node: + if purple.PurpleBlistNodeIsGroup(node): + sys.stderr.write(str(node) + "\n") + tree.add_row_after(str(node), ["asd", ""], None, None) + #tree.add_row_after(node, [str(purple.PurpleGroupGetName(node)), ""], None, None) + #tree.add_row_after(node, ["aasd", ""], None, None) + elif purple.PurpleBlistNodeIsContact(node): + buddy = purple.PurpleContactGetPriorityBuddy(node) + group = purple.PurpleBuddyGetGroup(buddy) + #tree.add_row_after(node, [str(purple.PurpleBuddyGetName(buddy)), ""], group, None) - node = purple.PurpleBlistNodeNext(node, False) - win.show() + node = purple.PurpleBlistNodeNext(node, False) + win.show() gnt.gnt_init()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/libgnt/pygnt/example/rss/gnthtml.py Thu Aug 09 02:38:54 2007 +0000 @@ -0,0 +1,87 @@ +#!/usr/bin/env python + +""" +gr - An RSS-reader built using libgnt and feedparser. + +Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im> + +This application is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This application 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this application; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA +""" + +""" +This file defines GParser, which is a simple HTML parser to display HTML +in a GntTextView nicely. +""" + +import sgmllib +import gnt + +class GParser(sgmllib.SGMLParser): + def __init__(self, view): + sgmllib.SGMLParser.__init__(self, False) + self.link = None + self.view = view + self.flag = gnt.TEXT_FLAG_NORMAL + + def parse(self, s): + self.feed(s) + self.close() + + def unknown_starttag(self, tag, attrs): + if tag in ["b", "i", "blockquote", "strong"]: + self.flag = self.flag | gnt.TEXT_FLAG_BOLD + elif tag in ["p", "hr", "br"]: + self.view.append_text_with_flags("\n", self.flag) + else: + print tag + + def unknown_endtag(self, tag): + if tag in ["b", "i", "blockquote", "strong"]: + self.flag = self.flag & ~gnt.TEXT_FLAG_BOLD + elif tag in ["p", "hr", "br"]: + self.view.append_text_with_flags("\n", self.flag) + else: + print tag + + def start_u(self, attrs): + self.flag = self.flag | gnt.TEXT_FLAG_UNDERLINE + + def end_u(self): + self.flag = self.flag & ~gnt.TEXT_FLAG_UNDERLINE + + def start_a(self, attributes): + for name, value in attributes: + if name == "href": + self.link = value + + def do_img(self, attrs): + for name, value in attrs: + if name == 'src': + self.view.append_text_with_flags("[img:" + value + "]", self.flag) + + def end_a(self): + if not self.link: + return + self.view.append_text_with_flags(" (", self.flag) + self.view.append_text_with_flags(self.link, self.flag | gnt.TEXT_FLAG_UNDERLINE) + self.view.append_text_with_flags(")", self.flag) + self.link = None + + def handle_data(self, data): + if len(data.strip()) == 0: + return + self.view.append_text_with_flags(data, self.flag) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/libgnt/pygnt/example/rss/gntrss-ui.py Thu Aug 09 02:38:54 2007 +0000 @@ -0,0 +1,399 @@ +#!/usr/bin/env python + +""" +gr - An RSS-reader built using libgnt and feedparser. + +Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im> + +This application is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This application 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this application; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA +""" + +""" +This file deals with the UI part (gnt) of the application + +TODO: + - Allow showing feeds of only selected 'category' and/or 'priority'. A different + window should be used to change such filtering. + - Display details of each item in its own window. + - Add search capability, and allow searching only in title/body. Also allow + filtering in the search results. + - Show the data and time for feed items (probably in a separate column .. perhaps not) + - Have a simple way to add a feed. + - Allow renaming a feed. +""" + +import gntrss +import gnthtml +import gnt +import gobject +import sys + +__version__ = "0.0.1alpha" +__author__ = "Sadrul Habib Chowdhury (sadrul@pidgin.im)" +__copyright__ = "Copyright 2007, Sadrul Habib Chowdhury" +__license__ = "GPL" # see full license statement above + +gnt.gnt_init() + +class RssTree(gnt.Tree): + __gsignals__ = { + 'active_changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,)) + } + + __gntbindings__ = { + 'jump-next-unread' : ('jump_next_unread', 'J') + } + + def jump_next_unread(self, null): + first = None + next = None + all = self.get_rows() + for item in all: + if item.unread: + if next: + first = item + break + elif not first and self.active != item: + first = item + if self.active == item: + next = item + if first: + self.set_active(first) + self.set_selected(first) + + def __init__(self): + self.active = None + gnt.Tree.__init__(self) + gnt.set_flag(self, 8) # remove borders + self.connect('key_pressed', self.do_key_pressed) + + def set_active(self, active): + if self.active == active: + return + if self.active: + flag = gnt.TEXT_FLAG_NORMAL + if self.active.unread: + flag = flag | gnt.TEXT_FLAG_BOLD + self.set_row_flags(self.active, flag) + old = self.active + self.active = active + flag = gnt.TEXT_FLAG_UNDERLINE + if self.active.unread: + flag = flag | gnt.TEXT_FLAG_BOLD + self.set_row_flags(self.active, flag) + self.emit('active_changed', old) + + def do_key_pressed(self, null, text): + if text == '\r': + now = self.get_selection_data() + self.set_active(now) + return True + return False + +gobject.type_register(RssTree) +gnt.register_bindings(RssTree) + +win = gnt.Box(homo = False, vert = True) +win.set_toplevel(True) +win.set_title("GntRss") +win.set_pad(0) + +# +# [[[ Generic feed/item callbacks +# +def feed_item_added(feed, item): + add_feed_item(item) + +def add_feed(feed): + if not feed.get_data('gntrss-connected'): + feed.connect('added', feed_item_added) + feed.connect('notify', update_feed_title) + feed.set_data('gntrss-connected', True) + feeds.add_row_after(feed, [feed.title, str(feed.unread)], None, None) + +def remove_item(item, feed): + items.remove(item) + +def update_feed_item(item, property): + if property.name == 'unread': + if feeds.active == item.parent: + flag = 0 + if item == items.active: + flag = gnt.TEXT_FLAG_UNDERLINE + if item.unread: + flag = flag | gnt.TEXT_FLAG_BOLD + else: + flag = flag | gnt.TEXT_FLAG_NORMAL + items.set_row_flags(item, flag) + + unread = item.parent.unread + if item.unread: + unread = unread + 1 + else: + unread = unread - 1 + item.parent.set_property('unread', unread) + +def add_feed_item(item): + currentfeed = feeds.active + if item.parent != currentfeed: + return + months = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + dt = str(item.date_parsed[2]) + "." + months[item.date_parsed[1]] + "." + str(item.date_parsed[0]) + items.add_row_after(item, [str(item.title), dt], None, None) + if item.unread: + items.set_row_flags(item, gnt.TEXT_FLAG_BOLD) + if not item.get_data('gntrss-connected'): + item.set_data('gntrss-connected', True) + # this needs to happen *without* having to add the item in the tree + item.connect('notify', update_feed_item) + item.connect('delete', remove_item) + +# +# ]]] Generic feed/item callbacks +# + + +#### +# [[[ The list of feeds +### + +# 'Add Feed' dialog +add_feed_win = None +def add_feed_win_closed(win): + global add_feed_win + add_feed_win = None + +def add_new_feed(): + global add_feed_win + + if add_feed_win: + gnt.gnt_window_present(add_feed_win) + return + win = gnt.Window() + win.set_title("New Feed") + + box = gnt.Box(False, False) + label = gnt.Label("Link") + box.add_widget(label) + entry = gnt.Entry("") + entry.set_size(40, 1) + box.add_widget(entry) + + win.add_widget(box) + win.show() + add_feed_win = win + add_feed_win.connect("destroy", add_feed_win_closed) + +# +# The active row in the feed-list has changed. Update the feed-item table. +def feed_active_changed(tree, old): + items.remove_all() + if not tree.active: + return + update_items_title() + for item in tree.active.items: + add_feed_item(item) + win.give_focus_to_child(items) + +# +# Check for the action keys and decide how to deal with them. +def feed_key_pressed(tree, text): + if tree.is_searching(): + return + if text == 'r': + feed = tree.get_selection_data() + tree.perform_action_key('j') + #tree.perform_action('move-down') + feed.refresh() + elif text == 'R': + feeds = tree.get_rows() + for feed in feeds: + feed.refresh() + elif text == 'm': + feed = tree.get_selection_data() + if feed: + feed.mark_read() + feed.set_property('unread', 0) + elif text == 'a': + add_new_feed() + else: + return False + return True + +feeds = RssTree() +feeds.set_property('columns', 2) +feeds.set_col_width(0, 20) +feeds.set_col_width(1, 6) +feeds.set_column_resizable(0, False) +feeds.set_column_resizable(1, False) +feeds.set_column_is_right_aligned(1, True) +feeds.set_show_separator(False) +feeds.set_column_title(0, "Feeds") +feeds.set_show_title(True) + +feeds.connect('active_changed', feed_active_changed) +feeds.connect('key_pressed', feed_key_pressed) +gnt.unset_flag(feeds, 256) # Fix the width + +#### +# ]]] The list of feeds +### + +#### +# [[[ The list of items in the feed +#### + +# +# The active item in the feed-item list has changed. Update the +# summary content. +def item_active_changed(tree, old): + details.clear() + if not tree.active: + return + item = tree.active + details.append_text_with_flags(str(item.title) + "\n", gnt.TEXT_FLAG_BOLD) + details.append_text_with_flags("Link: ", gnt.TEXT_FLAG_BOLD) + details.append_text_with_flags(str(item.link) + "\n", gnt.TEXT_FLAG_UNDERLINE) + details.append_text_with_flags("Date: ", gnt.TEXT_FLAG_BOLD) + details.append_text_with_flags(str(item.date) + "\n", gnt.TEXT_FLAG_NORMAL) + details.append_text_with_flags("\n", gnt.TEXT_FLAG_NORMAL) + parser = gnthtml.GParser(details) + parser.parse(str(item.summary)) + item.mark_unread(False) + + if old and old.unread: # If the last selected item is marked 'unread', then make sure it's bold + items.set_row_flags(old, gnt.TEXT_FLAG_BOLD) + +# +# Look for action keys in the feed-item list. +def item_key_pressed(tree, text): + if tree.is_searching(): + return + current = tree.get_selection_data() + if text == 'M': # Mark all of the items 'read' + feed = feeds.active + if feed: + feed.mark_read() + elif text == 'm': # Mark the current item 'read' + current.mark_unread(False) + tree.perform_action_key('j') + elif text == 'U': # Mark the current item 'unread' + current.mark_unread(True) + elif text == 'd': + current.remove() + tree.perform_action_key('j') + else: + return False + return True + +items = RssTree() +items.set_property('columns', 2) +items.set_col_width(0, 40) +items.set_col_width(1, 11) +items.set_column_resizable(1, False) +items.set_column_title(0, "Items") +items.set_column_title(1, "Date") +items.set_show_title(True) +items.connect('key_pressed', item_key_pressed) +items.connect('active_changed', item_active_changed) + +#### +# ]]] The list of items in the feed +#### + +# +# Update the title of the items list depending on the selection in the feed list +def update_items_title(): + feed = feeds.active + if feed: + items.set_column_title(0, str(feed.title) + ": " + str(feed.unread) + "(" + str(len(feed.items)) + ")") + else: + items.set_column_title(0, "Items") + items.draw() + +# The container on the top +line = gnt.Line(vertical = False) + +# The textview to show the details of a feed +details = gnt.TextView() +details.set_take_focus(True) +details.set_flag(gnt.TEXT_VIEW_TOP_ALIGN) +details.attach_scroll_widget(details) + +# Make it look nice +s = feeds.get_size() +size = gnt.screen_size() +size[0] = size[0] - s[0] +items.set_size(size[0], size[1] / 2) +details.set_size(size[0], size[1] / 2) + +# Category tree +cat = gnt.Tree() +cat.set_property('columns', 1) +cat.set_column_title(0, 'Category') +cat.set_show_title(True) +gnt.set_flag(cat, 8) # remove borders + +box = gnt.Box(homo = False, vert = False) +box.set_pad(0) + +vbox = gnt.Box(homo = False, vert = True) +vbox.set_pad(0) +vbox.add_widget(feeds) +vbox.add_widget(gnt.Line(False)) +vbox.add_widget(cat) +box.add_widget(vbox) + +box.add_widget(gnt.Line(True)) + +vbox = gnt.Box(homo = False, vert = True) +vbox.set_pad(0) +vbox.add_widget(items) +vbox.add_widget(gnt.Line(False)) +vbox.add_widget(details) +box.add_widget(vbox) + +win.add_widget(box) +win.show() + +def update_feed_title(feed, property): + if property.name == 'title': + if feed.customtitle: + title = feed.customtitle + else: + title = feed.title + feeds.change_text(feed, 0, title) + elif property.name == 'unread': + feeds.change_text(feed, 1, str(feed.unread) + "(" + str(len(feed.items)) + ")") + flag = 0 + if feeds.active == feed: + flag = gnt.TEXT_FLAG_UNDERLINE + update_items_title() + if feed.unread > 0: + flag = flag | gnt.TEXT_FLAG_BOLD + feeds.set_row_flags(feed, flag) + +# populate everything +for feed in gntrss.feeds: + feed.refresh() + feed.set_auto_refresh(True) + add_feed(feed) + +gnt.gnt_register_action("Stuff", add_new_feed) +gnt.gnt_main() + +gnt.gnt_quit() +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/libgnt/pygnt/example/rss/gntrss.py Thu Aug 09 02:38:54 2007 +0000 @@ -0,0 +1,250 @@ +#!/usr/bin/env python + +""" +gr - An RSS-reader built using libgnt and feedparser. + +Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im> + +This application is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This application 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 +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this application; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA +""" + +""" +This file deals with the rss parsing part (feedparser) of the application +""" + +import os +import tempfile, urllib2 +import feedparser +import gobject +import sys +import time + +## +# The FeedItem class. It will update emit 'delete' signal when it's +# destroyed. +## +class FeedItem(gobject.GObject): + __gproperties__ = { + 'unread' : (gobject.TYPE_BOOLEAN, 'read', + 'The unread state of the item.', + False, gobject.PARAM_READWRITE) + } + __gsignals__ = { + 'delete' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,)) + } + def __init__(self, item, parent): + self.__gobject_init__() + try: + "Apparently some feed items don't have any dates in them" + self.date = item['date'] + self.date_parsed = item['date_parsed'] + except: + item['date'] = self.date = time.ctime() + self.date_parsed = feedparser._parse_date(self.date) + + self.title = item['title'] + sum = item['summary'] + self.summary = item['summary'].encode('utf8') + self.link = item['link'] + self.parent = parent + self.unread = True + + def remove(self): + self.emit('delete', self.parent) + if self.unread: + self.parent.set_property('unread', self.parent.unread - 1) + + def do_set_property(self, property, value): + if property.name == 'unread': + self.unread = value + + def mark_unread(self, unread): + if self.unread == unread: + return + self.set_property('unread', unread) + +gobject.type_register(FeedItem) + +def item_hash(item): + return str(item['date'] + item['title']) + +""" +The Feed class. It will update the 'link', 'title', 'desc' and 'items' +attributes if/when they are updated (triggering 'notify::<attr>' signal) + +TODO: + - Add a 'count' attribute + - Each feed will have a 'uidata', which will be its display window + - Look into 'category'. Is it something that feed defines, or the user? + - Have separate refresh times for each feed. + - Have 'priority' for each feed. (somewhat like category, perhaps?) +""" +class Feed(gobject.GObject): + __gproperties__ = { + 'link' : (gobject.TYPE_STRING, 'link', + 'The web page this feed is associated with.', + '...', gobject.PARAM_READWRITE), + 'title' : (gobject.TYPE_STRING, 'title', + 'The title of the feed.', + '...', gobject.PARAM_READWRITE), + 'desc' : (gobject.TYPE_STRING, 'description', + 'The description for the feed.', + '...', gobject.PARAM_READWRITE), + 'items' : (gobject.TYPE_POINTER, 'items', + 'The items in the feed.', gobject.PARAM_READWRITE), + 'unread' : (gobject.TYPE_INT, 'unread', + 'Number of unread items in the feed.', 0, 10000, 0, gobject.PARAM_READWRITE) + } + __gsignals__ = { + 'added' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,)) + } + + def __init__(self, feed): + self.__gobject_init__() + url = feed['link'] + name = feed['name'] + self.url = url # The url of the feed itself + self.link = url # The web page associated with the feed + self.desc = url + self.title = (name, url)[not name] + self.customtitle = name + self.unread = 0 + self.items = [] + self.hash = {} + self.pending = False + self._refresh = {'time' : 30, 'id' : 0} + + def do_set_property(self, property, value): + if property.name == 'link': + self.link = value + elif property.name == 'desc': + self.desc = value + elif property.name == 'title': + self.title = value + elif property.name == 'unread': + self.unread = value + pass + + def set_result(self, result): + # XXX Look at result['bozo'] first, and emit some signal that the UI can use + # to indicate (dim the row?) that the feed has invalid XML format or something + + try: + channel = result['channel'] + self.set_property('link', channel['link']) + self.set_property('desc', channel['description']) + self.set_property('title', channel['title']) + items = result['items'] + except: + items = () + + tmp = {} + for item in self.items: + tmp[hash(item)] = item + + unread = self.unread + for item in items: + try: + exist = self.hash[item_hash(item)] + del tmp[hash(exist)] + except: + itm = FeedItem(item, self) + self.items.append(itm) + self.emit('added', itm) + self.hash[item_hash(item)] = itm + unread = unread + 1 + + if unread != self.unread: + self.set_property('unread', unread) + + for hv in tmp: + tmp[hv].remove() + "Also notify the UI about the count change" + + self.pending = False + return False + + def refresh(self): + if self.pending: + return + self.pending = True + FeedReader(self).run() + return True + + def mark_read(self): + for item in self.items: + item.mark_unread(False) + + def set_auto_refresh(self, auto): + if auto: + if self._refresh['id']: + return + if self._refresh['time'] < 1: + self._refresh['time'] = 1 + self.id = gobject.timeout_add(self._refresh['time'] * 1000 * 60, self.refresh) + else: + if not self._refresh['id']: + return + gobject.source_remove(self._refresh['id']) + self._refresh['id'] = 0 + +gobject.type_register(Feed) + +""" +The FeedReader updates a Feed. It fork()s off a child to avoid blocking. +""" +class FeedReader: + def __init__(self, feed): + self.feed = feed + + def reap_child(self, pid, status): + result = feedparser.parse(self.tmpfile.name) + self.tmpfile.close() + self.feed.set_result(result) + + def run(self): + self.tmpfile = tempfile.NamedTemporaryFile() + self.pid = os.fork() + if self.pid == 0: + tmp = urllib2.urlopen(self.feed.url) + content = tmp.read() + tmp.close() + self.tmpfile.write(content) + self.tmpfile.flush() + # Do NOT close tmpfile here + os._exit(os.EX_OK) + gobject.child_watch_add(self.pid, self.reap_child) + +feeds = [] +urls = ( + {'name': '/.', + 'link': "http://rss.slashdot.org/Slashdot/slashdot"}, + {'name': 'KernelTrap', + 'link': "http://kerneltrap.org/node/feed"}, + {'name': None, + 'link': "http://pidgin.im/rss.php"}, + {'name': "F1", + 'link': "http://www.formula1.com/rss/news/latest.rss"}, + {'name': "Freshmeat", + 'link': "http://www.pheedo.com/f/freshmeatnet_announcements_unix"}, + {'name': "Cricinfo", + 'link': "http://www.cricinfo.com/rss/livescores.xml"} +) + +for url in urls: + feed = Feed(url) + feeds.append(feed) +
--- a/finch/libgnt/pygnt/gendef.sh Thu Aug 09 02:27:40 2007 +0000 +++ b/finch/libgnt/pygnt/gendef.sh Thu Aug 09 02:38:54 2007 +0000 @@ -28,7 +28,7 @@ gnt.h" # Generate the def file -rm gnt.def +rm -f gnt.def for file in $FILES do python /usr/share/pygtk/2.0/codegen/h2def.py ../$file >> gnt.def @@ -43,6 +43,7 @@ GNT_TYPE_KEY_PRESS_MODE GNT_TYPE_ENTRY_FLAG GNT_TYPE_TEXT_FORMAT_FLAGS +GNT_TYPE_TEXT_VIEW_FLAG " for enum in $ENUMS
--- a/finch/libgnt/pygnt/gnt.override Thu Aug 09 02:27:40 2007 +0000 +++ b/finch/libgnt/pygnt/gnt.override Thu Aug 09 02:38:54 2007 +0000 @@ -29,8 +29,10 @@ #include "common.h" %% include + gntbox.override gntfilesel.override gnttree.override + gntwidget.override %% modulename gnt %% @@ -39,3 +41,154 @@ ignore-glob *_get_gtype %% +define set_flag +static PyObject * +_wrap_set_flag(PyGObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"flags", NULL}; + PyGObject *widget; + int flags; + + if (!PyArg_ParseTuple(args, "O!i:gnt.set_flag", &PyGntWidget_Type, &widget, + &flags)) { + return NULL; + } + + GNT_WIDGET_SET_FLAGS(widget->obj, flags); + + Py_INCREF(Py_None); + return Py_None; +} +%% +define unset_flag +static PyObject * +_wrap_unset_flag(PyGObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"flags", NULL}; + PyGObject *widget; + int flags; + + if (!PyArg_ParseTuple(args, "O!i:gnt.unset_flag", &PyGntWidget_Type, &widget, + &flags)) { + return NULL; + } + + GNT_WIDGET_UNSET_FLAGS(widget->obj, flags); + + Py_INCREF(Py_None); + return Py_None; +} +%% +define screen_size noargs +static PyObject * +_wrap_screen_size(PyObject *self) +{ + PyObject *list = PyList_New(0); + + if (list == NULL) + return NULL; + + PyList_Append(list, PyInt_FromLong((long)getmaxx(stdscr))); + PyList_Append(list, PyInt_FromLong((long)getmaxy(stdscr))); + + return list; +} +%% +override gnt_register_action +static GHashTable *actions; + + + +static PyObject * +_wrap_gnt_register_action(PyGObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"name", "callback", NULL}; + PyGObject *callback; + GClosure *closure; + char *name; + + if (!PyArg_ParseTuple(args, "sO:gnt.gnt_register_action", &name, &callback)) { + return NULL; + } + + if (!PyCallable_Check(callback)) { + PyErr_SetString(PyExc_TypeError, "the callback must be callable ... doh!"); + return NULL; + } + + gnt_register_action(name, callback->obj); + + Py_INCREF(Py_None); + return Py_None; +} +%% +define register_bindings + +static gboolean +pygnt_binding_callback(GntBindable *bindable, GList *list) +{ + PyObject *wrapper = pygobject_new(G_OBJECT(bindable)); + PyObject_CallMethod(wrapper, list->data, "O", Py_None); + Py_DECREF(wrapper); + return TRUE; +} + +static PyObject * +_wrap_register_bindings(PyObject *self, PyObject *args) +{ + PyTypeObject *class; + int pos = 0; + PyObject *key, *value, *gbindings; + GntBindableClass *bindable; + + if (!PyArg_ParseTuple(args, "O!:gnt.register_bindings", + &PyType_Type, &class)) { + /* Make sure it's a GntBindableClass subclass */ + PyErr_SetString(PyExc_TypeError, + "argument must be a GntBindable subclass"); + return NULL; + } + + gbindings = PyDict_GetItemString(class->tp_dict, "__gntbindings__"); + if (!gbindings) + goto end; + + if (!PyDict_Check(gbindings)) { + PyErr_SetString(PyExc_TypeError, + "__gntbindings__ attribute not a dict!"); + return NULL; + } + + bindable = g_type_class_ref(pyg_type_from_object((PyObject *)class)); + while (PyDict_Next(gbindings, &pos, &key, &value)) { + const char *trigger, *callback, *name; + GList *list = NULL; + + if (!PyString_Check(key)) { + PyErr_SetString(PyExc_TypeError, + "__gntbindings__ keys must be strings"); + g_type_class_unref(bindable); + return NULL; + } + name = PyString_AsString(key); + + if (!PyTuple_Check(value) || + !PyArg_ParseTuple(value, "ss", &callback, &trigger)) { + PyErr_SetString(PyExc_TypeError, + "__gntbindings__ values must be (callback, trigger) tupples"); + g_type_class_unref(bindable); + return NULL; + } + + gnt_bindable_class_register_action(bindable, name, pygnt_binding_callback, + trigger, g_strdup(callback), NULL); + } + if (gbindings) + PyDict_DelItemString(class->tp_dict, "__gntbindings__"); + g_type_class_unref(bindable); + +end: + Py_INCREF(Py_None); + return Py_None; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/libgnt/pygnt/gntbox.override Thu Aug 09 02:38:54 2007 +0000 @@ -0,0 +1,38 @@ +/** + * pygnt- Python bindings for the GNT toolkit. + * Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im> + * + * gntbox.override: overrides for the box widget. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ +%% +override gnt_box_add_widget kwargs +static PyObject * +_wrap_gnt_box_add_widget(PyGObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = { "widget", NULL }; + PyGObject *widget; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!:GntBox.add_widget", kwlist, &PyGntWidget_Type, &widget)) + return NULL; + + gnt_box_add_widget(GNT_BOX(self->obj), GNT_WIDGET(widget->obj)); + Py_INCREF(widget); + + Py_INCREF(Py_None); + return Py_None; +}
--- a/finch/libgnt/pygnt/gntmodule.c Thu Aug 09 02:27:40 2007 +0000 +++ b/finch/libgnt/pygnt/gntmodule.c Thu Aug 09 02:38:54 2007 +0000 @@ -9,11 +9,12 @@ PyObject *m, *d; init_pygobject (); - + m = Py_InitModule ("gnt", gnt_functions); d = PyModule_GetDict (m); gnt_register_classes (d); + gnt_add_constants(m, "GNT_"); if (PyErr_Occurred ()) { Py_FatalError ("can't initialise module sad");
--- a/finch/libgnt/pygnt/gnttree.override Thu Aug 09 02:27:40 2007 +0000 +++ b/finch/libgnt/pygnt/gnttree.override Thu Aug 09 02:38:54 2007 +0000 @@ -20,7 +20,7 @@ * USA */ %% -headrs +headers #include "common.h" %% ignore @@ -49,7 +49,7 @@ return NULL; } while (list) { - PyObject *obj = pyg_pointer_new(G_TYPE_POINTER, list->data); + PyObject *obj = list->data; PyList_Append(py_list, obj); Py_DECREF(obj); list = list->next; @@ -100,4 +100,79 @@ Py_INCREF(Py_None); return Py_None; } +%% +override gnt_tree_get_selection_data noargs +static PyObject * +_wrap_gnt_tree_get_selection_data(PyGObject *self) +{ + PyObject *ret = gnt_tree_get_selection_data(GNT_TREE(self->obj)); + if (!ret) + ret = Py_None; + Py_INCREF(ret); + return ret; +} +%% +override gnt_tree_change_text +static PyObject * +_wrap_gnt_tree_change_text(PyGObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = { "key", "colno", "text", NULL }; + char *text; + int colno; + gpointer key; + if (!PyArg_ParseTupleAndKeywords(args, kwargs,"Ois:GntTree.change_text", kwlist, &key, &colno, &text)) + return NULL; + + gnt_tree_change_text(GNT_TREE(self->obj), key, colno, text); + + Py_INCREF(Py_None); + return Py_None; +} +%% +override gnt_tree_set_row_flags +static PyObject * +_wrap_gnt_tree_set_row_flags(PyGObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = { "key", "flag", NULL }; + int flag; + gpointer key; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs,"Oi:GntTree.set_row_flags", kwlist, &key, &flag)) + return NULL; + + gnt_tree_set_row_flags(GNT_TREE(self->obj), key, flag); + + Py_INCREF(Py_None); + return Py_None; +} +%% +override gnt_tree_remove +static PyObject * +_wrap_gnt_tree_remove(PyGObject *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = { "key", NULL }; + gpointer key; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs,"O:GntTree.remove", kwlist, &key)) + return NULL; + + gnt_tree_remove(GNT_TREE(self->obj), key); + + Py_INCREF(Py_None); + return Py_None; +} +%% +override gnt_tree_set_selected +static PyObject * +_wrap_gnt_tree_set_selected(PyGObject *self, PyObject *args) +{ + gpointer key; + if (!PyArg_ParseTuple(args, "O:GntTree.set_selected", &key)) { + return NULL; + } + gnt_tree_set_selected(GNT_TREE(self->obj), key); + Py_INCREF(Py_None); + return Py_None; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/finch/libgnt/pygnt/gntwidget.override Thu Aug 09 02:38:54 2007 +0000 @@ -0,0 +1,36 @@ +/** + * pygnt- Python bindings for the GNT toolkit. + * Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@pidgin.im> + * + * gntwidget.override: overrides for generic widgets. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ +%% +override gnt_widget_get_size args +static PyObject * +_wrap_gnt_widget_get_size(PyGObject *self) +{ + PyObject *list = PyList_New(0); + int x = 0, y = 0; + + gnt_widget_get_size(GNT_WIDGET(self->obj), &x, &y); + PyList_Append(list, PyInt_FromLong((long)x)); + PyList_Append(list, PyInt_FromLong((long)y)); + + return list; +} +
--- a/finch/libgnt/pygnt/test.py Thu Aug 09 02:27:40 2007 +0000 +++ b/finch/libgnt/pygnt/test.py Thu Aug 09 02:38:54 2007 +0000 @@ -1,18 +1,59 @@ #!/usr/bin/python +import gobject import gnt +class MyObject(gobject.GObject): + __gproperties__ = { + 'mytype': (gobject.TYPE_INT, 'mytype', 'the type of the object', + 0, 10000, 0, gobject.PARAM_READWRITE), + 'string': (gobject.TYPE_STRING, 'string property', 'the string', + None, gobject.PARAM_READWRITE), + 'gobject': (gobject.TYPE_OBJECT, 'object property', 'the object', + gobject.PARAM_READWRITE), + } + + def __init__(self, type = 'string', value = None): + self.__gobject_init__() + self.set_property(type, value) + + def do_set_property(self, pspec, value): + if pspec.name == 'string': + self.string = value + self.type = gobject.TYPE_STRING + elif pspec.name == 'gobject': + self.gobject = value + self.type = gobject.TYPE_OBJECT + else: + raise AttributeError, 'unknown property %s' % pspec.name + def do_get_property(self, pspec): + if pspec.name == 'string': + return self.string + elif pspec.name == 'gobject': + return self.gobject + elif pspec.name == 'mytype': + return self.type + else: + raise AttributeError, 'unknown property %s' % pspec.name +gobject.type_register(MyObject) + def button_activate(button, tree): - list = tree.get_selection_text_list() - str = "" - for i in list: - str = str + i - entry.set_text("clicked!!!" + str) + list = tree.get_selection_text_list() + ent = tree.get_selection_data() + if ent.type == gobject.TYPE_STRING: + str = "" + for i in list: + str = str + i + entry.set_text("clicked!!!" + str) + elif ent.type == gobject.TYPE_OBJECT: + ent.gobject.set_text("mwhahaha!!!") gnt.gnt_init() win = gnt.Window() entry = gnt.Entry("") +obj = MyObject() +obj.set_property('gobject', entry) win.add_widget(entry) win.set_title("Entry") @@ -27,12 +68,20 @@ # so random non-string values can be used as the key for a row in a GntTree! last = None for i in range(1, 100): - tree.add_row_after(i, [str(i), ""], None, i-1) -tree.add_row_after(entry, ["asd"], None, None) -tree.add_row_after("b", ["123", ""], entry, None) + key = MyObject('string', str(i)) + tree.add_row_after(key, [str(i)], None, last) + last = key + +tree.add_row_after(MyObject('gobject', entry), ["asd"], None, None) +tree.add_row_after(MyObject('string', "b"), ["123"], MyObject('gobject', entry), None) button.connect("activate", button_activate, tree) +tv = gnt.TextView() + +win.add_widget(tv) +tv.append_text_with_flags("What up!!", gnt.TEXT_FLAG_BOLD) + win.show() gnt.gnt_main()
--- a/libpurple/Makefile.am Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/Makefile.am Thu Aug 09 02:38:54 2007 +0000 @@ -148,7 +148,7 @@ # purple dbus server dbus_sources = dbus-server.c dbus-useful.c -dbus_headers = dbus-bindings.h dbus-purple.h dbus-server.h dbus-useful.h dbus-define-api.h +dbus_headers = dbus-bindings.h dbus-purple.h dbus-server.h dbus-useful.h dbus-define-api.h dbus-types.h dbus_exported = dbus-useful.h dbus-define-api.h account.h blist.h buddyicon.h \ connection.h conversation.h core.h ft.h log.h notify.h prefs.h roomlist.h \
--- a/libpurple/account.h Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/account.h Thu Aug 09 02:38:54 2007 +0000 @@ -51,20 +51,50 @@ PURPLE_ACCOUNT_REQUEST_AUTHORIZATION = 0 /* Account authorization request */ } PurpleAccountRequestType; + +/** Account UI operations, used to notify the user of status changes and when + * buddies add this account to their buddy lists. + */ struct _PurpleAccountUiOps { - /* A buddy we already have added us to their buddy list. */ - void (*notify_added)(PurpleAccount *account, const char *remote_user, - const char *id, const char *alias, + /** A buddy who is already on this account's buddy list added this account + * to their buddy list. + */ + void (*notify_added)(PurpleAccount *account, + const char *remote_user, + const char *id, + const char *alias, const char *message); - void (*status_changed)(PurpleAccount *account, PurpleStatus *status); - /* Someone we don't have on our list added us. Will prompt to add them. */ - void (*request_add)(PurpleAccount *account, const char *remote_user, - const char *id, const char *alias, + + /** This account's status changed. */ + void (*status_changed)(PurpleAccount *account, + PurpleStatus *status); + + /** Someone we don't have on our list added us; prompt to add them. */ + void (*request_add)(PurpleAccount *account, + const char *remote_user, + const char *id, + const char *alias, const char *message); - void *(*request_authorize)(PurpleAccount *account, const char *remote_user, const char *id, - const char *alias, const char *message, gboolean on_list, - GCallback authorize_cb, GCallback deny_cb, void *user_data); + + /** Prompt for authorization when someone adds this account to their buddy + * list. To authorize them to see this account's presence, call \a + * authorize_cb (\a user_data); otherwise call \a deny_cb (\a user_data); + * @return a UI-specific handle, as passed to #close_account_request. + */ + void *(*request_authorize)(PurpleAccount *account, + const char *remote_user, + const char *id, + const char *alias, + const char *message, + gboolean on_list, + GCallback authorize_cb, + GCallback deny_cb, + void *user_data); + + /** Close a pending request for authorization. \a ui_handle is a handle + * as returned by #request_authorize. + */ void (*close_account_request)(void *ui_handle); void (*_purple_reserved1)(void); @@ -193,7 +223,7 @@ /** * Notifies the user that a remote user has wants to add the local user - * to his or her buddy list and requires authorization to d oso. + * to his or her buddy list and requires authorization to do so. * * This will present a dialog informing the user of this and ask if the * user authorizes or denies the remote user from adding him.
--- a/libpurple/connection.h Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/connection.h Thu Aug 09 02:38:54 2007 +0000 @@ -60,15 +60,52 @@ #include "plugin.h" #include "status.h" +/** Connection UI operations. Used to notify the user of changes to + * connections, such as being disconnected, and to respond to the + * underlying network connection appearing and disappearing. UIs should + * call #purple_connections_set_ui_ops() with an instance of this struct. + * + * @see @ref ui-ops + */ typedef struct { - void (*connect_progress)(PurpleConnection *gc, const char *text, - size_t step, size_t step_count); + /** When an account is connecting, this operation is called to notify + * the UI of what is happening, as well as which @a step out of @a + * step_count has been reached (which might be displayed as a progress + * bar). + */ + void (*connect_progress)(PurpleConnection *gc, + const char *text, + size_t step, + size_t step_count); + /** Called when a connection is established (just before the + * @ref signed-on signal). + */ void (*connected)(PurpleConnection *gc); + /** Called when a connection is ended (between the @ref signing-off + * and @ref signed-off signals). + */ void (*disconnected)(PurpleConnection *gc); + /** Used to display connection-specific notices. (Pidgin's Gtk user + * interface implements this as a no-op; #purple_connection_notice(), + * which uses this operation, is not used by any of the protocols + * shipped with libpurple.) + */ void (*notice)(PurpleConnection *gc, const char *text); + /** Called when an error causes a connection to be disconnected. + * Called before #disconnected. + * @param text a localized error message. + */ void (*report_disconnect)(PurpleConnection *gc, const char *text); + /** Called when libpurple discovers that the computer's network + * connection is active. On Linux, this uses Network Manager if + * available; on Windows, it uses Win32's network change notification + * infrastructure. + */ void (*network_connected)(); + /** Called when libpurple discovers that the computer's network + * connection has gone away. + */ void (*network_disconnected)(); void (*_purple_reserved1)(void);
--- a/libpurple/conversation.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/conversation.c Thu Aug 09 02:38:54 2007 +0000 @@ -1106,7 +1106,7 @@ c = purple_conv_im_get_conversation(im); - /* Raise the window, if specified in prefs. */ + /* Pass this on to either the ops structure or the default write func. */ if (c->ui_ops != NULL && c->ui_ops->write_im != NULL) c->ui_ops->write_im(c, who, message, flags, mtime); else
--- a/libpurple/conversation.h Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/conversation.h Thu Aug 09 02:38:54 2007 +0000 @@ -149,27 +149,74 @@ */ struct _PurpleConversationUiOps { + /** Called when @a conv is created (but before the @ref + * conversation-created signal is emitted). + */ void (*create_conversation)(PurpleConversation *conv); + + /** Called just before @a conv is freed. */ void (*destroy_conversation)(PurpleConversation *conv); + /** Write a message to a chat. If this field is @c NULL, libpurple will + * fall back to using #write_conv. + * @see purple_conv_chat_write() + */ void (*write_chat)(PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime); + /** Write a message to an IM conversation. If this field is @c NULL, + * libpurple will fall back to using #write_conv. + * @see purple_conv_im_write() + */ void (*write_im)(PurpleConversation *conv, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime); - void (*write_conv)(PurpleConversation *conv, const char *name, const char *alias, - const char *message, PurpleMessageFlags flags, + /** Write a message to a conversation. This is used rather than + * the chat- or im-specific ops for generic messages, such as system + * messages like "x is now know as y". + * @see purple_conversation_write() + */ + void (*write_conv)(PurpleConversation *conv, + const char *name, + const char *alias, + const char *message, + PurpleMessageFlags flags, time_t mtime); - void (*chat_add_users)(PurpleConversation *conv, GList *cbuddies, gboolean new_arrivals); - + /** Add @a cbuddies to a chat. + * @param cbuddies A @C GList of #PurpleConvChatBuddy structs. + * @param new_arrivals Whether join notices should be shown. + * (Join notices are actually written to the + * conversation by #purple_conv_chat_add_users().) + */ + void (*chat_add_users)(PurpleConversation *conv, + GList *cbuddies, + gboolean new_arrivals); + /** Rename the user in this chat named @a old_name to @a new_name. (The + * rename message is written to the conversation by libpurple.) + * @param new_alias @a new_name's new alias, if they have one. + * @see purple_conv_chat_add_users() + */ void (*chat_rename_user)(PurpleConversation *conv, const char *old_name, const char *new_name, const char *new_alias); + /** Remove @a users from a chat. + * @param users A @C GList of <tt>const char *</tt>s. + * @see purple_conv_chat_rename_user() + */ void (*chat_remove_users)(PurpleConversation *conv, GList *users); + /** Called when a user's flags are changed. + * @see purple_conv_chat_user_set_flags() + */ void (*chat_update_user)(PurpleConversation *conv, const char *user); + /** Present this conversation to the user; for example, by displaying + * the IM dialog. + */ void (*present)(PurpleConversation *conv); + /** If this UI has a concept of focus (as in a windowing system) and + * this conversation has the focus, return @c TRUE; otherwise, return + * @c FALSE. + */ gboolean (*has_focus)(PurpleConversation *conv); /* Custom Smileys */ @@ -178,6 +225,11 @@ const guchar *data, gsize size); void (*custom_smiley_close)(PurpleConversation *conv, const char *smile); + /** Prompt the user for confirmation to send @a message. This function + * should arrange for the message to be sent if the user accepts. If + * this field is @c NULL, libpurple will fall back to using + * #purple_request_action(). + */ void (*send_confirm)(PurpleConversation *conv, const char *message); void (*_purple_reserved1)(void);
--- a/libpurple/plugins/log_reader.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/plugins/log_reader.c Thu Aug 09 02:38:54 2007 +0000 @@ -60,14 +60,14 @@ char *path; GDir *dir; - g_return_val_if_fail(sn != NULL, list); - g_return_val_if_fail(account != NULL, list); + g_return_val_if_fail(sn != NULL, NULL); + g_return_val_if_fail(account != NULL, NULL); logdir = purple_prefs_get_string("/plugins/core/log_reader/adium/log_directory"); /* By clearing the log directory path, this logger can be (effectively) disabled. */ - if (!*logdir) - return list; + if (!logdir || !*logdir) + return NULL; plugin = purple_find_prpl(purple_account_get_protocol_id(account)); if (!plugin) @@ -236,8 +236,8 @@ /* XXX: TODO: We probably want to set PURPLE_LOG_READ_NO_NEWLINE * XXX: TODO: for HTML logs. */ - if (flags != NULL) - *flags = 0; + if (flags != NULL) + *flags = 0; g_return_val_if_fail(log != NULL, g_strdup("")); @@ -626,17 +626,17 @@ const char *old_session_id = ""; struct msn_logger_data *data = NULL; - g_return_val_if_fail(sn != NULL, list); - g_return_val_if_fail(account != NULL, list); + g_return_val_if_fail(sn != NULL, NULL); + g_return_val_if_fail(account != NULL, NULL); if (strcmp(account->protocol_id, "prpl-msn")) - return list; + return NULL; logdir = purple_prefs_get_string("/plugins/core/log_reader/msn/log_directory"); /* By clearing the log directory path, this logger can be (effectively) disabled. */ - if (!*logdir) - return list; + if (!logdir || !*logdir) + return NULL; buddy = purple_find_buddy(account, sn); @@ -1121,7 +1121,7 @@ if (name_guessed != NAME_GUESS_UNKNOWN) text = g_string_append(text, "</span>"); - style = xmlnode_get_attrib(text_node, "Style"); + style = xmlnode_get_attrib(text_node, "Style"); tmp = xmlnode_get_data(text_node); if (style && *style) { @@ -1211,14 +1211,14 @@ gchar *line; gchar *c; - g_return_val_if_fail(sn != NULL, list); - g_return_val_if_fail(account != NULL, list); + g_return_val_if_fail(sn != NULL, NULL); + g_return_val_if_fail(account != NULL, NULL); logdir = purple_prefs_get_string("/plugins/core/log_reader/trillian/log_directory"); /* By clearing the log directory path, this logger can be (effectively) disabled. */ - if (!*logdir) - return list; + if (!logdir || !*logdir) + return NULL; plugin = purple_find_prpl(purple_account_get_protocol_id(account)); if (!plugin) @@ -1429,8 +1429,8 @@ char *c; const char *line; - if (flags != NULL) - *flags = PURPLE_LOG_READ_NO_NEWLINE; + if (flags != NULL) + *flags = PURPLE_LOG_READ_NO_NEWLINE; g_return_val_if_fail(log != NULL, g_strdup("")); @@ -1776,18 +1776,18 @@ int offset = 0; GError *error; - g_return_val_if_fail(sn != NULL, list); - g_return_val_if_fail(account != NULL, list); + g_return_val_if_fail(sn != NULL, NULL); + g_return_val_if_fail(account != NULL, NULL); /* QIP only supports ICQ. */ if (strcmp(account->protocol_id, "prpl-icq")) - return list; + return NULL; logdir = purple_prefs_get_string("/plugins/core/log_reader/qip/log_directory"); /* By clearing the log directory path, this logger can be (effectively) disabled. */ - if (!*logdir) - return list; + if (!logdir || !*logdir) + return NULL; plugin = purple_find_prpl(purple_account_get_protocol_id(account)); if (!plugin) @@ -1927,8 +1927,8 @@ char *utf8_string; FILE *file; - if (flags != NULL) - *flags = PURPLE_LOG_READ_NO_NEWLINE; + if (flags != NULL) + *flags = PURPLE_LOG_READ_NO_NEWLINE; g_return_val_if_fail(log != NULL, g_strdup(""));
--- a/libpurple/protocols/bonjour/Makefile.am Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/bonjour/Makefile.am Thu Aug 09 02:38:54 2007 +0000 @@ -1,7 +1,7 @@ EXTRA_DIST = \ - mdns_win32.c \ - mdns_win32.h \ - Makefile.mingw + mdns_win32.c \ + dns_sd_proxy.h \ + Makefile.mingw pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION) @@ -10,18 +10,24 @@ bonjour.h \ buddy.c \ buddy.h \ - dns_sd_proxy.h \ jabber.c \ jabber.h \ mdns_common.c \ mdns_common.h \ - mdns_howl.c \ - mdns_howl.h \ + mdns_interface.h \ mdns_types.h \ parser.c \ parser.h -AM_CFLAGS = $(st) -DUSE_BONJOUR_HOWL +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 @@ -31,14 +37,29 @@ noinst_LIBRARIES = libbonjour.a libbonjour_a_SOURCES = $(BONJOURSOURCES) libbonjour_a_CFLAGS = $(AM_CFLAGS) -libbonjour_a_LIBADD = $(HOWL_LIBS) +libbonjour_a_LIBADD = + +if MDNS_AVAHI + libbonjour_a_LIBADD += $(AVAHI_LIBS) +else +if MDNS_HOWL + libbonjour_a_LIBADD += $(HOWL_LIBS) +endif +endif else st = pkg_LTLIBRARIES = libbonjour.la libbonjour_la_SOURCES = $(BONJOURSOURCES) -libbonjour_la_LIBADD = $(GLIB_LIBS) $(HOWL_LIBS) $(LIBXML_LIBS) +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 endif @@ -48,5 +69,13 @@ -I$(top_builddir)/libpurple \ $(GLIB_CFLAGS) \ $(DEBUG_CFLAGS) \ - $(HOWL_CFLAGS) \ $(LIBXML_CFLAGS) + +if MDNS_AVAHI + AM_CPPFLAGS += $(AVAHI_CFLAGS) +else +if MDNS_HOWL + AM_CPPFLAGS += $(HOWL_CFLAGS) +endif +endif +
--- a/libpurple/protocols/bonjour/buddy.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/bonjour/buddy.c Thu Aug 09 02:38:54 2007 +0000 @@ -22,6 +22,7 @@ #include "account.h" #include "blist.h" #include "bonjour.h" +#include "mdns_interface.h" #include "debug.h" /** @@ -35,6 +36,8 @@ buddy->account = account; buddy->name = g_strdup(name); + _mdns_init_buddy(buddy); + return buddy; } @@ -102,8 +105,9 @@ { PurpleBuddy *buddy; PurpleGroup *group; - const char *status_id, *first, *last; - gchar *alias; + PurpleAccount *account = bonjour_buddy->account; + const char *status_id, *first, *last, *old_hash, *new_hash; + gchar *alias = NULL; /* Translate between the Bonjour status and the Purple status */ if (g_ascii_strcasecmp("dnd", bonjour_buddy->status) == 0) @@ -116,44 +120,55 @@ * field from the DNS SD. */ - /* Create the alias for the buddy using the first and the last name */ - first = bonjour_buddy->first; - last = bonjour_buddy->last; - alias = g_strdup_printf("%s%s%s", - (first && *first ? first : ""), - (first && *first && last && *last ? " " : ""), - (last && *last ? last : "")); - /* Make sure the Bonjour group exists in our buddy list */ group = purple_find_group(BONJOUR_GROUP_NAME); /* Use the buddy's domain, instead? */ - if (group == NULL) - { + if (group == NULL) { group = purple_group_new(BONJOUR_GROUP_NAME); purple_blist_add_group(group, NULL); } /* Make sure the buddy exists in our buddy list */ - buddy = purple_find_buddy(bonjour_buddy->account, bonjour_buddy->name); + buddy = purple_find_buddy(account, bonjour_buddy->name); - if (buddy == NULL) - { - buddy = purple_buddy_new(bonjour_buddy->account, bonjour_buddy->name, alias); + if (buddy == NULL) { + buddy = purple_buddy_new(account, bonjour_buddy->name, NULL); buddy->proto_data = bonjour_buddy; purple_blist_node_set_flags((PurpleBlistNode *)buddy, PURPLE_BLIST_NODE_FLAG_NO_SAVE); purple_blist_add_buddy(buddy, NULL, group, NULL); } + /* Create the alias for the buddy using the first and the last name */ + first = bonjour_buddy->first; + last = bonjour_buddy->last; + if ((first && *first) || (last && *last)) + alias = g_strdup_printf("%s%s%s", + (first && *first ? first : ""), + (first && *first && last && *last ? " " : ""), + (last && *last ? last : "")); + serv_got_alias(purple_account_get_connection(account), buddy->name, alias); + g_free(alias); + /* Set the user's status */ if (bonjour_buddy->msg != NULL) - purple_prpl_got_user_status(bonjour_buddy->account, buddy->name, status_id, - "message", bonjour_buddy->msg, - NULL); + purple_prpl_got_user_status(account, buddy->name, status_id, + "message", bonjour_buddy->msg, NULL); else - purple_prpl_got_user_status(bonjour_buddy->account, buddy->name, status_id, - NULL); - purple_prpl_got_user_idle(bonjour_buddy->account, buddy->name, FALSE, 0); + purple_prpl_got_user_status(account, buddy->name, status_id, NULL); + + purple_prpl_got_user_idle(account, buddy->name, FALSE, 0); + + /* TODO: Because we don't save Bonjour buddies in blist.xml, + * we will always have to look up the buddy icon at login time. + * I think we should figure out a way to do something about this. */ - g_free(alias); + /* Deal with the buddy icon */ + old_hash = purple_buddy_icons_get_checksum_for_user(buddy); + new_hash = (bonjour_buddy->phsh && *(bonjour_buddy->phsh)) ? bonjour_buddy->phsh : NULL; + if (new_hash && (!old_hash || strcmp(old_hash, new_hash) != 0)) { + /* Look up the new icon data */ + bonjour_dns_sd_retrieve_buddy_icon(bonjour_buddy); + } else + purple_buddy_icons_set_for_user(account, buddy->name, NULL, 0, NULL); } /** @@ -164,6 +179,7 @@ { g_free(buddy->name); g_free(buddy->ip); + g_free(buddy->full_service_name); g_free(buddy->first); g_free(buddy->phsh); @@ -182,13 +198,8 @@ bonjour_jabber_close_conversation(buddy->conversation); buddy->conversation = NULL; -#ifdef USE_BONJOUR_APPLE - if (buddy->txt_query != NULL) - { - purple_input_remove(buddy->txt_query_fd); - DNSServiceRefDeallocate(buddy->txt_query); - } -#endif + /* Clean up any mdns implementation data */ + _mdns_delete_buddy(buddy); g_free(buddy); }
--- a/libpurple/protocols/bonjour/buddy.h Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/bonjour/buddy.h Thu Aug 09 02:38:54 2007 +0000 @@ -19,22 +19,17 @@ #include <glib.h> -#include "config.h" #include "account.h" #include "jabber.h" -#ifdef USE_BONJOUR_APPLE -#include "dns_sd_proxy.h" -#else /* USE_BONJOUR_HOWL */ -#include <howl.h> -#endif - typedef struct _BonjourBuddy { PurpleAccount *account; gchar *name; + /* TODO: Remove and just use the hostname */ gchar *ip; + gchar *full_service_name; gint port_p2pj; gchar *first; @@ -53,11 +48,7 @@ BonjourJabberConversation *conversation; -#ifdef USE_BONJOUR_APPLE - DNSServiceRef txt_query; - int txt_query_fd; -#endif - + gpointer mdns_impl_data; } BonjourBuddy; static const char *const buddy_TXT_records[] = {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/bonjour/mdns_avahi.c Thu Aug 09 02:38:54 2007 +0000 @@ -0,0 +1,349 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "internal.h" + +#include "mdns_interface.h" +#include "debug.h" +#include "buddy.h" + +#include <avahi-client/client.h> +#include <avahi-client/lookup.h> +#include <avahi-client/publish.h> + +#include <avahi-common/address.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> +#include <avahi-common/strlst.h> + +#include <avahi-glib/glib-malloc.h> +#include <avahi-glib/glib-watch.h> + +/* data used by avahi bonjour implementation */ +typedef struct _avahi_session_impl_data { + AvahiClient *client; + AvahiGLibPoll *glib_poll; + AvahiServiceBrowser *sb; + AvahiEntryGroup *group; +} AvahiSessionImplData; + +static void +_resolver_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, + AvahiResolverEvent event, const char *name, const char *type, const char *domain, + const char *host_name, const AvahiAddress *a, uint16_t port, AvahiStringList *txt, + AvahiLookupResultFlags flags, void *userdata) { + + BonjourBuddy *buddy; + PurpleAccount *account = userdata; + AvahiStringList *l; + size_t size; + char *key, *value; + int ret; + + g_return_if_fail(r != NULL); + + switch (event) { + case AVAHI_RESOLVER_FAILURE: + purple_debug_error("bonjour", "_resolve_callback - Failure: %s\n", + avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r)))); + break; + case AVAHI_RESOLVER_FOUND: + /* create a buddy record */ + buddy = bonjour_buddy_new(name, account); + + /* Get the ip as a string */ + buddy->ip = g_malloc(AVAHI_ADDRESS_STR_MAX); + avahi_address_snprint(buddy->ip, AVAHI_ADDRESS_STR_MAX, a); + + buddy->port_p2pj = port; + + /* Obtain the parameters from the text_record */ + l = txt; + while (l != NULL) { + ret = avahi_string_list_get_pair(l, &key, &value, &size); + l = l->next; + if (ret < 0) + continue; + set_bonjour_buddy_value(buddy, key, value, size); + /* TODO: Since we're using the glib allocator, I think we + * can use the values instead of re-copying them */ + avahi_free(key); + avahi_free(value); + } + + if (!bonjour_buddy_check(buddy)) + bonjour_buddy_delete(buddy); + else + /* Add or update the buddy in our buddy list */ + bonjour_buddy_add_to_purple(buddy); + + break; + default: + purple_debug_info("bonjour", "Unrecognized Service Resolver event: %d.\n", event); + } + + avahi_service_resolver_free(r); +} + +static void +_browser_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, + AvahiProtocol protocol, AvahiBrowserEvent event, + const char *name, const char *type, const char *domain, + AvahiLookupResultFlags flags, void *userdata) { + + PurpleAccount *account = userdata; + PurpleBuddy *gb = NULL; + + switch (event) { + case AVAHI_BROWSER_FAILURE: + purple_debug_error("bonjour", "_browser_callback - Failure: %s\n", + avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b)))); + /* TODO: This is an error that should be handled. */ + break; + case AVAHI_BROWSER_NEW: + /* A new peer has joined the network and uses iChat bonjour */ + purple_debug_info("bonjour", "_browser_callback - new service\n"); + /* Make sure it isn't us */ + if (g_ascii_strcasecmp(name, account->username) != 0) { + if (!avahi_service_resolver_new(avahi_service_browser_get_client(b), + interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, + 0, _resolver_callback, account)) { + purple_debug_warning("bonjour", "_browser_callback -- Error initiating resolver: %s\n", + avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b)))); + } + } + break; + case AVAHI_BROWSER_REMOVE: + purple_debug_info("bonjour", "_browser_callback - Remove service\n"); + gb = purple_find_buddy(account, name); + if (gb != NULL) { + bonjour_buddy_delete(gb->proto_data); + purple_blist_remove_buddy(gb); + } + break; + case AVAHI_BROWSER_ALL_FOR_NOW: + case AVAHI_BROWSER_CACHE_EXHAUSTED: + purple_debug_warning("bonjour", "(Browser) %s\n", + event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW"); + break; + default: + purple_debug_info("bonjour", "Unrecognized Service browser event: %d.\n", event); + } +} + +static void +_entry_group_cb(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) { + AvahiSessionImplData *idata = userdata; + + g_return_if_fail(g == idata->group || idata->group == NULL); + + switch(state) { + case AVAHI_ENTRY_GROUP_ESTABLISHED: + purple_debug_info("bonjour", "Successfully registered service.\n"); + break; + case AVAHI_ENTRY_GROUP_COLLISION: + purple_debug_error("bonjour", "Collision registering entry group.\n"); + /* TODO: Handle error - this should log out the account. (Possibly with "wants to die")*/ + break; + case AVAHI_ENTRY_GROUP_FAILURE: + purple_debug_error("bonjour", "Error registering entry group: %s\n.", + avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); + /* TODO: Handle error - this should log out the account.*/ + break; + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + break; + } + +} + +/**************************** + * mdns_interface functions * + ****************************/ + +gboolean _mdns_init_session(BonjourDnsSd *data) { + AvahiSessionImplData *idata = g_new0(AvahiSessionImplData, 1); + const AvahiPoll *poll_api; + int error; + + /* Tell avahi to use g_malloc and g_free */ + avahi_set_allocator (avahi_glib_allocator ()); + + /* This currently depends on the glib mainloop, + * we should make it use the libpurple abstraction */ + + idata->glib_poll = avahi_glib_poll_new(NULL, G_PRIORITY_DEFAULT); + + poll_api = avahi_glib_poll_get(idata->glib_poll); + + idata->client = avahi_client_new(poll_api, 0, NULL, data, &error); + + if (idata->client == NULL) { + purple_debug_error("bonjour", "Error initializing Avahi: %s", avahi_strerror(error)); + avahi_glib_poll_free(idata->glib_poll); + g_free(idata); + return FALSE; + } + + data->mdns_impl_data = idata; + + return TRUE; +} + +gboolean _mdns_publish(BonjourDnsSd *data, PublishType type) { + int publish_result = 0; + char portstring[6]; + const char *jid, *aim, *email; + AvahiSessionImplData *idata = data->mdns_impl_data; + AvahiStringList *lst = NULL; + + g_return_val_if_fail(idata != NULL, FALSE); + + if (!idata->group) { + idata->group = avahi_entry_group_new(idata->client, + _entry_group_cb, idata); + if (!idata->group) { + purple_debug_error("bonjour", + "Unable to initialize the data for the mDNS (%s).\n", + avahi_strerror(avahi_client_errno(idata->client))); + return FALSE; + } + } + + /* Convert the port to a string */ + snprintf(portstring, sizeof(portstring), "%d", data->port_p2pj); + + jid = purple_account_get_string(data->account, "jid", NULL); + aim = purple_account_get_string(data->account, "AIM", NULL); + email = purple_account_get_string(data->account, "email", NULL); + + /* We should try to follow XEP-0174, but some clients have "issues", so we humor them. + * See http://telepathy.freedesktop.org/wiki/SalutInteroperability + */ + + /* Needed by iChat */ + lst = avahi_string_list_add_pair(lst,"txtvers", "1"); + /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */ + lst = avahi_string_list_add_pair(lst, "1st", data->first); + /* Needed by Gaim/Pidgin <= 2.0.1 (remove at some point) */ + lst = avahi_string_list_add_pair(lst, "last", data->last); + /* Needed by Adium */ + lst = avahi_string_list_add_pair(lst, "port.p2pj", portstring); + /* Needed by iChat, Gaim/Pidgin <= 2.0.1 */ + lst = avahi_string_list_add_pair(lst, "status", data->status); + /* Currently always set to "!" since we don't support AV and wont ever be in a conference */ + lst = avahi_string_list_add_pair(lst, "vc", data->vc); + lst = avahi_string_list_add_pair(lst, "ver", VERSION); + if (email != NULL && *email != '\0') + lst = avahi_string_list_add_pair(lst, "email", email); + if (jid != NULL && *jid != '\0') + lst = avahi_string_list_add_pair(lst, "jid", jid); + /* Nonstandard, but used by iChat */ + if (aim != NULL && *aim != '\0') + lst = avahi_string_list_add_pair(lst, "AIM", aim); + if (data->msg != NULL && *data->msg != '\0') + lst = avahi_string_list_add_pair(lst, "msg", data->msg); + if (data->phsh != NULL && *data->phsh != '\0') + lst = avahi_string_list_add_pair(lst, "phsh", data->phsh); + + /* TODO: ext, nick, node */ + + + /* Publish the service */ + switch (type) { + case PUBLISH_START: + publish_result = avahi_entry_group_add_service_strlst( + idata->group, AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, 0, + purple_account_get_username(data->account), + ICHAT_SERVICE, NULL, NULL, data->port_p2pj, lst); + break; + case PUBLISH_UPDATE: + publish_result = avahi_entry_group_update_service_txt_strlst( + idata->group, AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, 0, + purple_account_get_username(data->account), + ICHAT_SERVICE, NULL, lst); + break; + } + + /* Free the memory used by temp data */ + avahi_string_list_free(lst); + + if (publish_result < 0) { + purple_debug_error("bonjour", + "Failed to add the " ICHAT_SERVICE " service. Error: %s\n", + avahi_strerror(publish_result)); + return FALSE; + } + + if ((publish_result = avahi_entry_group_commit(idata->group)) < 0) { + purple_debug_error("bonjour", + "Failed to commit " ICHAT_SERVICE " service. Error: %s\n", + avahi_strerror(publish_result)); + return FALSE; + } + + return TRUE; +} + +gboolean _mdns_browse(BonjourDnsSd *data) { + AvahiSessionImplData *idata = data->mdns_impl_data; + + g_return_val_if_fail(idata != NULL, FALSE); + + idata->sb = avahi_service_browser_new(idata->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, ICHAT_SERVICE, NULL, 0, _browser_callback, data->account); + if (!idata->sb) { + + purple_debug_error("bonjour", + "Unable to initialize service browser. Error: %s\n.", + avahi_strerror(avahi_client_errno(idata->client))); + return FALSE; + } + + return TRUE; +} + +/* This is done differently than with Howl/Apple Bonjour */ +guint _mdns_register_to_mainloop(BonjourDnsSd *data) { + return 0; +} + +void _mdns_stop(BonjourDnsSd *data) { + AvahiSessionImplData *idata = data->mdns_impl_data; + + if (idata == NULL || idata->client == NULL) + return; + + if (idata->sb != NULL) + avahi_service_browser_free(idata->sb); + + avahi_client_free(idata->client); + avahi_glib_poll_free(idata->glib_poll); + + g_free(idata); + + data->mdns_impl_data = NULL; +} + +void _mdns_init_buddy(BonjourBuddy *buddy) { +} + +void _mdns_delete_buddy(BonjourBuddy *buddy) { +} + +void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) { +}
--- a/libpurple/protocols/bonjour/mdns_common.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_common.c Thu Aug 09 02:38:54 2007 +0000 @@ -17,8 +17,8 @@ #include <string.h> #include "internal.h" -#include "config.h" #include "mdns_common.h" +#include "mdns_interface.h" #include "bonjour.h" #include "buddy.h" #include "debug.h" @@ -73,65 +73,28 @@ gboolean bonjour_dns_sd_start(BonjourDnsSd *data) { - PurpleAccount *account; PurpleConnection *gc; - gint dns_sd_socket; - gpointer opaque_data; -#ifdef USE_BONJOUR_HOWL - sw_discovery_oid session_id; -#endif - - account = data->account; - gc = purple_account_get_connection(account); + gc = purple_account_get_connection(data->account); /* Initialize the dns-sd data and session */ -#ifndef USE_BONJOUR_APPLE - if (sw_discovery_init(&data->session) != SW_OKAY) - { - purple_debug_error("bonjour", "Unable to initialize an mDNS session.\n"); - - /* In Avahi, sw_discovery_init frees data->session but doesn't clear it */ - data->session = NULL; - + if (!_mdns_init_session(data)) return FALSE; - } -#endif /* Publish our bonjour IM client at the mDNS daemon */ - - if (0 != _mdns_publish(data, PUBLISH_START)) - { + if (!_mdns_publish(data, PUBLISH_START)) return FALSE; - } /* Advise the daemon that we are waiting for connections */ - -#ifdef USE_BONJOUR_APPLE - if (DNSServiceBrowse(&data->browser, 0, 0, ICHAT_SERVICE, NULL, _mdns_service_browse_callback, account) - != kDNSServiceErr_NoError) -#else /* USE_BONJOUR_HOWL */ - if (sw_discovery_browse(data->session, 0, ICHAT_SERVICE, NULL, _browser_reply, - account, &session_id) != SW_OKAY) -#endif - { + if (!_mdns_browse(data)) { purple_debug_error("bonjour", "Unable to get service."); return FALSE; } + /* Get the socket that communicates with the mDNS daemon and bind it to a */ /* callback that will handle the dns_sd packets */ - -#ifdef USE_BONJOUR_APPLE - dns_sd_socket = DNSServiceRefSockFD(data->browser); - opaque_data = data->browser; -#else /* USE_BONJOUR_HOWL */ - dns_sd_socket = sw_discovery_socket(data->session); - opaque_data = data->session; -#endif - - gc->inpa = purple_input_add(dns_sd_socket, PURPLE_INPUT_READ, - _mdns_handle_event, opaque_data); + gc->inpa = _mdns_register_to_mainloop(data); return TRUE; } @@ -143,34 +106,11 @@ void bonjour_dns_sd_stop(BonjourDnsSd *data) { - PurpleAccount *account; PurpleConnection *gc; -#ifdef USE_BONJOUR_APPLE - if (data->advertisement == NULL || data->browser == NULL) -#else /* USE_BONJOUR_HOWL */ - if (data->session == NULL) -#endif - return; - -#ifdef USE_BONJOUR_HOWL - sw_discovery_cancel(data->session, data->session_id); -#endif + _mdns_stop(data); - account = data->account; - gc = purple_account_get_connection(account); - purple_input_remove(gc->inpa); - -#ifdef USE_BONJOUR_APPLE - /* hack: for win32, we need to stop listening to the advertisement pipe too */ - purple_input_remove(data->advertisement_handler); - - DNSServiceRefDeallocate(data->advertisement); - DNSServiceRefDeallocate(data->browser); - data->advertisement = NULL; - data->browser = NULL; -#else /* USE_BONJOUR_HOWL */ - g_free(data->session); - data->session = NULL; -#endif + gc = purple_account_get_connection(data->account); + if (gc->inpa > 0) + purple_input_remove(gc->inpa); }
--- a/libpurple/protocols/bonjour/mdns_common.h Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_common.h Thu Aug 09 02:38:54 2007 +0000 @@ -19,11 +19,7 @@ #include "mdns_types.h" -#ifdef USE_BONJOUR_APPLE -#include "mdns_win32.h" -#elif defined USE_BONJOUR_HOWL -#include "mdns_howl.h" -#endif +#include "buddy.h" /** * Allocate space for the dns-sd data. @@ -41,6 +37,11 @@ void bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message); /** + * Retrieve the buddy icon blob + */ +void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy); + +/** * Advertise our presence within the dns-sd daemon and start * browsing for other bonjour peers. */
--- a/libpurple/protocols/bonjour/mdns_howl.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_howl.c Thu Aug 09 02:38:54 2007 +0000 @@ -15,12 +15,20 @@ */ #include "internal.h" -#include "mdns_howl.h" +#include "mdns_interface.h" #include "debug.h" #include "buddy.h" -sw_result HOWL_API +#include <howl.h> + +/* data used by howl bonjour implementation */ +typedef struct _howl_impl_data { + sw_discovery session; + sw_discovery_oid session_id; +} HowlSessionImplData; + +static sw_result HOWL_API _publish_reply(sw_discovery discovery, sw_discovery_oid oid, sw_discovery_publish_status status, sw_opaque extra) { @@ -46,7 +54,7 @@ return SW_OKAY; } -sw_result HOWL_API +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, @@ -95,7 +103,7 @@ return SW_OKAY; } -sw_result HOWL_API +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, @@ -137,7 +145,7 @@ break; case SW_DISCOVERY_BROWSE_REMOVE_SERVICE: purple_debug_info("bonjour", "_browser_reply --> Remove service\n"); - gb = purple_find_buddy((PurpleAccount*)extra, name); + gb = purple_find_buddy(account, name); if (gb != NULL) { bonjour_buddy_delete(gb->proto_data); @@ -154,19 +162,49 @@ return SW_OKAY; } -int -_mdns_publish(BonjourDnsSd *data, PublishType type) +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) { sw_text_record dns_data; sw_result publish_result = SW_OKAY; char portstring[6]; const char *jid, *aim, *email; + HowlSessionImplData *idata = data->mdns_impl_data; + + g_return_val_if_fail(idata != NULL, FALSE); /* Fill the data for the service */ - if (sw_text_record_init(&dns_data) != SW_OKAY) - { + if (sw_text_record_init(&dns_data) != SW_OKAY) { purple_debug_error("bonjour", "Unable to initialize the data for the mDNS.\n"); - return -1; + return FALSE; } /* Convert the port to a string */ @@ -208,32 +246,70 @@ /* TODO: ext, nick, node */ /* Publish the service */ - switch (type) - { + switch (type) { case PUBLISH_START: - publish_result = sw_discovery_publish(data->session, 0, purple_account_get_username(data->account), ICHAT_SERVICE, NULL, + 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, &data->session_id); + _publish_reply, NULL, &idata->session_id); break; case PUBLISH_UPDATE: - publish_result = sw_discovery_publish_update(data->session, data->session_id, + publish_result = sw_discovery_publish_update(idata->session, idata->session_id, sw_text_record_bytes(dns_data), sw_text_record_len(dns_data)); break; } - if (publish_result != SW_OKAY) - { - purple_debug_error("bonjour", "Unable to publish or change the status of the _presence._tcp service.\n"); - return -1; - } /* Free the memory used by temp data */ sw_text_record_fina(dns_data); - return 0; + 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); + + return (sw_discovery_browse(idata->session, 0, ICHAT_SERVICE, NULL, _browser_reply, + data->account, &session_id) == SW_OKAY); } -void -_mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition) -{ - sw_discovery_read_socket((sw_discovery)data); +guint _mdns_register_to_mainloop(BonjourDnsSd *data) { + HowlSessionImplData *idata = data->mdns_impl_data; + + g_return_val_if_fail(idata != NULL, 0); + + return purple_input_add(sw_discovery_socket(idata->session), + PURPLE_INPUT_READ, _mdns_handle_event, idata->session); } + +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); + + /* 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 bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) { +}
--- a/libpurple/protocols/bonjour/mdns_howl.h Thu Aug 09 02:27:40 2007 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Library General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#ifndef _BONJOUR_MDNS_HOWL -#define _BONJOUR_MDNS_HOWL - -#include "config.h" - -#ifdef USE_BONJOUR_HOWL - -#include <howl.h> -#include <glib.h> -#include "mdns_types.h" - -/* callback functions */ - -sw_result HOWL_API _publish_reply(sw_discovery discovery, sw_discovery_oid oid, sw_discovery_publish_status status, sw_opaque extra); - -sw_result HOWL_API _resolve_reply(sw_discovery discovery, sw_discovery_oid oid, sw_uint32 interface_index, sw_const_string name, - sw_const_string type, sw_const_string domain, sw_ipv4_address address, sw_port port, sw_octets text_record, - sw_ulong text_record_len, sw_opaque extra); - -sw_result HOWL_API _browser_reply(sw_discovery discovery, sw_discovery_oid oid, sw_discovery_browse_status status, - sw_uint32 interface_index, sw_const_string name, sw_const_string type, sw_const_string domain, sw_opaque_t extra); - - -/* interface functions */ - -int _mdns_publish(BonjourDnsSd *data, PublishType type); -void _mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition); - -#endif - -#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/bonjour/mdns_interface.h Thu Aug 09 02:38:54 2007 +0000 @@ -0,0 +1,40 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef _BONJOUR_MDNS_INTERFACE +#define _BONJOUR_MDNS_INTERFACE + +#include "mdns_types.h" +#include "buddy.h" + +gboolean _mdns_init_session(BonjourDnsSd *data); + +gboolean _mdns_publish(BonjourDnsSd *data, PublishType type); + +gboolean _mdns_browse(BonjourDnsSd *data); + +guint _mdns_register_to_mainloop(BonjourDnsSd *data); + +void _mdns_stop(BonjourDnsSd *data); + +void _mdns_init_buddy(BonjourBuddy *buddy); + +void _mdns_delete_buddy(BonjourBuddy *buddy); + +/* This doesn't quite belong here, but there really isn't any shared functionality */ +void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy); + +#endif
--- a/libpurple/protocols/bonjour/mdns_types.h Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_types.h Thu Aug 09 02:38:54 2007 +0000 @@ -19,31 +19,14 @@ #include <glib.h> #include "account.h" -#include "config.h" - -#ifdef USE_BONJOUR_APPLE -#include "dns_sd_proxy.h" -#else /* USE_BONJOUR_HOWL */ -#include <howl.h> -#endif #define ICHAT_SERVICE "_presence._tcp." /** * Data to be used by the dns-sd connection. */ -typedef struct _BonjourDnsSd -{ -#ifdef USE_BONJOUR_APPLE - DNSServiceRef advertisement; - DNSServiceRef browser; - - int advertisement_handler; /* hack... windows bonjour is broken, so we have to have this */ -#else /* USE_BONJOUR_HOWL */ - sw_discovery session; - sw_discovery_oid session_id; -#endif - +typedef struct _BonjourDnsSd { + gpointer mdns_impl_data; PurpleAccount *account; gchar *first; gchar *last; @@ -59,5 +42,4 @@ PUBLISH_UPDATE } PublishType; - #endif
--- a/libpurple/protocols/bonjour/mdns_win32.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/bonjour/mdns_win32.c Thu Aug 09 02:38:54 2007 +0000 @@ -15,22 +15,44 @@ */ #include "internal.h" -#include "mdns_win32.h" +#include "debug.h" -#include "debug.h" +#include "buddy.h" +#include "mdns_interface.h" +#include "dns_sd_proxy.h" +#include "dnsquery.h" + /* data structure for the resolve callback */ -typedef struct _ResolveCallbackArgs -{ +typedef struct _ResolveCallbackArgs { DNSServiceRef resolver; - int resolver_fd; + guint resolver_handler; PurpleDnsQueryData *query; - gchar *fqn; BonjourBuddy* buddy; } ResolveCallbackArgs; +/* data used by win32 bonjour implementation */ +typedef struct _win32_session_impl_data { + DNSServiceRef advertisement; + DNSServiceRef browser; + + guint advertisement_handler; /* hack... windows bonjour is broken, so we have to have this */ +} Win32SessionImplData; + +typedef struct _win32_buddy_impl_data { + DNSServiceRef txt_query; + guint txt_query_handler; + DNSServiceRef null_query; + guint null_query_handler; +} Win32BuddyImplData; + +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) { @@ -51,13 +73,32 @@ uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void *rdata, uint32_t ttl, void *context) { + if (kDNSServiceErr_NoError != errorCode) - purple_debug_error("bonjour", "text record query - callback error.\n"); + purple_debug_error("bonjour", "record query - callback error.\n"); else if (flags & kDNSServiceFlagsAdd) { - BonjourBuddy *buddy = (BonjourBuddy*)context; - _mdns_parse_text_record(buddy, rdata, rdlen); - bonjour_buddy_add_to_purple(buddy); + if (rrtype == kDNSServiceType_TXT) { + /* New Buddy */ + BonjourBuddy *buddy = (BonjourBuddy*) context; + _mdns_parse_text_record(buddy, rdata, rdlen); + bonjour_buddy_add_to_purple(buddy); + } else if (rrtype == kDNSServiceType_NULL) { + /* Buddy Icon response */ + BonjourBuddy *buddy = (BonjourBuddy*) context; + Win32BuddyImplData *idata = buddy->mdns_impl_data; + + g_return_if_fail(idata != NULL); + + purple_buddy_icons_set_for_user(buddy->account, buddy->name, + g_memdup(rdata, rdlen), rdlen, buddy->phsh); + + /* We've got what we need; stop listening */ + purple_input_remove(idata->null_query_handler); + idata->null_query_handler = -1; + DNSServiceRefDeallocate(idata->null_query); + idata->null_query = NULL; + } } } @@ -68,26 +109,26 @@ if (!hosts || !hosts->data) purple_debug_error("bonjour", "host resolution - callback error.\n"); - else - { + else { struct sockaddr_in *addr = (struct sockaddr_in*)g_slist_nth_data(hosts, 1); BonjourBuddy* buddy = args->buddy; + Win32BuddyImplData *idata = buddy->mdns_impl_data; + + g_return_if_fail(idata != NULL); buddy->ip = g_strdup(inet_ntoa(addr->sin_addr)); /* finally, set up the continuous txt record watcher, and add the buddy to purple */ - if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&buddy->txt_query, 0, 0, args->fqn, - kDNSServiceType_TXT, kDNSServiceClass_IN, _mdns_text_record_query_callback, buddy)) - { - gint fd = DNSServiceRefSockFD(buddy->txt_query); - buddy->txt_query_fd = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, buddy->txt_query); + if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->txt_query, 0, 0, buddy->full_service_name, + kDNSServiceType_TXT, kDNSServiceClass_IN, _mdns_text_record_query_callback, buddy)) { + int fd = DNSServiceRefSockFD(idata->txt_query); + idata->txt_query_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, idata->txt_query); bonjour_buddy_add_to_purple(buddy); purple_debug_info("bonjour", "Found buddy %s at %s:%d\n", buddy->name, buddy->ip, buddy->port_p2pj); - } - else + } else bonjour_buddy_delete(buddy); } @@ -97,7 +138,6 @@ /* free the remaining args memory */ purple_dnsquery_destroy(args->query); - g_free(args->fqn); g_free(args); } @@ -108,7 +148,7 @@ ResolveCallbackArgs *args = (ResolveCallbackArgs*)context; /* remove the input fd and destroy the service ref */ - purple_input_remove(args->resolver_fd); + purple_input_remove(args->resolver_handler); DNSServiceRefDeallocate(args->resolver); if (kDNSServiceErr_NoError != errorCode) @@ -125,14 +165,13 @@ _mdns_parse_text_record(args->buddy, txtRecord, txtLen); /* set more arguments, and start the host resolver */ - args->fqn = g_strdup(fullname); + args->buddy->full_service_name = g_strdup(fullname); if (!(args->query = purple_dnsquery_a(hosttarget, port, _mdns_resolve_host_callback, args))) { purple_debug_error("bonjour", "service resolver - host resolution failed.\n"); bonjour_buddy_delete(args->buddy); - g_free(args->fqn); g_free(args); } } @@ -150,7 +189,7 @@ purple_debug_info("bonjour", "service advertisement - callback.\n"); } -void DNSSD_API +static void DNSSD_API _mdns_service_browse_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context) { @@ -159,50 +198,52 @@ if (kDNSServiceErr_NoError != errorCode) purple_debug_error("bonjour", "service browser - callback error"); - else if (flags & kDNSServiceFlagsAdd) - { + else if (flags & kDNSServiceFlagsAdd) { /* A presence service instance has been discovered... check it isn't us! */ - if (g_ascii_strcasecmp(serviceName, account->username) != 0) - { + if (g_ascii_strcasecmp(serviceName, account->username) != 0) { /* OK, lets go ahead and resolve it to add to the buddy list */ ResolveCallbackArgs *args = g_new0(ResolveCallbackArgs, 1); args->buddy = bonjour_buddy_new(serviceName, account); - if (kDNSServiceErr_NoError != DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype, replyDomain, _mdns_service_resolve_callback, args)) - { + if (kDNSServiceErr_NoError != DNSServiceResolve(&args->resolver, 0, 0, serviceName, regtype, replyDomain, _mdns_service_resolve_callback, args)) { bonjour_buddy_delete(args->buddy); g_free(args); purple_debug_error("bonjour", "service browser - failed to resolve service.\n"); - } - else - { + } else { /* get a file descriptor for this service ref, and add it to the input list */ - gint resolver_fd = DNSServiceRefSockFD(args->resolver); - args->resolver_fd = purple_input_add(resolver_fd, PURPLE_INPUT_READ, _mdns_handle_event, args->resolver); + gint fd = DNSServiceRefSockFD(args->resolver); + args->resolver_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, args->resolver); } } - } - else - { + } else { /* A peer has sent a goodbye packet, remove them from the buddy list */ purple_debug_info("bonjour", "service browser - remove notification\n"); gb = purple_find_buddy(account, serviceName); - if (gb != NULL) - { + if (gb != NULL) { bonjour_buddy_delete(gb->proto_data); purple_blist_remove_buddy(gb); } } } -int -_mdns_publish(BonjourDnsSd *data, PublishType type) -{ +/**************************** + * mdns_interface functions * + ****************************/ + +gboolean _mdns_init_session(BonjourDnsSd *data) { + data->mdns_impl_data = g_new0(Win32SessionImplData, 1); + return TRUE; +} + +gboolean _mdns_publish(BonjourDnsSd *data, PublishType type) { TXTRecordRef dns_data; char portstring[6]; - int ret = 0; + gboolean ret = TRUE; const char *jid, *aim, *email; DNSServiceErrorType set_ret; + Win32SessionImplData *idata = data->mdns_impl_data; + + g_return_val_if_fail(idata != NULL, FALSE); TXTRecordCreate(&dns_data, 256, NULL); @@ -250,41 +291,34 @@ /* TODO: ext, nick, node */ - if (set_ret != kDNSServiceErr_NoError) - { + if (set_ret != kDNSServiceErr_NoError) { purple_debug_error("bonjour", "Unable to allocate memory for text record.\n"); - ret = -1; - } - else - { + ret = FALSE; + } else { DNSServiceErrorType err = kDNSServiceErr_NoError; /* OK, we're done constructing the text record, (re)publish the service */ - switch (type) - { + switch (type) { case PUBLISH_START: purple_debug_info("bonjour", "Registering service on port %d\n", data->port_p2pj); - err = DNSServiceRegister(&data->advertisement, 0, 0, purple_account_get_username(data->account), ICHAT_SERVICE, + err = DNSServiceRegister(&idata->advertisement, 0, 0, purple_account_get_username(data->account), ICHAT_SERVICE, NULL, NULL, htons(data->port_p2pj), TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data), _mdns_service_register_callback, NULL); break; case PUBLISH_UPDATE: - err = DNSServiceUpdateRecord(data->advertisement, NULL, 0, TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data), 0); + err = DNSServiceUpdateRecord(idata->advertisement, NULL, 0, TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data), 0); break; } - if (kDNSServiceErr_NoError != err) - { + if (err != kDNSServiceErr_NoError) { purple_debug_error("bonjour", "Failed to publish presence service.\n"); - ret = -1; - } - else if (PUBLISH_START == type) - { + ret = FALSE; + } else if (type == PUBLISH_START) { /* hack: Bonjour on windows is broken. We don't care about the callback but we have to listen anyway */ - gint advertisement_fd = DNSServiceRefSockFD(data->advertisement); - data->advertisement_handler = purple_input_add(advertisement_fd, PURPLE_INPUT_READ, _mdns_handle_event, data->advertisement); + gint fd = DNSServiceRefSockFD(idata->advertisement); + idata->advertisement_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, idata->advertisement); } } @@ -293,8 +327,84 @@ return ret; } -void -_mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition) -{ - DNSServiceProcessResult((DNSServiceRef)data); +gboolean _mdns_browse(BonjourDnsSd *data) { + Win32SessionImplData *idata = data->mdns_impl_data; + + g_return_val_if_fail(idata != NULL, FALSE); + + return (DNSServiceBrowse(&idata->browser, 0, 0, ICHAT_SERVICE, NULL, + _mdns_service_browse_callback, data->account) + == kDNSServiceErr_NoError); +} + +guint _mdns_register_to_mainloop(BonjourDnsSd *data) { + Win32SessionImplData *idata = data->mdns_impl_data; + + g_return_val_if_fail(idata != NULL, 0); + + return purple_input_add(DNSServiceRefSockFD(idata->browser), + PURPLE_INPUT_READ, _mdns_handle_event, idata->browser); +} + +void _mdns_stop(BonjourDnsSd *data) { + Win32SessionImplData *idata = data->mdns_impl_data; + + if (idata == NULL || idata->advertisement == NULL || idata->browser == NULL) + return; + + /* hack: for win32, we need to stop listening to the advertisement pipe too */ + purple_input_remove(idata->advertisement_handler); + + DNSServiceRefDeallocate(idata->advertisement); + DNSServiceRefDeallocate(idata->browser); + + g_free(idata); + + data->mdns_impl_data = NULL; +} + +void _mdns_init_buddy(BonjourBuddy *buddy) { + buddy->mdns_impl_data = g_new0(Win32BuddyImplData, 1); } + +void _mdns_delete_buddy(BonjourBuddy *buddy) { + Win32BuddyImplData *idata = buddy->mdns_impl_data; + + g_return_if_fail(idata != NULL); + + if (idata->txt_query != NULL) { + purple_input_remove(idata->txt_query_handler); + DNSServiceRefDeallocate(idata->txt_query); + } + + if (idata->null_query != NULL) { + purple_input_remove(idata->null_query_handler); + DNSServiceRefDeallocate(idata->null_query); + } + + g_free(idata); + + buddy->mdns_impl_data = NULL; +} + +void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) { + Win32BuddyImplData *idata = buddy->mdns_impl_data; + + g_return_if_fail(idata != NULL); + + /* Cancel any existing query */ + if (idata->null_query != NULL) { + purple_input_remove(idata->null_query_handler); + idata->null_query_handler = 0; + DNSServiceRefDeallocate(idata->null_query); + idata->null_query = NULL; + } + + if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->null_query, 0, 0, buddy->full_service_name, + kDNSServiceType_NULL, kDNSServiceClass_IN, _mdns_text_record_query_callback, buddy)) { + int fd = DNSServiceRefSockFD(idata->null_query); + idata->null_query_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, idata->null_query); + } + +} +
--- a/libpurple/protocols/bonjour/mdns_win32.h Thu Aug 09 02:27:40 2007 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Library General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#ifndef _BONJOUR_MDNS_WIN32 -#define _BONJOUR_MDNS_WIN32 - -#ifdef USE_BONJOUR_APPLE - -#include <glib.h> -#include "mdns_types.h" -#include "buddy.h" -#include "dnsquery.h" -#include "dns_sd_proxy.h" - -/* Bonjour async callbacks */ - -void DNSSD_API _mdns_service_browse_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, - DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context); - -/* interface functions */ - -int _mdns_publish(BonjourDnsSd *data, PublishType type); -void _mdns_handle_event(gpointer data, gint source, PurpleInputCondition condition); - -#endif - -#endif
--- a/libpurple/protocols/oscar/flap_connection.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/oscar/flap_connection.c Thu Aug 09 02:38:54 2007 +0000 @@ -129,8 +129,8 @@ new_current = rateclass_get_new_current(conn, rateclass, &now); + /* (Add 100ms padding to account for inaccuracies in the calculation) */ if (new_current < rateclass->alert + 100) - /* (Add 100ms padding to account for inaccuracies in the calculation) */ /* Not ready to send this SNAC yet--keep waiting. */ return TRUE; @@ -186,9 +186,9 @@ gettimeofday(&now, NULL); new_current = rateclass_get_new_current(conn, rateclass, &now); + /* (Add 100ms padding to account for inaccuracies in the calculation) */ if (new_current < rateclass->alert + 100) { - /* (Add 100ms padding to account for inaccuracies in the calculation) */ enqueue = TRUE; } else
--- a/libpurple/protocols/oscar/oscar.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/oscar/oscar.c Thu Aug 09 02:38:54 2007 +0000 @@ -161,7 +161,6 @@ static int purple_conv_chat_info_update (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_conv_chat_incoming_msg(OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_email_parseupdate(OscarData *, FlapConnection *, FlapFrame *, ...); -static int purple_icon_error (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_icon_parseicon (OscarData *, FlapConnection *, FlapFrame *, ...); static int oscar_icon_req (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_parse_msgack (OscarData *, FlapConnection *, FlapFrame *, ...); @@ -195,7 +194,7 @@ static int purple_ssi_authreply (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_ssi_gotadded (OscarData *, FlapConnection *, FlapFrame *, ...); -static gboolean purple_icon_timerfunc(gpointer data); +static void purple_icons_fetch(PurpleConnection *gc); static void recent_buddies_cb(const char *name, PurplePrefType type, gconstpointer value, gpointer data); void oscar_set_info(PurpleConnection *gc, const char *info); @@ -1156,8 +1155,7 @@ od->iconconnecting = FALSE; - if (od->icontimer == 0) - od->icontimer = purple_timeout_add(100, purple_icon_timerfunc, gc); + purple_icons_fetch(gc); } static int @@ -1203,7 +1201,6 @@ oscar_data_addhandler(od, SNAC_FAMILY_AUTH, 0x0003, purple_parse_auth_resp, 0); oscar_data_addhandler(od, SNAC_FAMILY_AUTH, 0x0007, purple_parse_login, 0); oscar_data_addhandler(od, SNAC_FAMILY_AUTH, SNAC_SUBTYPE_AUTH_SECURID_REQUEST, purple_parse_auth_securid_request, 0); - oscar_data_addhandler(od, SNAC_FAMILY_BART, SNAC_SUBTYPE_BART_ERROR, purple_icon_error, 0); oscar_data_addhandler(od, SNAC_FAMILY_BART, SNAC_SUBTYPE_BART_RESPONSE, purple_icon_parseicon, 0); oscar_data_addhandler(od, SNAC_FAMILY_BOS, 0x0001, purple_parse_genericerr, 0); oscar_data_addhandler(od, SNAC_FAMILY_BOS, 0x0003, purple_bosrights, 0); @@ -1873,13 +1870,12 @@ saved_b16 = purple_buddy_icons_get_checksum_for_user(b); if (!b16 || !saved_b16 || strcmp(b16, saved_b16)) { - GSList *cur = od->requesticon; - while (cur && aim_sncmp((char *)cur->data, info->sn)) - cur = cur->next; - if (!cur) { - od->requesticon = g_slist_append(od->requesticon, g_strdup(purple_normalize(account, info->sn))); - if (od->icontimer == 0) - od->icontimer = purple_timeout_add(500, purple_icon_timerfunc, gc); + if (g_slist_find_custom(od->requesticon, info->sn, + (GCompareFunc)aim_sncmp) == NULL) + { + od->requesticon = g_slist_prepend(od->requesticon, + g_strdup(purple_normalize(account, info->sn))); + purple_icons_fetch(gc); } } g_free(b16); @@ -3237,24 +3233,8 @@ return 1; } -static int purple_icon_error(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { - PurpleConnection *gc = od->gc; - char *sn; - - sn = od->requesticon->data; - purple_debug_misc("oscar", "removing %s from hash table\n", sn); - od->requesticon = g_slist_remove(od->requesticon, sn); - g_free(sn); - - if (od->icontimer == 0) - od->icontimer = purple_timeout_add(500, purple_icon_timerfunc, gc); - - return 1; -} - static int purple_icon_parseicon(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { PurpleConnection *gc = od->gc; - GSList *cur; va_list ap; char *sn; guint8 iconcsumtype, *iconcsum, *icon; @@ -3280,38 +3260,23 @@ g_free(b16); } - cur = od->requesticon; - while (cur) { - char *cursn = cur->data; - if (!aim_sncmp(cursn, sn)) { - od->requesticon = g_slist_remove(od->requesticon, cursn); - g_free(cursn); - cur = od->requesticon; - } else - cur = cur->next; - } - - if (od->icontimer == 0) - od->icontimer = purple_timeout_add(250, purple_icon_timerfunc, gc); - return 1; } -static gboolean purple_icon_timerfunc(gpointer data) { - PurpleConnection *gc = data; +static void +purple_icons_fetch(PurpleConnection *gc) +{ OscarData *od = gc->proto_data; aim_userinfo_t *userinfo; FlapConnection *conn; - od->icontimer = 0; - conn = flap_connection_getbytype(od, SNAC_FAMILY_BART); if (!conn) { if (!od->iconconnecting) { aim_srv_requestnew(od, SNAC_FAMILY_BART); od->iconconnecting = TRUE; } - return FALSE; + return; } if (od->set_icon) { @@ -3322,32 +3287,24 @@ } else { purple_debug_info("oscar", "Uploading icon to icon server\n"); - aim_bart_upload(od, purple_imgstore_get_data(img), + aim_bart_upload(od, purple_imgstore_get_data(img), purple_imgstore_get_size(img)); purple_imgstore_unref(img); } od->set_icon = FALSE; } - if (!od->requesticon) { - purple_debug_misc("oscar", - "no more icons to request\n"); - return FALSE; - } - - userinfo = aim_locate_finduserinfo(od, (char *)od->requesticon->data); - if ((userinfo != NULL) && (userinfo->iconcsumlen > 0)) { - aim_bart_request(od, od->requesticon->data, userinfo->iconcsumtype, userinfo->iconcsum, userinfo->iconcsumlen); - return FALSE; - } else { - gchar *sn = od->requesticon->data; - od->requesticon = g_slist_remove(od->requesticon, sn); - g_free(sn); - } - - od->icontimer = purple_timeout_add(100, purple_icon_timerfunc, gc); - - return FALSE; + while (od->requesticon != NULL) + { + userinfo = aim_locate_finduserinfo(od, (char *)od->requesticon->data); + if ((userinfo != NULL) && (userinfo->iconcsumlen > 0)) + aim_bart_request(od, od->requesticon->data, userinfo->iconcsumtype, userinfo->iconcsum, userinfo->iconcsumlen); + + g_free(od->requesticon->data); + od->requesticon = g_slist_delete_link(od->requesticon, od->requesticon); + } + + purple_debug_misc("oscar", "no more icons to request\n"); } /*
--- a/libpurple/protocols/oscar/oscar.h Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/oscar/oscar.h Thu Aug 09 02:38:54 2007 +0000 @@ -449,7 +449,6 @@ GSList *requesticon; gboolean icq; - guint icontimer; guint getblisttimer; guint getinfotimer;
--- a/libpurple/protocols/oscar/oscar_data.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/oscar/oscar_data.c Thu Aug 09 02:38:54 2007 +0000 @@ -95,8 +95,6 @@ g_free(od->email); g_free(od->newp); g_free(od->oldp); - if (od->icontimer > 0) - purple_timeout_remove(od->icontimer); if (od->getblisttimer > 0) purple_timeout_remove(od->getblisttimer); if (od->getinfotimer > 0)
--- a/libpurple/protocols/qq/buddy_opt.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/qq/buddy_opt.c Thu Aug 09 02:38:54 2007 +0000 @@ -270,11 +270,11 @@ if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) { read_packet_b(data, &cursor, len, &reply); if (reply != QQ_ADD_BUDDY_AUTH_REPLY_OK) { - purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Add buddy with auth request fails\n"); + purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Add buddy with auth request failed\n"); if (NULL == (segments = split_data(data, len, "\x1f", 2))) return; msg_utf8 = qq_to_utf8(segments[1], QQ_CHARSET_DEFAULT); - purple_notify_error(gc, NULL, _("Add buddy with auth request fails"), msg_utf8); + purple_notify_error(gc, NULL, _("Add buddy with auth request failed"), msg_utf8); g_free(msg_utf8); } else { purple_debug(PURPLE_DEBUG_INFO, "QQ", "Add buddy with auth request OK\n"); @@ -305,6 +305,7 @@ purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Remove buddy fails\n"); } else { /* if reply */ purple_debug(PURPLE_DEBUG_INFO, "QQ", "Remove buddy OK\n"); + /* TODO: We don't really need to notify the user about this, do we? */ purple_notify_info(gc, NULL, _("You have successfully removed a buddy"), NULL); } } else { @@ -333,7 +334,8 @@ purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Remove self fails\n"); else { /* if reply */ purple_debug(PURPLE_DEBUG_INFO, "QQ", "Remove self from a buddy OK\n"); - purple_notify_info(gc, NULL, _("You have successfully removed yourself from a buddy"), NULL); + /* TODO: Does the user really need to be notified about this? */ + purple_notify_info(gc, NULL, _("You have successfully removed yourself from your friend's buddy list"), NULL); } } else { purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Error decrypt remove self reply\n"); @@ -413,7 +415,7 @@ g_free(nombre); } else { /* add OK */ qq_add_buddy_by_recv_packet(gc, for_uid, TRUE, TRUE); - msg = g_strdup_printf(_("You have added %d in buddy list"), for_uid); + msg = g_strdup_printf(_("You have added %d to buddy list"), for_uid); purple_notify_info(gc, NULL, msg, NULL); g_free(msg); }
--- a/libpurple/protocols/qq/group.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/qq/group.c Thu Aug 09 02:38:54 2007 +0000 @@ -117,7 +117,7 @@ purple_roomlist_set_in_progress(qd->roomlist, TRUE); purple_request_input(gc, _("QQ Qun"), - _("Please input external group ID"), + _("Please enter external group ID"), _("You can only search for permanent QQ groups\n"), NULL, FALSE, FALSE, NULL, _("Search"), G_CALLBACK(_qq_group_search_callback),
--- a/libpurple/protocols/qq/group_im.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/qq/group_im.c Thu Aug 09 02:38:54 2007 +0000 @@ -123,7 +123,7 @@ convert_as_pascal_string(*cursor, &reason_utf8, QQ_CHARSET_DEFAULT); - msg = g_strdup_printf(_("User %d applied to join group %d"), user_uid, external_group_id); + msg = g_strdup_printf(_("User %d requested to join group %d"), user_uid, external_group_id); reason = g_strdup_printf(_("Reason: %s"), reason_utf8); g = g_new0(group_member_opt, 1); @@ -177,7 +177,7 @@ convert_as_pascal_string(*cursor, &reason_utf8, QQ_CHARSET_DEFAULT); msg = g_strdup_printf - (_("You request to join group %d has been rejected by admin %d"), external_group_id, admin_uid); + (_("Your request to join group %d has been rejected by admin %d"), external_group_id, admin_uid); reason = g_strdup_printf(_("Reason: %s"), reason_utf8); purple_notify_warning(gc, _("QQ Qun Operation"), msg, reason); @@ -218,7 +218,7 @@ convert_as_pascal_string(*cursor, &reason_utf8, QQ_CHARSET_DEFAULT); msg = g_strdup_printf - (_("You request to join group %d has been approved by admin %d"), external_group_id, admin_uid); + (_("Your request to join group %d has been approved by admin %d"), external_group_id, admin_uid); purple_notify_warning(gc, _("QQ Qun Operation"), msg, NULL); @@ -254,7 +254,7 @@ g_return_if_fail(external_group_id > 0 && uid > 0); - msg = g_strdup_printf(_("You [%d] has exit group \"%d\""), uid, external_group_id); + msg = g_strdup_printf(_("You [%d] have left group \"%d\""), uid, external_group_id); purple_notify_info(gc, _("QQ Qun Operation"), msg, NULL); group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID); @@ -288,7 +288,7 @@ g_return_if_fail(external_group_id > 0 && uid > 0); - msg = g_strdup_printf(_("You [%d] has been added by group \"%d\""), uid, external_group_id); + msg = g_strdup_printf(_("You [%d] have been added to group \"%d\""), uid, external_group_id); purple_notify_info(gc, _("QQ Qun Operation"), msg, _("This group has been added to your buddy list")); group = qq_group_find_by_id(gc, internal_group_id, QQ_INTERNAL_ID);
--- a/libpurple/protocols/qq/group_internal.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/qq/group_internal.c Thu Aug 09 02:38:54 2007 +0000 @@ -38,7 +38,7 @@ switch (group->my_status) { case QQ_GROUP_MEMBER_STATUS_NOT_MEMBER: - status_desc = _("I am not member"); + status_desc = _("I am not a member"); break; case QQ_GROUP_MEMBER_STATUS_IS_MEMBER: status_desc = _("I am a member");
--- a/libpurple/protocols/qq/group_join.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/qq/group_join.c Thu Aug 09 02:38:54 2007 +0000 @@ -230,7 +230,7 @@ purple_blist_remove_chat(chat); qq_group_delete_internal_record(qd, internal_group_id); } - purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully exited the group"), NULL); + purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully left the group"), NULL); } else { purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Invalid exit group reply, expect %d bytes, read %d bytes\n", expected_bytes, bytes); @@ -254,8 +254,8 @@ if (bytes == expected_bytes) purple_notify_info - (gc, _("QQ Group Auth"), - _("Your authorization operation has been accepted by the QQ server"), NULL); + (gc, _("QQ Group Auth"), + _("Your authorization request has been accepted by the QQ server"), NULL); else purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Invalid join group reply, expect %d bytes, read %d bytes\n", expected_bytes, bytes); @@ -325,8 +325,8 @@ errno = 0; external_group_id = strtol(external_group_id_ptr, NULL, 10); if (errno != 0) { - purple_notify_error(gc, _("Error"), - _("You inputted a group id outside the acceptable range"), NULL); + purple_notify_error(gc, _("Error"), + _("You entered a group ID outside the acceptable range"), NULL); return; } @@ -357,12 +357,12 @@ g->uid = internal_group_id; purple_request_action(gc, _("QQ Qun Operation"), - _("Are you sure to exit this Qun?"), + _("Are you sure you want to leave this Qun?"), _ ("Note, if you are the creator, \nthis operation will eventually remove this Qun."), 1, purple_connection_get_account(gc), NULL, NULL, g, 2, _("Cancel"), G_CALLBACK(qq_do_nothing_with_gc_and_uid), - _("Go ahead"), G_CALLBACK(_qq_group_exit_with_gc_and_id)); + _("Continue"), G_CALLBACK(_qq_group_exit_with_gc_and_id)); }
--- a/libpurple/protocols/qq/group_opt.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/qq/group_opt.c Thu Aug 09 02:38:54 2007 +0000 @@ -120,8 +120,8 @@ { g_return_if_fail(g != NULL && g->gc != NULL && g->member > 0); - qq_send_packet_get_info(g->gc, g->member, TRUE); /* we wanna see window */ - purple_request_action(g->gc, NULL, _("Do you wanna approve the request?"), "", 2, + qq_send_packet_get_info(g->gc, g->member, TRUE); /* we want to see window */ + purple_request_action(g->gc, NULL, _("Do you want to approve the request?"), "", 2, purple_connection_get_account(g->gc), NULL, NULL, g, 2, _("Reject"), G_CALLBACK(qq_group_reject_application_with_struct), @@ -134,7 +134,7 @@ g_return_if_fail(g != NULL && g->gc != NULL && g->member > 0); msg1 = g_strdup_printf(_("You rejected %d's request"), g->member); - msg2 = g_strdup(_("Input your reason:")); + msg2 = g_strdup(_("Enter your reason:")); nombre = uid_to_purple_name(g->member); purple_request_input(g->gc, /* title */ NULL, msg1, msg2, @@ -232,7 +232,7 @@ purple_debug(PURPLE_DEBUG_INFO, "QQ", "Succeed in modify members for Qun %d\n", group->external_group_id); - purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully modify Qun member"), NULL); + purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully modified Qun member"), NULL); } void qq_group_modify_info(PurpleConnection *gc, qq_group *group) @@ -302,7 +302,7 @@ purple_debug(PURPLE_DEBUG_INFO, "QQ", "Succeed in modify info for Qun %d\n", group->external_group_id); qq_group_refresh(gc, group); - purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully modify Qun information"), NULL); + purple_notify_info(gc, _("QQ Qun Operation"), _("You have successfully modified Qun information"), NULL); } /* we create a very simple group first, and then let the user to modify */
--- a/libpurple/protocols/qq/im.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/qq/im.c Thu Aug 09 02:38:54 2007 +0000 @@ -573,7 +573,7 @@ read_packet_b(data, &cursor, len, &reply); if (reply != QQ_SEND_IM_REPLY_OK) { purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Send IM fail\n"); - purple_notify_error(gc, _("Server ACK"), _("Failed to send IM."), NULL); + purple_notify_error(gc, _("Error"), _("Failed to send IM."), NULL); } else purple_debug(PURPLE_DEBUG_INFO, "QQ", "IM ACK OK\n");
--- a/libpurple/protocols/qq/keep_alive.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/qq/keep_alive.c Thu Aug 09 02:38:54 2007 +0000 @@ -84,7 +84,7 @@ /* segments[0] and segment[1] are all 0x30 ("0") */ qd->all_online = strtol(segments[2], NULL, 10); if(0 == qd->all_online) - purple_connection_error(gc, _("Keep alive error, seems connection lost!")); + purple_connection_error(gc, _("Keep alive error")); g_free(qd->my_ip); qd->my_ip = g_strdup(segments[3]); qd->my_port = strtol(segments[4], NULL, 10);
--- a/libpurple/protocols/qq/login_logout.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/qq/login_logout.c Thu Aug 09 02:38:54 2007 +0000 @@ -405,7 +405,7 @@ ">>> %d bytes -> [default] decrypt and dump\n%s", buf_len, hex_dump); try_dump_as_gbk(buf, buf_len); - purple_connection_error(gc, _("Request login token error!")); + purple_connection_error(gc, _("Error requesting login token")); } g_free(hex_dump); }
--- a/libpurple/protocols/qq/qq.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/qq/qq.c Thu Aug 09 02:38:54 2007 +0000 @@ -195,7 +195,8 @@ { qq_buddy *q_bud; gchar *ip_str; - char *tmp, *tmp2; + char *tmp; + const char *tmp2; g_return_if_fail(b != NULL); @@ -206,11 +207,12 @@ { ip_str = gen_ip_str(q_bud->ip); if (strlen(ip_str) != 0) { - tmp = g_strdup_printf(_("%s Address"), - ((q_bud->comm_flag & QQ_COMM_FLAG_TCP_MODE) ? "TCP" : "UDP")); - tmp2 = g_strdup_printf("%s:%d", ip_str, q_bud->port); - purple_notify_user_info_add_pair(user_info, tmp, tmp2); - g_free(tmp2); + if (q_bud->comm_flag & QQ_COMM_FLAG_TCP_MODE) + tmp2 = _("TCP Address"); + else + tmp2 = _("UDP Address"); + tmp = g_strdup_printf("%s:%d", ip_str, q_bud->port); + purple_notify_user_info_add_pair(user_info, tmp2, tmp); g_free(tmp); } g_free(ip_str); @@ -238,7 +240,7 @@ if (q_bud->level) { tmp = g_strdup_printf("%d", q_bud->level); purple_notify_user_info_add_pair(user_info, _("Level"), tmp); - g_free(tmp); + g_free(tmp); } /* For debugging */ /* @@ -275,19 +277,19 @@ GList *types = NULL; status = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE, - "available", _("QQ: Available"), FALSE, TRUE, FALSE); + "available", _("Available"), FALSE, TRUE, FALSE); types = g_list_append(types, status); status = purple_status_type_new_full(PURPLE_STATUS_AWAY, - "away", _("QQ: Away"), FALSE, TRUE, FALSE); + "away", _("Away"), FALSE, TRUE, FALSE); types = g_list_append(types, status); status = purple_status_type_new_full(PURPLE_STATUS_INVISIBLE, - "invisible", _("QQ: Invisible"), FALSE, TRUE, FALSE); + "invisible", _("Invisible"), FALSE, TRUE, FALSE); types = g_list_append(types, status); status = purple_status_type_new_full(PURPLE_STATUS_OFFLINE, - "offline", _("QQ: Offline"), FALSE, TRUE, FALSE); + "offline", _("Offline"), FALSE, TRUE, FALSE); types = g_list_append(types, status); status = purple_status_type_new_full(PURPLE_STATUS_MOBILE, @@ -416,7 +418,7 @@ g->uid = uid; purple_request_action(gc, _("Block Buddy"), - _("Are you sure to block this buddy?"), NULL, + _("Are you sure you want to block this buddy?"), NULL, 1, g, 2, _("Cancel"), G_CALLBACK(qq_do_nothing_with_gc_and_uid), @@ -470,7 +472,7 @@ PurpleConnection *gc = (PurpleConnection *) action->context; purple_request_input(gc, _("Create QQ Qun"), _("Input Qun name here"), - _("Only QQ member can create permanent Qun"), + _("Only QQ members can create permanent Qun"), "OpenQ", FALSE, FALSE, NULL, _("Create"), G_CALLBACK(qq_group_create_with_name), _("Cancel"), NULL, gc); } @@ -528,7 +530,7 @@ PurplePluginAction *act; m = NULL; - act = purple_plugin_action_new(_("Modify My Information"), _qq_menu_modify_my_info); + act = purple_plugin_action_new(_("Set My Information"), _qq_menu_modify_my_info); m = g_list_append(m, act); act = purple_plugin_action_new(_("Change Password"), _qq_menu_change_password); @@ -555,7 +557,7 @@ PurpleMenuAction *act; m = NULL; - act = purple_menu_action_new(_("Exit this QQ Qun"), PURPLE_CALLBACK(_qq_menu_unsubscribe_group), NULL, NULL); + act = purple_menu_action_new(_("Leave this QQ Qun"), PURPLE_CALLBACK(_qq_menu_unsubscribe_group), NULL, NULL); m = g_list_append(m, act); /* TODO: enable this
--- a/libpurple/protocols/qq/qq_proxy.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/qq/qq_proxy.c Thu Aug 09 02:38:54 2007 +0000 @@ -493,13 +493,8 @@ errno = 0; ret = send(qd->fd, data, len, 0); } - if (ret == -1) { - purple_connection_error(qd->gc, _("Socket send error")); - return ret; - } else if (errno == ECONNREFUSED) { - purple_connection_error(qd->gc, _("Connection refused")); - return ret; - } + if (ret == -1) + purple_connection_error(qd->gc, strerror(errno)); return ret; }
--- a/libpurple/protocols/qq/sys_msg.c Thu Aug 09 02:27:40 2007 +0000 +++ b/libpurple/protocols/qq/sys_msg.c Thu Aug 09 02:38:54 2007 +0000 @@ -80,12 +80,11 @@ uid = g->uid; g_return_if_fail(gc != 0 && uid != 0); - qq_send_packet_get_info(gc, uid, TRUE); /* we wanna see window */ + qq_send_packet_get_info(gc, uid, TRUE); /* we want to see window */ nombre = uid_to_purple_name(uid); - /* TODO: 'wanna' is not an appropriate word for this string. Fix after string freeze. */ purple_request_action - (gc, NULL, _("Do you wanna approve the request?"), "", 2, + (gc, NULL, _("Do you want to approve the request?"), "", 2, purple_connection_get_account(gc), nombre, NULL, g, 2, _("Reject"), G_CALLBACK(qq_reject_add_request_with_gc_and_uid), @@ -105,11 +104,10 @@ uid = g->uid; g_return_if_fail(gc != 0 && uid != 0); - qq_send_packet_get_info(gc, uid, TRUE); /* we wanna see window */ - /* TODO: 'wanna' is not an appropriate word for this string. Fix after string freeze. */ + qq_send_packet_get_info(gc, uid, TRUE); /* we want to see window */ nombre = uid_to_purple_name(uid); purple_request_action - (gc, NULL, _("Do you wanna add this buddy?"), "", 2, + (gc, NULL, _("Do you want to add this buddy?"), "", 2, purple_connection_get_account(gc), nombre, NULL, g, 2, _("Cancel"), NULL, @@ -175,7 +173,7 @@ _("Add"), G_CALLBACK(qq_add_buddy_with_gc_and_uid), _("Search"), G_CALLBACK(_qq_search_before_add_with_gc_and_uid)); } else { - message = g_strdup_printf(_("%s has added you [%s]"), from, to); + message = g_strdup_printf(_("%s has added you [%s] to his or her buddy list"), from, to); _qq_sys_msg_log_write(gc, message, from); purple_notify_info(gc, NULL, message, NULL); } @@ -211,7 +209,7 @@ qd = (qq_data *) gc->proto_data; qq_add_buddy_by_recv_packet(gc, strtol(from, NULL, 10), TRUE, TRUE); - message = g_strdup_printf(_("User %s has approved your request"), from); + message = g_strdup_printf(_("User %s approved your request"), from); _qq_sys_msg_log_write(gc, message, from); purple_notify_info(gc, NULL, message, NULL); @@ -236,9 +234,8 @@ name = uid_to_purple_name(uid); - /* TODO: 'wanna' is not an appropriate word for this string. Fix after string freeze */ /* TODO: this should go through purple_account_request_authorization() */ - message = g_strdup_printf(_("%s wanna add you [%s] as friends"), from, to); + message = g_strdup_printf(_("%s wants to add you [%s] as a friend"), from, to); reason = g_strdup_printf(_("Message: %s"), msg_utf8); _qq_sys_msg_log_write(gc, message, from);
--- a/pidgin/gtkconv.c Thu Aug 09 02:27:40 2007 +0000 +++ b/pidgin/gtkconv.c Thu Aug 09 02:38:54 2007 +0000 @@ -213,8 +213,8 @@ * Callbacks **************************************************************************/ -static gint -close_conv_cb(GtkWidget *w, PidginConversation *gtkconv) +static gboolean +close_conv_cb(GtkWidget *w, GdkEventButton *event, PidginConversation *gtkconv) { GList *list = g_list_copy(gtkconv->convs); @@ -1331,7 +1331,7 @@ { PidginWindow *win = data; - close_conv_cb(NULL, PIDGIN_CONVERSATION(pidgin_conv_window_get_active_conversation(win))); + close_conv_cb(NULL, NULL, PIDGIN_CONVERSATION(pidgin_conv_window_get_active_conversation(win))); } static void @@ -6256,13 +6256,15 @@ (fields & PIDGIN_CONV_SET_TITLE) || (fields & PIDGIN_CONV_TOPIC)) { - char *title; + char *title, *truncate = NULL, truncchar = '\0'; PurpleConvIm *im = NULL; PurpleAccount *account = purple_conversation_get_account(conv); + PurpleBuddy *buddy = NULL; + PurplePresence *p = NULL; char *markup = NULL; AtkObject *accessibility_obj; /* I think this is a little longer than it needs to be but I'm lazy. */ - char style[51]; + char *style, *status_style; if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) im = PURPLE_CONV_IM(conv); @@ -6275,12 +6277,20 @@ else title = g_strdup(purple_conversation_get_title(conv)); + if ((truncate = strchr(title, ' ')) || + (truncate = strchr(title, '@'))) { + truncchar = *truncate; + *truncate = '\0'; + } + if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) { - PurpleBuddy *buddy = purple_find_buddy(account, conv->name); - if (buddy) + buddy = purple_find_buddy(account, conv->name); + if (buddy) { + p = purple_buddy_get_presence(buddy); markup = pidgin_blist_get_name_markup(buddy, FALSE, FALSE); - else + } else { markup = title; + } } else if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) { PurpleConvChat *chat = PURPLE_CONV_CHAT(conv); const char *topic = purple_conv_chat_get_topic(chat); @@ -6296,54 +6306,56 @@ if (title != markup) g_free(markup); - *style = '\0'; - if (!GTK_WIDGET_REALIZED(gtkconv->tab_label)) gtk_widget_realize(gtkconv->tab_label); accessibility_obj = gtk_widget_get_accessible(gtkconv->tab_cont); if (im != NULL && - purple_conv_im_get_typing_state(im) == PURPLE_TYPING) - { + purple_conv_im_get_typing_state(im) == PURPLE_TYPING) { atk_object_set_description(accessibility_obj, _("Typing")); - strncpy(style, "color=\"#4e9a06\"", sizeof(style)); - } - else if (im != NULL && - purple_conv_im_get_typing_state(im) == PURPLE_TYPED) - { + style = "color=\"#4e9a06\""; + } else if (im != NULL && + purple_conv_im_get_typing_state(im) == PURPLE_TYPED) { atk_object_set_description(accessibility_obj, _("Stopped Typing")); - strncpy(style, "color=\"#c4a000\"", sizeof(style)); - } - else if (gtkconv->unseen_state == PIDGIN_UNSEEN_NICK) - { + style = "color=\"#c4a000\""; + } else if (gtkconv->unseen_state == PIDGIN_UNSEEN_NICK) { atk_object_set_description(accessibility_obj, _("Nick Said")); - strncpy(style, "color=\"#204a87\" style=\"italic\" weight=\"bold\"", sizeof(style)); - } - else if (gtkconv->unseen_state == PIDGIN_UNSEEN_TEXT) - { + style = "color=\"#204a87\" weight=\"bold\""; + } else if (gtkconv->unseen_state == PIDGIN_UNSEEN_TEXT) { atk_object_set_description(accessibility_obj, _("Unread Messages")); - strncpy(style, "color=\"#cc0000\" weight=\"bold\"", sizeof(style)); + style = "color=\"#cc0000\" weight=\"bold\""; + } else if (gtkconv->unseen_state == PIDGIN_UNSEEN_EVENT) { + atk_object_set_description(accessibility_obj, _("New Event")); + style = "color=\"#888a85\" weight=\"bold\""; + } else { + style = ""; } - else if (gtkconv->unseen_state == PIDGIN_UNSEEN_EVENT) - { - atk_object_set_description(accessibility_obj, _("New Event")); - strncpy(style, "color=\"#888a85\" style=\"italic\"", sizeof(style)); + + if (p && purple_presence_is_status_primitive_active(p, PURPLE_STATUS_OFFLINE)) { + status_style = "strikethrough='true'"; + } else if (p && !purple_presence_is_status_primitive_active(p, PURPLE_STATUS_AVAILABLE) && + !purple_presence_is_status_primitive_active(p, PURPLE_STATUS_INVISIBLE)) { + status_style = "style='italic'"; + } else { + status_style = ""; } - if (*style != '\0') + if (*style != '\0' || *status_style != '\0') { char *html_title,*label; html_title = g_markup_escape_text(title, -1); - - label = g_strdup_printf("<span %s>%s</span>", - style, html_title); + label = g_strdup_printf("<span %s %s>%s</span>", + style, status_style, html_title); g_free(html_title); gtk_label_set_markup(GTK_LABEL(gtkconv->tab_label), label); g_free(label); } else gtk_label_set_text(GTK_LABEL(gtkconv->tab_label), title); + + if (truncate) + *truncate = truncchar; if (pidgin_conv_window_is_active_conversation(conv)) update_typing_icon(gtkconv); @@ -6587,6 +6599,7 @@ event = gtk_event_box_new(); gtk_container_add(GTK_CONTAINER(gtkconv->u.im->icon_container), event); + gtk_event_box_set_visible_window(GTK_EVENT_BOX(event), FALSE); gtk_widget_add_events(event, GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK); g_signal_connect(G_OBJECT(event), "button-press-event", @@ -7483,7 +7496,7 @@ _("Confirm close"), GTK_WINDOW(gtkwin->window), GTK_DIALOG_MODAL, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - PIDGIN_STOCK_CLOSE_TABS, GTK_RESPONSE_OK, NULL); + GTK_STOCK_CLOSE, GTK_RESPONSE_OK, NULL); gtk_dialog_set_default_response(GTK_DIALOG(warn_close_dialog), GTK_RESPONSE_OK); @@ -7775,7 +7788,7 @@ return FALSE; gtkconv = pidgin_conv_window_get_gtkconv_at_index(win, tab_clicked); - close_conv_cb(NULL, gtkconv); + close_conv_cb(NULL, NULL, gtkconv); return TRUE; } @@ -8034,7 +8047,7 @@ if (gconv != gtkconv) { - close_conv_cb(NULL, gconv); + close_conv_cb(NULL, NULL, gconv); } } } @@ -8046,7 +8059,7 @@ gtkconv = g_object_get_data(menu, "clicked_tab"); if (gtkconv) - close_conv_cb(NULL, gtkconv); + close_conv_cb(NULL, NULL, gtkconv); } static gboolean @@ -8536,7 +8549,6 @@ GtkWidget *close_image; PurpleConversationType conv_type; const gchar *tmp_lab; - gint close_button_width, close_button_height, focus_width, focus_pad; conv_type = purple_conversation_get_type(conv); @@ -8548,28 +8560,16 @@ /* Close button. */ - gtkconv->close = gtk_button_new(); - gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &close_button_width, &close_button_height); - if (gtk_check_version(2, 4, 2) == NULL) { - /* Need to account for extra padding around the gtkbutton */ - gtk_widget_style_get(GTK_WIDGET(gtkconv->close), - "focus-line-width", &focus_width, - "focus-padding", &focus_pad, - NULL); - close_button_width += (focus_width + focus_pad) * 2; - close_button_height += (focus_width + focus_pad) * 2; - } - gtk_widget_set_size_request(GTK_WIDGET(gtkconv->close), - close_button_width, close_button_height); - - gtk_button_set_relief(GTK_BUTTON(gtkconv->close), GTK_RELIEF_NONE); - close_image = gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU); + gtkconv->close = gtk_event_box_new(); + gtk_event_box_set_visible_window(GTK_EVENT_BOX(gtkconv->close), FALSE); + close_image = gtk_label_new(NULL); + gtk_label_set_markup(GTK_LABEL(close_image),"<b>×</b>"); gtk_widget_show(close_image); gtk_container_add(GTK_CONTAINER(gtkconv->close), close_image); gtk_tooltips_set_tip(gtkconv->tooltips, gtkconv->close, _("Close conversation"), NULL); - g_signal_connect(G_OBJECT(gtkconv->close), "clicked", + g_signal_connect(G_OBJECT(gtkconv->close), "button-press-event", G_CALLBACK(close_conv_cb), gtkconv); #if !GTK_CHECK_VERSION(2,6,0) @@ -8601,6 +8601,7 @@ gtk_misc_set_alignment(GTK_MISC(gtkconv->menu_label), 0, 0); gtk_widget_show(gtkconv->menu_tabby); + gtk_widget_set_size_request(gtkconv->menu_tabby, 0, -1); if (purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM) pidgin_conv_update_buddy_icon(conv); @@ -8717,7 +8718,7 @@ TRUE, GTK_PACK_START); /* show the widgets */ - gtk_widget_show(gtkconv->icon); +/* gtk_widget_show(gtkconv->icon); */ gtk_widget_show(gtkconv->tab_label); if (purple_prefs_get_bool(PIDGIN_PREFS_ROOT "/conversations/close_on_tabs")) gtk_widget_show(gtkconv->close);
--- a/pidgin/gtkdocklet.c Thu Aug 09 02:27:40 2007 +0000 +++ b/pidgin/gtkdocklet.c Thu Aug 09 02:38:54 2007 +0000 @@ -121,7 +121,7 @@ /* determine if any ims have unseen messages */ convs = get_pending_list(DOCKLET_TOOLTIP_LINE_LIMIT); - if (!strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/docklet/show"), "pending")) { + if (!strcmp(purple_prefs_get_string(PIDGIN_PREFS_ROOT "/docklet/show"), "always")) { if (convs && ui_ops->create && !visible) { g_list_free(convs); ui_ops->create();
--- a/pidgin/gtkimhtml.c Thu Aug 09 02:27:40 2007 +0000 +++ b/pidgin/gtkimhtml.c Thu Aug 09 02:38:54 2007 +0000 @@ -1053,7 +1053,7 @@ if (!gtk_text_view_get_editable(GTK_TEXT_VIEW(imhtml))) return; - if (selection_data->length < 0) { + if (imhtml->wbfo || selection_data->length < 0) { gtk_clipboard_request_text(clipboard, paste_plaintext_received_cb, imhtml); return; } else {
--- a/pidgin/gtkimhtmltoolbar.c Thu Aug 09 02:27:40 2007 +0000 +++ b/pidgin/gtkimhtmltoolbar.c Thu Aug 09 02:38:54 2007 +0000 @@ -941,10 +941,8 @@ static void pidgin_menu_clicked(GtkWidget *button, GtkMenu *menu) { - if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) { - gtk_widget_show_all(GTK_WIDGET(menu)); - gtk_menu_popup(menu, NULL, NULL, menu_position_func, button, 0, gtk_get_current_event_time()); - } + gtk_widget_show_all(GTK_WIDGET(menu)); + gtk_menu_popup(menu, NULL, NULL, menu_position_func, button, 0, gtk_get_current_event_time()); } static void pidgin_menu_deactivate(GtkWidget *menu, GtkToggleButton *button) @@ -1098,6 +1096,13 @@ g_signal_handlers_unblock_by_func(G_OBJECT(item), G_CALLBACK(gtk_button_clicked), button); } +static void +enable_markup(GtkWidget *widget, gpointer null) +{ + if (GTK_IS_LABEL(widget)) + g_object_set(G_OBJECT(widget), "use-markup", TRUE, NULL); +} + static void gtk_imhtmltoolbar_init (GtkIMHtmlToolbar *toolbar) { GtkWidget *hbox = GTK_WIDGET(toolbar); @@ -1109,7 +1114,6 @@ GtkWidget *font_menu; GtkWidget *insert_menu; GtkWidget *menuitem; - GtkWidget *button; GtkWidget *sep; int i; struct { @@ -1117,19 +1121,22 @@ GtkWidget **button; gboolean check; } buttons[] = { - {_("_Bold"), &toolbar->bold, TRUE}, - {_("_Italic"), &toolbar->italic, TRUE}, - {_("_Underline"), &toolbar->underline, TRUE}, - {_("_Larger"), &toolbar->larger_size, TRUE}, + {_("<b>_Bold</b>"), &toolbar->bold, TRUE}, + {_("<i>_Italic</i>"), &toolbar->italic, TRUE}, + {_("<u>_Underline</u>"), &toolbar->underline, TRUE}, + {_("<span size='larger'>_Larger</span>"), &toolbar->larger_size, TRUE}, #if 0 {_("_Normal"), &toolbar->normal_size, TRUE}, #endif - {_("_Smaller"), &toolbar->smaller_size, TRUE}, + {_("<span size='smaller'>_Smaller</span>"), &toolbar->smaller_size, TRUE}, + /* If we want to show the formatting for the following items, we would + * need to update them when formatting changes. The above items don't need + * no updating nor nothin' */ {_("_Font face"), &toolbar->font, TRUE}, - {_("_Foreground color"), &toolbar->fgcolor, TRUE}, - {_("_Background color"), &toolbar->bgcolor, TRUE}, + {_("Foreground _color"), &toolbar->fgcolor, TRUE}, + {_("Bac_kground color"), &toolbar->bgcolor, TRUE}, {_("_Reset formatting"), &toolbar->clear, FALSE}, - {NULL, NULL} + {NULL, NULL, FALSE} }; @@ -1178,9 +1185,11 @@ gtk_menu_shell_append(GTK_MENU_SHELL(font_menu), menuitem); g_signal_connect(G_OBJECT(old), "notify::sensitive", G_CALLBACK(button_sensitiveness_changed), menuitem); + gtk_container_foreach(GTK_CONTAINER(menuitem), (GtkCallback)enable_markup, NULL); } - g_signal_connect(G_OBJECT(font_button), "clicked", G_CALLBACK(pidgin_menu_clicked), font_menu); + g_signal_connect_swapped(G_OBJECT(font_button), "button-press-event", G_CALLBACK(gtk_widget_activate), font_button); + g_signal_connect(G_OBJECT(font_button), "activate", G_CALLBACK(pidgin_menu_clicked), font_menu); g_signal_connect(G_OBJECT(font_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), font_button); /* Sep */ @@ -1221,7 +1230,8 @@ g_signal_connect(G_OBJECT(toolbar->link), "notify::sensitive", G_CALLBACK(button_sensitiveness_changed), menuitem); - g_signal_connect(G_OBJECT(insert_button), "clicked", G_CALLBACK(pidgin_menu_clicked), insert_menu); + g_signal_connect_swapped(G_OBJECT(insert_button), "button-press-event", G_CALLBACK(gtk_widget_activate), insert_button); + g_signal_connect(G_OBJECT(insert_button), "activate", G_CALLBACK(pidgin_menu_clicked), insert_menu); g_signal_connect(G_OBJECT(insert_menu), "deactivate", G_CALLBACK(pidgin_menu_deactivate), insert_button); toolbar->sml = NULL; }
--- a/pidgin/gtkmain.c Thu Aug 09 02:27:40 2007 +0000 +++ b/pidgin/gtkmain.c Thu Aug 09 02:38:54 2007 +0000 @@ -695,7 +695,9 @@ return 1; } +#if GLIB_CHECK_VERSION(2,2,0) g_set_application_name(_("Pidgin")); +#endif /* glib-2.0 >= 2.2.0 */ #ifdef _WIN32 winpidgin_init(hint);
--- a/pidgin/gtkutils.c Thu Aug 09 02:27:40 2007 +0000 +++ b/pidgin/gtkutils.c Thu Aug 09 02:38:54 2007 +0000 @@ -2047,11 +2047,11 @@ entry.entry.buddy->account, entry.entry.buddy->name ); +#else + item->data = g_strdup(entry.entry.buddy->name); + g_completion_add_items(data->completion, item); +#endif /* NEW_STYLE_COMPLETION */ } -#else - item->data = g_strdup(buddy->name); - g_completion_add_items(data->completion, item); -#endif /* NEW_STYLE_COMPLETION */ } } }
--- a/pidgin/pixmaps/Makefile.am Thu Aug 09 02:27:40 2007 +0000 +++ b/pidgin/pixmaps/Makefile.am Thu Aug 09 02:38:54 2007 +0000 @@ -12,7 +12,7 @@ pidgin.ico pidginbuttonpixdir = $(datadir)/pixmaps/pidgin/buttons -pidginbuttonpix_DATA = edit.png pause.png +pidginbuttonpix_DATA = edit.png pause.png pidgindistpixdir = $(datadir)/pixmaps/pidgin pidgindistpix_DATA = logo.png arrow-down.xpm arrow-left.xpm arrow-right.xpm arrow-up.xpm
--- a/pidgin/pixmaps/icons/22/Makefile.am Thu Aug 09 02:27:40 2007 +0000 +++ b/pidgin/pixmaps/icons/22/Makefile.am Thu Aug 09 02:38:54 2007 +0000 @@ -2,7 +2,7 @@ EXTRA_DIST = pidgin.png -pidginiconspixdir = $(datadir)/icons/hicolor/24x24/apps +pidginiconspixdir = $(datadir)/icons/hicolor/22x22/apps pidginiconspix_DATA = $(EXTRA_DIST)