changeset 8113:d60272410bd5

[gaim-migrate @ 8817] Tim 'marv' Ringenbach wrote us a wonderful core/ui split chat room list thing-a-ma-jig. I've taken the liberty of adding jabber support to it as well. committer: Tailor Script <tailor@pidgin.im>
author Nathan Walp <nwalp@pidgin.im>
date Thu, 15 Jan 2004 22:20:29 +0000
parents 67387ec77301
children 7a6e30eb7aad
files ChangeLog src/Makefile.am src/gtkblist.c src/gtkroomlist.c src/gtkroomlist.h src/main.c src/protocols/jabber/chat.c src/protocols/jabber/chat.h src/protocols/jabber/jabber.c src/protocols/jabber/jabber.h src/protocols/yahoo/yahoo.c src/protocols/yahoo/yahoo.h src/protocols/yahoo/yahoochat.c src/protocols/yahoo/yahoochat.h src/prpl.h src/roomlist.c src/roomlist.h
diffstat 17 files changed, 1855 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Thu Jan 15 17:08:38 2004 +0000
+++ b/ChangeLog	Thu Jan 15 22:20:29 2004 +0000
@@ -3,6 +3,7 @@
 version 0.76cvs:
 	* WYSIWYG text input (with scrollbars, too!)
 	* Improved accessibility in conversation windows (Nathan Fredrickson)
+	* Chatroom List support (thanks, Tim Ringenbach)
 	* Catalan translation updated (Xan (DXpublica))
 	* English (British) translation updated (Luke Ross (lukeross))
 	* Hebrew translation had 1 character removed (Ambrose C. LI)
--- a/src/Makefile.am	Thu Jan 15 17:08:38 2004 +0000
+++ b/src/Makefile.am	Thu Jan 15 22:20:29 2004 +0000
@@ -92,6 +92,8 @@
 	prpl.h \
 	request.c \
 	request.h \
+	roomlist.c \
+	roomlist.h \
 	server.c \
 	server.h \
 	sha.c \
@@ -157,6 +159,8 @@
 	gtkpounce.h \
 	gtkrequest.c \
 	gtkrequest.h \
+	gtkroomlist.c \
+	gtkroomlist.h \
 	gtksound.c \
 	gtksound.h \
 	gtksourceiter.c \
--- a/src/gtkblist.c	Thu Jan 15 17:08:38 2004 +0000
+++ b/src/gtkblist.c	Thu Jan 15 22:20:29 2004 +0000
@@ -44,6 +44,7 @@
 #include "gtkpounce.h"
 #include "gtkprefs.h"
 #include "gtkprivacy.h"
+#include "gtkroomlist.h"
 #include "gtkutils.h"
 
 #include "ui.h"
@@ -1882,6 +1883,7 @@
 	{ "/Tools/sep1", NULL, NULL, 0, "<Separator>" },
 	{ N_("/Tools/A_ccounts"), "<CTL>A", gaim_gtk_accounts_window_show, 0, "<StockItem>", GAIM_STOCK_ACCOUNTS },
 	{ N_("/Tools/_File Transfers"), NULL, gaim_show_xfer_dialog, 0, "<StockItem>", GAIM_STOCK_FILE_TRANSFER },
+	{ N_("/Tools/R_oom List"), NULL, gaim_gtk_roomlist_dialog_show, 0, NULL },
 	{ N_("/Tools/Pr_eferences"), "<CTL>P", gaim_gtk_prefs_show, 0, "<StockItem>", GTK_STOCK_PREFERENCES },
 	{ N_("/Tools/Pr_ivacy"), NULL, gaim_gtk_privacy_dialog_show, 0, NULL },
 #if 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gtkroomlist.c	Thu Jan 15 22:20:29 2004 +0000
