# HG changeset patch # User Paul Aurich # Date 1244003539 0 # Node ID b709ab0cb4bcb5abe0f35bd1d0c4a7d9e02aa92d # Parent 7358c1f6df195f73bd43afea35e5bcf2ef18d22b# Parent 1c8d7165df6bf7ae9e9645e2c77025d4017a5ee1 propagate from branch 'im.pidgin.pidgin' (head a24ba8f3be7fe637dd9123af47b4c500944f40ae) to branch 'im.pidgin.cpw.darkrain42.xmpp.disco' (head d75cba73217aa91d4e2547cd9084ca64c3610eaf) diff -r 7358c1f6df19 -r b709ab0cb4bc configure.ac --- a/configure.ac Tue Jun 02 21:41:03 2009 +0000 +++ b/configure.ac Wed Jun 03 04:32:19 2009 +0000 @@ -2476,6 +2476,7 @@ pidgin/pixmaps/emotes/small/16/Makefile pidgin/plugins/Makefile pidgin/plugins/cap/Makefile + pidgin/plugins/disco/Makefile pidgin/plugins/gestures/Makefile pidgin/plugins/gevolution/Makefile pidgin/plugins/musicmessaging/Makefile diff -r 7358c1f6df19 -r b709ab0cb4bc libpurple/protocols/jabber/disco.c --- a/libpurple/protocols/jabber/disco.c Tue Jun 02 21:41:03 2009 +0000 +++ b/libpurple/protocols/jabber/disco.c Wed Jun 03 04:32:19 2009 +0000 @@ -1,5 +1,5 @@ /* - * purple - Jabber Protocol Plugin + * purple - Jabber Service Discovery * * Copyright (C) 2003, Nathan Walp * @@ -22,6 +22,7 @@ #include "internal.h" #include "prefs.h" #include "debug.h" +#include "request.h" #include "adhoccommands.h" #include "buddy.h" @@ -40,6 +41,11 @@ JabberDiscoInfoCallback *callback; }; +struct _jabber_disco_items_cb_data { + gpointer data; + JabberDiscoItemsCallback *callback; +}; + #define SUPPORT_FEATURE(x) { \ feature = xmlnode_new_child(query, "feature"); \ xmlnode_set_attrib(feature, "var", x); \ @@ -163,11 +169,11 @@ #endif } else { xmlnode *error, *inf; - + /* XXX: gross hack, implement jabber_iq_set_type or something */ xmlnode_set_attrib(iq->node, "type", "error"); iq->type = JABBER_IQ_ERROR; - + error = xmlnode_new_child(query, "error"); xmlnode_set_attrib(error, "code", "404"); xmlnode_set_attrib(error, "type", "cancel"); @@ -176,13 +182,42 @@ } g_free(node_uri); jabber_iq_send(iq); - } else if(type == JABBER_IQ_RESULT) { + } else if (type == JABBER_IQ_SET) { + /* wtf? seriously. wtf‽ */ + JabberIq *iq = jabber_iq_new(js, JABBER_IQ_ERROR); + xmlnode *error, *bad_request; + + /* Free the */ + xmlnode_free(xmlnode_get_child(iq->node, "query")); + /* Add an error */ + error = xmlnode_new_child(iq->node, "error"); + xmlnode_set_attrib(error, "type", "modify"); + bad_request = xmlnode_new_child(error, "bad-request"); + xmlnode_set_namespace(bad_request, "urn:ietf:params:xml:ns:xmpp-stanzas"); + + jabber_iq_set_id(iq, id); + xmlnode_set_attrib(iq->node, "to", from); + + jabber_iq_send(iq); + } +} + +static void jabber_disco_info_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ + struct _jabber_disco_info_cb_data *jdicd = data; + xmlnode *query; + + query = xmlnode_get_child_with_namespace(packet, "query", + "http://jabber.org/protocol/disco#info"); + + if (type == JABBER_IQ_RESULT && query) { xmlnode *child; JabberID *jid; JabberBuddy *jb; JabberBuddyResource *jbr = NULL; JabberCapabilities capabilities = JABBER_CAP_NONE; - struct _jabber_disco_info_cb_data *jdicd; if((jid = jabber_id_new(from))) { if(jid->resource && (jb = jabber_buddy_find(js, from, TRUE))) @@ -193,7 +228,7 @@ if(jbr) capabilities = jbr->capabilities; - for(child = in_query->child; child; child = child->next) { + for(child = query->child; child; child = child->next) { if(child->type != XMLNODE_TYPE_TAG) continue; @@ -245,6 +280,8 @@ capabilities |= JABBER_CAP_IQ_REGISTER; else if(!strcmp(var, "urn:xmpp:ping")) 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; } @@ -260,19 +297,12 @@ if(jbr) jbr->capabilities = capabilities; - if((jdicd = g_hash_table_lookup(js->disco_callbacks, from))) { - jdicd->callback(js, from, capabilities, jdicd->data); - g_hash_table_remove(js->disco_callbacks, from); - } - } else if(type == JABBER_IQ_ERROR) { + jdicd->callback(js, from, capabilities, jdicd->data); + } else { /* type == JABBER_IQ_ERROR or query == NULL */ JabberID *jid; JabberBuddy *jb; JabberBuddyResource *jbr = NULL; JabberCapabilities capabilities = JABBER_CAP_NONE; - struct _jabber_disco_info_cb_data *jdicd; - - if(!(jdicd = g_hash_table_lookup(js->disco_callbacks, from))) - return; if((jid = jabber_id_new(from))) { if(jid->resource && (jb = jabber_buddy_find(js, from, TRUE))) @@ -284,7 +314,6 @@ capabilities = jbr->capabilities; jdicd->callback(js, from, capabilities, jdicd->data); - g_hash_table_remove(js->disco_callbacks, from); } } @@ -529,10 +558,10 @@ jdicd->data = data; jdicd->callback = callback; - g_hash_table_insert(js->disco_callbacks, g_strdup(who), jdicd); - iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#info"); xmlnode_set_attrib(iq->node, "to", who); + jabber_iq_set_callback(iq, jabber_disco_info_cb, jdicd); jabber_iq_send(iq); } + diff -r 7358c1f6df19 -r b709ab0cb4bc libpurple/protocols/jabber/disco.h --- a/libpurple/protocols/jabber/disco.h Tue Jun 02 21:41:03 2009 +0000 +++ b/libpurple/protocols/jabber/disco.h Wed Jun 03 04:32:19 2009 +0000 @@ -1,5 +1,5 @@ /** - * @file disco.h service discovery handlers + * @file disco.h Jabber Service Discovery * * purple * @@ -24,9 +24,18 @@ #include "jabber.h" +typedef struct _JabberDiscoItem { + const char *jid; /* MUST */ + const char *node; /* SHOULD */ + const char *name; /* MAY */ +} JabberDiscoItem; + typedef void (JabberDiscoInfoCallback)(JabberStream *js, const char *who, JabberCapabilities capabilities, gpointer data); +typedef void (JabberDiscoItemsCallback)(JabberStream *js, const char *jid, + const char *node, GSList *items, gpointer data); + void jabber_disco_info_parse(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *in_query); void jabber_disco_items_parse(JabberStream *js, const char *from, diff -r 7358c1f6df19 -r b709ab0cb4bc libpurple/protocols/jabber/jabber.c --- a/libpurple/protocols/jabber/jabber.c Tue Jun 02 21:41:03 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.c Wed Jun 03 04:32:19 2009 +0000 @@ -766,8 +766,6 @@ js->fd = -1; js->iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); - js->disco_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, g_free); js->buddies = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)jabber_buddy_free); js->chats = g_hash_table_new_full(g_str_hash, g_str_equal, @@ -1089,6 +1087,24 @@ jabber_iq_send(iq); } +static const struct { + const char *name; + const char *label; +} registration_fields[] = { + { "email", N_("Email") }, + { "nick", N_("Nickname") }, + { "first", N_("First name") }, + { "last", N_("Last name") }, + { "address", N_("Address") }, + { "city", N_("City") }, + { "state", N_("State") }, + { "zip", N_("Postal code") }, + { "phone", N_("Phone") }, + { "url", N_("URL") }, + { "date", N_("Date") }, + { NULL, NULL } +}; + void jabber_register_parse(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *query) { @@ -1096,10 +1112,11 @@ PurpleRequestFields *fields; PurpleRequestFieldGroup *group; PurpleRequestField *field; - xmlnode *x, *y; + xmlnode *x, *y, *node; char *instructions; JabberRegisterCBData *cbdata; gboolean registered = FALSE; + int i; if (type != JABBER_IQ_RESULT) return; @@ -1156,74 +1173,53 @@ 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, "name")) { + if((node = xmlnode_get_child(query, "username"))) { + char *data = xmlnode_get_data(node); + if(js->registration) + field = purple_request_field_string_new("username", _("Username"), data ? data : js->user->node, FALSE); + else + field = purple_request_field_string_new("username", _("Username"), data, FALSE); + + purple_request_field_group_add_field(group, field); + g_free(data); + } + if((node = xmlnode_get_child(query, "password"))) { + if(js->registration) + field = purple_request_field_string_new("password", _("Password"), + purple_connection_get_password(js->gc), FALSE); + else { + char *data = xmlnode_get_data(node); + field = purple_request_field_string_new("password", _("Password"), data, FALSE); + g_free(data); + } + + purple_request_field_string_set_masked(field, TRUE); + purple_request_field_group_add_field(group, field); + } + + if((node = xmlnode_get_child(query, "name"))) { if(js->registration) field = purple_request_field_string_new("name", _("Name"), purple_account_get_alias(js->gc->account), FALSE); - else - field = purple_request_field_string_new("name", _("Name"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "email")) { - field = purple_request_field_string_new("email", _("Email"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "nick")) { - field = purple_request_field_string_new("nick", _("Nickname"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "first")) { - field = purple_request_field_string_new("first", _("First name"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "last")) { - field = purple_request_field_string_new("last", _("Last name"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "address")) { - field = purple_request_field_string_new("address", _("Address"), NULL, FALSE); + else { + char *data = xmlnode_get_data(node); + field = purple_request_field_string_new("name", _("Name"), data, FALSE); + g_free(data); + } purple_request_field_group_add_field(group, field); } - if(xmlnode_get_child(query, "city")) { - field = purple_request_field_string_new("city", _("City"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "state")) { - field = purple_request_field_string_new("state", _("State"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "zip")) { - field = purple_request_field_string_new("zip", _("Postal code"), NULL, FALSE); - purple_request_field_group_add_field(group, field); + + for (i = 0; registration_fields[i].name != NULL; ++i) { + if ((node = xmlnode_get_child(query, registration_fields[i].name))) { + char *data = xmlnode_get_data(node); + field = purple_request_field_string_new(registration_fields[i].name, + _(registration_fields[i].label), + data, FALSE); + purple_request_field_group_add_field(group, field); + g_free(data); + } } - if(xmlnode_get_child(query, "phone")) { - field = purple_request_field_string_new("phone", _("Phone"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "url")) { - field = purple_request_field_string_new("url", _("URL"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } - if(xmlnode_get_child(query, "date")) { - field = purple_request_field_string_new("date", _("Date"), NULL, FALSE); - purple_request_field_group_add_field(group, field); - } + if(registered) { field = purple_request_field_bool_new("unregister", _("Unregister"), FALSE); purple_request_field_group_add_field(group, field); @@ -1296,8 +1292,6 @@ js->registration = TRUE; js->iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); - js->disco_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, - g_free, g_free); js->user = jabber_id_new(purple_account_get_username(account)); js->next_id = g_random_int(); js->old_length = 0; @@ -1486,8 +1480,6 @@ if(js->iq_callbacks) g_hash_table_destroy(js->iq_callbacks); - if(js->disco_callbacks) - g_hash_table_destroy(js->disco_callbacks); if(js->buddies) g_hash_table_destroy(js->buddies); if(js->chats) diff -r 7358c1f6df19 -r b709ab0cb4bc libpurple/protocols/jabber/jabber.h --- a/libpurple/protocols/jabber/jabber.h Tue Jun 02 21:41:03 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.h Wed Jun 03 04:32:19 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; @@ -53,12 +55,12 @@ #include #include "circbuffer.h" #include "connection.h" +#include "dnsquery.h" #include "dnssrv.h" #include "media.h" #include "mediamanager.h" #include "roomlist.h" #include "sslconn.h" -#include "dnsquery.h" #include "iq.h" #include "jutil.h" @@ -153,7 +155,6 @@ GList *user_directories; GHashTable *iq_callbacks; - GHashTable *disco_callbacks; int next_id; GList *bs_proxies; diff -r 7358c1f6df19 -r b709ab0cb4bc libpurple/protocols/jabber/libxmpp.c --- a/libpurple/protocols/jabber/libxmpp.c Tue Jun 02 21:41:03 2009 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Wed Jun 03 04:32:19 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" @@ -117,6 +118,7 @@ jabber_unregister_account, /* unregister_user */ jabber_send_attention, /* send_attention */ jabber_attention_types, /* attention_types */ + sizeof(PurplePluginProtocolInfo), /* struct_size */ NULL, /* get_account_text_table */ jabber_initiate_media, /* initiate_media */ diff -r 7358c1f6df19 -r b709ab0cb4bc pidgin/gtkutils.c --- a/pidgin/gtkutils.c Tue Jun 02 21:41:03 2009 +0000 +++ b/pidgin/gtkutils.c Wed Jun 03 04:32:19 2009 +0000 @@ -525,7 +525,7 @@ GtkWidget *item = gtk_menu_get_active(GTK_MENU(menu)); if (p_item) (*p_item) = item; - return g_object_get_data(G_OBJECT(item), "aop_per_item_data"); + return item ? g_object_get_data(G_OBJECT(item), "aop_per_item_data") : NULL; } static void diff -r 7358c1f6df19 -r b709ab0cb4bc pidgin/plugins/Makefile.am --- a/pidgin/plugins/Makefile.am Tue Jun 02 21:41:03 2009 +0000 +++ b/pidgin/plugins/Makefile.am Wed Jun 03 04:32:19 2009 +0000 @@ -1,4 +1,4 @@ -DIST_SUBDIRS = cap gestures gevolution musicmessaging perl ticker +DIST_SUBDIRS = cap disco gestures gevolution musicmessaging perl ticker if BUILD_GEVOLUTION GEVOLUTION_DIR = gevolution @@ -26,6 +26,7 @@ $(GEVOLUTION_DIR) \ $(MUSICMESSAGING_DIR) \ $(PERL_DIR) \ + disco \ ticker plugindir = $(libdir)/pidgin diff -r 7358c1f6df19 -r b709ab0cb4bc pidgin/plugins/disco/Makefile.am --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/disco/Makefile.am Wed Jun 03 04:32:19 2009 +0000 @@ -0,0 +1,23 @@ +plugindir = $(libdir)/pidgin + +xmppdisco_la_LDFLAGS = -module -avoid-version + +if PLUGINS + +plugin_LTLIBRARIES = xmppdisco.la + +xmppdisco_la_SOURCES = \ + gtkdisco.c \ + xmppdisco.c + +xmppdisco_la_LIBADD = $(GTK_LIBS) + +endif + +AM_CPPFLAGS = \ + -DDATADIR=\"$(datadir)\" \ + -I$(top_srcdir)/libpurple \ + -I$(top_builddir)/libpurple \ + -I$(top_srcdir)/pidgin \ + $(DEBUG_CFLAGS) \ + $(GTK_CFLAGS) diff -r 7358c1f6df19 -r b709ab0cb4bc pidgin/plugins/disco/gtkdisco.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/disco/gtkdisco.c Wed Jun 03 04:32:19 2009 +0000 @@ -0,0 +1,583 @@ +/** + * @file gtkdisco.c GTK+ Service Discovery UI + * @ingroup pidgin + */ + +/* pidgin + * + * Pidgin is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" +#include "debug.h" +#include "gtkutils.h" +#include "pidgin.h" +#include "request.h" + +#include "gtkdisco.h" +#include "xmppdisco.h" + +GList *dialogs = NULL; + +struct _menu_cb_info { + PidginDiscoList *list; + XmppDiscoService *service; +}; + +enum { + PIXBUF_COLUMN = 0, + NAME_COLUMN, + DESCRIPTION_COLUMN, + SERVICE_COLUMN, + NUM_OF_COLUMNS +}; + +static void +pidgin_disco_list_destroy(PidginDiscoList *list) +{ + g_hash_table_destroy(list->services); + if (list->dialog && list->dialog->discolist == list) + list->dialog->discolist = NULL; + + if (list->tree) { + gtk_widget_destroy(list->tree); + list->tree = NULL; + } + + g_free((gchar*)list->server); + g_free(list); +} + +PidginDiscoList *pidgin_disco_list_ref(PidginDiscoList *list) +{ + g_return_val_if_fail(list != NULL, NULL); + + ++list->ref; + purple_debug_misc("xmppdisco", "reffing list, ref count now %d\n", list->ref); + + return list; +} + +void pidgin_disco_list_unref(PidginDiscoList *list) +{ + g_return_if_fail(list != NULL); + + --list->ref; + + purple_debug_misc("xmppdisco", "unreffing list, ref count now %d\n", list->ref); + if (list->ref == 0) + pidgin_disco_list_destroy(list); +} + +void pidgin_disco_list_set_in_progress(PidginDiscoList *list, gboolean in_progress) +{ + PidginDiscoDialog *dialog = list->dialog; + + if (!dialog) + return; + + list->in_progress = in_progress; + + if (in_progress) { + gtk_widget_set_sensitive(dialog->account_widget, FALSE); + gtk_widget_set_sensitive(dialog->stop_button, TRUE); + gtk_widget_set_sensitive(dialog->browse_button, FALSE); + } else { + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(dialog->progress), 0.0); + + gtk_widget_set_sensitive(dialog->account_widget, TRUE); + + gtk_widget_set_sensitive(dialog->stop_button, FALSE); + gtk_widget_set_sensitive(dialog->browse_button, TRUE); +/* + gtk_widget_set_sensitive(dialog->register_button, FALSE); + gtk_widget_set_sensitive(dialog->add_button, FALSE); +*/ + } +} + +static void pidgin_disco_create_tree(PidginDiscoList *pdl); + +static void dialog_select_account_cb(GObject *w, PurpleAccount *account, + PidginDiscoDialog *dialog) +{ + dialog->account = account; + gtk_widget_set_sensitive(dialog->browse_button, account != NULL); +} + +static void register_button_cb(GtkButton *button, PidginDiscoDialog *dialog) +{ + struct _menu_cb_info *info = g_object_get_data(G_OBJECT(button), "disco-info"); + + xmpp_disco_service_register(info->service); +} + +static void discolist_cancel_cb(PidginDiscoList *pdl, const char *server) +{ + pidgin_disco_list_set_in_progress(pdl, FALSE); + pidgin_disco_list_unref(pdl); +} + +static void discolist_ok_cb(PidginDiscoList *pdl, const char *server) +{ + gtk_widget_set_sensitive(pdl->dialog->browse_button, TRUE); + + if (!server || !*server) { + purple_notify_error(my_plugin, _("Invalid Server"), _("Invalid Server"), + NULL); + + pidgin_disco_list_set_in_progress(pdl, FALSE); + pidgin_disco_list_unref(pdl); + return; + } + + pdl->server = g_strdup(server); + pidgin_disco_list_set_in_progress(pdl, TRUE); + xmpp_disco_start(pdl); +} + +static void browse_button_cb(GtkButton *button, PidginDiscoDialog *dialog) +{ + PurpleConnection *pc; + PidginDiscoList *pdl; + const char *username; + const char *at, *slash; + char *server = NULL; + + pc = purple_account_get_connection(dialog->account); + if (!pc) + return; + + gtk_widget_set_sensitive(dialog->browse_button, FALSE); + gtk_widget_set_sensitive(dialog->add_button, FALSE); + gtk_widget_set_sensitive(dialog->register_button, FALSE); + + if (dialog->discolist != NULL) { + if (dialog->discolist->tree) { + gtk_widget_destroy(dialog->discolist->tree); + dialog->discolist->tree = NULL; + } + pidgin_disco_list_unref(dialog->discolist); + } + + pdl = dialog->discolist = g_new0(PidginDiscoList, 1); + pdl->services = g_hash_table_new_full(NULL, NULL, NULL, + (GDestroyNotify)gtk_tree_row_reference_free); + pdl->pc = pc; + /* We keep a copy... */ + pidgin_disco_list_ref(pdl); + + pdl->dialog = dialog; + pidgin_disco_create_tree(pdl); + + if (dialog->account_widget) + gtk_widget_set_sensitive(dialog->account_widget, FALSE); + + username = purple_account_get_username(dialog->account); + at = g_utf8_strchr(username, -1, '@'); + slash = g_utf8_strchr(username, -1, '/'); + if (at && !slash) { + server = g_strdup_printf("%s", at + 1); + } else if (at && slash && at + 1 < slash) { + server = g_strdup_printf("%.*s", (int)(slash - (at + 1)), at + 1); + } + + if (server == NULL) + /* This shouldn't ever happen since the account is connected */ + server = g_strdup("jabber.org"); + + purple_request_input(my_plugin, _("Server name request"), _("Enter an XMPP Server"), + _("Select an XMPP server to query"), + server, FALSE, FALSE, NULL, + _("Find Services"), PURPLE_CALLBACK(discolist_ok_cb), + _("Cancel"), PURPLE_CALLBACK(discolist_cancel_cb), + purple_connection_get_account(pc), NULL, NULL, pdl); + + g_free(server); +} + +static void add_room_to_blist_cb(GtkButton *button, PidginDiscoDialog *dialog) +{ + struct _menu_cb_info *info = g_object_get_data(G_OBJECT(button), "disco-info"); + PurpleAccount *account; + const char *name; + + g_return_if_fail(info != NULL); + + account = purple_connection_get_account(info->list->pc); + name = info->service->name; + + if (info->service->type == XMPP_DISCO_SERVICE_TYPE_CHAT) + purple_blist_request_add_chat(account, NULL, NULL, name); + else + purple_blist_request_add_buddy(account, name, NULL, NULL); +} + +static void +selection_changed_cb(GtkTreeSelection *selection, PidginDiscoList *pdl) +{ + XmppDiscoService *service; + GtkTreeIter iter; + GValue val; + static struct _menu_cb_info *info; + PidginDiscoDialog *dialog = pdl->dialog; + + if (gtk_tree_selection_get_selected(selection, NULL, &iter)) { + val.g_type = 0; + gtk_tree_model_get_value(GTK_TREE_MODEL(pdl->model), &iter, SERVICE_COLUMN, &val); + service = g_value_get_pointer(&val); + if (!service) { + gtk_widget_set_sensitive(dialog->add_button, FALSE); + gtk_widget_set_sensitive(dialog->register_button, FALSE); + return; + } + + info = g_new0(struct _menu_cb_info, 1); + info->list = dialog->discolist; + info->service = service; + + g_object_set_data_full(G_OBJECT(dialog->add_button), "disco-info", + info, g_free); + g_object_set_data(G_OBJECT(dialog->register_button), "disco-info", info); + + gtk_widget_set_sensitive(dialog->add_button, service->flags & XMPP_DISCO_ADD); + gtk_widget_set_sensitive(dialog->register_button, service->flags & XMPP_DISCO_REGISTER); + } else { + gtk_widget_set_sensitive(dialog->add_button, FALSE); + gtk_widget_set_sensitive(dialog->register_button, FALSE); + } +} + +static void +row_expanded_cb(GtkTreeView *tree, GtkTreeIter *arg1, GtkTreePath *rg2, + gpointer user_data) +{ + PidginDiscoList *pdl; + XmppDiscoService *service; + GValue val; + + pdl = user_data; + + val.g_type = 0; + gtk_tree_model_get_value(GTK_TREE_MODEL(pdl->model), arg1, SERVICE_COLUMN, + &val); + service = g_value_get_pointer(&val); + xmpp_disco_service_expand(service); +} + +static void +destroy_win_cb(GtkWidget *window, gpointer d) +{ + PidginDiscoDialog *dialog = d; + PidginDiscoList *list = dialog->discolist; + + if (list) { + list->dialog = NULL; + + if (list->in_progress) + list->in_progress = FALSE; + + pidgin_disco_list_unref(list); + } + + dialogs = g_list_remove(dialogs, d); + g_free(dialog); +} + +static void stop_button_cb(GtkButton *button, PidginDiscoDialog *dialog) +{ + pidgin_disco_list_set_in_progress(dialog->discolist, FALSE); +} + +static void close_button_cb(GtkButton *button, PidginDiscoDialog *dialog) +{ + GtkWidget *window = dialog->window; + + gtk_widget_destroy(window); +} + +static gboolean account_filter_func(PurpleAccount *account) +{ + return purple_strequal(purple_account_get_protocol_id(account), XMPP_PLUGIN_ID); +} + +static void pidgin_disco_create_tree(PidginDiscoList *pdl) +{ + GtkCellRenderer *text_renderer, *pixbuf_renderer; + GtkTreeViewColumn *column; + GtkTreeSelection *selection; + + pdl->model = gtk_tree_store_new(NUM_OF_COLUMNS, + GDK_TYPE_PIXBUF, /* PIXBUF_COLUMN */ + G_TYPE_STRING, /* NAME_COLUMN */ + G_TYPE_STRING, /* DESCRIPTION_COLUMN */ + G_TYPE_POINTER /* SERVICE_COLUMN */ + ); + + pdl->tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(pdl->model)); + gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(pdl->tree), TRUE); + + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(pdl->tree)); + g_signal_connect(G_OBJECT(selection), "changed", + G_CALLBACK(selection_changed_cb), pdl); + + g_object_unref(pdl->model); + + gtk_container_add(GTK_CONTAINER(pdl->dialog->sw), pdl->tree); + gtk_widget_show(pdl->tree); + + text_renderer = gtk_cell_renderer_text_new(); + pixbuf_renderer = gtk_cell_renderer_pixbuf_new(); + + column = gtk_tree_view_column_new(); + gtk_tree_view_column_set_title(column, _("Name")); + + gtk_tree_view_column_pack_start(column, pixbuf_renderer, FALSE); + gtk_tree_view_column_set_attributes(column, pixbuf_renderer, + "pixbuf", PIXBUF_COLUMN, NULL); + + gtk_tree_view_column_pack_start(column, text_renderer, TRUE); + gtk_tree_view_column_set_attributes(column, text_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(pdl->tree), column); + + column = gtk_tree_view_column_new_with_attributes(_("Description"), text_renderer, + "text", DESCRIPTION_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), DESCRIPTION_COLUMN); + gtk_tree_view_column_set_reorderable(GTK_TREE_VIEW_COLUMN(column), TRUE); + gtk_tree_view_append_column(GTK_TREE_VIEW(pdl->tree), column); + + g_signal_connect(G_OBJECT(pdl->tree), "row-expanded", G_CALLBACK(row_expanded_cb), pdl); +} + +void pidgin_disco_signed_off_cb(PurpleConnection *pc) +{ + GList *node; + + for (node = dialogs; node; node = node->next) { + PidginDiscoDialog *dialog = node->data; + PidginDiscoList *list = dialog->discolist; + + if (list && list->pc == pc) { + if (list->in_progress) + pidgin_disco_list_set_in_progress(list, FALSE); + + if (list->tree) { + gtk_widget_destroy(list->tree); + list->tree = NULL; + } + + pidgin_disco_list_unref(list); + dialog->discolist = NULL; + + gtk_widget_set_sensitive(dialog->browse_button, + pidgin_account_option_menu_get_selected(dialog->account_widget) != NULL); + + gtk_widget_set_sensitive(dialog->register_button, FALSE); + gtk_widget_set_sensitive(dialog->add_button, FALSE); + } + } +} + +void pidgin_disco_dialogs_destroy_all(void) +{ + while (dialogs) { + PidginDiscoDialog *dialog = dialogs->data; + + gtk_widget_destroy(dialog->window); + /* destroy_win_cb removes the dialog from the list */ + } +} + +PidginDiscoDialog *pidgin_disco_dialog_new(void) +{ + PidginDiscoDialog *dialog; + GtkWidget *window, *vbox, *vbox2, *bbox; + + dialog = g_new0(PidginDiscoDialog, 1); + dialogs = g_list_prepend(dialogs, dialog); + + /* Create the window. */ + dialog->window = window = pidgin_create_dialog(_("Service Discovery"), PIDGIN_HIG_BORDER, "service discovery", TRUE); + + g_signal_connect(G_OBJECT(window), "destroy", + G_CALLBACK(destroy_win_cb), dialog); + + /* Create the parent vbox for everything. */ + vbox = pidgin_dialog_get_vbox_with_properties(GTK_DIALOG(window), FALSE, PIDGIN_HIG_BORDER); + + vbox2 = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); + gtk_container_add(GTK_CONTAINER(vbox), vbox2); + gtk_widget_show(vbox2); + + /* accounts dropdown list */ + dialog->account_widget = pidgin_account_option_menu_new(NULL, FALSE, + G_CALLBACK(dialog_select_account_cb), account_filter_func, dialog); + dialog->account = pidgin_account_option_menu_get_selected(dialog->account_widget); + pidgin_add_widget_to_vbox(GTK_BOX(vbox2), _("_Account:"), NULL, dialog->account_widget, TRUE, NULL); + + /* scrolled 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(vbox2), dialog->sw, TRUE, TRUE, 0); + gtk_widget_set_size_request(dialog->sw, -1, 250); + gtk_widget_show(dialog->sw); + + /* progress bar */ + dialog->progress = gtk_progress_bar_new(); + gtk_progress_bar_set_pulse_step(GTK_PROGRESS_BAR(dialog->progress), 0.1); + gtk_box_pack_start(GTK_BOX(vbox2), dialog->progress, FALSE, FALSE, 0); + gtk_widget_show(dialog->progress); + + /* button box */ + bbox = pidgin_dialog_get_action_area(GTK_DIALOG(window)); + gtk_box_set_spacing(GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE); + gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END); + + /* stop button */ + dialog->stop_button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_STOP, + G_CALLBACK(stop_button_cb), dialog); + gtk_widget_set_sensitive(dialog->stop_button, FALSE); + + /* browse button */ + dialog->browse_button = pidgin_pixbuf_button_from_stock(_("_Browse"), GTK_STOCK_REFRESH, + PIDGIN_BUTTON_HORIZONTAL); + gtk_box_pack_start(GTK_BOX(bbox), dialog->browse_button, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(dialog->browse_button), "clicked", + G_CALLBACK(browse_button_cb), dialog); + gtk_widget_set_sensitive(dialog->browse_button, dialog->account != NULL); + gtk_widget_show(dialog->browse_button); + + /* register button */ + dialog->register_button = pidgin_dialog_add_button(GTK_DIALOG(dialog->window), _("Register"), + G_CALLBACK(register_button_cb), dialog); + gtk_widget_set_sensitive(dialog->register_button, FALSE); + + /* add button */ + dialog->add_button = pidgin_pixbuf_button_from_stock(_("_Add"), GTK_STOCK_ADD, + PIDGIN_BUTTON_HORIZONTAL); + gtk_box_pack_start(GTK_BOX(bbox), dialog->add_button, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(dialog->add_button), "clicked", + G_CALLBACK(add_room_to_blist_cb), dialog); + gtk_widget_set_sensitive(dialog->add_button, FALSE); + gtk_widget_show(dialog->add_button); + + /* close button */ + dialog->close_button = pidgin_dialog_add_button(GTK_DIALOG(window), GTK_STOCK_CLOSE, + G_CALLBACK(close_button_cb), dialog); + + /* show the dialog window and return the dialog */ + gtk_widget_show(dialog->window); + + return dialog; +} + +void pidgin_disco_add_service(PidginDiscoList *pdl, XmppDiscoService *service, XmppDiscoService *parent) +{ + PidginDiscoDialog *dialog; + GtkTreeIter iter, parent_iter, child; + char *filename = NULL; + GdkPixbuf *pixbuf = NULL; + gboolean append = TRUE; + + dialog = pdl->dialog; + g_return_if_fail(dialog != NULL); + + purple_debug_info("xmppdisco", "Adding service \"%s\"\n", service->name); + + gtk_progress_bar_pulse(GTK_PROGRESS_BAR(dialog->progress)); + + if (parent) { + GtkTreeRowReference *rr; + GtkTreePath *path; + + rr = g_hash_table_lookup(pdl->services, parent); + path = gtk_tree_row_reference_get_path(rr); + if (path) { + gtk_tree_model_get_iter(GTK_TREE_MODEL(pdl->model), &parent_iter, path); + gtk_tree_path_free(path); + + if (gtk_tree_model_iter_children(GTK_TREE_MODEL(pdl->model), &child, + &parent_iter)) { + PidginDiscoList *tmp; + gtk_tree_model_get(GTK_TREE_MODEL(pdl->model), &child, + SERVICE_COLUMN, &tmp, -1); + if (!tmp) + append = FALSE; + } + } + } + + if (append) + gtk_tree_store_append(pdl->model, &iter, (parent ? &parent_iter : NULL)); + else + iter = child; + + if (service->flags & XMPP_DISCO_BROWSE) { + GtkTreeRowReference *rr; + GtkTreePath *path; + + gtk_tree_store_append(pdl->model, &child, &iter); + + path = gtk_tree_model_get_path(GTK_TREE_MODEL(pdl->model), &iter); + rr = gtk_tree_row_reference_new(GTK_TREE_MODEL(pdl->model), path); + g_hash_table_insert(pdl->services, service, rr); + gtk_tree_path_free(path); + } + + if (service->type == XMPP_DISCO_SERVICE_TYPE_GATEWAY && service->gateway_type) { + char *tmp = g_strconcat(service->gateway_type, ".png", NULL); + filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", "22", tmp, NULL); + g_free(tmp); +#if 0 + } else if (service->type == XMPP_DISCO_SERVICE_TYPE_USER) { + filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "status", "22", "person.png", NULL); +#endif + } else if (service->type == XMPP_DISCO_SERVICE_TYPE_CHAT) + filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "status", "22", "chat.png", NULL); + + if (filename) { + pixbuf = gdk_pixbuf_new_from_file(filename, NULL); + g_free(filename); + } + + gtk_tree_store_set(pdl->model, &iter, + PIXBUF_COLUMN, pixbuf, + NAME_COLUMN, service->name, + DESCRIPTION_COLUMN, service->description, + SERVICE_COLUMN, service, + -1); + + if (pixbuf) + g_object_unref(pixbuf); +} + diff -r 7358c1f6df19 -r b709ab0cb4bc pidgin/plugins/disco/gtkdisco.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/disco/gtkdisco.h Wed Jun 03 04:32:19 2009 +0000 @@ -0,0 +1,79 @@ +/* pidgin + * + * Pidgin is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef PIDGIN_XMPP_DISCO_UI_H +#define PIDGIN_XMPP_DISCO_UI_H + +typedef struct _PidginDiscoDialog PidginDiscoDialog; +typedef struct _PidginDiscoList PidginDiscoList; + +#include "xmppdisco.h" + +struct _PidginDiscoDialog { + GtkWidget *window; + GtkWidget *account_widget; + + GtkWidget *sw; + GtkWidget *progress; + + GtkWidget *stop_button; + GtkWidget *browse_button; + GtkWidget *register_button; + GtkWidget *add_button; + GtkWidget *close_button; + + PurpleAccount *account; + PidginDiscoList *discolist; +}; + +struct _PidginDiscoList { + PurpleConnection *pc; + gboolean in_progress; + const gchar *server; + + gint ref; + guint fetch_count; + + PidginDiscoDialog *dialog; + GtkTreeStore *model; + GtkWidget *tree; + GHashTable *services; +}; + +/** + * Shows a new service discovery dialog. + */ +PidginDiscoDialog *pidgin_disco_dialog_new(void); + +/** + * Destroy all the open dialogs (called when unloading the plugin). + */ +void pidgin_disco_dialogs_destroy_all(void); +void pidgin_disco_signed_off_cb(PurpleConnection *pc); + +void pidgin_disco_add_service(PidginDiscoList *list, XmppDiscoService *service, + XmppDiscoService *parent); + +PidginDiscoList *pidgin_disco_list_ref(PidginDiscoList *list); +void pidgin_disco_list_unref(PidginDiscoList *list); + +void pidgin_disco_list_set_in_progress(PidginDiscoList *list, gboolean in_progress); +#endif /* PIDGIN_XMPP_DISCO_UI_H */ diff -r 7358c1f6df19 -r b709ab0cb4bc pidgin/plugins/disco/xmppdisco.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/disco/xmppdisco.c Wed Jun 03 04:32:19 2009 +0000 @@ -0,0 +1,646 @@ +/* + * Purple - XMPP Service Disco Browser + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + * + */ + +#include "internal.h" +#include "pidgin.h" + +#include "debug.h" +#include "signals.h" +#include "version.h" + +#include "gtkconv.h" +#include "gtkimhtml.h" +#include "gtkplugin.h" + +#include "xmppdisco.h" +#include "gtkdisco.h" + +/* Variables */ +PurplePlugin *my_plugin = NULL; +static GHashTable *iq_callbacks = NULL; +static gboolean iq_listening = FALSE; + +typedef void (*XmppIqCallback)(PurpleConnection *pc, const char *type, + const char *id, const char *from, xmlnode *iq, + gpointer data); + +struct xmpp_iq_cb_data +{ + gpointer context; + PurpleConnection *pc; + XmppIqCallback cb; +}; + +struct item_data { + PidginDiscoList *list; + XmppDiscoService *parent; + char *name; + char *node; /* disco#info replies don't always include the node */ +}; + +static char* +generate_next_id() +{ + static guint32 index = 0; + + if (index == 0) { + do { + index = g_random_int(); + } while (index == 0); + } + + return g_strdup_printf("purpledisco%x", index++); +} + +static gboolean +remove_iq_callbacks_by_pc(gpointer key, gpointer value, gpointer user_data) +{ + struct xmpp_iq_cb_data *cb_data = value; + + if (cb_data && cb_data->pc == user_data) { + /* + * This is a hack. All the IQ callback datas in this code are + * the same structure so that we can free them here. Ideally they'd + * be objects and this would be polymorphic. That's overkill, here. + */ + struct item_data *item_data = cb_data->context; + + if (item_data) { + pidgin_disco_list_unref(item_data->list); + g_free(item_data->name); + g_free(item_data->node); + g_free(item_data); + } + + return TRUE; + } else + return FALSE; +} + +static gboolean +xmpp_iq_received(PurpleConnection *pc, const char *type, const char *id, + const char *from, xmlnode *iq) +{ + struct xmpp_iq_cb_data *cb_data; + + cb_data = g_hash_table_lookup(iq_callbacks, id); + if (!cb_data) + return FALSE; + + cb_data->cb(cb_data->pc, type, id, from, iq, cb_data->context); + + g_hash_table_remove(iq_callbacks, id); + if (g_hash_table_size(iq_callbacks) == 0) { + PurplePlugin *prpl = purple_connection_get_prpl(pc); + iq_listening = FALSE; + purple_signal_disconnect(prpl, "jabber-receiving-iq", my_plugin, + PURPLE_CALLBACK(xmpp_iq_received)); + } + + /* Om nom nom nom */ + return TRUE; +} + +static void +xmpp_iq_register_callback(PurpleConnection *pc, gchar *id, gpointer data, + XmppIqCallback cb) +{ + struct xmpp_iq_cb_data *cbdata = g_new0(struct xmpp_iq_cb_data, 1); + + cbdata->context = data; + cbdata->cb = cb; + cbdata->pc = pc; + + g_hash_table_insert(iq_callbacks, id, cbdata); + + if (!iq_listening) { + PurplePlugin *prpl = purple_plugins_find_with_id(XMPP_PLUGIN_ID); + iq_listening = TRUE; + purple_signal_connect(prpl, "jabber-receiving-iq", my_plugin, + PURPLE_CALLBACK(xmpp_iq_received), NULL); + } +} + +static void +xmpp_disco_info_do(PurpleConnection *pc, gpointer cbdata, const char *jid, + const char *node, XmppIqCallback cb) +{ + PurplePlugin *prpl; + PurplePluginProtocolInfo *prpl_info; + xmlnode *iq, *query; + char *id = generate_next_id(); + char *str; + + iq = xmlnode_new("iq"); + xmlnode_set_attrib(iq, "type", "get"); + xmlnode_set_attrib(iq, "to", jid); + xmlnode_set_attrib(iq, "id", id); + + query = xmlnode_new_child(iq, "query"); + xmlnode_set_namespace(query, NS_DISCO_INFO); + if (node) + xmlnode_set_attrib(query, "node", node); + + xmpp_iq_register_callback(pc, id, cbdata, cb); + + str = xmlnode_to_str(iq, NULL); + prpl = purple_connection_get_prpl(pc); + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); + prpl_info->send_raw(pc, str, -1); + g_free(str); +} + +static void +xmpp_disco_items_do(PurpleConnection *pc, gpointer cbdata, const char *jid, + const char *node, XmppIqCallback cb) +{ + PurplePlugin *prpl; + PurplePluginProtocolInfo *prpl_info; + xmlnode *iq, *query; + char *id = generate_next_id(); + char *str; + + iq = xmlnode_new("iq"); + xmlnode_set_attrib(iq, "type", "get"); + xmlnode_set_attrib(iq, "to", jid); + xmlnode_set_attrib(iq, "id", id); + + query = xmlnode_new_child(iq, "query"); + xmlnode_set_namespace(query, NS_DISCO_ITEMS); + if (node) + xmlnode_set_attrib(query, "node", node); + + xmpp_iq_register_callback(pc, id, cbdata, cb); + + str = xmlnode_to_str(iq, NULL); + prpl = purple_connection_get_prpl(pc); + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); + prpl_info->send_raw(pc, str, -1); + g_free(str); +} + +static XmppDiscoServiceType +disco_service_type_from_identity(xmlnode *identity) +{ + const char *category, *type; + + if (!identity) + return XMPP_DISCO_SERVICE_TYPE_OTHER; + + category = xmlnode_get_attrib(identity, "category"); + type = xmlnode_get_attrib(identity, "type"); + + if (!category) + return XMPP_DISCO_SERVICE_TYPE_OTHER; + + if (g_str_equal(category, "conference")) + return XMPP_DISCO_SERVICE_TYPE_CHAT; + else if (g_str_equal(category, "directory")) + return XMPP_DISCO_SERVICE_TYPE_DIRECTORY; + else if (g_str_equal(category, "gateway")) + return XMPP_DISCO_SERVICE_TYPE_GATEWAY; + else if (g_str_equal(category, "pubsub")) { + if (!type || g_str_equal(type, "collection")) + return XMPP_DISCO_SERVICE_TYPE_PUBSUB_COLLECTION; + else if (g_str_equal(type, "leaf")) + return XMPP_DISCO_SERVICE_TYPE_PUBSUB_LEAF; + else if (g_str_equal(type, "service")) + return XMPP_DISCO_SERVICE_TYPE_OTHER; + else { + purple_debug_warning("xmppdisco", "Unknown pubsub type '%s'\n", type); + return XMPP_DISCO_SERVICE_TYPE_OTHER; + } + } + + return XMPP_DISCO_SERVICE_TYPE_OTHER; +} + +static const struct { + const char *from; + const char *to; +} disco_type_mappings[] = { + { "gadu-gadu", "gadu-gadu" }, /* the prpl is prpl-gg, but list_icon returns "gadu-gadu" */ + { "sametime", "meanwhile" }, + { "myspaceim", "myspace" }, + { "xmpp", "jabber" }, /* prpl-jabber (mentioned in case the prpl is renamed so this line will match) */ + { NULL, NULL } +}; + +static const gchar * +disco_type_from_string(const gchar *str) +{ + int i = 0; + + g_return_val_if_fail(str != NULL, ""); + + for ( ; disco_type_mappings[i].from; ++i) { + if (!strcasecmp(str, disco_type_mappings[i].from)) + return disco_type_mappings[i].to; + } + + /* fallback to the string itself */ + return str; +} + +static void +got_info_cb(PurpleConnection *pc, const char *type, const char *id, + const char *from, xmlnode *iq, gpointer data) +{ + struct item_data *item_data = data; + PidginDiscoList *list = item_data->list; + xmlnode *query; + + --list->fetch_count; + + if (!list->in_progress) + goto out; + + if (g_str_equal(type, "result") && + (query = xmlnode_get_child(iq, "query"))) { + xmlnode *identity = xmlnode_get_child(query, "identity"); + XmppDiscoService *service; + xmlnode *feature; + + service = g_new0(XmppDiscoService, 1); + service->list = item_data->list; + purple_debug_info("xmppdisco", "parent for %s is %p\n", from, item_data->parent); + service->parent = item_data->parent; + service->flags = XMPP_DISCO_ADD; + service->type = disco_service_type_from_identity(identity); + + if (item_data->node) { + if (item_data->name) { + service->name = item_data->name; + item_data->name = NULL; + } else + service->name = g_strdup(item_data->node); + + service->node = item_data->node; + item_data->node = NULL; + + if (service->type == XMPP_DISCO_SERVICE_TYPE_PUBSUB_COLLECTION) + service->flags |= XMPP_DISCO_BROWSE; + } else + service->name = g_strdup(from); + + if (item_data->name) { + service->description = item_data->name; + item_data->name = NULL; + } else if (identity) + service->description = g_strdup(xmlnode_get_attrib(identity, "name")); + + /* TODO: Overlap with service->name a bit */ + service->jid = g_strdup(from); + + for (feature = xmlnode_get_child(query, "feature"); feature; + feature = xmlnode_get_next_twin(feature)) { + const char *var; + if (!(var = xmlnode_get_attrib(feature, "var"))) + continue; + + if (g_str_equal(var, NS_REGISTER)) + service->flags |= XMPP_DISCO_REGISTER; + else if (g_str_equal(var, NS_DISCO_ITEMS)) + service->flags |= XMPP_DISCO_BROWSE; + else if (g_str_equal(var, NS_MUC)) { + service->flags |= XMPP_DISCO_BROWSE; + service->type = XMPP_DISCO_SERVICE_TYPE_CHAT; + } + } + + if (service->type == XMPP_DISCO_SERVICE_TYPE_GATEWAY) + service->gateway_type = g_strdup(disco_type_from_string( + xmlnode_get_attrib(identity, "type"))); + + pidgin_disco_add_service(list, service, service->parent); + } + +out: + if (list->fetch_count == 0) + pidgin_disco_list_set_in_progress(list, FALSE); + + g_free(item_data->name); + g_free(item_data->node); + g_free(item_data); + pidgin_disco_list_unref(list); +} + +static void +got_items_cb(PurpleConnection *pc, const char *type, const char *id, + const char *from, xmlnode *iq, gpointer data) +{ + struct item_data *item_data = data; + PidginDiscoList *list = item_data->list; + xmlnode *query; + + --list->fetch_count; + + if (!list->in_progress) + goto out; + + if (g_str_equal(type, "result") && + (query = xmlnode_get_child(iq, "query"))) { + xmlnode *item; + + for (item = xmlnode_get_child(query, "item"); item; + item = xmlnode_get_next_twin(item)) { + const char *jid = xmlnode_get_attrib(item, "jid"); + const char *name = xmlnode_get_attrib(item, "name"); + const char *node = xmlnode_get_attrib(item, "node"); + + if (item_data->parent->type == XMPP_DISCO_SERVICE_TYPE_CHAT) { + /* This is a hacky first-order approximation. Any MUC + * component that has a >1 level hierarchy (a Yahoo MUC + * transport component probably does) will violate this. + * + * On the other hand, this is better than querying all the + * chats at conference.jabber.org to enumerate them. + */ + XmppDiscoService *service = g_new0(XmppDiscoService, 1); + service->list = item_data->list; + service->parent = item_data->parent; + service->flags = XMPP_DISCO_ADD; + service->type = XMPP_DISCO_SERVICE_TYPE_CHAT; + + service->name = g_strdup(name); + service->jid = g_strdup(jid); + service->node = g_strdup(node); + pidgin_disco_add_service(list, service, item_data->parent); + } else { + struct item_data *item_data2 = g_new0(struct item_data, 1); + + item_data2->list = item_data->list; + item_data2->parent = item_data->parent; + item_data2->name = g_strdup(name); + item_data2->node = g_strdup(node); + + ++list->fetch_count; + pidgin_disco_list_ref(list); + xmpp_disco_info_do(pc, item_data2, jid, node, got_info_cb); + } + } + } + +out: + if (list->fetch_count == 0) + pidgin_disco_list_set_in_progress(list, FALSE); + + g_free(item_data); + pidgin_disco_list_unref(list); +} + +static void +server_items_cb(PurpleConnection *pc, const char *type, const char *id, + const char *from, xmlnode *iq, gpointer data) +{ + struct item_data *cb_data = data; + PidginDiscoList *list = cb_data->list; + xmlnode *query; + + g_free(cb_data); + --list->fetch_count; + + if (g_str_equal(type, "result") && + (query = xmlnode_get_child(iq, "query"))) { + xmlnode *item; + + for (item = xmlnode_get_child(query, "item"); item; + item = xmlnode_get_next_twin(item)) { + const char *jid = xmlnode_get_attrib(item, "jid"); + struct item_data *item_data; + + if (!jid) + continue; + + item_data = g_new0(struct item_data, 1); + item_data->list = list; + + ++list->fetch_count; + pidgin_disco_list_ref(list); + xmpp_disco_info_do(pc, item_data, jid, NULL, got_info_cb); + } + } + + if (list->fetch_count == 0) + pidgin_disco_list_set_in_progress(list, FALSE); + + pidgin_disco_list_unref(list); +} + +static void +server_info_cb(PurpleConnection *pc, const char *type, const char *id, + const char *from, xmlnode *iq, gpointer data) +{ + struct item_data *cb_data = data; + PidginDiscoList *list = cb_data->list; + xmlnode *query; + gboolean items = FALSE; + + --list->fetch_count; + + if (g_str_equal(type, "result") && + (query = xmlnode_get_child(iq, "query"))) { + xmlnode *feature; + + for (feature = xmlnode_get_child(query, "feature"); feature; + feature = xmlnode_get_next_twin(feature)) { + const char *var = xmlnode_get_attrib(feature, "var"); + if (purple_strequal(var, NS_DISCO_ITEMS)) { + items = TRUE; + break; + } + } + } + + if (items) { + xmpp_disco_items_do(pc, cb_data, from, NULL /* node */, server_items_cb); + ++list->fetch_count; + pidgin_disco_list_ref(list); + } else { + purple_notify_error(my_plugin, _("Error"), + _("Server does not support service discovery"), + NULL); + pidgin_disco_list_set_in_progress(list, FALSE); + g_free(cb_data); + } + + pidgin_disco_list_unref(list); +} + +void xmpp_disco_start(PidginDiscoList *list) +{ + struct item_data *cb_data; + + g_return_if_fail(list != NULL); + + ++list->fetch_count; + pidgin_disco_list_ref(list); + + cb_data = g_new0(struct item_data, 1); + cb_data->list = list; + + xmpp_disco_info_do(list->pc, cb_data, list->server, NULL, server_info_cb); +} + +void xmpp_disco_service_expand(XmppDiscoService *service) +{ + struct item_data *item_data; + + g_return_if_fail(service != NULL); + + if (service->expanded) + return; + + item_data = g_new0(struct item_data, 1); + item_data->list = service->list; + item_data->parent = service; + + ++service->list->fetch_count; + pidgin_disco_list_ref(service->list); + + pidgin_disco_list_set_in_progress(service->list, TRUE); + + xmpp_disco_items_do(service->list->pc, item_data, service->jid, service->node, + got_items_cb); + service->expanded = TRUE; +} + +void xmpp_disco_service_register(XmppDiscoService *service) +{ + PurplePlugin *prpl; + PurplePluginProtocolInfo *prpl_info; + xmlnode *iq, *query; + char *id = generate_next_id(); + char *str; + + iq = xmlnode_new("iq"); + xmlnode_set_attrib(iq, "type", "get"); + xmlnode_set_attrib(iq, "to", service->jid); + xmlnode_set_attrib(iq, "id", id); + + query = xmlnode_new_child(iq, "query"); + xmlnode_set_namespace(query, NS_REGISTER); + + str = xmlnode_to_str(iq, NULL); + prpl = purple_connection_get_prpl(service->list->pc); + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); + prpl_info->send_raw(service->list->pc, str, -1); + g_free(str); + g_free(id); +} + +static void +create_dialog(PurplePluginAction *action) +{ + pidgin_disco_dialog_new(); +} + +static GList * +actions(PurplePlugin *plugin, gpointer context) +{ + GList *l = NULL; + PurplePluginAction *action = NULL; + + action = purple_plugin_action_new(_("XMPP Service Discovery"), + create_dialog); + l = g_list_prepend(l, action); + + return l; +} + +static void +signed_off_cb(PurpleConnection *pc, gpointer unused) +{ + /* Deal with any dialogs */ + pidgin_disco_signed_off_cb(pc); + + /* Remove all the IQ callbacks for this connection */ + g_hash_table_foreach_remove(iq_callbacks, remove_iq_callbacks_by_pc, pc); +} + +static gboolean +plugin_load(PurplePlugin *plugin) +{ + PurplePlugin *xmpp_prpl; + + my_plugin = plugin; + + xmpp_prpl = purple_plugins_find_with_id(XMPP_PLUGIN_ID); + if (NULL == xmpp_prpl) + return FALSE; + + purple_signal_connect(purple_connections_get_handle(), "signing-off", + plugin, PURPLE_CALLBACK(signed_off_cb), NULL); + + iq_callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + + return TRUE; +} + +static gboolean +plugin_unload(PurplePlugin *plugin) +{ + g_hash_table_destroy(iq_callbacks); + iq_callbacks = NULL; + + purple_signals_disconnect_by_handle(plugin); + pidgin_disco_dialogs_destroy_all(); + + return TRUE; +} + +static PurplePluginInfo info = +{ + PURPLE_PLUGIN_MAGIC, + PURPLE_MAJOR_VERSION, + PURPLE_MINOR_VERSION, + PURPLE_PLUGIN_STANDARD, + PIDGIN_PLUGIN_TYPE, + 0, + NULL, + PURPLE_PRIORITY_DEFAULT, + "gtk-xmppdisco", + N_("XMPP Service Discovery"), + DISPLAY_VERSION, + N_("Allows browsing and registering services."), + N_("This plugin is useful for registering with legacy transports or other " + "XMPP services."), + "Paul Aurich ", + PURPLE_WEBSITE, + plugin_load, + plugin_unload, + NULL, /**< destroy */ + NULL, /**< ui_info */ + NULL, /**< extra_info */ + NULL, /**< prefs_info */ + actions, + + /* padding */ + NULL, + NULL, + NULL, + NULL +}; + +static void +init_plugin(PurplePlugin *plugin) +{ +} + +PURPLE_INIT_PLUGIN(xmppdisco, init_plugin, info) diff -r 7358c1f6df19 -r b709ab0cb4bc pidgin/plugins/disco/xmppdisco.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/plugins/disco/xmppdisco.h Wed Jun 03 04:32:19 2009 +0000 @@ -0,0 +1,107 @@ +/* pidgin + * + * Pidgin is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef PIDGIN_XMPP_DISCO_H +#define PIDGIN_XMPP_DISCO_H + +typedef struct _XmppDiscoService XmppDiscoService; + +#include "gtkdisco.h" + +#define XMPP_PLUGIN_ID "prpl-jabber" +#define NS_DISCO_INFO "http://jabber.org/protocol/disco#info" +#define NS_DISCO_ITEMS "http://jabber.org/protocol/disco#items" +#define NS_MUC "http://jabber.org/protocol/muc" +#define NS_REGISTER "jabber:iq:register" + +#include "plugin.h" +extern PurplePlugin *my_plugin; + +/** + * The types of services. + */ +typedef enum +{ + XMPP_DISCO_SERVICE_TYPE_UNSET, + /** + * A registerable gateway to another protocol. An example would be + * XMPP legacy transports. + */ + XMPP_DISCO_SERVICE_TYPE_GATEWAY, + + /** + * A directory (e.g. allows the user to search for other users). + */ + XMPP_DISCO_SERVICE_TYPE_DIRECTORY, + + /** + * A chat (multi-user conversation). + */ + XMPP_DISCO_SERVICE_TYPE_CHAT, + + /** + * A pubsub collection (contains nodes) + */ + XMPP_DISCO_SERVICE_TYPE_PUBSUB_COLLECTION, + + /** + * A pubsub leaf (contains stuff, not nodes). + */ + XMPP_DISCO_SERVICE_TYPE_PUBSUB_LEAF, + + /** + * Something else. Do we need more categories? + */ + XMPP_DISCO_SERVICE_TYPE_OTHER +} XmppDiscoServiceType; + +/** + * The flags of services. + */ +typedef enum +{ + XMPP_DISCO_NONE = 0x0000, + XMPP_DISCO_ADD = 0x0001, /**< Supports an 'add' operation */ + XMPP_DISCO_BROWSE = 0x0002, /**< Supports browsing */ + XMPP_DISCO_REGISTER = 0x0004 /**< Supports a 'register' operation */ +} XmppDiscoServiceFlags; + +struct _XmppDiscoService { + PidginDiscoList *list; + gchar *name; + gchar *description; + + gchar *gateway_type; + XmppDiscoServiceType type; + XmppDiscoServiceFlags flags; + + XmppDiscoService *parent; + gchar *jid; + gchar *node; + gboolean expanded; +}; + +void xmpp_disco_start(PidginDiscoList *list); + +void xmpp_disco_service_expand(XmppDiscoService *service); +void xmpp_disco_service_register(XmppDiscoService *service); + +#endif /* PIDGIN_XMPP_DISCO_H */