changeset 18844:a53992a4437a

merge of 'dd8e64b939e2f2a23c37594e322f73dd5548a85c' and 'ed08f14497364a33538ffa789e8ce459f14552d7'
author Daniel Atallah <daniel.atallah@gmail.com>
date Wed, 08 Aug 2007 01:57:39 +0000
parents 2ae9b483c4db (current diff) 7bf6b9a70b41 (diff)
children 32a44f7e9c52
files libpurple/protocols/bonjour/mdns_avahi.c
diffstat 38 files changed, 1611 insertions(+), 137 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Wed Aug 08 01:57:08 2007 +0000
+++ b/ChangeLog	Wed Aug 08 01:57:39 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:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/ui-ops.dox	Wed Aug 08 01:57:39 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	Wed Aug 08 01:57:08 2007 +0000
+++ b/finch/finch.c	Wed Aug 08 01:57:39 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	Wed Aug 08 01:57:08 2007 +0000
+++ b/finch/libgnt/gntbindable.c	Wed Aug 08 01:57:39 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	Wed Aug 08 01:57:08 2007 +0000
+++ b/finch/libgnt/gntmain.c	Wed Aug 08 01:57:39 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	Wed Aug 08 01:57:08 2007 +0000
+++ b/finch/libgnt/gnttextview.c	Wed Aug 08 01:57:39 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	Wed Aug 08 01:57:08 2007 +0000
+++ b/finch/libgnt/gnttextview.h	Wed Aug 08 01:57:39 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	Wed Aug 08 01:57:08 2007 +0000
+++ b/finch/libgnt/gnttree.c	Wed Aug 08 01:57:39 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;
@@ -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	Wed Aug 08 01:57:08 2007 +0000
+++ b/finch/libgnt/gntwm.c	Wed Aug 08 01:57:39 2007 +0000
@@ -684,6 +684,8 @@
 		{'j', "&#x2518;"},
 		{'a', "&#x2592;"},
 		{'n', "&#x253c;"},
+		{'w', "&#x252c;"},
+		{'v', "&#x2534;"},
 		{'\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	Wed Aug 08 01:57:39 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	Wed Aug 08 01:57:08 2007 +0000
+++ b/finch/libgnt/pygnt/Makefile.make	Wed Aug 08 01:57:39 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	Wed Aug 08 01:57:08 2007 +0000
+++ b/finch/libgnt/pygnt/dbus-gnt	Wed Aug 08 01:57:39 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	Wed Aug 08 01:57:39 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	Wed Aug 08 01:57:39 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	Wed Aug 08 01:57:39 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	Wed Aug 08 01:57:08 2007 +0000
+++ b/finch/libgnt/pygnt/gendef.sh	Wed Aug 08 01:57:39 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	Wed Aug 08 01:57:08 2007 +0000
+++ b/finch/libgnt/pygnt/gnt.override	Wed Aug 08 01:57:39 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	Wed Aug 08 01:57:39 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	Wed Aug 08 01:57:08 2007 +0000
+++ b/finch/libgnt/pygnt/gntmodule.c	Wed Aug 08 01:57:39 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	Wed Aug 08 01:57:08 2007 +0000
+++ b/finch/libgnt/pygnt/gnttree.override	Wed Aug 08 01:57:39 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	Wed Aug 08 01:57:39 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	Wed Aug 08 01:57:08 2007 +0000
+++ b/finch/libgnt/pygnt/test.py	Wed Aug 08 01:57:39 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/connection.h	Wed Aug 08 01:57:08 2007 +0000
+++ b/libpurple/connection.h	Wed Aug 08 01:57:39 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.h	Wed Aug 08 01:57:08 2007 +0000
+++ b/libpurple/conversation.h	Wed Aug 08 01:57:39 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/protocols/bonjour/bonjour.c	Wed Aug 08 01:57:08 2007 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Wed Aug 08 01:57:39 2007 +0000
@@ -138,6 +138,8 @@
 		return;
 	}
 
+	bonjour_dns_sd_update_buddy_icon(bd->dns_sd_data);
+
 	/* Create a group for bonjour buddies */
 	bonjour_group = purple_group_new(BONJOUR_GROUP_NAME);
 	purple_blist_add_group(bonjour_group, NULL);
@@ -283,6 +285,14 @@
 	bb->conversation = NULL;
 }
 