@@ -0,0 +1,627 @@
+/**
+ * @file gtkroomlist.c Gtk Room List UI
+ * @ingroup gtkui
+ *
+ * gaim
+ *
+ * Copyright (C) 2003, Timothy Ringenbach <omarvo@hotmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "gtkinternal.h"
+#include "gtkutils.h"
+#include "stock.h"
+#include "debug.h"
+#include "account.h"
+#include "connection.h"
+#include "notify.h"
+
+#include "gtkroomlist.h"
+
+typedef struct _GaimGtkRoomlist {
+	GaimGtkRoomlistDialog *dialog;
+	GtkTreeStore *model;
+	GtkWidget *tree;
+	GHashTable *cats; /**< Meow. */
+	gint num_rooms, total_rooms;
+} GaimGtkRoomlist;
+
+struct _GaimGtkRoomlistDialog {
+	GtkWidget *window;
+	GtkWidget *account_widget;
+	GtkWidget *progress;
+	GtkWidget *sw;
+
+	GtkWidget *list_button;
+	GtkWidget *stop_button;
+	GtkWidget *close_button;
+
+	GaimAccount *account;
+	GaimRoomlist *roomlist;
+
+};
+
+enum {
+	NAME_COLUMN = 0,
+	ROOM_COLUMN,
+	NUM_OF_COLUMNS,
+};
+
+static GList *roomlists = NULL;
+
+
+
+static gint delete_win_cb(GtkWidget *w, GdkEventAny *e, gpointer d)
+{
+	GaimGtkRoomlistDialog *dialog;
+
+	dialog = (GaimGtkRoomlistDialog *) d;
+
+	/* free stuff here */
+	if (dialog->roomlist)
+		gaim_roomlist_unref(dialog->roomlist);
+	g_free(dialog);
+
+	return FALSE;
+}
+
+static void dialog_select_account_cb(GObject *w, GaimAccount *account,
+                                     GaimGtkRoomlistDialog *dialog)
+{
+	dialog->account = account;
+}
+
+static void list_button_cb(GtkButton *button, GaimGtkRoomlistDialog *dialog)
+{
+	GaimConnection *gc;
+	GaimGtkRoomlist *rl;
+
+	gc = gaim_account_get_connection(dialog->account);
+	if (!gc)
+		return;
+
+	dialog->roomlist = gaim_roomlist_get_list(gc);
+	gaim_roomlist_ref(dialog->roomlist);
+	rl = dialog->roomlist->ui_data;
+	rl->dialog = dialog;
+	if (dialog->account_widget)
+		gtk_widget_set_sensitive(dialog->account_widget, FALSE);
+	gtk_widget_set_sensitive(dialog->list_button, FALSE);
+	gtk_container_add(GTK_CONTAINER(dialog->sw), rl->tree); /* XXX */
+}
+
+static void stop_button_cb(GtkButton *button, GaimGtkRoomlistDialog *dialog)
+{
+	gaim_roomlist_cancel_get_list(dialog->roomlist);
+	gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE);
+}
+
+static void close_button_cb(GtkButton *button, GaimGtkRoomlistDialog *dialog)
+{
+	GtkWidget *window = dialog->window;
+
+	delete_win_cb(NULL, NULL, dialog);
+	gtk_widget_destroy(window);
+}
+
+struct _menu_cb_info {
+	GaimRoomlist *list;
+	GaimRoomlistRoom *room;
+};
+
+static void do_join_cb(GtkWidget *w, struct _menu_cb_info *info)
+{
+	GHashTable *components;
+	GList *l, *j;
+	GaimConnection *gc;
+
+	gc = gaim_account_get_connection(info->list->account);
+	if (!gc)
+		return;
+
+	components = g_hash_table_new(g_str_hash, g_str_equal);
+
+	g_hash_table_replace(components, g_strdup("name"), g_strdup(info->room->name));
+	for (l = info->list->fields, j = info->room->fields; l && j; l = l->next, j = j->next) {
+		GaimRoomlistField *f = l->data;
+
+		g_hash_table_replace(components, f->name, j->data);
+	}
+
+	serv_join_chat(gc, components);
+
+	g_hash_table_destroy(components);
+}
+
+static void row_activated_cb(GtkTreeView *tv, GtkTreePath *path, GtkTreeViewColumn *arg2,
+                      GaimRoomlist *list)
+{
+	GaimGtkRoomlist *grl = list->ui_data;
+	GtkTreeIter iter;
+	GaimRoomlistRoom *room;
+	GValue val = { 0, };
+	struct _menu_cb_info info;
+
+	gtk_tree_model_get_iter(GTK_TREE_MODEL(grl->model), &iter, path);
+	gtk_tree_model_get_value(GTK_TREE_MODEL(grl->model), &iter, ROOM_COLUMN, &val);
+	room = g_value_get_pointer(&val);
+	if (!room || !(room->type & GAIM_ROOMLIST_ROOMTYPE_ROOM))
+		return;
+
+	info.list = list;
+	info.room = room;
+
+	do_join_cb(GTK_WIDGET(tv), &info);
+}
+
+static gboolean room_click_cb(GtkWidget *tv, GdkEventButton *event, GaimRoomlist *list)
+{
+	GtkTreePath *path;
+	GaimGtkRoomlist *grl = list->ui_data;
+	GValue val = { 0, };
+	GaimRoomlistRoom *room;
+	GtkTreeIter iter;
+	GtkWidget *menu;
+	static struct _menu_cb_info info; /* XXX? */
+
+	if (event->button != 3 || event->type != GDK_BUTTON_PRESS)
+		return FALSE;
+
+	/* Here we figure out which room was clicked */
+	if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tv), event->x, event->y, &path, NULL, NULL, NULL))
+		return FALSE;
+	gtk_tree_model_get_iter(GTK_TREE_MODEL(grl->model), &iter, path);
+	gtk_tree_path_free(path);
+	gtk_tree_model_get_value (GTK_TREE_MODEL(grl->model), &iter, ROOM_COLUMN, &val);
+	room = g_value_get_pointer(&val);
+
+	if (!room || !(room->type & GAIM_ROOMLIST_ROOMTYPE_ROOM))
+		return FALSE;
+
+	info.list = list;
+	info.room = room;
+
+
+	menu = gtk_menu_new();
+	gaim_new_item_from_stock(menu, _("_Join"), GAIM_STOCK_CHAT,
+		                         G_CALLBACK(do_join_cb), &info, 0, 0, NULL);
+
+
+	gtk_widget_show_all(menu);
+	gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, event->time);
+
+	return FALSE;
+}
+
+static void row_expanded_cb(GtkTreeView *treeview, GtkTreeIter *arg1, GtkTreePath *arg2, gpointer user_data)
+{
+	GaimRoomlist *list = user_data;
+	GaimRoomlistRoom *catagory;
+	GValue val = { 0, };
+
+	gtk_tree_model_get_value(gtk_tree_view_get_model(treeview), arg1, ROOM_COLUMN, &val);
+	catagory = g_value_get_pointer(&val);
+
+	if (!catagory->expanded_once) {
+		gaim_roomlist_expand_catagory(list, catagory);
+		catagory->expanded_once = TRUE;
+	}
+}
+
+static gboolean accounts_filter_func(GaimAccount *account)
+{
+	GaimConnection *gc;
+
+	gc = gaim_account_get_connection(account);
+	if (!gc)
+		return FALSE;
+	return gaim_roomlist_is_possible(gc);
+}
+
+GaimGtkRoomlistDialog *gaim_gtk_roomlist_dialog_new_with_account(GaimAccount *account)
+{
+	GaimGtkRoomlistDialog *dialog;
+	GtkWidget *window;
+	GtkWidget *vbox1, *vbox2;
+	GtkWidget *account_hbox;
+	GtkWidget *bbox;
+	GtkWidget *label;
+	GtkWidget *button;
+	GaimAccount *first_account = NULL;
+
+	if (!account) {
+		GList *c;
+		GaimConnection *gc;
+
+		for (c = gaim_connections_get_all(); c != NULL; c = c->next) {
+			gc = c->data;
+
+			if (gaim_roomlist_is_possible(gc)) {
+				first_account = gaim_connection_get_account(gc);
+				break;
+			}
+		}
+
+		if (first_account == NULL) {
+			gaim_notify_error(NULL, NULL,
+			                  _("You are not currently signed on with any "
+		                            "protocols that have the ability to list rooms."),
+			                     NULL);
+
+			return NULL;
+		}
+	}
+
+	dialog = g_new0(GaimGtkRoomlistDialog, 1);
+
+	/* Create the window. */
+	dialog->window = window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+	gtk_window_set_role(GTK_WINDOW(window), "room list");
+	gtk_window_set_title(GTK_WINDOW(window), _("Room List"));
+
+	gtk_container_set_border_width(GTK_CONTAINER(window), 12);
+	gtk_widget_realize(window);
+
+	g_signal_connect(G_OBJECT(window), "delete_event",
+					 G_CALLBACK(delete_win_cb), dialog);
+
+	/* Create the parent vbox for everything. */
+	vbox1 = gtk_vbox_new(FALSE, 12);
+	gtk_container_add(GTK_CONTAINER(window), vbox1);
+	gtk_widget_show(vbox1);
+
+	/* Create the main vbox for top half of the window. */
+	vbox2 = gtk_vbox_new(FALSE, 6);
+	gtk_box_pack_start(GTK_BOX(vbox1), vbox2, FALSE, FALSE, 0);
+	gtk_widget_show(vbox2);
+
+	account_hbox = gtk_hbox_new(FALSE, 0);
+	gtk_box_pack_start(GTK_BOX(vbox2), account_hbox, TRUE, TRUE, 0);
+	gtk_widget_show(account_hbox);
+
+	/* accounts dropdown list */
+	if (!account) {
+		dialog->account = first_account;
+		label = gtk_label_new(NULL);
+		gtk_box_pack_start(GTK_BOX(account_hbox), label, TRUE, TRUE, 0);
+		gtk_label_set_markup_with_mnemonic(GTK_LABEL(label), _("_Account:"));
+		gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+
+
+		dialog->account_widget = gaim_gtk_account_option_menu_new(first_account, FALSE,
+				G_CALLBACK(dialog_select_account_cb), accounts_filter_func, dialog);
+
+		gtk_box_pack_start(GTK_BOX(account_hbox), dialog->account_widget, TRUE, TRUE, 0);
+		gtk_label_set_mnemonic_widget(GTK_LABEL(label), GTK_WIDGET(dialog->account_widget));
+		gtk_widget_show(label);
+		gtk_widget_show(dialog->account_widget);
+	} else {
+		dialog->account = account;
+	}
+
+
+	/* Now the button box for the buttons */
+	bbox = gtk_hbutton_box_new();
+	gtk_box_set_spacing(GTK_BOX(bbox), 6);
+	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_EDGE);
+	gtk_box_pack_start(GTK_BOX(vbox2), bbox, FALSE, TRUE, 0);
+	gtk_widget_show(bbox);
+
+	/* Get list button */
+	button = gtk_button_new_with_mnemonic(_("Get _list"));
+	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	gtk_widget_show(button);
+	dialog->list_button = button;
+
+	g_signal_connect(G_OBJECT(button), "clicked",
+					 G_CALLBACK(list_button_cb), dialog);
+	/* Stop button */
+	button = gtk_button_new_from_stock(GTK_STOCK_STOP);
+	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	gtk_widget_show(button);
+	gtk_widget_set_sensitive(button, FALSE);
+	dialog->stop_button = button;
+
+	g_signal_connect(G_OBJECT(button), "clicked",
+					 G_CALLBACK(stop_button_cb), dialog);
+	/* Close button */
+	button = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
+	gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
+	gtk_widget_show(button);
+	dialog->close_button = button;
+
+	g_signal_connect(G_OBJECT(button), "clicked",
+					 G_CALLBACK(close_button_cb), dialog);
+
+	/* The pusling dilly */
+	dialog->progress = gtk_progress_bar_new();
+	gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(dialog->progress), 0.1);
+		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(dialog->progress),
+		                          " ");
+	gtk_box_pack_start(GTK_BOX(vbox2), dialog->progress, TRUE, TRUE, 0);
+	gtk_widget_show(dialog->progress);
+
+
+	gtk_widget_show(dialog->window);
+
+	dialog->sw = gtk_scrolled_window_new(NULL, NULL);
+	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(dialog->sw),
+	                                    GTK_SHADOW_IN);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(dialog->sw),
+	                               GTK_POLICY_AUTOMATIC,
+	                               GTK_POLICY_AUTOMATIC);
+	gtk_box_pack_start(GTK_BOX(vbox1), dialog->sw, TRUE, TRUE, 0);
+	gtk_widget_show(dialog->sw);
+
+	return dialog;
+}
+
+GaimGtkRoomlistDialog *gaim_gtk_roomlist_dialog_new(void)
+{
+	return gaim_gtk_roomlist_dialog_new_with_account(NULL);
+}
+
+void gaim_gtk_roomlist_dialog_show(void)
+{
+	gaim_gtk_roomlist_dialog_new();
+}
+
+static void gaim_gtk_roomlist_new(GaimRoomlist *list)
+{
+	GaimGtkRoomlist *rl;
+
+	rl = g_new0(GaimGtkRoomlist, 1);
+
+	list->ui_data = rl;
+
+	rl->cats = g_hash_table_new_full(NULL, NULL, NULL, (GDestroyNotify)gtk_tree_row_reference_free);
+
+	roomlists = g_list_append(roomlists, list);
+}
+
+static void int_cell_data_func(GtkTreeViewColumn *col, GtkCellRenderer *renderer,
+                                   GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
+{
+	gchar buf[16];
+	int myint;
+
+	gtk_tree_model_get(model, iter, GPOINTER_TO_INT(user_data), &myint, -1);
+
+	if (myint)
+		g_snprintf(buf, sizeof(buf), "%d", myint);
+	else
+		buf[0] = '\0';
+
+	g_object_set(renderer, "text", buf, NULL);
+}
+
+/* this sorts backwards on purpose, so that clicking name sorts a-z, while clicking users sorts
+   infinity-0. you can still click again to reverse it on any of them. */
+static gint int_sort_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data)
+{
+	int c, d;
+
+	c = d = 0;
+
+	gtk_tree_model_get(model, a, GPOINTER_TO_INT(user_data), &c, -1);
+	gtk_tree_model_get(model, b, GPOINTER_TO_INT(user_data), &d, -1);
+
+	if (c == d)
+		return 0;
+	else if (c > d)
+		return -1;
+	else
+		return 1;
+}
+
+static void gaim_gtk_roomlist_set_fields(GaimRoomlist *list, GList *fields)
+{
+	GaimGtkRoomlist *grl = list->ui_data;
+	gint columns = NUM_OF_COLUMNS;
+	int j;
+	GtkTreeStore *model;
+	GtkWidget *tree;
+	GtkCellRenderer *renderer;
+	GtkTreeViewColumn *column;
+	GList *l;
+	GType *types;
+
+	g_return_if_fail(grl != NULL);
+
+	columns += g_list_length(fields);
+	types = g_new(GType, columns);
+
+	types[NAME_COLUMN] = G_TYPE_STRING;
+	types[ROOM_COLUMN] = G_TYPE_POINTER;
+
+	for (j = NUM_OF_COLUMNS, l = fields; l; l = l->next, j++) {
+		GaimRoomlistField *f = l->data;
+
+		switch (f->type) {
+		case GAIM_ROOMLIST_FIELD_BOOL:
+			types[j] = G_TYPE_BOOLEAN;
+			break;
+		case GAIM_ROOMLIST_FIELD_INT:
+			types[j] = G_TYPE_INT;
+			break;
+		case GAIM_ROOMLIST_FIELD_STRING:
+			types[j] = G_TYPE_STRING;
+			break;
+		}
+	}
+
+	model = gtk_tree_store_newv(columns, types);
+	g_free(types);
+
+	tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
+	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree), TRUE);
+
+	g_object_unref(model);
+
+	grl->model = model;
+	grl->tree = tree;
+	gtk_widget_show(grl->tree);
+
+	renderer = gtk_cell_renderer_text_new();
+	column = gtk_tree_view_column_new_with_attributes(_("Name"), renderer,
+				"text", NAME_COLUMN, NULL);
+	gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column),
+	                                GTK_TREE_VIEW_COLUMN_GROW_ONLY);
+	gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
+	gtk_tree_view_column_set_sort_column_id(GTK_TREE_VIEW_COLUMN(column), NAME_COLUMN);
+	gtk_tree_view_column_set_reorderable(GTK_TREE_VIEW_COLUMN(column), TRUE);
+	gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+
+	for (j = NUM_OF_COLUMNS, l = fields; l; l = l->next, j++) {
+		GaimRoomlistField *f = l->data;
+
+		if (f->hidden)
+			continue;
+
+		renderer = gtk_cell_renderer_text_new();
+		column = gtk_tree_view_column_new_with_attributes(f->label, renderer,
+		                                                  "text", j, NULL);
+		gtk_tree_view_column_set_sizing(GTK_TREE_VIEW_COLUMN(column),
+		                                GTK_TREE_VIEW_COLUMN_GROW_ONLY);
+		gtk_tree_view_column_set_resizable(GTK_TREE_VIEW_COLUMN(column), TRUE);
+		gtk_tree_view_column_set_sort_column_id(GTK_TREE_VIEW_COLUMN(column), j);
+		gtk_tree_view_column_set_reorderable(GTK_TREE_VIEW_COLUMN(column), TRUE);
+		if (f->type == GAIM_ROOMLIST_FIELD_INT) {
+			gtk_tree_view_column_set_cell_data_func(column, renderer, int_cell_data_func,
+			                                        GINT_TO_POINTER(j), NULL);
+			gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(model), j, int_sort_func,
+			                                GINT_TO_POINTER(j), NULL);
+		}
+		gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);
+	}
+
+	g_signal_connect(G_OBJECT(tree), "button-press-event", G_CALLBACK(room_click_cb), list);
+	g_signal_connect(G_OBJECT(tree), "row-expanded", G_CALLBACK(row_expanded_cb), list);
+	g_signal_connect(G_OBJECT(tree), "row-activated", G_CALLBACK(row_activated_cb), list);
+	/* gtk_container_add(GTK_CONTAINER(grl->sw), tree); */
+}
+
+static void gaim_gtk_roomlist_add_room(GaimRoomlist *list, GaimRoomlistRoom *room)
+{
+	GaimGtkRoomlist *rl= list->ui_data;
+	GtkTreeRowReference *rr, *parentrr = NULL;
+	GtkTreePath *path;
+	GtkTreeIter iter, parent, child;
+	GList *l, *k;
+	int j;
+	gboolean append = TRUE;
+
+	rl->total_rooms++;
+	if (room->type == GAIM_ROOMLIST_ROOMTYPE_ROOM)
+		rl->num_rooms++;
+
+	if (rl->dialog) {
+		if (rl->total_rooms > 100)
+			gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(rl->dialog->progress),
+			                                0.01);
+		gtk_progress_bar_pulse(GTK_PROGRESS_BAR(rl->dialog->progress));
+	}
+	if (room->parent) {
+		parentrr = g_hash_table_lookup(rl->cats, room->parent);
+		path = gtk_tree_row_reference_get_path(parentrr);
+		if (path) {
+			GaimRoomlistRoom *tmproom = NULL;
+
+			gtk_tree_model_get_iter(GTK_TREE_MODEL(rl->model), &parent, path);
+			gtk_tree_path_free(path);
+
+			if (gtk_tree_model_iter_children(GTK_TREE_MODEL(rl->model), &child, &parent)) {
+				gtk_tree_model_get(GTK_TREE_MODEL(rl->model), &child, ROOM_COLUMN, &tmproom, -1);
+				if (!tmproom)
+					append = FALSE;
+			}
+		}
+	}
+
+	if (append)
+		gtk_tree_store_append(rl->model, &iter, (parentrr ? &parent : NULL));
+	else
+		iter = child;
+
+	if (room->type & GAIM_ROOMLIST_ROOMTYPE_CATAGORY)
+		gtk_tree_store_append(rl->model, &child, &iter);
+
+	path = gtk_tree_model_get_path(GTK_TREE_MODEL(rl->model), &iter);
+
+	if (room->type & GAIM_ROOMLIST_ROOMTYPE_CATAGORY) {
+		rr = gtk_tree_row_reference_new(GTK_TREE_MODEL(rl->model), path);
+		g_hash_table_insert(rl->cats, room, rr);
+	}
+
+	gtk_tree_path_free(path);
+
+	gtk_tree_store_set(rl->model, &iter, NAME_COLUMN, room->name, -1);
+	gtk_tree_store_set(rl->model, &iter, ROOM_COLUMN, room, -1);
+
+	for (j = NUM_OF_COLUMNS, l = room->fields, k = list->fields; l && k; j++, l = l->next, k = k->next) {
+		GaimRoomlistField *f = k->data;
+		if (f->hidden)
+			continue;
+		gtk_tree_store_set(rl->model, &iter, j, l->data, -1);
+	}
+}
+
+static void gaim_gtk_roomlist_in_progress(GaimRoomlist *list, gboolean flag)
+{
+	GaimGtkRoomlist *rl = list->ui_data;
+
+	if (!rl || !rl->dialog)
+		return;
+
+	gtk_widget_set_sensitive(rl->dialog->stop_button, flag);
+	if (flag) {
+		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(rl->dialog->progress),
+		                          _("Downloading List..."));
+	} else {
+		gtk_progress_bar_set_text(GTK_PROGRESS_BAR(rl->dialog->progress),
+		                          " ");
+		gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(rl->dialog->progress), 0.0);
+	}
+}
+
+static void gaim_gtk_roomlist_destroy(GaimRoomlist *list)
+{
+	GaimGtkRoomlist *rl;
+
+	roomlists = g_list_remove(roomlists, list);
+
+	rl = list->ui_data;
+
+	g_return_if_fail(rl != NULL);
+
+	g_hash_table_destroy(rl->cats);
+	g_free(rl);
+	list->ui_data = NULL;
+}
+
+static GaimRoomlistUiOps ops = {
+	gaim_gtk_roomlist_new,
+	gaim_gtk_roomlist_set_fields,
+	gaim_gtk_roomlist_add_room,
+	gaim_gtk_roomlist_in_progress,
+	gaim_gtk_roomlist_destroy
+};
+
+
+void gaim_gtk_roomlist_init(void)
+{
+	gaim_roomlist_set_ui_ops(&ops);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gtkroomlist.h	Thu Jan 15 22:20:29 2004 +0000
@@ -0,0 +1,53 @@
+/**
+ * @file gtkroomlist.h Gtk Room List UI
+ * @ingroup gtkui
+ *
+ * gaim
+ *
+ * Copyright (C) 2003, Timothy Ringenbach <omarvo@hotmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _GAIM_GTK_ROOMLIST_H_
+#define _GAIM_GTK_ROOMLIST_H_
+
+#include "roomlist.h"
+
+
+typedef struct _GaimGtkRoomlistDialog GaimGtkRoomlistDialog;
+
+/**
+ * Initializes the room list subsystem.
+ */
+void gaim_gtk_roomlist_init(void);
+
+void gaim_gtk_roomlist_dialog_show(void);
+/**
+ * Create a new room list dialog.
+ *
+ * @return The new dialog.
+ */
+GaimGtkRoomlistDialog *gaim_gtk_roomlist_dialog_new(void);
+
+/**
+ * Create a new room list dialog with no account selector.
+ *
+ * @param account The account to force.
+ * @return The new dialog.
+ */
+GaimGtkRoomlistDialog *gaim_gtk_roomlist_dialog_new_with_account(GaimAccount *account);
+
+#endif /* _GAIM_GTK_ROOMLIST_H_ */
--- a/src/main.c	Thu Jan 15 17:08:38 2004 +0000
+++ b/src/main.c	Thu Jan 15 22:20:29 2004 +0000
@@ -49,6 +49,7 @@
 #include "gtkprefs.h"
 #include "gtkprivacy.h"
 #include "gtkrequest.h"
+#include "gtkroomlist.h"
 #include "gtksound.h"
 #include "gtkutils.h"
 #include "stock.h"
@@ -509,6 +510,7 @@
 	gaim_gtk_pounces_init();
 	gaim_gtk_privacy_init();
 	gaim_gtk_xfers_init();
+	gaim_gtk_roomlist_init();
 }
 
 static void
