changeset 26247:f5e613e05332

Applied disco-2.patch from nops with some modifications: * Alphabetized includes and Makefiles * Removed purple_disco_set_ui_ops(NULL) in finch; ops is NULL by default. * A few string changes * Removed DISCO_PREF_LAST_SERVER. Default to our server, but store the last requested in the JabberStream* and use it if available.
author Paul Aurich <paul@darkrain42.org>
date Sun, 29 Mar 2009 19:29:22 +0000
parents c422c7b1bde7
children f19c214201db
files libpurple/Makefile.am libpurple/Makefile.mingw libpurple/protocols/jabber/disco.c libpurple/protocols/jabber/disco.h libpurple/protocols/jabber/jabber.c libpurple/protocols/jabber/jabber.h libpurple/protocols/jabber/libxmpp.c libpurple/prpl.h libpurple/purple.h.in pidgin/Makefile.am pidgin/Makefile.mingw pidgin/gtkblist.c pidgin/gtkmain.c
diffstat 13 files changed, 452 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/libpurple/Makefile.am	Sat Mar 28 16:58:32 2009 +0000
+++ b/libpurple/Makefile.am	Sun Mar 29 19:29:22 2009 +0000
@@ -46,6 +46,7 @@
 	core.c \
 	debug.c \
 	desktopitem.c \
+	disco.c \
 	eventloop.c \
 	ft.c \
 	idle.c \
@@ -103,6 +104,7 @@
 	dbus-maybe.h \
 	debug.h \
 	desktopitem.h \
+	disco.h \
 	eventloop.h \
 	ft.h \
 	gaim-compat.h \
@@ -165,7 +167,7 @@
 dbus_headers  = dbus-bindings.h dbus-purple.h dbus-server.h dbus-useful.h dbus-define-api.h dbus-types.h
 
 dbus_exported = dbus-useful.h dbus-define-api.h account.h blist.h buddyicon.h \
-                connection.h conversation.h core.h ft.h log.h notify.h prefs.h roomlist.h \
+                connection.h conversation.h core.h disco.h ft.h log.h notify.h prefs.h roomlist.h \
                 savedstatuses.h smiley.h status.h server.h util.h xmlnode.h prpl.h
 
 purple_build_coreheaders = $(addprefix $(srcdir)/, $(purple_coreheaders)) \
--- a/libpurple/Makefile.mingw	Sat Mar 28 16:58:32 2009 +0000
+++ b/libpurple/Makefile.mingw	Sun Mar 29 19:29:22 2009 +0000
@@ -40,6 +40,7 @@
 			conversation.c \
 			core.c \
 			debug.c \
+			disco.c \
 			dnsquery.c \
 			dnssrv.c \
 			eventloop.c \