+static
+void bonjour_set_buddy_icon(PurpleConnection *conn, PurpleStoredImage *img)
+{
+	BonjourData *bd = conn->proto_data;
+	bonjour_dns_sd_update_buddy_icon(bd->dns_sd_data);
+}
+
+
 static char *
 bonjour_status_text(PurpleBuddy *buddy)
 {
@@ -339,8 +349,7 @@
 	OPT_PROTO_NO_PASSWORD,
 	NULL,                                                    /* user_splits */
 	NULL,                                                    /* protocol_options */
-	/* {"png", 0, 0, 96, 96, 0, PURPLE_ICON_SCALE_DISPLAY}, */ /* icon_spec */
-	NO_BUDDY_ICONS, /* not yet */                            /* icon_spec */
+	{"png,gif,jpeg", 0, 0, 96, 96, 65535, PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */
 	bonjour_list_icon,                                       /* list_icon */
 	NULL,													 /* list_emblem */
 	bonjour_status_text,                                     /* status_text */
@@ -384,7 +393,7 @@
 	NULL,                                                    /* buddy_free */
 	bonjour_convo_closed,                                    /* convo_closed */
 	NULL,                                                    /* normalize */
-	NULL,                                                    /* set_buddy_icon */
+	bonjour_set_buddy_icon,                                  /* set_buddy_icon */
 	NULL,                                                    /* remove_group */
 	NULL,                                                    /* get_cb_real_name */
 	NULL,                                                    /* set_chat_topic */
@@ -533,7 +542,7 @@
 	{
 		default_firstname = g_strndup(fullname, splitpoint - fullname);
 		tmp = &splitpoint[1];
-		
+
 		/* The last name may be followed by a comma and additional data.
 		 * Only use the last name itself.
 		 */
--- a/libpurple/protocols/bonjour/buddy.c	Wed Aug 08 01:57:08 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.c	Wed Aug 08 01:57:39 2007 +0000
@@ -179,8 +179,6 @@
 {
 	g_free(buddy->name);
 	g_free(buddy->ip);
-	g_free(buddy->full_service_name);
-
 	g_free(buddy->first);
 	g_free(buddy->phsh);
 	g_free(buddy->status);
--- a/libpurple/protocols/bonjour/buddy.h	Wed Aug 08 01:57:08 2007 +0000
+++ b/libpurple/protocols/bonjour/buddy.h	Wed Aug 08 01:57:39 2007 +0000
@@ -29,7 +29,6 @@
 	gchar *name;
 	/* TODO: Remove and just use the hostname */
 	gchar *ip;
-	gchar *full_service_name;
 	gint port_p2pj;
 
 	gchar *first;
--- a/libpurple/protocols/bonjour/mdns_avahi.c	Wed Aug 08 01:57:08 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_avahi.c	Wed Aug 08 01:57:39 2007 +0000
@@ -341,11 +341,10 @@
 	return TRUE;
 }
 
-/* This is done differently than with Howl/Apple Bonjour */
-guint _mdns_register_to_mainloop(BonjourDnsSd *data) {
-	return 0;
+void _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len) {
 }
 
+
 void _mdns_stop(BonjourDnsSd *data) {
 	AvahiSessionImplData *idata = data->mdns_impl_data;
 
--- a/libpurple/protocols/bonjour/mdns_common.c	Wed Aug 08 01:57:08 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_common.c	Wed Aug 08 01:57:39 2007 +0000
@@ -17,11 +17,13 @@
 #include <string.h>
 
 #include "internal.h"
+#include "cipher.h"
+#include "debug.h"
+
 #include "mdns_common.h"
 #include "mdns_interface.h"
 #include "bonjour.h"
 #include "buddy.h"
-#include "debug.h"
 
 
 /**
@@ -54,8 +56,7 @@
  * Send a new dns-sd packet updating our status.
  */
 void
-bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message)
-{
+bonjour_dns_sd_send_status(BonjourDnsSd *data, const char *status, const char *status_message) {
 	g_free(data->status);
 	g_free(data->msg);
 
@@ -66,6 +67,70 @@
 	_mdns_publish(data, PUBLISH_UPDATE); /* <--We must control the errors */
 }
 
+void
+bonjour_dns_sd_buddy_icon_data_set(BonjourDnsSd *data) {
+	PurpleStoredImage *img = purple_buddy_icons_find_account_icon(data->account);
+	gconstpointer avatar_data;
+	gsize avatar_len;
+	gchar *enc;
+	int i;
+	unsigned char hashval[20];
+	char *p, hash[41];
+
+	g_return_if_fail(img != NULL);
+
+	avatar_data = purple_imgstore_get_data(img);
+	avatar_len = purple_imgstore_get_size(img);
+
+	enc = purple_base64_encode(avatar_data, avatar_len);
+
+	purple_cipher_digest_region("sha1", avatar_data,
+				    avatar_len, sizeof(hashval),
+				    hashval, NULL);
+
+	purple_imgstore_unref(img);
+
+	p = hash;
+	for(i=0; i<20; i++, p+=2)
+		snprintf(p, 3, "%02x", hashval[i]);
+
+	g_free(data->phsh);
+	data->phsh = g_strdup(hash);
+
+	g_free(enc);
+
+	/* Update our TXT record */
+	_mdns_publish(data, PUBLISH_UPDATE);
+}
+
+void
+bonjour_dns_sd_update_buddy_icon(BonjourDnsSd *data) {
+	PurpleStoredImage *img;
+
+	if ((img = purple_buddy_icons_find_account_icon(data->account))) {
+		gconstpointer avatar_data;
+		gsize avatar_len;
+
+		avatar_data = purple_imgstore_get_data(img);
+		avatar_len = purple_imgstore_get_size(img);
+
+		_mdns_set_buddy_icon_data(data, avatar_data, avatar_len);
+
+		purple_imgstore_unref(img);
+	} else {
+		/* We need to do this regardless of whether data->phsh is set so that we
+		 * cancel any icons that are currently in the process of being set */
+		_mdns_set_buddy_icon_data(data, NULL, 0);
+		if (data->phsh != NULL) {
+			/* Clear the buddy icon */
+			g_free(data->phsh);
+			data->phsh = NULL;
+			/* Update our TXT record */
+			_mdns_publish(data, PUBLISH_UPDATE);
+		}
+	}
+}
+
 /**
  * Advertise our presence within the dns-sd daemon and start browsing
  * for other bonjour peers.
@@ -91,11 +156,6 @@
 		return FALSE;
 	}
 
-
-	/* Get the socket that communicates with the mDNS daemon and bind it to a */
-	/* callback that will handle the dns_sd packets */
-	gc->inpa = _mdns_register_to_mainloop(data);
-
 	return TRUE;
 }
 
@@ -104,13 +164,6 @@
  */
 
 void
-bonjour_dns_sd_stop(BonjourDnsSd *data)
-{
-	PurpleConnection *gc;
-
+bonjour_dns_sd_stop(BonjourDnsSd *data) {
 	_mdns_stop(data);
-
-	gc = purple_account_get_connection(data->account);
-	if (gc->inpa > 0)
-		purple_input_remove(gc->inpa);
 }
--- a/libpurple/protocols/bonjour/mdns_common.h	Wed Aug 08 01:57:08 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_common.h	Wed Aug 08 01:57:39 2007 +0000
@@ -42,6 +42,16 @@
 void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy);
 
 /**
+ * Deal with a buddy icon update
+ */
+void bonjour_dns_sd_update_buddy_icon(BonjourDnsSd *data);
+
+/**
+ * The buddy icon blob has been set, notify everyone watching the TXT record
+ */
+void bonjour_dns_sd_buddy_icon_data_set(BonjourDnsSd *data);
+
+/**
  * Advertise our presence within the dns-sd daemon and start
  * browsing for other bonjour peers.
  */
--- a/libpurple/protocols/bonjour/mdns_howl.c	Wed Aug 08 01:57:08 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_howl.c	Wed Aug 08 01:57:39 2007 +0000
@@ -26,6 +26,7 @@
 typedef struct _howl_impl_data {
 	sw_discovery session;
 	sw_discovery_oid session_id;
+	guint session_handler;
 } HowlSessionImplData;
 
 static sw_result HOWL_API
@@ -276,18 +277,19 @@
 
 	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);
+	if (sw_discovery_browse(idata->session, 0, ICHAT_SERVICE, NULL, _browser_reply,
+				    data->account, &session_id) == SW_OKAY) {
+		idata->session_handler = purple_input_add(sw_discovery_socket(idata->session),
+				PURPLE_INPUT_READ, _mdns_handle_event, idata->session);
+		return TRUE;
+	}
+
+	return FALSE;
 }
 
-guint _mdns_register_to_mainloop(BonjourDnsSd *data) {
-	HowlSessionImplData *idata = data->mdns_impl_data;
+void _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len) {
+}
 
-	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;
@@ -297,6 +299,8 @@
 
 	sw_discovery_cancel(idata->session, idata->session_id);
 
+	purple_input_remove(idata->session_handler);
+
 	/* TODO: should this really be g_free()'d ??? */
 	g_free(idata->session);
 
@@ -313,3 +317,5 @@
 
 void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) {
 }