--- a/src/protocols/jabber/chat.c	Thu Jan 15 17:08:38 2004 +0000
+++ b/src/protocols/jabber/chat.c	Thu Jan 15 22:20:29 2004 +0000
@@ -22,6 +22,8 @@
 #include "debug.h"
 #include "multi.h" /* for proto_chat_entry */
 #include "notify.h"
+#include "request.h"
+#include "roomlist.h"
 #include "util.h"
 
 #include "chat.h"
@@ -152,9 +154,12 @@
 	handle = g_hash_table_lookup(data, "handle");
 	passwd = g_hash_table_lookup(data, "password");
 
-	if(!room || !server || !handle)
+	if(!room || !server)
 		return;
 
+	if(!handle)
+		handle = js->user->node;
+
 	if(!jabber_nodeprep_validate(room)) {
 		char *buf = g_strdup_printf(_("%s is not a valid room name"), room);
 		gaim_notify_error(gc, _("Invalid Room Name"), _("Invalid Room Name"),
@@ -602,4 +607,123 @@
 	g_free(room_jid);
 }
 
+static void roomlist_disco_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+	xmlnode *query;
+	xmlnode *item;
+	const char *type;
 
+	if(!js->roomlist)
+		return;
+
+	if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "result")) {
+		/* XXX: error msg */
+		gaim_roomlist_set_in_progress(js->roomlist, FALSE);
+		return;
+	}
+
+	if(!(query = xmlnode_get_child(packet, "query"))) {
+		/* XXX: error msg */
+		gaim_roomlist_set_in_progress(js->roomlist, FALSE);
+		return;
+	}
+
+	for(item = query->child; item; item = item->next) {
+		const char *name;
+		GaimRoomlistRoom *room;
+		JabberID *jid;
+
+		if(item->type != NODE_TYPE_TAG)
+			continue;
+		if(strcmp(item->name, "item"))
+			continue;
+
+		if(!(jid = jabber_id_new(xmlnode_get_attrib(item, "jid"))))
+			continue;
+		name = xmlnode_get_attrib(item, "name");
+
+
+		room = gaim_roomlist_room_new(GAIM_ROOMLIST_ROOMTYPE_ROOM, jid->node, NULL);
+		gaim_roomlist_room_add_field(js->roomlist, room, jid->node);
+		gaim_roomlist_room_add_field(js->roomlist, room, jid->domain);
+		gaim_roomlist_room_add_field(js->roomlist, room, name ? name : "");
+		gaim_roomlist_room_add(js->roomlist, room);
+
+		jabber_id_free(jid);
+	}
+	gaim_roomlist_set_in_progress(js->roomlist, FALSE);
+	gaim_roomlist_unref(js->roomlist);
+	js->roomlist = NULL;
+}
+
+static void roomlist_ok_cb(JabberStream *js, const char *server)
+{
+	JabberIq *iq;
+
+	if(!js->roomlist)
+		return;
+
+	if(!server || !*server) {
+		gaim_notify_error(js->gc, _("Invalid Server"), _("Invalid Server"), NULL);
+		return;
+	}
+
+	gaim_roomlist_set_in_progress(js->roomlist, TRUE);
+
+	iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#items");
+
+	xmlnode_set_attrib(iq->node, "to", server);
+
+	jabber_iq_set_callback(iq, roomlist_disco_result_cb, NULL);
+
+	jabber_iq_send(iq);
+}
+
+GaimRoomlist *jabber_roomlist_get_list(GaimConnection *gc)
+{
+	JabberStream *js = gc->proto_data;
+	GList *fields = NULL;
+	GaimRoomlistField *f;
+
+	if(js->roomlist)
+		gaim_roomlist_unref(js->roomlist);
+
+	js->roomlist = gaim_roomlist_new(gaim_connection_get_account(gc));
+
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, "", "room", TRUE);
+	fields = g_list_append(fields, f);
+
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, "", "server", TRUE);
+	fields = g_list_append(fields, f);
+
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, _("Description"), "description", FALSE);
+	fields = g_list_append(fields, f);
+
+	gaim_roomlist_set_fields(js->roomlist, fields);
+
+	gaim_request_input(gc, _("Enter a Conference Server"), _("Enter a Conference Server"),
+			_("Select a conference server to query"),
+			js->chat_servers ? js->chat_servers->data : "conference.jabber.org",
+			FALSE, FALSE, _("Find Rooms"), G_CALLBACK(roomlist_ok_cb), _("Cancel"), NULL, js);
+
+	return js->roomlist;
+}
+
+void jabber_roomlist_cancel(GaimRoomlist *list)
+{
+	GaimConnection *gc;
+	JabberStream *js;
+
+	gc = gaim_account_get_connection(list->account);
+	js = gc->proto_data;
+
+	gaim_roomlist_set_in_progress(list, FALSE);
+
+	if (js->roomlist == list) {
+		js->roomlist = NULL;
+		gaim_roomlist_unref(list);
+	}
+}
+
+
+
--- a/src/protocols/jabber/chat.h	Thu Jan 15 17:08:38 2004 +0000
+++ b/src/protocols/jabber/chat.h	Thu Jan 15 22:20:29 2004 +0000
@@ -25,6 +25,7 @@
 #include "internal.h"
 #include "connection.h"
 #include "conversation.h"