--- a/libpurple/protocols/jabber/disco.c	Sat Mar 28 16:58:32 2009 +0000
+++ b/libpurple/protocols/jabber/disco.c	Sun Mar 29 19:29:22 2009 +0000
@@ -1,5 +1,5 @@
 /*
- * purple - Jabber Protocol Plugin
+ * purple - Jabber Service Discovery
  *
  * Copyright (C) 2003, Nathan Walp <faceprint@faceprint.com>
  *
@@ -22,6 +22,8 @@
 #include "internal.h"
 #include "prefs.h"
 #include "debug.h"
+#include "request.h"
+#include "notify.h"
 
 #include "buddy.h"
 #include "google.h"
@@ -32,7 +34,8 @@
 #include "roster.h"
 #include "pep.h"
 #include "adhoccommands.h"
-
+#include "xdata.h"
+#include "libpurple/disco.h"
 
 struct _jabber_disco_info_cb_data {
 	gpointer data;
@@ -268,6 +271,8 @@
 					capabilities |= JABBER_CAP_IQ_REGISTER;
 				else if(!strcmp(var, "http://www.xmpp.org/extensions/xep-0199.html#ns"))
 					capabilities |= JABBER_CAP_PING;
+				else if(!strcmp(var, "http://jabber.org/protocol/disco#items"))
+					capabilities |= JABBER_CAP_ITEMS;
 				else if(!strcmp(var, "http://jabber.org/protocol/commands")) {
 					capabilities |= JABBER_CAP_ADHOC;
 				}
@@ -311,7 +316,8 @@
 	}
 }
 
-void jabber_disco_items_parse(JabberStream *js, xmlnode *packet) {
+void jabber_disco_items_parse(JabberStream *js, xmlnode *packet)
+{
 	const char *from = xmlnode_get_attrib(packet, "from");
 	const char *type = xmlnode_get_attrib(packet, "type");
 
@@ -396,6 +402,12 @@
 
 }
 
+struct _disco_data {
+	PurpleDiscoList *list;
+	PurpleDiscoService *parent;
+	char *node;
+};
+
 static void
 jabber_disco_server_info_result_cb(JabberStream *js, xmlnode *packet, gpointer data)
 {
@@ -558,4 +570,371 @@
 	jabber_iq_send(iq);
 }
 
+static PurpleDiscoServiceCategory
+jabber_disco_category_from_string(const gchar *str)
+{
+	if (!strcasecmp(str, "gateway"))
+		return PURPLE_DISCO_SERVICE_CAT_GATEWAY;
+	else if (!strcasecmp(str, "directory"))
+		return PURPLE_DISCO_SERVICE_CAT_DIRECTORY;
+	else if (!strcasecmp(str, "conference"))
+		return PURPLE_DISCO_SERVICE_CAT_MUC;
 
+	return PURPLE_DISCO_SERVICE_CAT_NONE;
+}
+
+static PurpleDiscoServiceType
+jabber_disco_type_from_string(const gchar *str)
+{
+	if (!strcasecmp(str, "xmpp"))
+		return PURPLE_DISCO_SERVICE_TYPE_XMPP;
+	else if (!strcasecmp(str, "icq"))
+		return PURPLE_DISCO_SERVICE_TYPE_ICQ;
+	else if (!strcasecmp(str, "mrim"))
+		return PURPLE_DISCO_SERVICE_TYPE_MAIL;
+	else if (!strcasecmp(str, "user"))
+		return PURPLE_DISCO_SERVICE_TYPE_USER;
+	else if (!strcasecmp(str, "yahoo"))
+		return PURPLE_DISCO_SERVICE_TYPE_YAHOO;
+	else if (!strcasecmp(str, "irc"))
+		return PURPLE_DISCO_SERVICE_TYPE_IRC;
+	else if (!strcasecmp(str, "gadu-gadu"))
+		return PURPLE_DISCO_SERVICE_TYPE_GG;
+	else if (!strcasecmp(str, "aim"))
+		return PURPLE_DISCO_SERVICE_TYPE_AIM;
+	else if (!strcasecmp(str, "qq"))
+		return PURPLE_DISCO_SERVICE_TYPE_QQ;
+	else if (!strcasecmp(str, "msn"))
+		return PURPLE_DISCO_SERVICE_TYPE_MSN;
+
+	return PURPLE_DISCO_SERVICE_TYPE_NONE;
+}
+
+static void
+jabber_disco_service_info_cb(JabberStream *js, xmlnode *packet, gpointer data);
+
+static void
+jabber_disco_service_items_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+	struct _disco_data *disco_data = data;
+	PurpleDiscoList *list = disco_data->list;
+	PurpleDiscoService *parent = disco_data->parent;
+	const char *parent_node = disco_data->node;
+	xmlnode *query = xmlnode_get_child(packet, "query");
+	const char *from = xmlnode_get_attrib(packet, "from");
+	const char *result = xmlnode_get_attrib(packet, "type");
+	xmlnode *child;
+	gboolean has_items = FALSE;
+
+	purple_disco_list_set_fetch_count(list, purple_disco_list_get_fetch_count(list) - 1);
+
+	if (!from || !result || !query || (strcmp(result, "result")
+			|| !purple_disco_list_get_proto_data(list))) {
+		if (!purple_disco_list_get_fetch_count(list))
+			purple_disco_set_in_progress(list, FALSE);
+
+		purple_disco_list_unref(list);
+		return;
+	}
+
+	query = xmlnode_get_child(packet, "query");
+
+	for(child = xmlnode_get_child(query, "item"); child;
+			child = xmlnode_get_next_twin(child)) {
+		JabberIq *iq;
+		xmlnode *q;
+		const char *jid, *node;
+		struct _disco_data *disco_data;
+		char *full_node;
+
+		if(!(jid = xmlnode_get_attrib(child, "jid")) || !purple_disco_list_get_proto_data(list))
+			continue;
+
+		node = xmlnode_get_attrib(child, "node");
+
+		if (parent_node) {
+			if (node) {
+				full_node = g_new0(char, strlen(parent_node) + 1 + strlen(node) + 1);
+				strcat(full_node, parent_node);
+				strcat(full_node, "/");
+				strcat(full_node, node);
+			} else {
+				continue;
+			}
+		} else {
+			full_node = g_strdup(node);
+		}
+
+		disco_data = g_new0(struct _disco_data, 1);
+		disco_data->list = list;
+		disco_data->parent = parent;
+		disco_data->node = full_node;
+
+		has_items = TRUE;
+		purple_disco_list_set_fetch_count(list, purple_disco_list_get_fetch_count(list) + 1);
+		purple_disco_list_ref(list);
+		iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#info");
+		xmlnode_set_attrib(iq->node, "to", jid);
+		if (full_node && (q = xmlnode_get_child(iq->node, "query")))
+			xmlnode_set_attrib(q, "node", full_node);
+		jabber_iq_set_callback(iq, jabber_disco_service_info_cb, disco_data);
+
+		jabber_iq_send(iq);
+	}
+
+	if (!purple_disco_list_get_fetch_count(list))
+		purple_disco_set_in_progress(list, FALSE);
+	purple_disco_list_unref(list);
+
+	g_free(disco_data->node);
+	g_free(disco_data);
+}
+
+static void
+jabber_disco_service_info_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+	struct _disco_data *disco_data = data;
+	PurpleDiscoList *list = disco_data->list;
+	PurpleDiscoService *parent = disco_data->parent;
+	char *node = g_strdup(disco_data->node);
+	xmlnode *query, *ident, *child;
+	const char *from = xmlnode_get_attrib(packet, "from");
+	const char *result = xmlnode_get_attrib(packet, "type");
+	const char *acat, *atype, *adesc, *anode;
+	char *aname;
+	PurpleDiscoService *s;
+	PurpleDiscoServiceCategory cat;
+	PurpleDiscoServiceType type;
+	int flags = PURPLE_DISCO_FLAG_ADD;
+
+	g_free(disco_data->node);
+	g_free(disco_data);
+	purple_disco_list_set_fetch_count(list, purple_disco_list_get_fetch_count(list) - 1);
+
+	if (!from || !result || (strcmp(result, "result") || !purple_disco_list_get_proto_data(list))
+			|| (!(query = xmlnode_get_child(packet, "query")))
+			|| (!(ident = xmlnode_get_child(query, "identity")))) {
+		if (!purple_disco_list_get_fetch_count(list))
+			purple_disco_set_in_progress(list, FALSE);
+
+		purple_disco_list_unref(list);
+		return;
+	}
+
+	acat = xmlnode_get_attrib(ident, "category");
+	atype = xmlnode_get_attrib(ident, "type");
+	adesc = xmlnode_get_attrib(ident, "name");
+	anode = xmlnode_get_attrib(query, "node");
+
+	if (anode) {
+		aname = g_new0(char, strlen(from) + strlen(anode) + 1);
+		strcat(aname, from);
+		strcat(aname, anode);
+	} else {
+		aname = g_strdup(from);
+	}
+
+	cat = jabber_disco_category_from_string(acat);
+	type = jabber_disco_type_from_string(atype);
+
+	for (child = xmlnode_get_child(query, "feature"); child;
+			child = xmlnode_get_next_twin(child)) {
+		const char *var;
+
+		if (!(var = xmlnode_get_attrib(child, "var")))
+			continue;
+		
+		if (!strcmp(var, "jabber:iq:register"))
+			flags |= PURPLE_DISCO_FLAG_REGISTER;
+		
+		if (!strcmp(var, "http://jabber.org/protocol/disco#items"))
+			flags |= PURPLE_DISCO_FLAG_BROWSE;
+
+		if (!strcmp(var, "http://jabber.org/protocol/muc"))
+			cat = PURPLE_DISCO_SERVICE_CAT_MUC;
+	}
+
+	purple_debug_info("disco", "service %s, category %s (%d), type %s (%d), description %s, flags %04x\n",
+			aname,
+			acat, cat,
+			atype, type,
+			adesc, flags);
+
+	s = purple_disco_list_service_new(cat, aname, type, adesc, flags);
+	purple_disco_list_service_add(list, s, parent);
+
+	/* if (flags & PURPLE_DISCO_FLAG_BROWSE) - not all browsable services has this future */
+	{
+		xmlnode *q;
+		JabberIq *iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#items");
+			
+		purple_disco_list_set_fetch_count(list, purple_disco_list_get_fetch_count(list) + 1);
+		purple_disco_list_ref(list);
+		disco_data = g_new0(struct _disco_data, 1);
+		disco_data->list = list;
+		disco_data->parent = s;
+
+		xmlnode_set_attrib(iq->node, "to", from);
+		jabber_iq_set_callback(iq, jabber_disco_service_items_cb, disco_data);
+		if (anode && (q = xmlnode_get_child(iq->node, "query")))
+			xmlnode_set_attrib(q, "node", node);
+		jabber_iq_send(iq);
+	}
+
+	if (!purple_disco_list_get_fetch_count(list))
+		purple_disco_set_in_progress(list, FALSE);
+
+	purple_disco_list_unref(list);
+
+	g_free(aname);
+	g_free(node);
+}
+
+static void
+jabber_disco_server_items_cb(JabberStream *js, xmlnode *packet, gpointer data)
+{
+	PurpleDiscoList *list = data;
+	xmlnode *query, *child;
+	const char *from = xmlnode_get_attrib(packet, "from");
+	const char *type = xmlnode_get_attrib(packet, "type");
+	gboolean has_items = FALSE;
+
+	if (!from || !type)
+		return;
+
+	if (strcmp(type, "result"))
+		return;
+
+	query = xmlnode_get_child(packet, "query");
+
+	for(child = xmlnode_get_child(query, "item"); child;
+			child = xmlnode_get_next_twin(child)) {
+		JabberIq *iq;
+		const char *jid;
+		struct _disco_data *disco_data;
+
+		if(!(jid = xmlnode_get_attrib(child, "jid")) || !purple_disco_list_get_proto_data(list))
+			continue;
+
+		disco_data = g_new0(struct _disco_data, 1);
+		disco_data->list = list;
+
+		has_items = TRUE;
+		purple_disco_list_set_fetch_count(list, purple_disco_list_get_fetch_count(list) + 1);
+		purple_disco_list_ref(list);
+		iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#info");
+		xmlnode_set_attrib(iq->node, "to", jid);
+		jabber_iq_set_callback(iq, jabber_disco_service_info_cb, disco_data);
+
+		jabber_iq_send(iq);
+	}
+
+	if (!has_items)
+		purple_disco_set_in_progress(list, FALSE);
+
+	purple_disco_list_unref(list);
+}
+
+static void
+jabber_disco_server_info_cb(JabberStream *js, const char *who, JabberCapabilities caps, gpointer data)
+{
+	PurpleDiscoList *list = data;
+	JabberIq *iq;
+
+	if (caps & JABBER_CAP_ITEMS) {
+		iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#items");
+		xmlnode_set_attrib(iq->node, "to", who);
+		jabber_iq_set_callback(iq, jabber_disco_server_items_cb, list);
+	
+		if (purple_disco_list_get_proto_data(list))
+			jabber_iq_send(iq);
+		else
+			purple_disco_list_unref(list);
+	
+	} else {
+		purple_notify_error(NULL, _("Error"), _("Server doesn't support service discovery"), NULL); 
+		purple_disco_set_in_progress(list, FALSE);
+		purple_disco_list_unref(list);
+	}
+}
+
+static void
+jabber_disco_server_cb(PurpleDiscoList *list, PurpleRequestFields *fields)
+{
+	JabberStream *js;
+	const char *server_name;
+
+	server_name = purple_request_fields_get_string(fields, "server");
+
+	js = purple_disco_list_get_proto_data(list);
+	if (!js) {
+		purple_debug_error("jabber", "Service discovery requested for %s "
+		                             "without proto_data", server_name);
+		return;
+	}
+
+	purple_disco_set_in_progress(list, TRUE);
+	purple_debug_misc("jabber", "Service discovery for %s\n", server_name);
+	if (js->last_disco_server)
+		g_free(js->last_disco_server);
+	js->last_disco_server = g_strdup(server_name);
+
+	jabber_disco_info_do(js, server_name, jabber_disco_server_info_cb, list);
+}
+
+void
+jabber_disco_get_list(PurpleConnection *gc, PurpleDiscoList *list)
+{
+	PurpleRequestFields *fields;
+	PurpleRequestFieldGroup *g;
+	PurpleRequestField *f;
+	JabberStream *js;
+	const char *last_server;
+	
+	purple_debug_misc("disco.c", "get_list\n");
+
+	js = purple_connection_get_protocol_data(gc);
+	purple_disco_list_set_proto_data(list, js);
+
+	last_server = js->last_disco_server;
+	if (last_server == NULL)
+		last_server = js->user->domain;
+
+
+	fields = purple_request_fields_new();
+	g = purple_request_field_group_new(NULL);
+	f = purple_request_field_string_new("server", _("Server"), 
+		last_server ? last_server : js->user->domain, FALSE);
+
+	purple_request_field_group_add_field(g, f);
+	purple_request_fields_add_group(fields, g);
+	
+	purple_disco_list_ref(list);
+	
+	purple_request_fields(gc,
+		_("Server name request"),
+		_("Enter server name"),
+		NULL,
+		fields,
+		_("OK"), G_CALLBACK(jabber_disco_server_cb),
+		_("Cancel"), NULL,
+		purple_connection_get_account(gc), NULL, NULL, list);
+}
+
+void
+jabber_disco_cancel(PurpleDiscoList *list)
+{
+	purple_disco_list_set_proto_data(list, NULL);
+	purple_disco_set_in_progress(list, FALSE);	
+}
+
+int
+jabber_disco_service_register(PurpleConnection *gc, PurpleDiscoService *service)
+{
+	JabberStream *js = gc->proto_data;
+	
+	jabber_register_gateway(js, service->name);
+
+	return 0;
+}
+
--- a/libpurple/protocols/jabber/disco.h	Sat Mar 28 16:58:32 2009 +0000
+++ b/libpurple/protocols/jabber/disco.h	Sun Mar 29 19:29:22 2009 +0000
@@ -1,5 +1,5 @@
 /**
- * @file iq.h JabberID handlers
+ * @file disco.h Jabber Service Discovery
  *
  * purple
  *
@@ -35,4 +35,9 @@
 void jabber_disco_info_do(JabberStream *js, const char *who,
 		JabberDiscoInfoCallback *callback, gpointer data);
 
+void jabber_disco_get_list(PurpleConnection *gc, PurpleDiscoList* list);
+void jabber_disco_cancel(PurpleDiscoList *list);
+
+int jabber_disco_service_register(PurpleConnection *gc, PurpleDiscoService *service);
+
 #endif /* _PURPLE_JABBER_DISCO_H_ */