+
+
--- a/libpurple/protocols/bonjour/mdns_interface.h	Wed Aug 08 01:57:08 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_interface.h	Wed Aug 08 01:57:39 2007 +0000
@@ -26,9 +26,9 @@
 
 gboolean _mdns_browse(BonjourDnsSd *data);
 
-guint _mdns_register_to_mainloop(BonjourDnsSd *data);
+void _mdns_stop(BonjourDnsSd *data);
 
-void _mdns_stop(BonjourDnsSd *data);
+void _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len);
 
 void _mdns_init_buddy(BonjourBuddy *buddy);
 
--- a/libpurple/protocols/bonjour/mdns_win32.c	Wed Aug 08 01:57:08 2007 +0000
+++ b/libpurple/protocols/bonjour/mdns_win32.c	Wed Aug 08 01:57:39 2007 +0000
@@ -21,12 +21,14 @@
 #include "mdns_interface.h"
 #include "dns_sd_proxy.h"
 #include "dnsquery.h"
+#include "mdns_common.h"
 
 
 /* data structure for the resolve callback */
 typedef struct _ResolveCallbackArgs {
 	DNSServiceRef resolver;
 	guint resolver_handler;
+	gchar *full_service_name;
 
 	PurpleDnsQueryData *query;
 
@@ -35,10 +37,14 @@
 
 /* data used by win32 bonjour implementation */
 typedef struct _win32_session_impl_data {
-	DNSServiceRef advertisement;
-	DNSServiceRef browser;
+	DNSServiceRef advertisement_svc;
+	DNSServiceRef browser_svc;
+	DNSServiceRef buddy_icon_svc;
+	DNSRecordRef buddy_icon_rec;
 
 	guint advertisement_handler; /* hack... windows bonjour is broken, so we have to have this */
+	guint browser_handler;
+	guint buddy_icon_handler;
 } Win32SessionImplData;
 
 typedef struct _win32_buddy_impl_data {
@@ -120,7 +126,7 @@
 
 		/* finally, set up the continuous txt record watcher, and add the buddy to purple */
 
-		if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->txt_query, 0, 0, buddy->full_service_name,
+		if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->txt_query, 0, 0, args->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);
@@ -138,6 +144,7 @@
 
 	/* free the remaining args memory */
 	purple_dnsquery_destroy(args->query);
+	g_free(args->full_service_name);
 	g_free(args);
 }
 