+#include "roomlist.h"
 
 #include "jabber.h"
 
@@ -59,5 +60,8 @@
 void jabber_chat_change_nick(JabberChat *chat, const char *nick);
 void jabber_chat_part(JabberChat *chat, const char *msg);
 
+GaimRoomlist *jabber_roomlist_get_list(GaimConnection *gc);
+void jabber_roomlist_cancel(GaimRoomlist *list);
+
 
 #endif /* _GAIM_JABBER_CHAT_H_ */
--- a/src/protocols/jabber/jabber.c	Thu Jan 15 17:08:38 2004 +0000
+++ b/src/protocols/jabber/jabber.c	Thu Jan 15 22:20:29 2004 +0000
@@ -1170,7 +1170,10 @@
 	NULL, /* remove_group */
 	jabber_chat_buddy_real_name,
 	jabber_chat_set_topic,
-	jabber_find_blist_chat
+	jabber_find_blist_chat,
+	jabber_roomlist_get_list,
+	jabber_roomlist_cancel,
+	NULL
 };
 
 static GaimPluginInfo info =
--- a/src/protocols/jabber/jabber.h	Thu Jan 15 17:08:38 2004 +0000
+++ b/src/protocols/jabber/jabber.h	Thu Jan 15 22:20:29 2004 +0000
@@ -24,6 +24,7 @@
 
 #include <glib.h>
 #include "connection.h"
+#include "roomlist.h"
 #include "sslconn.h"
 
 #include "jutil.h"
@@ -65,6 +66,7 @@
 
 	GHashTable *chats;
 	GList *chat_servers;
+	GaimRoomlist *roomlist;
 
 	GHashTable *callbacks;
 	int next_id;
--- a/src/protocols/yahoo/yahoo.c	Thu Jan 15 17:08:38 2004 +0000
+++ b/src/protocols/yahoo/yahoo.c	Thu Jan 15 22:20:29 2004 +0000
@@ -2707,15 +2707,12 @@
 		return;
 	/* It seems to work better without this */
 
-
-	/* 
-	 * if (gc->account->perm_deny != 4)
-	 *	return;
- 	 *
-	 * if (!who || who[0] == '\0')
-	 *		return;
-	 */
-    
+	/* if (gc->account->perm_deny != 4)
+		return; */
+
+	if (!who || who[0] == '\0')
+		return;
+
 	pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT, YAHOO_STATUS_AVAILABLE, 0);
 	yahoo_packet_hash(pkt, 1, gaim_connection_get_display_name(gc));
 	yahoo_packet_hash(pkt, 7, who);
@@ -3158,8 +3155,13 @@
 #if 0
 	yahoo_ask_send_file,
 	yahoo_send_file,
-	yahoo_has_send_file
+	yahoo_has_send_file,
 #endif
+	NULL,
+	NULL,
+	yahoo_roomlist_get_list,
+	yahoo_roomlist_cancel,
+	yahoo_roomlist_expand_catagory,
 };
 
 static GaimPluginInfo info =