--- a/libpurple/protocols/jabber/jabber.c	Sat Mar 28 16:58:32 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Sun Mar 29 19:29:22 2009 +0000
@@ -1062,22 +1062,24 @@
 	group = purple_request_field_group_new(NULL);
 	purple_request_fields_add_group(fields, group);
 
-	if(js->registration)
-		field = purple_request_field_string_new("username", _("Username"), js->user->node, FALSE);
-	else
-		field = purple_request_field_string_new("username", _("Username"), NULL, FALSE);
-
-	purple_request_field_group_add_field(group, field);
-
-	if(js->registration)
-		field = purple_request_field_string_new("password", _("Password"),
-									purple_connection_get_password(js->gc), FALSE);
-	else
-		field = purple_request_field_string_new("password", _("Password"), NULL, FALSE);
-
-	purple_request_field_string_set_masked(field, TRUE);
-	purple_request_field_group_add_field(group, field);
-
+	if(xmlnode_get_child(query, "username")) {
+		if(js->registration)
+			field = purple_request_field_string_new("username", _("Username"), js->user->node, FALSE);
+		else
+			field = purple_request_field_string_new("username", _("Username"), NULL, FALSE);
+
+		purple_request_field_group_add_field(group, field);
+	}
+	if(xmlnode_get_child(query, "password")) {
+		if(js->registration)
+			field = purple_request_field_string_new("password", _("Password"),
+										purple_connection_get_password(js->gc), FALSE);
+		else
+			field = purple_request_field_string_new("password", _("Password"), NULL, FALSE);
+
+		purple_request_field_string_set_masked(field, TRUE);
+		purple_request_field_group_add_field(group, field);
+	}
 	if(xmlnode_get_child(query, "name")) {
 		if(js->registration)
 			field = purple_request_field_string_new("name", _("Name"),
@@ -1414,6 +1416,7 @@
 	g_free(js->old_uri);
 	g_free(js->old_track);
 	g_free(js->expected_rspauth);
+	g_free(js->last_disco_server);
 
 	if (js->keepalive_timeout != -1)
 		purple_timeout_remove(js->keepalive_timeout);
--- a/libpurple/protocols/jabber/jabber.h	Sat Mar 28 16:58:32 2009 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Sun Mar 29 19:29:22 2009 +0000
@@ -44,6 +44,8 @@
 	JABBER_CAP_ADHOC		  = 1 << 12,
 	JABBER_CAP_BLOCKING       = 1 << 13,
 
+	JABBER_CAP_ITEMS          = 1 << 14,
+
 	JABBER_CAP_RETRIEVED      = 1 << 31
 } JabberCapabilities;
 
@@ -242,6 +244,12 @@
 	 * for when we lookup buddy icons from a url
 	 */
 	GSList *url_datas;
+
+	/**
+	 * The last server the user disco'd (or NULL) via the server discovery
+	 * API.
+	 */
+	char *last_disco_server;
 };
 
 typedef gboolean (JabberFeatureEnabled)(JabberStream *js, const gchar *shortname, const gchar *namespace);