@@ -165,13 +172,14 @@
 		_mdns_parse_text_record(args->buddy, txtRecord, txtLen);
 
 		/* set more arguments, and start the host resolver */
-		args->buddy->full_service_name = g_strdup(fullname);
+		args->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->full_service_name);
 			g_free(args);
 		}
 	}
@@ -180,16 +188,28 @@
 
 static void DNSSD_API
 _mdns_service_register_callback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode,
-    const char *name, const char *regtype, const char *domain, void *context)
-{
+				const char *name, const char *regtype, const char *domain, void *context) {
+
 	/* we don't actually care about anything said in this callback - this is only here because Bonjour for windows is broken */
+	/* TODO: deal with collision */
 	if (kDNSServiceErr_NoError != errorCode)
-		purple_debug_error("bonjour", "service advertisement - callback error.\n");
+		purple_debug_error("bonjour", "service advertisement - callback error (%d).\n", errorCode);
 	else
 		purple_debug_info("bonjour", "service advertisement - callback.\n");
 }
 
 static void DNSSD_API
+_mdns_set_buddy_icon_cb(DNSServiceRef sdRef, DNSRecordRef RecordRef, DNSServiceFlags flags, DNSServiceErrorType errorCode,
+			void *context) {
+	if (kDNSServiceErr_NoError != errorCode)
+		purple_debug_error("bonjour", "Error (%d) registering buddy icon data.\n", errorCode);
+	else {
+		purple_debug_info("bonjour", "Registered buddy icon data.\n");
+		bonjour_dns_sd_buddy_icon_data_set((BonjourDnsSd *) context);
+	}
+}
+
+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)
 {
@@ -302,13 +322,13 @@
 		switch (type) {
 			case PUBLISH_START:
 				purple_debug_info("bonjour", "Registering service on port %d\n", data->port_p2pj);
-				err = DNSServiceRegister(&idata->advertisement, 0, 0, purple_account_get_username(data->account), ICHAT_SERVICE,
+				err = DNSServiceRegister(&idata->advertisement_svc, 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(idata->advertisement, NULL, 0, TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data), 0);
+				err = DNSServiceUpdateRecord(idata->advertisement_svc, NULL, 0, TXTRecordGetLength(&dns_data), TXTRecordGetBytesPtr(&dns_data), 0);
 				break;
 		}
 
@@ -317,8 +337,8 @@
 			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 fd = DNSServiceRefSockFD(idata->advertisement);
-			idata->advertisement_handler = purple_input_add(fd, PURPLE_INPUT_READ, _mdns_handle_event, idata->advertisement);
+			idata->advertisement_handler = purple_input_add(DNSServiceRefSockFD(idata->advertisement_svc),
+				PURPLE_INPUT_READ, _mdns_handle_event, idata->advertisement_svc);
 		}
 	}
 
