# HG changeset patch # User Paul Aurich # Date 1241047251 0 # Node ID 413006df9828793e4344d9f614be5f7a5a0c6429 # Parent 836a36564ff504efb7f8c4f07c95f7d0304993f2# Parent 464dbfad4474c7395947ae0f7736ce96aae5f0af propagate from branch 'im.pidgin.pidgin' (head 13ac492a493b4d31c8b29905174b43a533304300) to branch 'im.pidgin.cpw.darkrain42.xmpp.disco' (head e73974597bbcc75504a88eac45d6893c182d058e) diff -r 836a36564ff5 -r 413006df9828 libpurple/Makefile.am --- a/libpurple/Makefile.am Wed Apr 29 17:50:14 2009 +0000 +++ b/libpurple/Makefile.am Wed Apr 29 23:20:51 2009 +0000 @@ -47,6 +47,7 @@ core.c \ debug.c \ desktopitem.c \ + disco.c \ eventloop.c \ ft.c \ idle.c \ @@ -107,6 +108,7 @@ dbus-maybe.h \ debug.h \ desktopitem.h \ + disco.h \ eventloop.h \ ft.h \ gaim-compat.h \ @@ -183,7 +185,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)) \ diff -r 836a36564ff5 -r 413006df9828 libpurple/Makefile.mingw --- a/libpurple/Makefile.mingw Wed Apr 29 17:50:14 2009 +0000 +++ b/libpurple/Makefile.mingw Wed Apr 29 23:20:51 2009 +0000 @@ -41,6 +41,7 @@ conversation.c \ core.c \ debug.c \ + disco.c \ dnsquery.c \ dnssrv.c \ eventloop.c \ diff -r 836a36564ff5 -r 413006df9828 libpurple/dbus-analyze-functions.py --- a/libpurple/dbus-analyze-functions.py Wed Apr 29 17:50:14 2009 +0000 +++ b/libpurple/dbus-analyze-functions.py Wed Apr 29 23:20:51 2009 +0000 @@ -31,6 +31,13 @@ "purple_account_unregister", "purple_connection_new_unregister", + # Similar to all the above: + "purple_disco_list_set_service_close_func", + "purple_disco_list_set_cancel_func", + "purple_disco_list_set_protocol_data", + "purple_disco_list_set_expand_func", + "purple_disco_list_set_register_func", + # This is excluded because this script treats PurpleLogReadFlags* # as pointer to a struct, instead of a pointer to an enum. This # causes a compilation error. Someone should fix this script. diff -r 836a36564ff5 -r 413006df9828 libpurple/disco.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/disco.c Wed Apr 29 23:20:51 2009 +0000 @@ -0,0 +1,411 @@ +/** + * @file disco.c Service Discovery API + * @ingroup core + */ + +/* purple + * + * Purple 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 "disco.h" + +/**************************************************************************/ +/* Main structures, members and constants */ +/**************************************************************************/ + +/** + * Represents a list of services for a given connection on a given protocol. + */ +struct _PurpleDiscoList { + PurpleAccount *account; /**< The account this list belongs to. */ + GList *services; /**< The list of services. */ + + gboolean in_progress; + + gpointer ui_data; /**< UI private data. */ + gpointer proto_data; /**< Prpl private data. */ + guint ref; /**< The reference count. */ + + struct { + PurpleDiscoCancelFunc cancel_cb; + PurpleDiscoCloseFunc close_cb; /**< Callback to free the prpl data */ + PurpleDiscoServiceCloseFunc service_close_cb; + PurpleDiscoServiceExpandFunc expand_cb; /**< Expand a service (iteratively + look for things that are + "sub-elements" in the tree */ + PurpleDiscoRegisterFunc register_cb; + } ops; +}; + +/** + * Represents a list of services for a given connection on a given protocol. + */ +struct _PurpleDiscoService { + PurpleDiscoList *list; + gchar *name; /**< The name of the service. */ + gchar *description; /**< The name of the service. */ + + gpointer proto_data; + + gchar *gateway_type; /**< The type of the gateway service. */ + PurpleDiscoServiceType type; /**< The type of service. */ + PurpleDiscoServiceFlags flags; +}; + +static PurpleDiscoUiOps *ops = NULL; + +PurpleDiscoList *purple_disco_list_new(PurpleAccount *account) +{ + PurpleDiscoList *list; + + g_return_val_if_fail(account != NULL, NULL); + + list = g_new0(PurpleDiscoList, 1); + list->account = account; + list->ref = 1; + + if (ops && ops->create) + ops->create(list); + + return list; +} + +PurpleDiscoList *purple_disco_list_ref(PurpleDiscoList *list) +{ + g_return_val_if_fail(list != NULL, NULL); + + list->ref++; + purple_debug_misc("disco", "reffing list, ref count now %d\n", list->ref); + + return list; +} + +static void purple_disco_list_service_destroy(PurpleDiscoList *list, PurpleDiscoService *r) +{ + if (list->ops.service_close_cb) + list->ops.service_close_cb(r); + + g_free(r->name); + g_free(r->description); + g_free(r->gateway_type); + g_free(r); +} + +static void purple_disco_list_destroy(PurpleDiscoList *list) +{ + GList *l; + + purple_debug_misc("disco", "destroying list %p\n", list); + + if (ops && ops->destroy) + ops->destroy(list); + + if (list->ops.close_cb) + list->ops.close_cb(list); + + for (l = list->services; l; l = l->next) { + PurpleDiscoService *s = l->data; + purple_disco_list_service_destroy(list, s); + } + g_list_free(list->services); + + g_free(list); +} + +void purple_disco_list_unref(PurpleDiscoList *list) +{ + g_return_if_fail(list != NULL); + g_return_if_fail(list->ref > 0); + + list->ref--; + + purple_debug_misc("disco", "unreffing list, ref count now %d\n", list->ref); + if (list->ref == 0) + purple_disco_list_destroy(list); +} + +void purple_disco_list_service_add(PurpleDiscoList *list, + PurpleDiscoService *service, + PurpleDiscoService *parent) +{ + g_return_if_fail(list != NULL); + g_return_if_fail(service != NULL); + + list->services = g_list_append(list->services, service); + service->list = list; + + if (ops && ops->add_service) + ops->add_service(list, service, parent); +} + +PurpleDiscoService *purple_disco_list_service_new(PurpleDiscoServiceType type, + const gchar *name, const gchar *description, + PurpleDiscoServiceFlags flags, gpointer proto_data) +{ + PurpleDiscoService *s; + + g_return_val_if_fail(name != NULL, NULL); + g_return_val_if_fail(type != PURPLE_DISCO_SERVICE_TYPE_UNSET, NULL); + + s = g_new0(PurpleDiscoService, 1); + s->name = g_strdup(name); + s->type = type; + s->description = g_strdup(description); + s->flags = flags; + s->proto_data = proto_data; + + return s; +} + +PurpleDiscoList *purple_disco_get_list(PurpleConnection *pc) +{ + PurplePlugin *prpl = NULL; + PurplePluginProtocolInfo *prpl_info = NULL; + + g_return_val_if_fail(pc != NULL, NULL); + + prpl = purple_connection_get_prpl(pc); + if (prpl != NULL) + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); + + if (prpl_info && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, disco_get_list)) + return prpl_info->disco_get_list(pc); + + return NULL; +} + +void purple_disco_cancel_get_list(PurpleDiscoList *list) +{ + PurpleConnection *pc; + + g_return_if_fail(list != NULL); + + pc = purple_account_get_connection(list->account); + + g_return_if_fail(pc != NULL); + + if (list->ops.cancel_cb) + list->ops.cancel_cb(list); + + purple_disco_list_set_in_progress(list, FALSE); +} + +void purple_disco_service_expand(PurpleDiscoService *service) +{ + PurpleDiscoList *list; + PurpleConnection *pc; + + g_return_if_fail(service != NULL); + g_return_if_fail((service->flags & PURPLE_DISCO_BROWSE)); + + list = service->list; + pc = purple_account_get_connection(list->account); + + g_return_if_fail(pc != NULL); + + purple_disco_list_set_in_progress(list, TRUE); + + if (list->ops.expand_cb) + list->ops.expand_cb(list, service); + else + purple_debug_warning("disco", "Cannot expand %s for account %s, " + "protocol did not provide expand op.\n", + service->name, + purple_account_get_username(list->account)); +} + +void purple_disco_service_register(PurpleDiscoService *service) +{ + PurpleDiscoList *list; + PurpleConnection *pc; + + g_return_if_fail(service != NULL); + + list = service->list; + pc = purple_account_get_connection(list->account); + + g_return_if_fail(pc != NULL); + + if (list->ops.register_cb) + list->ops.register_cb(pc, service); + else + purple_debug_warning("disco", "Cannot register to %s for account %s, " + "protocol did not provide register op.\n", + service->name, + purple_account_get_username(list->account)); +} + +const gchar *purple_disco_service_get_name(PurpleDiscoService *service) +{ + g_return_val_if_fail(service != NULL, NULL); + + return service->name; +} + +const gchar *purple_disco_service_get_description(PurpleDiscoService *service) +{ + g_return_val_if_fail(service != NULL, NULL); + + return service->description; +} + +gpointer +purple_disco_service_get_protocol_data(PurpleDiscoService *service) +{ + g_return_val_if_fail(service != NULL, NULL); + + return service->proto_data; +} + +PurpleDiscoServiceType +purple_disco_service_get_type(PurpleDiscoService *service) +{ + g_return_val_if_fail(service != NULL, PURPLE_DISCO_SERVICE_TYPE_UNSET); + + return service->type; +} + +PurpleDiscoServiceFlags +purple_disco_service_get_flags(PurpleDiscoService *service) +{ + g_return_val_if_fail(service != NULL, PURPLE_DISCO_NONE); + + return service->flags; +} + +void purple_disco_service_set_gateway_type(PurpleDiscoService *service, const gchar *type) +{ + g_return_if_fail(service != NULL); + + g_free(service->gateway_type); + service->gateway_type = g_strdup(type); +} + +const gchar *purple_disco_service_get_gateway_type(PurpleDiscoService *service) +{ + g_return_val_if_fail(service != NULL, NULL); + + return service->gateway_type; +} + +PurpleAccount* purple_disco_list_get_account(PurpleDiscoList *list) +{ + g_return_val_if_fail(list != NULL, NULL); + + return list->account; +} + +GList* purple_disco_list_get_services(PurpleDiscoList *list) +{ + g_return_val_if_fail(list != NULL, NULL); + + return list->services; +} + +void purple_disco_list_set_ui_data(PurpleDiscoList *list, gpointer ui_data) +{ + g_return_if_fail(list != NULL); + + list->ui_data = ui_data; +} + +gpointer purple_disco_list_get_ui_data(PurpleDiscoList *list) +{ + g_return_val_if_fail(list != NULL, NULL); + + return list->ui_data; +} + +void purple_disco_list_set_in_progress(PurpleDiscoList *list, + gboolean in_progress) +{ + g_return_if_fail(list != NULL); + + list->in_progress = in_progress; + + if (ops && ops->in_progress) + ops->in_progress(list, in_progress); +} + +gboolean purple_disco_list_get_in_progress(PurpleDiscoList *list) +{ + g_return_val_if_fail(list != NULL, FALSE); + + return list->in_progress; +} + +void purple_disco_list_set_protocol_data(PurpleDiscoList *list, + gpointer proto_data, + PurpleDiscoCloseFunc cb) +{ + g_return_if_fail(list != NULL); + + list->proto_data = proto_data; + list->ops.close_cb = cb; +} + +gpointer purple_disco_list_get_protocol_data(PurpleDiscoList *list) +{ + g_return_val_if_fail(list != NULL, NULL); + + return list->proto_data; +} + +void purple_disco_list_set_service_close_func(PurpleDiscoList *list, + PurpleDiscoServiceCloseFunc cb) +{ + g_return_if_fail(list != NULL); + + list->ops.service_close_cb = cb; +} + +void purple_disco_list_set_cancel_func(PurpleDiscoList *list, PurpleDiscoCancelFunc cb) +{ + g_return_if_fail(list != NULL); + + list->ops.cancel_cb = cb; +} + +void purple_disco_list_set_expand_func(PurpleDiscoList *list, PurpleDiscoServiceExpandFunc cb) +{ + g_return_if_fail(list != NULL); + + list->ops.expand_cb = cb; +} + +void purple_disco_list_set_register_func(PurpleDiscoList *list, PurpleDiscoRegisterFunc cb) +{ + g_return_if_fail(list != NULL); + + list->ops.register_cb = cb; +} + +void purple_disco_set_ui_ops(PurpleDiscoUiOps *ui_ops) +{ + ops = ui_ops; +} + +PurpleDiscoUiOps *purple_disco_get_ui_ops(void) +{ + return ops; +} diff -r 836a36564ff5 -r 413006df9828 libpurple/disco.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/disco.h Wed Apr 29 23:20:51 2009 +0000 @@ -0,0 +1,414 @@ +/** + * @file disco.h Service Discovery API + * @ingroup core + */ + +/* purple + * + * Purple 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 _PURPLE_DISCO_H_ +#define _PURPLE_DISCO_H_ + + +typedef struct _PurpleDiscoList PurpleDiscoList; +typedef struct _PurpleDiscoService PurpleDiscoService; +typedef struct _PurpleDiscoUiOps PurpleDiscoUiOps; + +#include "account.h" +#include "connection.h" + +/** + * A prpl callback called to tell the prpl to cancel creating the list. + * The prpl must implement this and must not add services to the + * PurpleDiscoList after this is called. + * + * @param list The disco list. + */ +typedef void (*PurpleDiscoCancelFunc)(PurpleDiscoList *list); + +/** + * A prpl callback called to tell the prpl to tell it to destroy + * prpl-specific data relating to this PurpleDiscoList (which will be + * destroyed imminently). + * + * @param list The disco list. + */ +typedef void (*PurpleDiscoCloseFunc)(PurpleDiscoList *list); +typedef void (*PurpleDiscoServiceCloseFunc)(PurpleDiscoService *service); + +typedef void (*PurpleDiscoServiceExpandFunc)(PurpleDiscoList *list, + PurpleDiscoService *service); + +/** + * A prpl callback called to initiate registration with the specificed + * service. + * + * @param pc The connection. + * @param service The service to which to register. + * + */ +typedef void (*PurpleDiscoRegisterFunc)(PurpleConnection *pc, + PurpleDiscoService *service); + +/** + * The types of services. + */ +typedef enum +{ + PURPLE_DISCO_SERVICE_TYPE_UNSET, + /** + * A registerable gateway to another protocol. An example would be + * XMPP legacy transports. + */ + PURPLE_DISCO_SERVICE_TYPE_GATEWAY, + + /** + * A directory (e.g. allows the user to search for other users). + */ + PURPLE_DISCO_SERVICE_TYPE_DIRECTORY, + + /** + * A chat (multi-user conversation). + */ + PURPLE_DISCO_SERVICE_TYPE_CHAT, + + /** + * Something else. Do we need more categories? + */ + PURPLE_DISCO_SERVICE_TYPE_OTHER +} PurpleDiscoServiceType; + +/** + * The flags of services. + */ +typedef enum +{ + PURPLE_DISCO_NONE = 0x0000, + PURPLE_DISCO_ADD = 0x0001, /**< Supports an 'add' operation */ + PURPLE_DISCO_BROWSE = 0x0002, /**< Supports browsing */ + PURPLE_DISCO_REGISTER = 0x0004 /**< Supports a 'register' operation */ +} PurpleDiscoServiceFlags; + +struct _PurpleDiscoUiOps { + /** Ask the UI to display a dialog for the specified account. + */ + void (*dialog_show_with_account)(PurpleAccount* account); + void (*create)(PurpleDiscoList *list); /**< Sets UI-specific data on a disco list */ + void (*destroy)(PurpleDiscoList *list); /**< Free UI-specific data on the disco list */ + void (*add_service)(PurpleDiscoList *list, PurpleDiscoService *service, PurpleDiscoService *parent); /**< Add service to dialog */ + void (*in_progress)(PurpleDiscoList *list, gboolean in_progress); /**< Set progress to dialog */ + + /* Padding */ + void (*_purple_reserved1)(void); + void (*_purple_reserved2)(void); + void (*_purple_reserved3)(void); + void (*_purple_reserved4)(void); +}; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Returns a newly created service discovery object. + * + * It has an initial reference count of 1. + * + * @param account The account that's listing rooms. + * @return The new service discovery list handle. + */ +PurpleDiscoList *purple_disco_list_new(PurpleAccount *account); + +/** + * Increases the reference count on the service discovery list. + * + * @param list The disco list to ref. + */ +PurpleDiscoList *purple_disco_list_ref(PurpleDiscoList *list); + +/** + * Decreases the reference count on the service discovery list. + * + * The room list will be destroyed when this reaches 0. + * + * @param list The room list object to unref and possibly + * destroy. + */ +void purple_disco_list_unref(PurpleDiscoList *list); + +/** + * Instructs the prpl to start fetching the list. + * + */ +PurpleDiscoList *purple_disco_get_list(PurpleConnection *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 service list to cancel a get_list on. + */ +void purple_disco_cancel_get_list(PurpleDiscoList *list); + +/** + * Create new service object + */ +PurpleDiscoService * +purple_disco_list_service_new(PurpleDiscoServiceType type, const gchar *name, + const gchar *description, + PurpleDiscoServiceFlags flags, + gpointer proto_data); + +/** + * Add service to list + */ +void purple_disco_list_service_add(PurpleDiscoList *list, PurpleDiscoService *service, PurpleDiscoService *parent); + +/** + * Expand a (browsable) service. The UI should call this in order to + * iteratively browse the children of this service. The service must + * have the PURPLE_DISCO_BROWSE flag set. + * + * You probably don't want to call this if the service already has children. + * + * @param service The browsable disco service. + * + * @since TODO + */ +void purple_disco_service_expand(PurpleDiscoService *service); + +/** + * Register service + * @param service The service that will be registered + */ +void purple_disco_service_register(PurpleDiscoService *service); + +/** + * Returns a service's name. + * + * @param service The service. + * @return The name. + * + * @since TODO + */ +const gchar *purple_disco_service_get_name(PurpleDiscoService *service); + +/** + * Return a service's description. + * + * @param service The service. + * @return The description. + * + * @since TODO + */ +const gchar* purple_disco_service_get_description(PurpleDiscoService *service); + +/** + * Returns the service's protocol-specific data. + * + * This should only be called from the associated prpl. + * + * @param service The disco service. + * @return The protocol data. + * + * @since TODO + */ +gpointer purple_disco_service_get_protocol_data(PurpleDiscoService *service); + +/** + * Return a service's type. + * + * @param service The service. + * @return The type. + * + * @since TODO + */ +PurpleDiscoServiceType purple_disco_service_get_type(PurpleDiscoService *service); + +/** + * Return a service's flags. + * + * @param service The service. + * @return The flags. + * + * @since TODO + */ +PurpleDiscoServiceFlags purple_disco_service_get_flags(PurpleDiscoService *service); + +/** + * Set the gateway type for a gateway service. The gateway type is a string + * that represents a canonical name of the protocol to which this service is + * a gateway. For example, for an XMPP legacy transport to AIM, this would + * be "aim". + * + * These strings should conform to the names of the libpurple prpls where + * possible (so a UI can easily map types to icons) and, as a backup, the + * XMPP registry list of gateways at + * http://xmpp.org/registrar/disco-categories.html#gateway. + */ +void purple_disco_service_set_gateway_type(PurpleDiscoService *service, + const gchar *type); + +/** + * Get the gateway type for a gateway service. + * + * @param service The service. + * @returns The gateway type or NULL if none was set or service is not + * a gateway. + * + * @see purple_disco_service_set_gateway_type(). + */ +const gchar *purple_disco_service_get_gateway_type(PurpleDiscoService *service); + +/** + * Get the account associated with a service list. + * + * @param list The service list. + * @return The account + * + * @since TODO + */ +PurpleAccount* purple_disco_list_get_account(PurpleDiscoList *list); + +/** + * Get a list of the services associated with this service list. + * + * @param dl The serivce list. + * @returns A list of PurpleDiscoService items. + * + * @since TODO + */ +GList* purple_disco_list_get_services(PurpleDiscoList *dl); + +/** + * Set the service list's UI data. + * + * @param list The service list. + * @param data The data. + * + * @see purple_disco_list_get_ui_data() + * @since TODO + */ +void purple_disco_list_set_ui_data(PurpleDiscoList *list, gpointer data); + +/** + * Get the service list's UI data. + * + * @param list The service list. + * @return The data. + * + * @see purple_disco_list_set_ui_data() + * @since TODO + */ +gpointer purple_disco_list_get_ui_data(PurpleDiscoList *list); + +/** + * Set the "in progress" state of the Service Discovery. + * + * The UI is encouraged to somehow hint to the user + * whether or not we're busy downloading a service list or not. + * + * @param list The service list. + * @param in_progress We're downloading it, or we're not. + * + * @see purple_disco_list_get_in_progress() + * @since TODO + */ +void purple_disco_list_set_in_progress(PurpleDiscoList *list, gboolean in_progress); + +/** + * Gets the "in progress" state of the Service Discovery. + * + * The UI is encouraged to somehow hint to the user + * whether or not we're busy downloading a service list or not. + * + * @param list The service list. + * @return True if we're downloading it, or false if we're not. + * + * @see purple_disco_list_set_in_progress() + * @since TODO + */ +gboolean purple_disco_list_get_in_progress(PurpleDiscoList *list); + +/** + * Sets the disco list's protocol-specific data. + * + * This should only be called from the associated prpl. + * + * @param list The disco list. + * @param data The protocol data. + * @param close_cb The function to be called when destroying the disco list to + * free the protocol data. May be NULL if the data does not need + * to be freed. + * + * @see purple_disco_list_get_protocol_data() + * @since TODO + */ +void purple_disco_list_set_protocol_data(PurpleDiscoList *list, gpointer data, + PurpleDiscoCloseFunc cb); + +/** + * Returns the disco list's protocol-specific data. + * + * This should only be called from the associated prpl. + * + * @param list The disco list. + * @return The protocol data. + * + * @see purple_disco_list_set_protocol_data() + * @since TODO + */ +gpointer purple_disco_list_get_protocol_data(PurpleDiscoList *list); + +void purple_disco_list_set_service_close_func(PurpleDiscoList *list, + PurpleDiscoServiceCloseFunc cb); +void purple_disco_list_set_cancel_func(PurpleDiscoList *list, + PurpleDiscoCancelFunc cb); +void purple_disco_list_set_expand_func(PurpleDiscoList *list, + PurpleDiscoServiceExpandFunc cb); +void purple_disco_list_set_register_func(PurpleDiscoList *list, + PurpleDiscoRegisterFunc cb); + +/** + * Sets the UI operations structure to be used in all purple service discovery. + * + * @param ops The UI operations structure. + * + * @since TODO + */ +void purple_disco_set_ui_ops(PurpleDiscoUiOps *ui_ops); + +/** + * Returns the service discovery UI operations structure. + * + * @return A filled-out PurpleDiscoUiOps structure. + * + * @since TODO + */ +PurpleDiscoUiOps *purple_disco_get_ui_ops(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _PURPLE_DISCO_H_ */ diff -r 836a36564ff5 -r 413006df9828 libpurple/protocols/jabber/disco.c --- a/libpurple/protocols/jabber/disco.c Wed Apr 29 17:50:14 2009 +0000 +++ b/libpurple/protocols/jabber/disco.c Wed Apr 29 23:20:51 2009 +0000 @@ -1,5 +1,5 @@ /* - * purple - Jabber Protocol Plugin + * purple - Jabber Service Discovery * * Copyright (C) 2003, Nathan Walp * @@ -40,11 +40,28 @@ 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); \ } +struct jabber_disco_list_data { + JabberStream *js; /* TODO: Needed? */ + PurpleDiscoList *list; + char *server; + int fetch_count; +}; + +struct jabber_disco_service_data { + char *jid; + char *node; +}; + static void jabber_disco_bytestream_server_cb(JabberStream *js, const char *from, JabberIqType type, const char *id, @@ -160,11 +177,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"); @@ -173,7 +190,37 @@ } 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; @@ -190,7 +237,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; @@ -242,6 +289,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; } @@ -257,19 +306,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))) @@ -281,7 +323,6 @@ capabilities = jbr->capabilities; jdicd->callback(js, from, capabilities, jdicd->data); - g_hash_table_remove(js->disco_callbacks, from); } } @@ -371,6 +412,12 @@ } +struct _disco_data { + struct jabber_disco_list_data *list_data; + PurpleDiscoService *parent; + char *node; +}; + static void jabber_disco_server_info_result_cb(JabberStream *js, const char *from, JabberIqType type, const char *id, @@ -526,10 +573,541 @@ 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); } + +static void +jabber_disco_list_data_destroy(struct jabber_disco_list_data *data) +{ + g_free(data->server); + g_free(data); +} + +static void +disco_proto_data_destroy_cb(PurpleDiscoList *list) +{ + struct jabber_disco_list_data *data; + + data = purple_disco_list_get_protocol_data(list); + jabber_disco_list_data_destroy(data); +} + +static PurpleDiscoServiceType +jabber_disco_category_from_string(const gchar *str) +{ + if (!g_ascii_strcasecmp(str, "gateway")) + return PURPLE_DISCO_SERVICE_TYPE_GATEWAY; + else if (!g_ascii_strcasecmp(str, "directory")) + return PURPLE_DISCO_SERVICE_TYPE_DIRECTORY; + else if (!g_ascii_strcasecmp(str, "conference")) + return PURPLE_DISCO_SERVICE_TYPE_CHAT; + + return PURPLE_DISCO_SERVICE_TYPE_OTHER; +} + +static const struct { + const char *from; + const char *to; +} disco_type_mappings[] = { + { "gadu-gadu", "gg" }, + { "sametime", "meanwhile" }, + { "myspaceim", "myspace" }, + { "xmpp", "jabber" }, + { NULL, NULL } +}; + +static const gchar * +jabber_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 +jabber_disco_service_info_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data); + +static void +jabber_disco_service_items_cb(JabberStream *js, const char *who, const char *node, + GSList *items, gpointer data) +{ + GSList *l; + struct _disco_data *disco_data; + struct jabber_disco_list_data *list_data; + PurpleDiscoList *list; + PurpleDiscoService *parent; + PurpleDiscoServiceType parent_type; + const char *parent_node; + + disco_data = data; + list_data = disco_data->list_data; + list = list_data->list; + + --list_data->fetch_count; + + if (list_data->list == NULL) { + if (list_data->fetch_count == 0) + jabber_disco_list_data_destroy(list_data); + + return; + } + + if (items == NULL) { + if (list_data->fetch_count == 0) + purple_disco_list_set_in_progress(list, FALSE); + + purple_disco_list_unref(list); + return; + } + + parent = disco_data->parent; + parent_type = purple_disco_service_get_type(parent); + parent_node = disco_data->node; + + for (l = items; l; l = l->next) { + JabberDiscoItem *item = l->data; + + if (parent_type & PURPLE_DISCO_SERVICE_TYPE_CHAT) { + /* A chat server's items are chats. I promise. */ + PurpleDiscoService *service; + struct jabber_disco_service_data *service_data; + JabberID *jid = jabber_id_new(item->jid); + + if (jid) { + service_data = g_new0(struct jabber_disco_service_data, 1); + service_data->jid = g_strdup(item->jid); + + service = purple_disco_list_service_new(PURPLE_DISCO_SERVICE_TYPE_CHAT, + jid->node, item->name, PURPLE_DISCO_ADD, service_data); + + purple_disco_list_service_add(list, service, parent); + + jabber_id_free(jid); + } + } else { + JabberIq *iq; + struct _disco_data *req_data; + char *full_node; + + if (parent_node && !item->node) + continue; + + if (parent_node) + full_node = g_strconcat(parent_node, "/", item->node, NULL); + else + full_node = g_strdup(item->node); + + req_data = g_new0(struct _disco_data, 1); + req_data->list_data = list_data; + req_data->parent = parent; + req_data->node = full_node; + + ++list_data->fetch_count; + 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", item->jid); + if (full_node) + xmlnode_set_attrib(xmlnode_get_child(iq->node, "query"), + "node", full_node); + jabber_iq_set_callback(iq, jabber_disco_service_info_cb, req_data); + + jabber_iq_send(iq); + } + } + + g_slist_foreach(items, (GFunc)jabber_disco_item_free, NULL); + g_slist_free(items); + + if (list_data->fetch_count == 0) + purple_disco_list_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, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ + struct _disco_data *disco_data; + struct jabber_disco_list_data *list_data; + xmlnode *query, *identity, *child; + const char *anode; + char *aname, *node; + + PurpleDiscoList *list; + PurpleDiscoService *parent, *service; + PurpleDiscoServiceType service_type; + PurpleDiscoServiceFlags flags; + struct jabber_disco_service_data *service_data; + + disco_data = data; + list_data = disco_data->list_data; + list = list_data->list; + parent = disco_data->parent; + + node = disco_data->node; + disco_data->node = NULL; + g_free(disco_data); + + --list_data->fetch_count; + + if (!purple_disco_list_get_in_progress(list)) { + purple_disco_list_unref(list); + return; + } + + if (!from || type == JABBER_IQ_ERROR + || (!(query = xmlnode_get_child(packet, "query"))) + || (!(identity = xmlnode_get_child(query, "identity")))) { + if (list_data->fetch_count == 0) + purple_disco_list_set_in_progress(list, FALSE); + + purple_disco_list_unref(list); + return; + } + + service_type = jabber_disco_category_from_string( + xmlnode_get_attrib(identity, "category")); + + /* Default to allowing things to be add-able */ + flags = PURPLE_DISCO_ADD; + + 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 (g_str_equal(var, "jabber:iq:register")) + flags |= PURPLE_DISCO_REGISTER; + + if (g_str_equal(var, "http://jabber.org/protocol/disco#items")) + flags |= PURPLE_DISCO_BROWSE; + + if (g_str_equal(var, "http://jabber.org/protocol/muc")) { + flags |= PURPLE_DISCO_BROWSE; + service_type = PURPLE_DISCO_SERVICE_TYPE_CHAT; + } + } + + if ((anode = xmlnode_get_attrib(query, "node"))) + aname = g_strconcat(from, anode, NULL); + else + aname = g_strdup(from); + + service_data = g_new0(struct jabber_disco_service_data, 1); + service_data->jid = g_strdup(from); + if (anode) + service_data->node = g_strdup(anode); + + service = purple_disco_list_service_new(service_type, aname, + xmlnode_get_attrib(identity, "name"), flags, service_data); + g_free(aname); + + if (service_type == PURPLE_DISCO_SERVICE_TYPE_GATEWAY) + purple_disco_service_set_gateway_type(service, + jabber_disco_type_from_string(xmlnode_get_attrib(identity, + "type"))); + + purple_disco_list_service_add(list, service, parent); + + if (list_data->fetch_count == 0) + purple_disco_list_set_in_progress(list, FALSE); + + purple_disco_list_unref(list); + + g_free(node); +} + +static void +jabber_disco_server_items_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ + struct jabber_disco_list_data *list_data; + xmlnode *query, *child; + gboolean has_items = FALSE; + + list_data = data; + --list_data->fetch_count; + + if (!from || type == JABBER_IQ_ERROR || + !purple_disco_list_get_in_progress(list_data->list)) { + purple_disco_list_unref(list_data->list); + 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"))) + continue; + + disco_data = g_new0(struct _disco_data, 1); + disco_data->list_data = list_data; + + has_items = TRUE; + ++list_data->fetch_count; + purple_disco_list_ref(list_data->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_list_set_in_progress(list_data->list, FALSE); + + purple_disco_list_unref(list_data->list); +} + +static void +jabber_disco_server_info_cb(JabberStream *js, const char *who, JabberCapabilities caps, gpointer data) +{ + struct jabber_disco_list_data *list_data; + + list_data = data; + --list_data->fetch_count; + + if (!list_data->list) { + purple_disco_list_unref(list_data->list); + + if (list_data->fetch_count == 0) + jabber_disco_list_data_destroy(list_data); + + return; + } + + if (caps & JABBER_CAP_ITEMS) { + JabberIq *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_data); + + ++list_data->fetch_count; + purple_disco_list_ref(list_data->list); + + jabber_iq_send(iq); + } else { + purple_notify_error(NULL, _("Error"), _("Server doesn't support service discovery"), NULL); + purple_disco_list_set_in_progress(list_data->list, FALSE); + } + + purple_disco_list_unref(list_data->list); +} + +static void discolist_cancel_cb(struct jabber_disco_list_data *list_data, const char *server) +{ + purple_disco_list_set_in_progress(list_data->list, FALSE); + purple_disco_list_unref(list_data->list); +} + +static void discolist_ok_cb(struct jabber_disco_list_data *list_data, const char *server) +{ + JabberStream *js; + + js = list_data->js; + + if (!server || !*server) { + purple_notify_error(js->gc, _("Invalid Server"), _("Invalid Server"), NULL); + + purple_disco_list_set_in_progress(list_data->list, FALSE); + purple_disco_list_unref(list_data->list); + return; + } + + list_data->server = g_strdup(server); + if (js->last_disco_server) + g_free(js->last_disco_server); + js->last_disco_server = g_strdup(server); + + purple_disco_list_set_in_progress(list_data->list, TRUE); + + ++list_data->fetch_count; + jabber_disco_info_do(js, list_data->server, jabber_disco_server_info_cb, list_data); +} + +static void +jabber_disco_service_close(PurpleDiscoService *service) +{ + struct jabber_disco_service_data *data; + + data = purple_disco_service_get_protocol_data(service); + g_free(data->jid); + g_free(data->node); + g_free(data); +} + +#if 0 +static void +jabber_disco_cancel(PurpleDiscoList *list) +{ + /* This space intentionally letft blank */ +} +#endif + +static void +jabber_disco_list_expand(PurpleDiscoList *list, PurpleDiscoService *service) +{ + PurpleAccount *account; + PurpleConnection *pc; + JabberStream *js; + struct jabber_disco_list_data *list_data; + struct jabber_disco_service_data *service_data; + struct _disco_data *disco_data; + + account = purple_disco_list_get_account(list); + pc = purple_account_get_connection(account); + js = purple_connection_get_protocol_data(pc); + + list_data = purple_disco_list_get_protocol_data(list); + + ++list_data->fetch_count; + purple_disco_list_ref(list); + disco_data = g_new0(struct _disco_data, 1); + disco_data->list_data = list_data; + disco_data->parent = service; + + service_data = purple_disco_service_get_protocol_data(service); + + jabber_disco_items_do(js, service_data->jid, service_data->node, + jabber_disco_service_items_cb, disco_data); +} + +static void +jabber_disco_service_register(PurpleConnection *gc, PurpleDiscoService *service) +{ + JabberStream *js = purple_connection_get_protocol_data(gc); + + jabber_register_gateway(js, purple_disco_service_get_name(service)); +} + + +PurpleDiscoList * +jabber_disco_get_list(PurpleConnection *gc) +{ + PurpleAccount *account; + PurpleDiscoList *list; + JabberStream *js; + struct jabber_disco_list_data *disco_list_data; + + account = purple_connection_get_account(gc); + js = purple_connection_get_protocol_data(gc); + + /* We start with a ref */ + list = purple_disco_list_new(account); + + disco_list_data = g_new0(struct jabber_disco_list_data, 1); + disco_list_data->list = list; + disco_list_data->js = js; + purple_disco_list_set_protocol_data(list, disco_list_data, + disco_proto_data_destroy_cb); + purple_disco_list_set_service_close_func(list, jabber_disco_service_close); +#if 0 + purple_disco_list_set_cancel_func(list, jabber_disco_cancel); +#endif + purple_disco_list_set_expand_func(list, jabber_disco_list_expand); + purple_disco_list_set_register_func(list, jabber_disco_service_register); + + purple_request_input(gc, _("Server name request"), _("Enter an XMPP Server"), + _("Select an XMPP server to query"), + js->last_disco_server ? js->last_disco_server : js->user->domain, + FALSE, FALSE, NULL, + _("Find Services"), PURPLE_CALLBACK(discolist_ok_cb), + _("Cancel"), PURPLE_CALLBACK(discolist_cancel_cb), + account, NULL, NULL, disco_list_data); + + return list; +} + +static void +jabber_disco_items_cb(JabberStream *js, const char *from, JabberIqType type, + const char *id, xmlnode *packet, gpointer data) +{ + struct _jabber_disco_items_cb_data *jdicd; + xmlnode *query, *child; + const char *node = NULL; + GSList *items = NULL; + + jdicd = data; + + query = xmlnode_get_child(packet, "query"); + + if (query) + node = xmlnode_get_attrib(query, "node"); + + if (!from || !query || type == JABBER_IQ_ERROR) { + jdicd->callback(js, from, node, NULL, jdicd->data); + g_free(jdicd); + return; + } + + for (child = xmlnode_get_child(query, "item"); child; + child = xmlnode_get_next_twin(child)) { + JabberDiscoItem *item; + + item = g_new0(JabberDiscoItem, 1); + item->jid = g_strdup(xmlnode_get_attrib(child, "jid")); + item->node = g_strdup(xmlnode_get_attrib(child, "node")); + item->name = g_strdup(xmlnode_get_attrib(child, "name")); + + items = g_slist_prepend(items, item); + } + + items = g_slist_reverse(items); + jdicd->callback(js, from, node, items, jdicd->data); + g_free(jdicd); +} + +void jabber_disco_items_do(JabberStream *js, const char *who, const char *node, + JabberDiscoItemsCallback *callback, gpointer data) +{ + struct _jabber_disco_items_cb_data *jdicd; + JabberIq *iq; + + jdicd = g_new0(struct _jabber_disco_items_cb_data, 1); + jdicd->data = data; + jdicd->callback = callback; + + iq = jabber_iq_new_query(js, JABBER_IQ_GET, "http://jabber.org/protocol/disco#items"); + if (node) + xmlnode_set_attrib(xmlnode_get_child(iq->node, "query"), "node", node); + + xmlnode_set_attrib(iq->node, "to", who); + + jabber_iq_set_callback(iq, jabber_disco_items_cb, jdicd); + jabber_iq_send(iq); +} + +void jabber_disco_item_free(JabberDiscoItem *item) +{ + g_free((char *)item->jid); + g_free((char *)item->node); + g_free((char *)item->name); + g_free(item); +} diff -r 836a36564ff5 -r 413006df9828 libpurple/protocols/jabber/disco.h --- a/libpurple/protocols/jabber/disco.h Wed Apr 29 17:50:14 2009 +0000 +++ b/libpurple/protocols/jabber/disco.h Wed Apr 29 23:20:51 2009 +0000 @@ -1,5 +1,5 @@ /** - * @file iq.h JabberID 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, @@ -37,4 +46,10 @@ void jabber_disco_info_do(JabberStream *js, const char *who, JabberDiscoInfoCallback *callback, gpointer data); +PurpleDiscoList *jabber_disco_get_list(PurpleConnection *gc); + +void jabber_disco_items_do(JabberStream *js, const char *jid, const char *node, + JabberDiscoItemsCallback *callback, gpointer data); +void jabber_disco_item_free(JabberDiscoItem *); + #endif /* PURPLE_JABBER_DISCO_H_ */ diff -r 836a36564ff5 -r 413006df9828 libpurple/protocols/jabber/jabber.c --- a/libpurple/protocols/jabber/jabber.c Wed Apr 29 17:50:14 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.c Wed Apr 29 23:20:51 2009 +0000 @@ -767,8 +767,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, @@ -1090,6 +1088,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) { @@ -1097,10 +1113,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; @@ -1157,74 +1174,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); @@ -1297,8 +1293,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) @@ -1555,6 +1547,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); diff -r 836a36564ff5 -r 413006df9828 libpurple/protocols/jabber/jabber.h --- a/libpurple/protocols/jabber/jabber.h Wed Apr 29 17:50:14 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.h Wed Apr 29 23:20:51 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" @@ -151,7 +153,6 @@ GList *user_directories; GHashTable *iq_callbacks; - GHashTable *disco_callbacks; int next_id; GList *bs_proxies; @@ -266,6 +267,12 @@ int stun_port; PurpleDnsQueryData *stun_query; /* later add stuff to handle TURN relays... */ + + /** + * 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 *namespace); diff -r 836a36564ff5 -r 413006df9828 libpurple/protocols/jabber/libxmpp.c --- a/libpurple/protocols/jabber/libxmpp.c Wed Apr 29 17:50:14 2009 +0000 +++ b/libpurple/protocols/jabber/libxmpp.c Wed Apr 29 23:20:51 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,10 +118,12 @@ 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 */ jabber_get_media_caps, /* get_media_caps */ + jabber_disco_get_list, /* disco_get_list */ }; static gboolean load_plugin(PurplePlugin *plugin) diff -r 836a36564ff5 -r 413006df9828 libpurple/prpl.h --- a/libpurple/prpl.h Wed Apr 29 17:50:14 2009 +0000 +++ b/libpurple/prpl.h Wed Apr 29 23:20:51 2009 +0000 @@ -70,6 +70,7 @@ #include "proxy.h" #include "plugin.h" #include "roomlist.h" +#include "disco.h" #include "status.h" #include "whiteboard.h" @@ -481,6 +482,11 @@ */ PurpleMediaCaps (*get_media_caps)(PurpleAccount *account, const char *who); + + /** + * Service discovery prpl callbacks + */ + PurpleDiscoList *(*disco_get_list)(PurpleConnection *gc); }; #define PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl, member) \ diff -r 836a36564ff5 -r 413006df9828 libpurple/purple.h.in --- a/libpurple/purple.h.in Wed Apr 29 17:50:14 2009 +0000 +++ b/libpurple/purple.h.in Wed Apr 29 23:20:51 2009 +0000 @@ -79,6 +79,7 @@ #include #include #include +#include #include #include #include diff -r 836a36564ff5 -r 413006df9828 pidgin/Makefile.am --- a/pidgin/Makefile.am Wed Apr 29 17:50:14 2009 +0000 +++ b/pidgin/Makefile.am Wed Apr 29 23:20:51 2009 +0000 @@ -90,6 +90,7 @@ gtkconv.c \ gtkdebug.c \ gtkdialogs.c \ + gtkdisco.c \ gtkdnd-hints.c \ gtkdocklet.c \ gtkdocklet-x11.c \ @@ -149,6 +150,7 @@ gtkconvwin.h \ gtkdebug.h \ gtkdialogs.h \ + gtkdisco.h \ gtkdnd-hints.h \ gtkdocklet.h \ gtkeventloop.h \ diff -r 836a36564ff5 -r 413006df9828 pidgin/Makefile.mingw --- a/pidgin/Makefile.mingw Wed Apr 29 17:50:14 2009 +0000 +++ b/pidgin/Makefile.mingw Wed Apr 29 23:20:51 2009 +0000 @@ -65,6 +65,7 @@ gtkconv.c \ gtkdebug.c \ gtkdialogs.c \ + gtkdisco.c \ gtkdnd-hints.c \ gtkdocklet.c \ gtkeventloop.c \ diff -r 836a36564ff5 -r 413006df9828 pidgin/gtkblist.c --- a/pidgin/gtkblist.c Wed Apr 29 17:50:14 2009 +0000 +++ b/pidgin/gtkblist.c Wed Apr 29 23:20:51 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" @@ -3380,6 +3381,7 @@ { N_("/Tools/Pr_ivacy"), NULL, pidgin_privacy_dialog_show, 0, "", NULL }, { "/Tools/sep2", NULL, NULL, 0, "", NULL }, { N_("/Tools/_File Transfers"), "T", pidgin_xfer_dialog_show, 0, "", PIDGIN_STOCK_TOOLBAR_TRANSFER }, + { N_("/Tools/Service _Discovery"), NULL, pidgin_disco_dialog_show, 0, "", NULL }, { N_("/Tools/R_oom List"), NULL, pidgin_roomlist_dialog_show, 0, "", NULL }, { N_("/Tools/System _Log"), NULL, gtk_blist_show_systemlog_cb, 3, "", NULL }, { "/Tools/sep3", NULL, NULL, 0, "", NULL }, @@ -4263,6 +4265,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 diff -r 836a36564ff5 -r 413006df9828 pidgin/gtkdisco.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkdisco.c Wed Apr 29 23:20:51 2009 +0000 @@ -0,0 +1,582 @@ +/** + * @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 "pidgin.h" +#include "gtkutils.h" +#include "debug.h" +#include "disco.h" + +#include "gtkdisco.h" + +typedef struct _PidginDiscoDialog { + GtkWidget *window; + GtkWidget *account_widget; + + GtkWidget *sw; + GtkWidget *progress; + + GtkWidget *stop_button; + GtkWidget *list_button; + GtkWidget *register_button; + GtkWidget *add_button; + GtkWidget *close_button; + + PurpleAccount *account; + PurpleDiscoList *discolist; +} PidginDiscoDialog; + +typedef struct _PidginDiscoList { + PidginDiscoDialog *dialog; + GtkTreeStore *model; + GtkWidget *tree; + GHashTable *services; +} PidginDiscoList; + +struct _menu_cb_info { + PurpleDiscoList *list; + PurpleDiscoService *service; +}; + +enum { + PIXBUF_COLUMN = 0, + NAME_COLUMN, + DESCRIPTION_COLUMN, + SERVICE_COLUMN, + NUM_OF_COLUMNS +}; + +static void pidgin_disco_create_tree(PidginDiscoList *pdl); + +static void dialog_select_account_cb(GObject *w, PurpleAccount *account, + PidginDiscoDialog *dialog) +{ + dialog->account = account; +} + +static void register_button_cb(GtkButton *button, PidginDiscoDialog *dialog) +{ + struct _menu_cb_info *info = g_object_get_data(G_OBJECT(button), "disco-info"); + PurpleAccount *account; + PurpleConnection *gc; + + account = purple_disco_list_get_account(info->list); + gc = purple_account_get_connection(account); + + purple_disco_service_register(info->service); +} + +static void list_button_cb(GtkButton *button, PidginDiscoDialog *dialog) +{ + PurpleConnection *gc; + PidginDiscoList *pdl; + + gc = purple_account_get_connection(dialog->account); + if (!gc) + return; + + gtk_widget_set_sensitive(dialog->add_button, FALSE); + gtk_widget_set_sensitive(dialog->register_button, FALSE); + + if (dialog->discolist != NULL) { + pdl = purple_disco_list_get_ui_data(dialog->discolist); + gtk_widget_destroy(pdl->tree); + purple_disco_list_unref(dialog->discolist); + } + + dialog->discolist = purple_disco_get_list(gc); + if (!dialog->discolist) + return; + /* We keep a copy... */ + purple_disco_list_ref(dialog->discolist); + + pdl = purple_disco_list_get_ui_data(dialog->discolist); + pdl->dialog = dialog; + + pidgin_disco_create_tree(pdl); + + if (dialog->account_widget) + gtk_widget_set_sensitive(dialog->account_widget, FALSE); +} + +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_disco_list_get_account(info->list); + name = purple_disco_service_get_name(info->service); + + if (purple_disco_service_get_type(info->service) == PURPLE_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) +{ + PurpleDiscoService *service; + PurpleDiscoServiceFlags flags; + 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); + + flags = purple_disco_service_get_flags(service); + + gtk_widget_set_sensitive(dialog->add_button, flags & PURPLE_DISCO_ADD); + gtk_widget_set_sensitive(dialog->register_button, flags & PURPLE_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; + PurpleDiscoService *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); + purple_disco_service_expand(service); +} + +static gint +delete_win_cb(GtkWidget *w, GdkEventAny *e, gpointer d) +{ + PidginDiscoDialog *dialog = d; + + if (dialog->discolist && purple_disco_list_get_in_progress(dialog->discolist)) + purple_disco_cancel_get_list(dialog->discolist); + + if (dialog->discolist) { + PidginDiscoList *pdl = purple_disco_list_get_ui_data(dialog->discolist); + + if (pdl) + pdl->dialog = NULL; + purple_disco_list_unref(dialog->discolist); + } + + g_free(dialog); + + return FALSE; +} + +static void stop_button_cb(GtkButton *button, PidginDiscoDialog *dialog) +{ + purple_disco_cancel_get_list(dialog->discolist); + + if (dialog->account_widget) + gtk_widget_set_sensitive(dialog->account_widget, TRUE); + + gtk_widget_set_sensitive(dialog->stop_button, FALSE); + gtk_widget_set_sensitive(dialog->list_button, TRUE); + gtk_widget_set_sensitive(dialog->add_button, FALSE); +} + +static void close_button_cb(GtkButton *button, PidginDiscoDialog *dialog) +{ + GtkWidget *window = dialog->window; + + delete_win_cb(NULL, NULL, dialog); + gtk_widget_destroy(window); +} + +static gboolean account_filter_func(PurpleAccount *account) +{ + PurpleConnection *conn = purple_account_get_connection(account); + PurplePluginProtocolInfo *prpl_info = NULL; + + if (conn) + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(conn->prpl); + + return (prpl_info && PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, disco_get_list)); +} + +gboolean +pidgin_disco_is_showable() +{ + GList *c; + PurpleConnection *gc; + + for (c = purple_connections_get_all(); c != NULL; c = c->next) { + gc = c->data; + + if (account_filter_func(purple_connection_get_account(gc))) + return TRUE; + } + + return FALSE; +} + +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); +} + +static PidginDiscoDialog* +pidgin_disco_dialog_new_with_account(PurpleAccount *account) +{ + PidginDiscoDialog *dialog; + GtkWidget *window, *vbox, *vbox2, *bbox; + + dialog = g_new0(PidginDiscoDialog, 1); + dialog->account = account; + + /* Create the window. */ + dialog->window = window = pidgin_create_dialog(_("Service Discovery"), PIDGIN_HIG_BORDER, "service discovery", TRUE); + + g_signal_connect(G_OBJECT(window), "delete_event", + G_CALLBACK(delete_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(dialog->account, FALSE, + G_CALLBACK(dialog_select_account_cb), account_filter_func, dialog); + if (!dialog->account) /* this is normally null, and we normally don't care what the first selected item is */ + 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); + + /* list button */ + dialog->list_button = pidgin_pixbuf_button_from_stock(_("_Browse"), GTK_STOCK_REFRESH, + PIDGIN_BUTTON_HORIZONTAL); + gtk_box_pack_start(GTK_BOX(bbox), dialog->list_button, FALSE, FALSE, 0); + g_signal_connect(G_OBJECT(dialog->list_button), "clicked", + G_CALLBACK(list_button_cb), dialog); + gtk_widget_show(dialog->list_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_dialog_show(void) +{ + pidgin_disco_dialog_new_with_account(NULL); +} + +void +pidgin_disco_dialog_show_with_account(PurpleAccount* account) +{ + PidginDiscoDialog *dialog = pidgin_disco_dialog_new_with_account(account); + + if (!dialog) + return; + + list_button_cb(GTK_BUTTON(dialog->list_button), dialog); +} + +static void +pidgin_disco_create(PurpleDiscoList *list) +{ + PidginDiscoList *pdl = g_new0(PidginDiscoList, 1); + + purple_disco_list_set_ui_data(list, pdl); + + pdl->services = g_hash_table_new_full(NULL, NULL, NULL, + (GDestroyNotify)gtk_tree_row_reference_free); +} + + +static void +pidgin_disco_destroy(PurpleDiscoList *list) +{ + PidginDiscoList *pdl; + + pdl = purple_disco_list_get_ui_data(list); + + g_hash_table_destroy(pdl->services); + g_free(pdl); + + purple_disco_list_set_ui_data(list, NULL); +} + +static void pidgin_disco_in_progress(PurpleDiscoList *list, gboolean in_progress) +{ + PidginDiscoList *pdl; + PidginDiscoDialog *dialog; + + pdl = purple_disco_list_get_ui_data(list); + if (!pdl) + return; + + dialog = pdl->dialog; + if (!dialog) + return; + + if (in_progress) { + if (dialog->account_widget) + gtk_widget_set_sensitive(dialog->account_widget, FALSE); + gtk_widget_set_sensitive(dialog->stop_button, TRUE); + gtk_widget_set_sensitive(dialog->list_button, FALSE); + } else { + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(dialog->progress), 0.0); + if (dialog->account_widget) + gtk_widget_set_sensitive(dialog->account_widget, TRUE); + gtk_widget_set_sensitive(dialog->stop_button, FALSE); + gtk_widget_set_sensitive(dialog->list_button, TRUE); + } +} + +static void pidgin_disco_add_service(PurpleDiscoList *list, PurpleDiscoService *service, PurpleDiscoService *parent) +{ + PidginDiscoList *pdl; + PidginDiscoDialog *dialog; + PurpleDiscoServiceType type; + const char *gateway_type; + GtkTreeIter iter, parent_iter, child; + char *filename = NULL; + GdkPixbuf *pixbuf = NULL; + gboolean append = TRUE; + + pdl = purple_disco_list_get_ui_data(list); + dialog = pdl->dialog; + + purple_debug_info("disco", "Add_service \"%s\"\n", purple_disco_service_get_name(service)); + + 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)) { + PurpleDiscoList *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 (purple_disco_service_get_flags(service) & PURPLE_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); + } + + type = purple_disco_service_get_type(service); + gateway_type = purple_disco_service_get_gateway_type(service); + + if (type == PURPLE_DISCO_SERVICE_TYPE_GATEWAY && gateway_type) { + char *tmp = g_strconcat(gateway_type, ".png", NULL); + filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "protocols", "22", tmp, NULL); + g_free(tmp); +#if 0 + } else if (type == PURPLE_DISCO_SERVICE_TYPE_USER) { + filename = g_build_filename(DATADIR, "pixmaps", "pidgin", "status", "22", "person.png", NULL); +#endif + } else if (type == PURPLE_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, purple_disco_service_get_name(service), + DESCRIPTION_COLUMN, purple_disco_service_get_description(service), + SERVICE_COLUMN, service, + -1); + + if (pixbuf) + g_object_unref(pixbuf); +} + +static PurpleDiscoUiOps ops = { + pidgin_disco_dialog_show_with_account, + pidgin_disco_create, + pidgin_disco_destroy, + pidgin_disco_add_service, + pidgin_disco_in_progress, + /* padding */ + NULL, + NULL, + NULL, + NULL +}; + +void pidgin_disco_init() { + purple_disco_set_ui_ops(&ops); +} diff -r 836a36564ff5 -r 413006df9828 pidgin/gtkdisco.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/gtkdisco.h Wed Apr 29 23:20:51 2009 +0000 @@ -0,0 +1,56 @@ +/** + * @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 + */ + +#ifndef _GTK_DISCO_H_ +#define _GTK_DISCO_H_ + + +/** + * Initializes the service discovery subsystem. + */ +void pidgin_disco_init(void); + +/** + * Determines if showing the service discovery dialog is a valid action. + * + * @return TRUE if there are accounts online that support service + * discovery. Otherwise return FALSE. + */ +gboolean pidgin_disco_is_showable(void); + +/** + * Shows a new service discovery dialog. + */ +void pidgin_disco_dialog_show(void); + +/** + * Shows a new service discovery dialog and fetches the list for the specified account. + * + * @param account The account to use. + */ +void pidgin_disco_dialog_show_with_account(PurpleAccount *account); + +#endif /* _GTK_DISCO_H_ */ diff -r 836a36564ff5 -r 413006df9828 pidgin/gtkmain.c --- a/pidgin/gtkmain.c Wed Apr 29 17:50:14 2009 +0000 +++ b/pidgin/gtkmain.c Wed Apr 29 23:20:51 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" @@ -308,6 +309,7 @@ pidgin_privacy_init(); pidgin_xfers_init(); pidgin_roomlist_init(); + pidgin_disco_init(); pidgin_log_init(); pidgin_docklet_init(); pidgin_smileys_init();