--- a/libpurple/protocols/jabber/libxmpp.c	Sat Mar 28 16:58:32 2009 +0000
+++ b/libpurple/protocols/jabber/libxmpp.c	Sun Mar 29 19:29:22 2009 +0000
@@ -34,6 +34,7 @@
 #include "iq.h"
 #include "jabber.h"
 #include "chat.h"
+#include "disco.h"
 #include "message.h"
 #include "roster.h"
 #include "si.h"
@@ -119,7 +120,11 @@
 	jabber_attention_types,			/* attention_types */
 
 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
-	NULL
+	NULL,
+	jabber_disco_get_list,			/* disco_get_list */
+	jabber_disco_cancel,			/* disco_cancel */
+	jabber_disco_service_register	/* disco_service_register */
+
 };
 
 static gboolean load_plugin(PurplePlugin *plugin)
--- a/libpurple/prpl.h	Sat Mar 28 16:58:32 2009 +0000
+++ b/libpurple/prpl.h	Sun Mar 29 19:29:22 2009 +0000
@@ -69,6 +69,7 @@
 #include "proxy.h"
 #include "plugin.h"
 #include "roomlist.h"
+#include "disco.h"
 #include "status.h"
 #include "whiteboard.h"
 
@@ -459,6 +460,21 @@
 	 *         destroyed by the caller when it's no longer needed.
 	 */
 	GHashTable *(*get_account_text_table)(PurpleAccount *account);