@@ -332,37 +352,96 @@
 
 	g_return_val_if_fail(idata != NULL, FALSE);
 
-	return (DNSServiceBrowse(&idata->browser, 0, 0, ICHAT_SERVICE, NULL,
+	if (DNSServiceBrowse(&idata->browser_svc, 0, 0, ICHAT_SERVICE, NULL,
 				 _mdns_service_browse_callback, data->account)
-			== kDNSServiceErr_NoError);
-}
+			== kDNSServiceErr_NoError) {
+		idata->browser_handler = purple_input_add(DNSServiceRefSockFD(idata->browser_svc),
+			PURPLE_INPUT_READ, _mdns_handle_event, idata->browser_svc);
+		return TRUE;
+	}
 
-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);
+	return FALSE;
 }
 
 void _mdns_stop(BonjourDnsSd *data) {
 	Win32SessionImplData *idata = data->mdns_impl_data;
 
-	if (idata == NULL || idata->advertisement == NULL || idata->browser == NULL)
+	if (idata == NULL)
 		return;
 
-	/* hack: for win32, we need to stop listening to the advertisement pipe too */
-	purple_input_remove(idata->advertisement_handler);
+	if (idata->advertisement_svc != NULL) {
+		/* hack: for win32, we need to stop listening to the advertisement pipe too */
+		purple_input_remove(idata->advertisement_handler);
+		DNSServiceRefDeallocate(idata->advertisement_svc);
+	}
 
-	DNSServiceRefDeallocate(idata->advertisement);
-	DNSServiceRefDeallocate(idata->browser);
+	if (idata->browser_svc != NULL) {
+		purple_input_remove(idata->browser_handler);
+		DNSServiceRefDeallocate(idata->browser_svc);
+	}
+
+	if (idata->buddy_icon_svc != NULL) {
+		purple_input_remove(idata->buddy_icon_handler);
+		DNSServiceRefDeallocate(idata->buddy_icon_svc);
+	}
+
 
 	g_free(idata);
 
 	data->mdns_impl_data = NULL;
 }
 
+void _mdns_set_buddy_icon_data(BonjourDnsSd *data, gconstpointer avatar_data, gsize avatar_len) {
+	Win32SessionImplData *idata = data->mdns_impl_data;
+	gboolean new_svc = FALSE;
+	DNSServiceErrorType err = kDNSServiceErr_NoError;
+
+	g_return_if_fail(idata != NULL);
+
+	if (avatar_data != NULL && idata->buddy_icon_svc == NULL) {
+		gchar *svc_name = g_strdup_printf("%s." ICHAT_SERVICE "local", purple_account_get_username(data->account));
+
+		purple_debug_info("bonjour", "Setting new buddy icon.\n");
+
+		err = DNSServiceCreateConnection(&idata->buddy_icon_svc);
+		if (err == kDNSServiceErr_NoError) {
+			err = DNSServiceRegisterRecord(idata->buddy_icon_svc,
+				&idata->buddy_icon_rec, kDNSServiceFlagsShared, kDNSServiceInterfaceIndexAny, svc_name,
+				kDNSServiceType_NULL, kDNSServiceClass_IN,
+				avatar_len, avatar_data, 10, _mdns_set_buddy_icon_cb, data);
+
+			if (err != kDNSServiceErr_NoError) {
+				DNSServiceRefDeallocate(idata->buddy_icon_svc);
+				idata->buddy_icon_svc = NULL;
+			}
+		}
+
+		g_free(svc_name);
+
+		if (err == kDNSServiceErr_NoError)
+			idata->buddy_icon_handler = purple_input_add(DNSServiceRefSockFD(idata->buddy_icon_svc),
+				PURPLE_INPUT_READ, _mdns_handle_event, idata->buddy_icon_svc);
+
+		new_svc = TRUE;
+	} else if (avatar_data != NULL) {
+		purple_debug_info("bonjour", "Updating existing buddy icon.\n");
+		err = DNSServiceUpdateRecord(idata->buddy_icon_svc, idata->buddy_icon_rec,
+			0, avatar_len, avatar_data, 10);
+	} else if (idata->buddy_icon_svc != NULL) {
+		purple_debug_info("bonjour", "Removing existing buddy icon.\n");
+		/* Must be removing the buddy icon */
+		purple_input_remove(idata->buddy_icon_handler);
+		idata->buddy_icon_handler = 0;
+		DNSServiceRefDeallocate(idata->buddy_icon_svc);
+		idata->buddy_icon_svc = NULL;
+		idata->buddy_icon_rec = NULL;
+	}
+
+	if (err != kDNSServiceErr_NoError)
+		purple_debug_error("bonjour", "Error (%d) setting buddy icon record.\n", err);
+}
+
+
 void _mdns_init_buddy(BonjourBuddy *buddy) {
 	buddy->mdns_impl_data = g_new0(Win32BuddyImplData, 1);
 }