@@ -3206,6 +3208,9 @@
 	option = gaim_account_option_int_new(_("File transfer port"), "xfer_port", YAHOO_XFER_PORT);
 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
 
+	option = gaim_account_option_string_new(_("Chat Room List Url"), "room_list", YAHOO_ROOMLIST_URL);
+	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
+	
 	my_protocol = plugin;
 
 	yahoo_init_colorht();
--- a/src/protocols/yahoo/yahoo.h	Thu Jan 15 17:08:38 2004 +0000
+++ b/src/protocols/yahoo/yahoo.h	Thu Jan 15 22:20:29 2004 +0000
@@ -34,6 +34,7 @@
 #define YAHOO_PROFILE_URL "http://profiles.yahoo.com/"
 #define YAHOO_XFER_HOST "filetransfer.msg.yahoo.com"
 #define YAHOO_XFER_PORT 80
+#define YAHOO_ROOMLIST_URL "http://insider.msg.yahoo.com/ycontent/"
 
 #define WEBMESSENGER_URL "http://login.yahoo.com/config/login?.src=pg"
 
--- a/src/protocols/yahoo/yahoochat.c	Thu Jan 15 17:08:38 2004 +0000
+++ b/src/protocols/yahoo/yahoochat.c	Thu Jan 15 22:20:29 2004 +0000
@@ -1004,3 +1004,403 @@
 	}
 }
 