+
+	/**
+	 * Service discovery prpl callbacks
+	 */
+	void (*disco_get_list)(PurpleConnection *gc, PurpleDiscoList *list);
+
+	/**
+	 * Cancel fetching service list
+	 */
+	void (*disco_cancel)(PurpleDiscoList *list);
+
+	/**
+	 * Register service
+	 */
+	int (*disco_service_register)(PurpleConnection *gc, PurpleDiscoService *service);
 };
 
 #define PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl, member) \
--- a/libpurple/purple.h.in	Sat Mar 28 16:58:32 2009 +0000
+++ b/libpurple/purple.h.in	Sun Mar 29 19:29:22 2009 +0000
@@ -79,6 +79,7 @@
 #include <prpl.h>
 #include <request.h>
 #include <roomlist.h>
+#include <disco.h>
 #include <savedstatuses.h>
 #include <server.h>
 #include <signals.h>
--- a/pidgin/Makefile.am	Sat Mar 28 16:58:32 2009 +0000
+++ b/pidgin/Makefile.am	Sun Mar 29 19:29:22 2009 +0000
@@ -90,6 +90,7 @@
 	gtkconv.c \
 	gtkdebug.c \
 	gtkdialogs.c \
+	gtkdisco.c \
 	gtkdnd-hints.c \
 	gtkdocklet.c \
 	gtkdocklet-x11.c \