@@ -389,6 +468,7 @@
 
 void bonjour_dns_sd_retrieve_buddy_icon(BonjourBuddy* buddy) {
 	Win32BuddyImplData *idata = buddy->mdns_impl_data;
+	gchar *svc_name;
 
 	g_return_if_fail(idata != NULL);
 
@@ -400,11 +480,13 @@
 		idata->null_query = NULL;
 	}
 
-	if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->null_query, 0, 0, buddy->full_service_name,
+	svc_name = g_strdup_printf("%s." ICHAT_SERVICE "local", buddy->name);
+	if (kDNSServiceErr_NoError == DNSServiceQueryRecord(&idata->null_query, 0, 0, svc_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);
+		idata->null_query_handler = purple_input_add(DNSServiceRefSockFD(idata->null_query),
+			PURPLE_INPUT_READ, _mdns_handle_event, idata->null_query);
 	}
+	g_free(svc_name);
 
 }
 
--- a/pidgin/gtkconv.c	Wed Aug 08 01:57:08 2007 +0000
+++ b/pidgin/gtkconv.c	Wed Aug 08 01:57:39 2007 +0000
@@ -6320,13 +6320,13 @@
 			style = "color=\"#c4a000\"";
 		} else if (gtkconv->unseen_state == PIDGIN_UNSEEN_NICK)	{
 			atk_object_set_description(accessibility_obj, _("Nick Said"));
-			style = "color=\"#204a87\" style=\"italic\" weight=\"bold\"";
+			style = "color=\"#204a87\" weight=\"bold\"";
 		} else if (gtkconv->unseen_state == PIDGIN_UNSEEN_TEXT)	{
 			atk_object_set_description(accessibility_obj, _("Unread Messages"));
 			style = "color=\"#cc0000\" weight=\"bold\"";
 		} else if (gtkconv->unseen_state == PIDGIN_UNSEEN_EVENT) {
 			atk_object_set_description(accessibility_obj, _("New Event"));
-			style = "color=\"#888a85\" style=\"italic\"";
+			style = "color=\"#888a85\" weight=\"bold\"";
 		} else {
 			style = "";
 		}
--- a/pidgin/gtkdocklet.c	Wed Aug 08 01:57:08 2007 +0000
+++ b/pidgin/gtkdocklet.c	Wed Aug 08 01:57:39 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/gtkimhtmltoolbar.c	Wed Aug 08 01:57:08 2007 +0000
+++ b/pidgin/gtkimhtmltoolbar.c	Wed Aug 08 01:57:39 2007 +0000
@@ -1096,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);
@@ -1114,14 +1121,17 @@
 		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},
 		{_("Bac_kground color"), &toolbar->bgcolor, TRUE},
@@ -1175,6 +1185,7 @@
 		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), "button-press-event", G_CALLBACK(pidgin_menu_clicked), font_menu);
--- a/pidgin/gtkmain.c	Wed Aug 08 01:57:08 2007 +0000
+++ b/pidgin/gtkmain.c	Wed Aug 08 01:57:39 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	Wed Aug 08 01:57:08 2007 +0000
+++ b/pidgin/gtkutils.c	Wed Aug 08 01:57:39 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 */
 			}
 		}
 	}