+
+struct yahoo_roomlist {
+	int fd;
+	int inpa;
+	guchar *rxqueue;
+	int rxlen;
+	gboolean started;
+	char *path;
+	char *host;
+	GaimRoomlist *list;
+	GaimRoomlistRoom *cat;
+	GaimRoomlistRoom *ucat;
+	GMarkupParseContext *parse;
+
+};
+
+static void yahoo_roomlist_destroy(struct yahoo_roomlist *yrl)
+{
+	if (yrl->inpa)
+		gaim_input_remove(yrl->inpa);
+	if (yrl->rxqueue)
+		g_free(yrl->rxqueue);
+	if (yrl->path)
+		g_free(yrl->path);
+	if (yrl->host)
+		g_free(yrl->host);
+	if (yrl->parse)
+		g_markup_parse_context_free(yrl->parse);
+}
+
+enum yahoo_room_type {
+	yrt_yahoo,
+	yrt_user,
+};
+
+struct yahoo_chatxml_state {
+	GaimRoomlist *list;
+	struct yahoo_roomlist *yrl;
+	GQueue *q;
+	struct {
+		enum yahoo_room_type type;
+		char *name;
+		char *topic;
+		char *id;
+		int users, voices, webcams;
+	} room;
+};
+
+struct yahoo_lobby {
+	int count, users, voices, webcams;
+};
+
+static struct yahoo_chatxml_state *yahoo_chatxml_state_new(GaimRoomlist *list, struct yahoo_roomlist *yrl)
+{
+	struct yahoo_chatxml_state *s;
+
+	s = g_new0(struct yahoo_chatxml_state, 1);
+
+	s->list = list;
+	s->yrl = yrl;
+	s->q = g_queue_new();
+
+	return s;
+}
+
+static void yahoo_chatxml_state_destroy(struct yahoo_chatxml_state *s)
+{
+	g_queue_free(s->q);
+	if (s->room.name)
+		g_free(s->room.name);
+	if (s->room.topic)
+		g_free(s->room.topic);
+	if (s->room.id)
+		g_free(s->room.id);
+	g_free(s);
+}
+
+static void yahoo_chatlist_start_element(GMarkupParseContext *context, const gchar *ename,
+                                  const gchar **anames, const gchar **avalues,
+                                  gpointer user_data, GError **error)
+{
+	struct yahoo_chatxml_state *s = user_data;
+	GaimRoomlist *list = s->list;
+	GaimRoomlistRoom *r;
+	GaimRoomlistRoom *parent;
+	int i;
+
+	if (!strcmp(ename, "category")) {
+		const gchar *name = NULL, *id = NULL;
+
+		for (i = 0; anames[i]; i++) {
+			if (!strcmp(anames[i], "id"))
+				id = avalues[i];
+			if (!strcmp(anames[i], "name"))
+				name = avalues[i];
+		}
+		if (!name || !id)
+			return;
+
+		parent = g_queue_peek_head(s->q);
+		r = gaim_roomlist_room_new(GAIM_ROOMLIST_ROOMTYPE_CATAGORY, name, parent);
+		gaim_roomlist_room_add_field(list, r, (gpointer)name);
+		gaim_roomlist_room_add_field(list, r, (gpointer)id);
+		gaim_roomlist_room_add(list, r);
+		g_queue_push_head(s->q, r);
+	} else if (!strcmp(ename, "room")) {
+		s->room.users = s->room.voices = s->room.webcams = 0;
+
+		for (i = 0; anames[i]; i++) {
+			if (!strcmp(anames[i], "id")) {
+				if (s->room.id)
+					g_free(s->room.id);
+				s->room.id = g_strdup(avalues[i]);
+			} else if (!strcmp(anames[i], "name")) {
+				if (s->room.name)
+					g_free(s->room.name);
+				s->room.name = g_strdup(avalues[i]);
+			} else if (!strcmp(anames[i], "topic")) {
+				if (s->room.topic)
+					g_free(s->room.topic);
+				s->room.topic = g_strdup(avalues[i]);
+			} else if (!strcmp(anames[i], "type")) {
+				if (!strcmp("yahoo", avalues[i]))
+					s->room.type = yrt_yahoo;
+				else
+					s->room.type = yrt_user;
+			}
+		}
+
+	} else if (!strcmp(ename, "lobby")) {
+		struct yahoo_lobby *lob = g_new0(struct yahoo_lobby, 1);
+
+		for (i = 0; anames[i]; i++) {
+			if (!strcmp(anames[i], "count")) {
+				lob->count = strtol(avalues[i], NULL, 10);
+			} else if (!strcmp(anames[i], "users")) {
+				s->room.users += lob->users = strtol(avalues[i], NULL, 10);
+			} else if (!strcmp(anames[i], "voices")) {
+				s->room.voices += lob->voices = strtol(avalues[i], NULL, 10);
+			} else if (!strcmp(anames[i], "webcams")) {
+				s->room.webcams += lob->webcams = strtol(avalues[i], NULL, 10);
+			}
+		}
+
+		g_queue_push_head(s->q, lob);
+	}
+
+}
+
+static void yahoo_chatlist_end_element(GMarkupParseContext *context, const gchar *ename,
+                                       gpointer user_data, GError **error)
+{
+	struct yahoo_chatxml_state *s = user_data;
+
+	if (!strcmp(ename, "category")) {
+		g_queue_pop_head(s->q);
+	} else if (!strcmp(ename, "room")) {
+		struct yahoo_lobby *lob;
+		GaimRoomlistRoom *r, *l;
+
+		if (s->room.type == yrt_yahoo)
+			r = gaim_roomlist_room_new(GAIM_ROOMLIST_ROOMTYPE_CATAGORY|GAIM_ROOMLIST_ROOMTYPE_ROOM,
+		                                   s->room.name, s->yrl->cat);
+		else
+			r = gaim_roomlist_room_new(GAIM_ROOMLIST_ROOMTYPE_CATAGORY|GAIM_ROOMLIST_ROOMTYPE_ROOM,
+		                                   s->room.name, s->yrl->ucat);
+
+		gaim_roomlist_room_add_field(s->list, r, s->room.name);
+		gaim_roomlist_room_add_field(s->list, r, s->room.id);
+		gaim_roomlist_room_add_field(s->list, r, GINT_TO_POINTER(s->room.users));
+		gaim_roomlist_room_add_field(s->list, r, GINT_TO_POINTER(s->room.voices));
+		gaim_roomlist_room_add_field(s->list, r, GINT_TO_POINTER(s->room.webcams));
+		gaim_roomlist_room_add_field(s->list, r, s->room.topic);
+		gaim_roomlist_room_add(s->list, r);
+
+		while ((lob = g_queue_pop_head(s->q))) {
+			char *name = g_strdup_printf("%s:%d", s->room.name, lob->count);
+			l = gaim_roomlist_room_new(GAIM_ROOMLIST_ROOMTYPE_ROOM, name, r);
+
+			gaim_roomlist_room_add_field(s->list, l, name);
+			gaim_roomlist_room_add_field(s->list, l, s->room.id);
+			gaim_roomlist_room_add_field(s->list, l, GINT_TO_POINTER(lob->users));
+			gaim_roomlist_room_add_field(s->list, l, GINT_TO_POINTER(lob->voices));
+			gaim_roomlist_room_add_field(s->list, l, GINT_TO_POINTER(lob->webcams));
+			gaim_roomlist_room_add_field(s->list, l, s->room.topic);
+			gaim_roomlist_room_add(s->list, l);
+
+			g_free(name);
+			g_free(lob);
+		}
+
+	}
+
+}
+
+static GMarkupParser parser = {
+	yahoo_chatlist_start_element,
+	yahoo_chatlist_end_element,
+	NULL,
+	NULL,
+	NULL
+};
+
+static void yahoo_roomlist_cleanup(GaimRoomlist *list, struct yahoo_roomlist *yrl)
+{
+	gaim_roomlist_set_in_progress(list, FALSE);
+
+	if (yrl) {
+		list->proto_data = g_list_remove(list->proto_data, yrl);
+		yahoo_roomlist_destroy(yrl);
+	}
+
+	gaim_roomlist_unref(list);
+}
+
+static void yahoo_roomlist_pending(gpointer data, gint source, GaimInputCondition cond)
+{
+	struct yahoo_roomlist *yrl = data;
+	GaimRoomlist *list = yrl->list;
+	char buf[1024];
+	int len;
+	guchar *start;
+	struct yahoo_chatxml_state *s;
+
+	len = read(yrl->fd, buf, sizeof(buf));
+
+	if (len <= 0) {
+		if (yrl->parse)
+			g_markup_parse_context_end_parse(yrl->parse, NULL);
+		yahoo_roomlist_cleanup(list, yrl);
+		return;
+	}
+
+
+	yrl->rxqueue = g_realloc(yrl->rxqueue, len + yrl->rxlen);
+	memcpy(yrl->rxqueue + yrl->rxlen, buf, len);
+	yrl->rxlen += len;
+
+	if (!yrl->started) {
+		yrl->started = TRUE;
+		start = g_strstr_len(yrl->rxqueue, yrl->rxlen, "\r\n\r\n");
+		if (!start || (start - yrl->rxqueue + 4) >= yrl->rxlen)
+			return;
+		start += 4;
+	} else {
+		start = yrl->rxqueue;
+	}
+
+	if (yrl->parse == NULL) {
+		s = yahoo_chatxml_state_new(list, yrl);
+		yrl->parse = g_markup_parse_context_new(&parser, 0, s,
+		             (GDestroyNotify)yahoo_chatxml_state_destroy);
+	}
+
+	if (!g_markup_parse_context_parse(yrl->parse, start, (yrl->rxlen - (start - yrl->rxqueue)), NULL)) {
+
+		yahoo_roomlist_cleanup(list, yrl);
+		return;
+	}
+
+	yrl->rxlen = 0;
+}
+
+static void yahoo_roomlist_got_connected(gpointer data, gint source, GaimInputCondition cond)
+{
+	struct yahoo_roomlist *yrl = data;
+	GaimRoomlist *list = yrl->list;
+	char *buf, *cookie;
+	struct yahoo_data *yd = gaim_account_get_connection(list->account)->proto_data;
+
+	if (source < 0) {
+		gaim_notify_error(gaim_account_get_connection(list->account), NULL, _("Unable to connect"), _("Fetching the room list failed."));
+		yahoo_roomlist_cleanup(list, yrl);
+		return;
+	}
+
+	yrl->fd = source;
+
+	cookie = g_strdup_printf("Y=%s; T=%s", yd->cookie_y, yd->cookie_t);
+	buf = g_strdup_printf("GET /%s HTTP/1.0\r\nHost: %s\r\nCookie: %s\r\n\r\n", yrl->path, yrl->host, cookie);
+	write(yrl->fd, buf, strlen(buf));
+	g_free(cookie);
+	g_free(buf);
+	yrl->inpa = gaim_input_add(yrl->fd, GAIM_INPUT_READ, yahoo_roomlist_pending, yrl);
+
+}
+
+GaimRoomlist *yahoo_roomlist_get_list(GaimConnection *gc)
+{
+	struct yahoo_roomlist *yrl;
+	GaimRoomlist *rl;
+	char *url;
+	GList *fields = NULL;
+	GaimRoomlistField *f;
+
+	url = g_strdup_printf("%s?chatcat=0",
+	                      gaim_account_get_string(
+	                      gaim_connection_get_account(gc),
+	                      "room_list", YAHOO_ROOMLIST_URL));
+
+	yrl = g_new0(struct yahoo_roomlist, 1);
+	rl = gaim_roomlist_new(gaim_connection_get_account(gc));
+	yrl->list = rl;
+
+	gaim_url_parse(url, &(yrl->host), NULL, &(yrl->path));
+	g_free(url);
+
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, "", "room", TRUE);
+	fields = g_list_append(fields, f);
+
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, "", "id", TRUE);
+	fields = g_list_append(fields, f);
+
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_INT, _("Users"), "users", FALSE);
+	fields = g_list_append(fields, f);
+
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_INT, _("Voices"), "voices", FALSE);
+	fields = g_list_append(fields, f);
+
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_INT, _("Webcams"), "webcams", FALSE);
+	fields = g_list_append(fields, f);
+
+	f = gaim_roomlist_field_new(GAIM_ROOMLIST_FIELD_STRING, _("Topic"), "topic", FALSE);
+	fields = g_list_append(fields, f);
+
+	gaim_roomlist_set_fields(rl, fields);
+
+	if (gaim_proxy_connect(gaim_connection_get_account(gc),
+	                       yrl->host, 80, yahoo_roomlist_got_connected, yrl) != 0)
+	{
+		gaim_notify_error(gc, NULL, _("Connection problem"), _("Unable to fetch room list."));
+		yahoo_roomlist_cleanup(rl, yrl);
+		return NULL;
+	}
+
+	rl->proto_data = g_list_append(rl->proto_data, yrl);
+
+	gaim_roomlist_set_in_progress(rl, TRUE);
+	return rl;
+}
+
+void yahoo_roomlist_cancel(GaimRoomlist *list)
+{
+	GList *l, *k;
+
+	k = l = list->proto_data;
+	list->proto_data = NULL;
+
+	gaim_roomlist_set_in_progress(list, FALSE);
+
+	for (; l; l = l->next) {
+		yahoo_roomlist_destroy(l->data);
+		gaim_roomlist_unref(l->data);
+	}
+	g_list_free(k);
+}
+
+void yahoo_roomlist_expand_catagory(GaimRoomlist *list, GaimRoomlistRoom *catagory)
+{
+	struct yahoo_roomlist *yrl;
+	char *url;
+	char *id;
+
+	if (catagory->type != GAIM_ROOMLIST_ROOMTYPE_CATAGORY)
+		return;
+
+	if (!(id = g_list_nth_data(catagory->fields, 1))) {
+		gaim_roomlist_set_in_progress(list, FALSE);
+		return;
+	}
+
+	url = g_strdup_printf("%s?chatroom_%s=0",
+	                      gaim_account_get_string(
+	                      list->account,
+	                      "room_list", YAHOO_ROOMLIST_URL), id);
+
+	yrl = g_new0(struct yahoo_roomlist, 1);
+	yrl->list = list;
+	yrl->cat = catagory;
+	list->proto_data = g_list_append(list->proto_data, yrl);
+
+	gaim_url_parse(url, &(yrl->host), NULL, &(yrl->path));
+	g_free(url);
+
+	yrl->ucat = gaim_roomlist_room_new(GAIM_ROOMLIST_ROOMTYPE_CATAGORY, _("User Rooms"), yrl->cat);
+	gaim_roomlist_room_add(list, yrl->ucat);
+
+	if (gaim_proxy_connect(list->account,
+	                       yrl->host, 80, yahoo_roomlist_got_connected, yrl) != 0)
+	{
+		gaim_notify_error(gaim_account_get_connection(list->account),
+		                  NULL, _("Connection problem"), _("Unable to fetch room list."));
+		yahoo_roomlist_cleanup(list, yrl);
+		return;
+	}
+
+	gaim_roomlist_set_in_progress(list, TRUE);
+	gaim_roomlist_ref(list);
+}
+
--- a/src/protocols/yahoo/yahoochat.h	Thu Jan 15 17:08:38 2004 +0000
+++ b/src/protocols/yahoo/yahoochat.h	Thu Jan 15 22:20:29 2004 +0000
@@ -25,6 +25,8 @@
 #ifndef _YAHOOCHAT_H_
 #define _YAHOOCHAT_H_
 
+#include "roomlist.h"
+
 void yahoo_process_conference_invite(GaimConnection *gc, struct yahoo_packet *pkt);
 void yahoo_process_conference_decline(GaimConnection *gc, struct yahoo_packet *pkt);
 void yahoo_process_conference_logon(GaimConnection *gc, struct yahoo_packet *pkt);
@@ -47,4 +49,9 @@
 
 void yahoo_chat_goto(GaimConnection *gc, const char *name);
 
+/* room listing functions */
+GaimRoomlist *yahoo_roomlist_get_list(GaimConnection *gc);
+void yahoo_roomlist_cancel(GaimRoomlist *list);
+void yahoo_roomlist_expand_catagory(GaimRoomlist *list, GaimRoomlistRoom *catagory);
+
 #endif /* _YAHOO_CHAT_H_ */
--- a/src/prpl.h	Thu Jan 15 17:08:38 2004 +0000
+++ b/src/prpl.h	Thu Jan 15 22:20:29 2004 +0000
@@ -188,6 +188,10 @@
 /** Custom away message. */
 #define GAIM_AWAY_CUSTOM _("Custom")
 