@@ -148,6 +149,7 @@
 	gtkconvwin.h \
 	gtkdebug.h \
 	gtkdialogs.h \
+	gtkdisco.h \
 	gtkdnd-hints.h \
 	gtkdocklet.h \
 	gtkeventloop.h \
--- a/pidgin/Makefile.mingw	Sat Mar 28 16:58:32 2009 +0000
+++ b/pidgin/Makefile.mingw	Sun Mar 29 19:29:22 2009 +0000
@@ -65,6 +65,7 @@
 			gtkconv.c \
 			gtkdebug.c \
 			gtkdialogs.c \
+			gtkdisco.c \
 			gtkdnd-hints.c \
 			gtkdocklet.c \
 			gtkeventloop.c \
--- a/pidgin/gtkblist.c	Sat Mar 28 16:58:32 2009 +0000
+++ b/pidgin/gtkblist.c	Sun Mar 29 19:29:22 2009 +0000
@@ -49,6 +49,7 @@
 #include "gtkconv.h"
 #include "gtkdebug.h"
 #include "gtkdialogs.h"
+#include "gtkdisco.h"
 #include "gtkft.h"
 #include "gtklog.h"
 #include "gtkmenutray.h"
@@ -3332,6 +3333,7 @@
 	{ N_("/Tools/Pr_ivacy"), NULL, pidgin_privacy_dialog_show, 0, "<Item>", NULL },
 	{ "/Tools/sep2", NULL, NULL, 0, "<Separator>", NULL },
 	{ N_("/Tools/_File Transfers"), "<CTL>T", pidgin_xfer_dialog_show, 0, "<StockItem>", PIDGIN_STOCK_TOOLBAR_TRANSFER },