+/** Some structs defined in roomlist.h */
+struct _GaimRoomlist;
+struct _GaimRoomlistRoom;
+
 /**
  * A protocol plugin information structure.
  *
@@ -318,6 +322,11 @@
 	void (*set_chat_topic)(GaimConnection *gc, int id, const char *topic);
 
 	GaimChat *(*find_blist_chat)(GaimAccount *account, const char *name);
+
+	/* room listing prpl callbacks */
+	struct _GaimRoomlist *(*roomlist_get_list)(GaimConnection *gc);
+	void (*roomlist_cancel)(struct _GaimRoomlist *list);
+	void (*roomlist_expand_catagory)(struct _GaimRoomlist *list, struct _GaimRoomlistRoom *catagory);
 };
 
 #define GAIM_IS_PROTOCOL_PLUGIN(plugin) \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/roomlist.c	Thu Jan 15 22:20:29 2004 +0000
@@ -0,0 +1,312 @@
+/**
+ * @file roomlist.c Room List API
+ * @ingroup core
+ *
+ * gaim
+ *
+ * Copyright (C) 2003, Timothy Ringenbach <omarvo@hotmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <glib.h>
+
+#include "account.h"
+#include "connection.h"
+#include "debug.h"
+#include "roomlist.h"
+
+
+static GaimRoomlistUiOps *ops = NULL;
+
+/**************************************************************************/
+/** @name Room List API                                                   */
+/**************************************************************************/
+/*@{*/
+
+GaimRoomlist *gaim_roomlist_new(GaimAccount *account)
+{
+	GaimRoomlist *list;
+
+	g_return_val_if_fail(account != NULL, NULL);
+
+	list = g_new0(GaimRoomlist, 1);
+	list->account = account;
+	list->rooms = NULL;
+	list->fields = NULL;
+	list->ref = 1;
+
+	if (ops && ops->new)
+		ops->new(list);
+
+	return list;
+}
+
+void gaim_roomlist_ref(GaimRoomlist *list)
+{
+	g_return_if_fail(list != NULL);
+
+	list->ref++;
+	gaim_debug_misc("roomlist", "reffing list, ref count now %d\n", list->ref);
+}
+
+static void gaim_roomlist_room_destroy(GaimRoomlist *list, GaimRoomlistRoom *r)
+{
+	GList *l, *j;
+
+	for (l = list->fields, j = r->fields; l && j; l = l->next, j = j->next) {
+		GaimRoomlistField *f = l->data;
+		if (f->type == GAIM_ROOMLIST_FIELD_STRING)
+			g_free(j->data);
+	}
+
+	g_list_free(r->fields);
+	g_free(r->name);
+	g_free(r);
+}
+
+static void gaim_roomlist_field_destroy(GaimRoomlistField *f)
+{
+	g_free(f->label);
+	g_free(f->name);
+	g_free(f);
+}
+
+static void gaim_roomlist_destroy(GaimRoomlist *list)
+{
+	GList *l;
+
+	gaim_debug_misc("roomlist", "destroying list %p\n", list);
+
+	if (ops && ops->destroy)
+		ops->destroy(list);
+
+	if (list->rooms) {
+		for (l = list->rooms; l; l = l->next) {
+			GaimRoomlistRoom *r = l->data;
+			gaim_roomlist_room_destroy(list, r);
+		}
+		g_list_free(list->rooms);
+	}
+
+	if (list->fields) {
+		for (l = list->fields; l; l = l->next) {
+			GaimRoomlistField *f = l->data;
+			gaim_roomlist_field_destroy(f);
+		}
+		g_list_free(list->fields);
+	}
+
+	g_free(list);
+}
+
+void gaim_roomlist_unref(GaimRoomlist *list)
+{
+	g_return_if_fail(list != NULL);
+
+	list->ref--;
+
+	gaim_debug_misc("roomlist", "unreffing list, ref count now %d\n", list->ref);
+	if (list->ref == 0)
+		gaim_roomlist_destroy(list);
+}
+
+void gaim_roomlist_set_fields(GaimRoomlist *list, GList *fields)
+{
+	g_return_if_fail(list != NULL);
+
+	list->fields = fields;
+
+	if (ops && ops->set_fields)
+		ops->set_fields(list, fields);
+}
+
+void gaim_roomlist_set_in_progress(GaimRoomlist *list, gboolean in_progress)
+{
+	g_return_if_fail(list != NULL);
+
+	if (ops && ops->in_progress)
+		ops->in_progress(list, in_progress);
+}
+
+void gaim_roomlist_room_add(GaimRoomlist *list, GaimRoomlistRoom *room)
+{
+	g_return_if_fail(list != NULL);
+	g_return_if_fail(room != NULL);
+
+	list->rooms = g_list_append(list->rooms, room);
+
+	if (ops && ops->add_room)
+		ops->add_room(list, room);
+}
+
+gboolean gaim_roomlist_is_possible(GaimConnection *gc)
+{
+	GaimPluginProtocolInfo *prpl_info = NULL;
+
+	g_return_val_if_fail(gc != NULL, FALSE);
+
+	if (gc->prpl != NULL)
+		prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl);
+
+	if (prpl_info && prpl_info->roomlist_get_list)
+		return TRUE;
+	return FALSE;
+}
+
+GaimRoomlist *gaim_roomlist_get_list(GaimConnection *gc)
+{
+	GaimPluginProtocolInfo *prpl_info = NULL;
+
+	g_return_val_if_fail(gc != NULL, NULL);
+
+	if (gc->prpl != NULL)
+		prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl);
+
+	if (prpl_info && prpl_info->roomlist_get_list)
+		return prpl_info->roomlist_get_list(gc);
+	return NULL;
+}
+
+void gaim_roomlist_cancel_get_list(GaimRoomlist *list)
+{
+	GaimPluginProtocolInfo *prpl_info = NULL;
+	GaimConnection *gc;
+
+	g_return_if_fail(list != NULL);
+
+	gc = gaim_account_get_connection(list->account);
+
+	g_return_if_fail(gc != NULL);
+
+	if (gc != NULL && gc->prpl != NULL)
+		prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl);
+
+	if (prpl_info && prpl_info->roomlist_cancel)
+		prpl_info->roomlist_cancel(list);
+}
+
+void gaim_roomlist_expand_catagory(GaimRoomlist *list, GaimRoomlistRoom *catagory)
+{
+	GaimPluginProtocolInfo *prpl_info = NULL;
+	GaimConnection *gc;
+
+	g_return_if_fail(list != NULL);
+	g_return_if_fail(catagory != NULL);
+	g_return_if_fail(catagory->type & GAIM_ROOMLIST_ROOMTYPE_CATAGORY);
+
+	gc = gaim_account_get_connection(list->account);
+	g_return_if_fail(gc != NULL);
+
+	if (gc->prpl != NULL)
+		prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl);
+
+	if (prpl_info && prpl_info->roomlist_expand_catagory)
+		prpl_info->roomlist_expand_catagory(list, catagory);
+}
+
+/*@}*/
+
+/**************************************************************************/
+/** @name Room API                                                        */
+/**************************************************************************/
+/*@{*/
+
+GaimRoomlistRoom *gaim_roomlist_room_new(GaimRoomlistRoomType type, const gchar *name,
+                                         GaimRoomlistRoom *parent)
+{
+	GaimRoomlistRoom *room;
+
+	g_return_val_if_fail(name != NULL, NULL);
+
+	room = g_new0(GaimRoomlistRoom, 1);
+	room->type = type;
+	room->name = g_strdup(name);
+	room->parent = parent;
+
+	return room;
+}
+
+void gaim_roomlist_room_add_field(GaimRoomlist *list, GaimRoomlistRoom *room, gconstpointer field)
+{
+	GaimRoomlistField *f;
+
+	g_return_if_fail(list != NULL);
+	g_return_if_fail(room != NULL);
+	g_return_if_fail(list->fields != NULL);
+
+	if (!room->fields)
+		f = list->fields->data;
+	else
+		f = g_list_nth_data(list->fields, g_list_length(room->fields));
+
+	g_return_if_fail(f != NULL);
+
+	switch(f->type) {
+		case GAIM_ROOMLIST_FIELD_STRING:
+			room->fields = g_list_append(room->fields, g_strdup(field));
+			break;
+		case GAIM_ROOMLIST_FIELD_BOOL:
+		case GAIM_ROOMLIST_FIELD_INT:
+			room->fields = g_list_append(room->fields, GINT_TO_POINTER(field));
+			break;
+	}
+}
+
+/*@}*/
+
+/**************************************************************************/
+/** @name Room Field API                                                  */
+/**************************************************************************/
+/*@{*/
+
+GaimRoomlistField *gaim_roomlist_field_new(GaimRoomlistFieldType type,
+                                           const gchar *label, const gchar *name,
+                                           gboolean hidden)
+{
+	GaimRoomlistField *f;
+
+	g_return_val_if_fail(label != NULL, NULL);
+	g_return_val_if_fail(name != NULL, NULL);
+
+	f = g_new0(GaimRoomlistField, 1);
+
+	f->type = type;
+	f->label = g_strdup(label);
+	f->name = g_strdup(name);
+	f->hidden = hidden;
+
+	return f;
+}
+
+/*@}*/
+
+/**************************************************************************/
+/** @name UI Registration Functions                                       */
+/**************************************************************************/
+/*@{*/
+
+
+void gaim_roomlist_set_ui_ops(GaimRoomlistUiOps *ui_ops)
+{
+	ops = ui_ops;
+}
+
+GaimRoomlistUiOps *gaim_roomlist_get_ui_ops(void)
+{
+	return ops;
+}
+
+/*@}*/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/roomlist.h	Thu Jan 15 22:20:29 2004 +0000
@@ -0,0 +1,287 @@
+/**
+ * @file roomlist.h Room List API
+ * @ingroup core
+ *
+ * gaim
+ *
+ * Copyright (C) 2003, Timothy Ringenbach <omarvo@hotmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _GAIM_ROOMLIST_H_
+#define _GAIM_ROOMLIST_H_
+
+/**************************************************************************/
+/** Data Structures                                                       */
+/**************************************************************************/
+
+typedef struct _GaimRoomlist GaimRoomlist;
+typedef struct _GaimRoomlistRoom GaimRoomlistRoom;
+typedef enum _GaimRoomlistRoomType GaimRoomlistRoomType;
+typedef struct _GaimRoomlistField GaimRoomlistField;
+typedef enum _GaimRoomlistFieldType GaimRoomlistFieldType;
+typedef struct _GaimRoomlistUiOps GaimRoomlistUiOps;
+
+/**
+ * Represents a list of rooms for a given connection on a given protocol.
+ */
+struct _GaimRoomlist {
+	GaimAccount *account; /**< The account this list belongs to. */
+	GList *fields; /**< The fields. */
+	GList *rooms; /**< The list of rooms. */
+	gpointer ui_data; /**< UI private data. */
+	gpointer proto_data; /** Prpl private data. */
+	guint ref; /**< The reference count. */
+};
+
+/**
+ * The types of rooms.
+ *
+ * These are ORable flags.
+ */
+enum _GaimRoomlistRoomType {
+	GAIM_ROOMLIST_ROOMTYPE_CATAGORY = 0x01, /**< It's a catagory, but not a room you can join. */
+	GAIM_ROOMLIST_ROOMTYPE_ROOM = 0x02,     /**< It's a room, like the kind you can join. */
+};
+
+/**
+ * Represents a room.
+ */
+struct _GaimRoomlistRoom {
+	GaimRoomlistRoomType type; /**< The type of room. */
+	gchar *name; /**< The name of the room. */
+	GList *fields; /**< Other fields. */
+	GaimRoomlistRoom *parent; /**< The parent room, or NULL. */
+	gboolean expanded_once; /**< A flag the UI uses to avoid multiple expand prpl cbs. */
+};
+
+/**
+ * The types of fields.
+ */
+enum _GaimRoomlistFieldType {
+	GAIM_ROOMLIST_FIELD_BOOL,
+	GAIM_ROOMLIST_FIELD_INT,
+	GAIM_ROOMLIST_FIELD_STRING, /**< We do a g_strdup on the passed value if it's this type. */
+};
+
+/**
+ * A field a room might have.
+ */
+struct _GaimRoomlistField {
+	GaimRoomlistFieldType type; /**< The type of field. */
+	gchar *label; /**< The i18n user displayed name of the field. */
+	gchar *name; /**< The internal name of the field. */
+	gboolean hidden; /**< Hidden? */
+};
+
+/**
+ * The room list ops to be filled out by the UI.
+ */
+struct _GaimRoomlistUiOps {
+	void (*new)(GaimRoomlist *list); /**< A new list was created. */
+	void (*set_fields)(GaimRoomlist *list, GList *fields); /**< Sets the columns. */
+	void (*add_room)(GaimRoomlist *list, GaimRoomlistRoom *room); /**< Add a room to the list. */
+	void (*in_progress)(GaimRoomlist *list, gboolean flag); /**< Are we fetching stuff still? */
+	void (*destroy)(GaimRoomlist *list); /**< We're destroying list. */
+};
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**************************************************************************/
+/** @name Room List API                                                   */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Returns a newly created room list object.
+ *
+ * It has an inital reference count of 1.
+ *
+ * @param account The account that's listing rooms.
+ * @return The new room list handle.
+ */
+GaimRoomlist *gaim_roomlist_new(GaimAccount *account);
+
+/**
+ * Increases the reference count on the room list.
+ *
+ * @param list The object to ref.
+ */
+void gaim_roomlist_ref(GaimRoomlist *list);
+
+/**
+ * Decreases the reference count on the room list.
+ *
+ * The room list will be destroyed when this reaches 0.
+ *
+ * @param list The room list object to unref and possibly
+ *             destroy.
+ */
+void gaim_roomlist_unref(GaimRoomlist *list);
+
+/**
+ * Set the different field types and their names for this protocol.
+ *
+ * This must be called before gaim_roomlist_room_add().
+ *
+ * @param list The room list.
+ * @param fields A GList of GaimRoomlistField's. UI's are encourged
+ *               to default to displaying them in the order given.
+ */
+void gaim_roomlist_set_fields(GaimRoomlist *list, GList *fields);
+
+/**
+ * Set the "in progress" state of the room list.
+ *
+ * The UI is encourged to somehow hint to the user
+ * whether or not we're busy downloading a room list or not.
+ *
+ * @param list The room list.
+ * @param in_progress We're downloading it, or we're not.
+ */
+void gaim_roomlist_set_in_progress(GaimRoomlist *list, gboolean in_progress);
+
+/**
+ * Adds a room to the list of them.
+ *
+ * @param list The room list.
+ * @param room The room to add to the list. The GList of fields must be in the same
+               order as was given in gaim_roomlist_set_fields().
+*/
+void gaim_roomlist_room_add(GaimRoomlist *list, GaimRoomlistRoom *room);
+
+/**
+ * Do we support room listing?
+ *
+ * @param gc The GaimConnection we're asking.
+ * @return @c TRUE if it's possible to get a room list.
+ */
+gboolean gaim_roomlist_is_possible(GaimConnection *gc);
+
+/**
+ * Returns a GaimRoomlist structure from the prpl, and
+ * instructs the prpl to start fetching the list.
+ *
+ * @param gc The GaimConnection to have get a list.
+ *
+ * @return A GaimRoomlist* or @c NULL if the protocol
+ *         doesn't support that.
+ */
+GaimRoomlist *gaim_roomlist_get_list(GaimConnection *gc);
+
+/**
+ * Tells the prpl to stop fetching the list.
+ * If this is possible and done, the prpl will
+ * call set_in_progress with @c FALSE and possibly
+ * unref the list if it took a reference.
+ *
+ * @param list The room list to cancel a get_list on.
+ */
+void gaim_roomlist_cancel_get_list(GaimRoomlist *list);
+
+/**
+ * Tells the prpl that a catagory was expanded.
+ *
+ * On some protocols, the rooms in the catagory
+ * won't be fetched until this is called.
+ *
+ * @param list The room list.
+ * @param room The catagory that was expanded. The expression
+ *             (catagory->type & GAIM_ROOMLIST_ROOMTYPE_CATAGORY)
+ *             must be true.
+ */
+void gaim_roomlist_expand_catagory(GaimRoomlist *list, GaimRoomlistRoom *catagory);
+
+/*@}*/
+
+/**************************************************************************/
+/** @name Room API                                                        */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Creates a new room, to be added to the list.
+ *
+ * @param type The type of room.
+ * @param name The name of the room.
+ * @param parent The room's parent, if any.
+ *
+ * @return A new room.
+ */
+GaimRoomlistRoom *gaim_roomlist_room_new(GaimRoomlistRoomType type, const gchar *name,
+                                         GaimRoomlistRoom *parent);
+
+/**
+ * Adds a field to a room.
+ *
+ * @param list The room list the room belongs to.
+ * @param room The room.
+ * @param field The field to append. Strings get g_strdup'd internally.
+ */
+void gaim_roomlist_room_add_field(GaimRoomlist *list, GaimRoomlistRoom *room, gconstpointer field);
+
+/*@}*/
+
+/**************************************************************************/
+/** @name Room Field API                                                  */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Creates a new field.
+ *
+ * @param type The type of the field.
+ * @param label The i18n'ed, user displayable name.
+ * @param name The internal name of the field.
+ *
+ * @return A new GaimRoomlistField, ready to be added to a GList and passed to
+ *         gaim_roomlist_set_fields().
+ */
+GaimRoomlistField *gaim_roomlist_field_new(GaimRoomlistFieldType type,
+                                           const gchar *label, const gchar *name,
+                                           gboolean hidden);
+/*@}*/
+
+/**************************************************************************/
+/** @name UI Registration Functions                                       */
+/**************************************************************************/
+/*@{*/
+
+/**
+ * Sets the UI operations structure to be used in all gaim room lists.
+ *
+ * @param ops The UI operations structure.
+ */
+void gaim_roomlist_set_ui_ops(GaimRoomlistUiOps *ops);
+
+/**
+ * Returns the gaim window UI operations structure to be used in
+ * new windows.
+ *
+ * @return A filled-out GaimRoomlistUiOps structure.
+ */
+GaimRoomlistUiOps *gaim_roomlist_get_ui_ops(void);
+
+/*@}*/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _GAIM_ROOMLIST_H_ */