+	{ N_("/Tools/Service _Discovery"), NULL, pidgin_disco_dialog_show, 0, "<Item>", NULL },
 	{ N_("/Tools/R_oom List"), NULL, pidgin_roomlist_dialog_show, 0, "<Item>", NULL },
 	{ N_("/Tools/System _Log"), NULL, gtk_blist_show_systemlog_cb, 3, "<Item>", NULL },
 	{ "/Tools/sep3", NULL, NULL, 0, "<Separator>", NULL },
@@ -4214,6 +4216,9 @@
 
 	widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Room List"));
 	gtk_widget_set_sensitive(widget, pidgin_roomlist_is_showable());
+
+	widget = gtk_item_factory_get_widget(gtkblist->ift, N_("/Tools/Service Discovery"));
+	gtk_widget_set_sensitive(widget, pidgin_disco_is_showable());
 }
 
 static void
--- a/pidgin/gtkmain.c	Sat Mar 28 16:58:32 2009 +0000
+++ b/pidgin/gtkmain.c	Sun Mar 29 19:29:22 2009 +0000
@@ -48,6 +48,7 @@
 #include "gtkconv.h"
 #include "gtkdebug.h"
 #include "gtkdialogs.h"
+#include "gtkdisco.h"
 #include "gtkdocklet.h"
 #include "gtkeventloop.h"
 #include "gtkft.h"
@@ -307,6 +308,7 @@
 	pidgin_privacy_init();
 	pidgin_xfers_init();
 	pidgin_roomlist_init();
+	pidgin_disco_init();
 	pidgin_log_init();
 	pidgin_docklet_init();
 	pidgin_smileys_init();