# HG changeset patch # User Will Thompson # Date 1194790795 0 # Node ID 1ce05db42eb3cabce7b519ff02317558358c30c2 # Parent 85fbadb8b8fd4e2491fefbba8b5b4dac94d59173# Parent 9fe16ecaa4ab42fc11ad855f24cfe18946ee3b81 propagate from branch 'im.pidgin.pidgin' (head 18b9b46acf72ed063fad2e2779a5202fd6d8126b) to branch 'im.pidgin.cpw.resiak.disconnectreason' (head 9ef6f213c70686d6e6f62c382a626a749bf4bf6c) diff -r 85fbadb8b8fd -r 1ce05db42eb3 COPYRIGHT --- a/COPYRIGHT Sun Nov 11 14:18:23 2007 +0000 +++ b/COPYRIGHT Sun Nov 11 14:19:55 2007 +0000 @@ -80,6 +80,7 @@ Graham Cole Jono Cole Lorenzo Colitti +Collabora Ltd. Jeff Connelly Nathan Conrad Felipe Contreras diff -r 85fbadb8b8fd -r 1ce05db42eb3 ChangeLog.API --- a/ChangeLog.API Sun Nov 11 14:18:23 2007 +0000 +++ b/ChangeLog.API Sun Nov 11 14:19:55 2007 +0000 @@ -15,6 +15,35 @@ to unload a plugin--fails. This then prevents the plugin from being saved in the saved plugins list, so it'll won't be loaded at the next startup. + + * PurpleDisconnectReason enumeration of machine-readable + types of connection error. + * purple_connection_error_reason(), to be used by prpls + (instead of purple_connection_error() and setting + gc->wants_to_die) to report errors along with a + PurpleDisconnectReason. + * PurpleConnectionUiOps.report_disconnect_reason, to be + implemented by UIs (rather than .report_disconnect) if + they want to use the reported PurpleDisconnectReason + to give a more specific error. + * A connection-error signal, fired just after the UiOp is + called with the same information. + * purple_connection_reason_is_fatal(), acting as a hint + to whether automatic reconnection should be attempted + after a connection error (rather than checking + gc->wants_to_die). + * PurpleConnectionErrorInfo, a struct to hold a + PurpleConnectionError and a const char *description. + * purple_account_get_current_error() to get the most recent + PurpleConnectionError and description (or NULL if the + account is happy with life), to allow bits of the UI to know + the last error without caching it themselves (as + PidginBuddyList does). + * purple_account_clear_current_error() to reset an account's + error state to NULL. + * An account-error-changed signal, firing when + purple_account_get_current_error()'s return value changes. + * purple_util_init() * purple_util_uninit() @@ -61,6 +90,11 @@ when a dependent plugin fails to unload. The UI should do something appropriate. + * pidgin_make_mini_dialog() now declares its return type to be + GtkWidget * rather than void *. This should not break any + existing code since any code using it must already rely on + the return type actually being GtkWidget * all along. + Deprecated: * pidgin_dialogs_about() * pidgin_log_show_contact() @@ -82,6 +116,10 @@ * purple_request_ok_cancel() * purple_request_yes_no() + * purple_connection_error() + * pidgin_blist_update_account_error_state() + * PidginBuddyList.connection_errors + MSN: * A new independant status type with PURPLE_STATUS_TUNE primitive, and PURPLE_TUNE_ARTIST, PURPLE_TUNE_ALBUM and PURPLE_TUNE_TITLE diff -r 85fbadb8b8fd -r 1ce05db42eb3 doc/account-signals.dox --- a/doc/account-signals.dox Sun Nov 11 14:18:23 2007 +0000 +++ b/doc/account-signals.dox Sun Nov 11 14:19:55 2007 +0000 @@ -12,6 +12,7 @@ @signal account-authorization-requested @signal account-authorization-denied @signal account-authorization-granted + @signal account-error-changed @endsignals @see account.h @@ -141,5 +142,23 @@ @since 2.3.0 @endsignaldef + @signaldef account-error-changed + @signalproto +void (*account_error_changed)(PurpleAccount *account, const PurpleConnectionErrorInfo *old_error, const PurpleConnectionErrorInfo *current_error); + @endsignalproto + @signaldesc + Emitted when @a account's error changes. + @param account The account whose error has changed. + @param old_error The account's previous error, or @c NULL if it had no + error. After this signal is emitted, @a old_error is + not guaranteed to be a valid pointer. + @param new_error The account's new error, or @c NULL if it has no error. + If not @c NULL, @a new_error will remain a valid until + pointer just after the next time this signal is emitted + for this @a account. + @see purple_account_get_current_error() + @since 2.3.0 + @endsignaldef + */ // vim: syntax=c.doxygen tw=75 et diff -r 85fbadb8b8fd -r 1ce05db42eb3 doc/connection-signals.dox --- a/doc/connection-signals.dox Sun Nov 11 14:18:23 2007 +0000 +++ b/doc/connection-signals.dox Sun Nov 11 14:19:55 2007 +0000 @@ -47,5 +47,16 @@ @param gc The connection that has signed off. @endsignaldef + @signaldef connection-error + @signalproto +void (*connection_error)(PurpleConnection *gc, PurpleConnectionError err, const gchar *desc) + @endsignalproto + @signaldesc + Emitted when a connection error occurs, before @ref signed-off. + @param gc The connection on which the error has occured + @param err The error that occured + @param desc A description of the error, giving more information. + @endsignaldef + */ // vim: syntax=c.doxygen tw=75 et diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/account.c --- a/libpurple/account.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/account.c Sun Nov 11 14:19:55 2007 +0000 @@ -41,6 +41,14 @@ #include "util.h" #include "xmlnode.h" +typedef struct +{ + PurpleConnectionErrorInfo *current_error; +} PurpleAccountPrivate; + +#define PURPLE_ACCOUNT_GET_PRIVATE(account) \ + ((PurpleAccountPrivate *) (account->priv)) + /* TODO: Should use PurpleValue instead of this? What about "ui"? */ typedef struct { @@ -77,6 +85,9 @@ static GList *handles = NULL; +static void set_current_error(PurpleAccount *account, + PurpleConnectionErrorInfo *new_err); + /********************************************************************* * Writing to disk * *********************************************************************/ @@ -310,8 +321,32 @@ } static xmlnode * +current_error_to_xmlnode(PurpleConnectionErrorInfo *err) +{ + xmlnode *node, *child; + char type_str[3]; + + node = xmlnode_new("current_error"); + + if(err == NULL) + return node; + + child = xmlnode_new_child(node, "type"); + snprintf(type_str, sizeof(type_str), "%u", err->type); + xmlnode_insert_data(child, type_str, -1); + + child = xmlnode_new_child(node, "description"); + if(err->description) + xmlnode_insert_data(child, err->description, -1); + + return node; +} + +static xmlnode * account_to_xmlnode(PurpleAccount *account) { + PurpleAccountPrivate *priv = PURPLE_ACCOUNT_GET_PRIVATE(account); + xmlnode *node, *child; const char *tmp; PurplePresence *presence; @@ -368,6 +403,9 @@ xmlnode_insert_child(node, child); } + child = current_error_to_xmlnode(priv->current_error); + xmlnode_insert_child(node, child); + return node; } @@ -672,6 +710,42 @@ purple_account_set_proxy_info(account, proxy_info); } +static void +parse_current_error(xmlnode *node, PurpleAccount *account) +{ + guint type; + char *type_str = NULL, *description = NULL; + xmlnode *child; + PurpleConnectionErrorInfo *current_error = NULL; + + child = xmlnode_get_child(node, "type"); + if (child == NULL || (type_str = xmlnode_get_data(child)) == NULL) + return; + type = atoi(type_str); + g_free(type_str); + + if (type > PURPLE_CONNECTION_ERROR_OTHER_ERROR) + { + purple_debug_error("account", + "Invalid PurpleConnectionError value %d found when " + "loading account information for %s\n", + type, purple_account_get_username(account)); + type = PURPLE_CONNECTION_ERROR_OTHER_ERROR; + } + + child = xmlnode_get_child(node, "description"); + if (child) + description = xmlnode_get_data(child); + if (description == NULL) + description = g_strdup(""); + + current_error = g_new0(PurpleConnectionErrorInfo, 1); + current_error->type = type; + current_error->description = description; + + set_current_error(account, current_error); +} + static PurpleAccount * parse_account(xmlnode *node) { @@ -781,6 +855,13 @@ parse_proxy_info(child, ret); } + /* Read current error */ + child = xmlnode_get_child(node, "current_error"); + if (child != NULL) + { + parse_current_error(child, ret); + } + return ret; } @@ -827,6 +908,7 @@ purple_account_new(const char *username, const char *protocol_id) { PurpleAccount *account = NULL; + PurpleAccountPrivate *priv = NULL; PurplePlugin *prpl = NULL; PurplePluginProtocolInfo *prpl_info = NULL; PurpleStatusType *status_type; @@ -841,6 +923,8 @@ account = g_new0(PurpleAccount, 1); PURPLE_DBUS_REGISTER_POINTER(account, PurpleAccount); + priv = g_new0(PurpleAccountPrivate, 1); + account->priv = priv; purple_account_set_username(account, username); @@ -881,6 +965,7 @@ void purple_account_destroy(PurpleAccount *account) { + PurpleAccountPrivate *priv = NULL; GList *l; g_return_if_fail(account != NULL); @@ -912,6 +997,10 @@ if(account->system_log) purple_log_free(account->system_log); + priv = PURPLE_ACCOUNT_GET_PRIVATE(account); + g_free(priv->current_error); + g_free(priv); + PURPLE_DBUS_UNREGISTER_POINTER(account); g_free(account); } @@ -2214,6 +2303,65 @@ return prpl_info->offline_message(buddy); } +static void +signed_on_cb(PurpleConnection *gc, + gpointer unused) +{ + PurpleAccount *account = purple_connection_get_account(gc); + purple_account_clear_current_error(account); +} + +static void +set_current_error(PurpleAccount *account, + PurpleConnectionErrorInfo *new_err) +{ + PurpleAccountPrivate *priv = PURPLE_ACCOUNT_GET_PRIVATE(account); + PurpleConnectionErrorInfo *old_err = priv->current_error; + + if(new_err == old_err) + return; + + priv->current_error = new_err; + + purple_signal_emit(purple_accounts_get_handle(), + "account-error-changed", + account, old_err, new_err); + schedule_accounts_save(); + + if(old_err) + g_free(old_err->description); + + g_free(old_err); +} + +static void +connection_error_cb(PurpleConnection *gc, + PurpleConnectionError type, + const gchar *description, + gpointer unused) +{ + PurpleAccount *account = purple_connection_get_account(gc); + PurpleConnectionErrorInfo *err = g_new0(PurpleConnectionErrorInfo, 1); + + err->type = type; + err->description = g_strdup(description); + + set_current_error(account, err); +} + +const PurpleConnectionErrorInfo * +purple_account_get_current_error(PurpleAccount *account) +{ + PurpleAccountPrivate *priv = PURPLE_ACCOUNT_GET_PRIVATE(account); + return priv->current_error; +} + +void +purple_account_clear_current_error(PurpleAccount *account) +{ + set_current_error(account, NULL); +} + void purple_accounts_add(PurpleAccount *account) { @@ -2238,6 +2386,11 @@ schedule_accounts_save(); + /* Clearing the error ensures that account-error-changed is emitted, + * which is the end of the guarantee that the the error's pointer is + * valid. + */ + purple_account_clear_current_error(account); purple_signal_emit(purple_accounts_get_handle(), "account-removed", account); } @@ -2443,6 +2596,7 @@ purple_accounts_init(void) { void *handle = purple_accounts_get_handle(); + void *conn_handle = purple_connections_get_handle(); purple_signal_register(handle, "account-connecting", purple_marshal_VOID__POINTER, NULL, 1, @@ -2513,6 +2667,19 @@ PURPLE_SUBTYPE_ACCOUNT), purple_value_new(PURPLE_TYPE_STRING)); + purple_signal_register(handle, "account-error-changed", + purple_marshal_VOID__POINTER_POINTER_POINTER, + NULL, 3, + purple_value_new(PURPLE_TYPE_SUBTYPE, + PURPLE_SUBTYPE_ACCOUNT), + purple_value_new(PURPLE_TYPE_POINTER), + purple_value_new(PURPLE_TYPE_POINTER)); + + purple_signal_connect(conn_handle, "signed-on", handle, + PURPLE_CALLBACK(signed_on_cb), NULL); + purple_signal_connect(conn_handle, "connection-error", handle, + PURPLE_CALLBACK(connection_error_cb), NULL); + load_accounts(); } @@ -2520,6 +2687,7 @@ void purple_accounts_uninit(void) { + gpointer handle = purple_accounts_get_handle(); if (save_timer != 0) { purple_timeout_remove(save_timer); @@ -2527,5 +2695,6 @@ sync_accounts(); } - purple_signals_unregister_by_instance(purple_accounts_get_handle()); + purple_signals_disconnect_by_handle(handle); + purple_signals_unregister_by_instance(handle); } diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/account.h --- a/libpurple/account.h Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/account.h Sun Nov 11 14:19:55 2007 +0000 @@ -140,6 +140,8 @@ void *ui_data; /**< The UI can put data here. */ PurpleAccountRegistrationCb registration_cb; void *registration_cb_user_data; + + gpointer priv; /**< Pointer to opaque private data. */ }; #ifdef __cplusplus @@ -893,6 +895,25 @@ */ gboolean purple_account_supports_offline_message(PurpleAccount *account, PurpleBuddy *buddy); +/** + * Get the error that caused the account to be disconnected, or @c NULL if the + * account is happily connected or disconnected without an error. + * + * @param account The account whose error should be retrieved. + * @constreturn The type of error and a human-readable description of the + * current error, or @c NULL if there is no current error. This + * pointer is guaranteed to remain valid until the @ref + * account-error-changed signal is emitted for @a account. + */ +const PurpleConnectionErrorInfo *purple_account_get_current_error(PurpleAccount *account); + +/** + * Clear an account's current error state, resetting it to @c NULL. + * + * @param account The account whose error state should be cleared. + */ +void purple_account_clear_current_error(PurpleAccount *account); + /*@}*/ /**************************************************************************/ diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/connection.c --- a/libpurple/connection.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/connection.c Sun Nov 11 14:19:55 2007 +0000 @@ -488,29 +488,119 @@ void purple_connection_error(PurpleConnection *gc, const char *text) { + /* prpls that have not been updated to use disconnection reasons will + * be setting wants_to_die before calling this function, so choose + * PURPLE_CONNECTION_ERROR_OTHER_ERROR (which is fatal) if it's true, + * and PURPLE_CONNECTION_ERROR_NETWORK_ERROR (which isn't) if not. See + * the documentation in connection.h. + */ + PurpleConnectionError reason = gc->wants_to_die + ? PURPLE_CONNECTION_ERROR_OTHER_ERROR + : PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + purple_connection_error_reason (gc, reason, text); +} + +void +purple_connection_error_reason (PurpleConnection *gc, + PurpleConnectionError reason, + const char *description) +{ PurpleConnectionUiOps *ops; g_return_if_fail(gc != NULL); + /* This sanity check relies on PURPLE_CONNECTION_ERROR_OTHER_ERROR + * being the last member of the PurpleConnectionError enum in + * connection.h; if other reasons are added after it, this check should + * be updated. + */ + if (reason > PURPLE_CONNECTION_ERROR_OTHER_ERROR) { + purple_debug_error("connection", + "purple_connection_error_reason: reason %u isn't a " + "valid reason\n", reason); + reason = PURPLE_CONNECTION_ERROR_OTHER_ERROR; + } - if (text == NULL) { - purple_debug_error("connection", "purple_connection_error: check `text != NULL' failed\n"); - text = _("Unknown error"); + if (description == NULL) { + purple_debug_error("connection", "purple_connection_error_reason: check `description != NULL' failed\n"); + description = _("Unknown error"); } /* If we've already got one error, we don't need any more */ if (gc->disconnect_timeout) return; + gc->wants_to_die = purple_connection_error_is_fatal (reason); + ops = purple_connections_get_ui_ops(); - if (ops != NULL && ops->report_disconnect != NULL) - ops->report_disconnect(gc, text); + if (ops != NULL) + { + if (ops->report_disconnect_reason != NULL) + ops->report_disconnect_reason (gc, reason, description); + if (ops->report_disconnect != NULL) + ops->report_disconnect (gc, description); + } + + purple_signal_emit(purple_connections_get_handle(), "connection-error", + gc, reason, description); gc->disconnect_timeout = purple_timeout_add(0, purple_connection_disconnect_cb, purple_connection_get_account(gc)); } void +purple_connection_ssl_error (PurpleConnection *gc, + PurpleSslErrorType ssl_error) +{ + PurpleConnectionError reason; + + switch (ssl_error) { + case PURPLE_SSL_HANDSHAKE_FAILED: + case PURPLE_SSL_CONNECT_FAILED: + reason = PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR; + break; + case PURPLE_SSL_CERTIFICATE_INVALID: + /* TODO: maybe PURPLE_SSL_* should be more specific? */ + reason = PURPLE_CONNECTION_ERROR_CERT_OTHER_ERROR; + break; + default: + g_assert_not_reached (); + reason = PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR; + } + + purple_connection_error_reason (gc, reason, + purple_ssl_strerror(ssl_error)); +} + +gboolean +purple_connection_error_is_fatal (PurpleConnectionError reason) +{ + switch (reason) + { + case PURPLE_CONNECTION_ERROR_NETWORK_ERROR: + case PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE: + case PURPLE_CONNECTION_ERROR_CERT_NOT_PROVIDED: + case PURPLE_CONNECTION_ERROR_CERT_UNTRUSTED: + case PURPLE_CONNECTION_ERROR_CERT_EXPIRED: + case PURPLE_CONNECTION_ERROR_CERT_NOT_ACTIVATED: + case PURPLE_CONNECTION_ERROR_CERT_HOSTNAME_MISMATCH: + case PURPLE_CONNECTION_ERROR_CERT_FINGERPRINT_MISMATCH: + case PURPLE_CONNECTION_ERROR_CERT_SELF_SIGNED: + case PURPLE_CONNECTION_ERROR_CERT_OTHER_ERROR: + return FALSE; + case PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED: + case PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT: + case PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR: + case PURPLE_CONNECTION_ERROR_NAME_IN_USE: + case PURPLE_CONNECTION_ERROR_INVALID_SETTINGS: + case PURPLE_CONNECTION_ERROR_OTHER_ERROR: + return TRUE; + default: + g_return_val_if_reached(TRUE); + } +} + +void purple_connections_disconnect_all(void) { GList *l; @@ -571,6 +661,14 @@ purple_marshal_VOID__POINTER, NULL, 1, purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_CONNECTION)); + + purple_signal_register(handle, "connection-error", + purple_marshal_VOID__POINTER_INT_POINTER, NULL, 1, + purple_value_new(PURPLE_TYPE_SUBTYPE, + PURPLE_SUBTYPE_CONNECTION), + purple_value_new(PURPLE_TYPE_ENUM), + purple_value_new(PURPLE_TYPE_STRING)); + } void diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/connection.h --- a/libpurple/connection.h Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/connection.h Sun Nov 11 14:19:55 2007 +0000 @@ -54,11 +54,93 @@ } PurpleConnectionState; +/** Possible errors that can cause a connection to be closed. + * @since 2.3.0 + */ +typedef enum +{ + /** There was an error sending or receiving on the network socket, or + * there was some protocol error (such as the server sending malformed + * data). + */ + PURPLE_CONNECTION_ERROR_NETWORK_ERROR = 0, + /** The username or password (or some other credential) was incorrect. + */ + PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED = 1, + /** libpurple doesn't speak any of the authentication methods the + * server offered. + */ + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE = 2, + /** libpurple was built without SSL support, and the connection needs + * SSL. + */ + PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT = 3, + /** There was an error negotiating SSL on this connection, or the + * server does not support encryption but an account option was set to + * require it. + */ + PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR = 4, + /** Someone is already connected to the server using the name you are + * trying to connect with. + */ + PURPLE_CONNECTION_ERROR_NAME_IN_USE = 5, + + /** The username/server/other preference for the account isn't valid. + * For instance, on IRC the screen name cannot contain white space. + * This reason should not be used for incorrect passwords etc: use + * #PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED for that. + * + * @todo This reason really shouldn't be necessary. Usernames and + * other account preferences should be validated when the + * account is created. + */ + PURPLE_CONNECTION_ERROR_INVALID_SETTINGS = 6, + + /** The server did not provide a SSL certificate. */ + PURPLE_CONNECTION_ERROR_CERT_NOT_PROVIDED = 7, + /** The server's SSL certificate could not be trusted. */ + PURPLE_CONNECTION_ERROR_CERT_UNTRUSTED = 8, + /** The server's SSL certificate has expired. */ + PURPLE_CONNECTION_ERROR_CERT_EXPIRED = 9, + /** The server's SSL certificate is not yet valid. */ + PURPLE_CONNECTION_ERROR_CERT_NOT_ACTIVATED = 10, + /** The server's SSL certificate did not match its hostname. */ + PURPLE_CONNECTION_ERROR_CERT_HOSTNAME_MISMATCH = 11, + /** The server's SSL certificate does not have the expected + * fingerprint. + */ + PURPLE_CONNECTION_ERROR_CERT_FINGERPRINT_MISMATCH = 12, + /** The server's SSL certificate is self-signed. */ + PURPLE_CONNECTION_ERROR_CERT_SELF_SIGNED = 13, + /** There was some other error validating the server's SSL certificate. + */ + PURPLE_CONNECTION_ERROR_CERT_OTHER_ERROR = 14, + + /** Some other error occured which fits into none of the other + * categories. + */ + /* purple_connection_error_reason() in connection.c uses the fact that + * this is the last member of the enum when sanity-checking; if other + * reasons are added after it, the check must be updated. + */ + PURPLE_CONNECTION_ERROR_OTHER_ERROR = 15 +} PurpleConnectionError; + +/** Holds the type of an error along with its description. */ +typedef struct +{ + /** The type of error. */ + PurpleConnectionError type; + /** A localised, human-readable description of the error. */ + char *description; +} PurpleConnectionErrorInfo; + #include #include "account.h" #include "plugin.h" #include "status.h" +#include "sslconn.h" /** Connection UI operations. Used to notify the user of changes to * connections, such as being disconnected, and to respond to the @@ -73,11 +155,13 @@ * the UI of what is happening, as well as which @a step out of @a * step_count has been reached (which might be displayed as a progress * bar). + * @see #purple_connection_update_progress */ void (*connect_progress)(PurpleConnection *gc, const char *text, size_t step, size_t step_count); + /** Called when a connection is established (just before the * @ref signed-on signal). */ @@ -86,17 +170,23 @@ * and @ref signed-off signals). */ void (*disconnected)(PurpleConnection *gc); + /** Used to display connection-specific notices. (Pidgin's Gtk user * interface implements this as a no-op; #purple_connection_notice(), * which uses this operation, is not used by any of the protocols * shipped with libpurple.) */ void (*notice)(PurpleConnection *gc, const char *text); + /** Called when an error causes a connection to be disconnected. * Called before #disconnected. * @param text a localized error message. + * @see #purple_connection_error + * @deprecated in favour of + * #PurpleConnectionUiOps.report_disconnect_reason. */ void (*report_disconnect)(PurpleConnection *gc, const char *text); + /** Called when libpurple discovers that the computer's network * connection is active. On Linux, this uses Network Manager if * available; on Windows, it uses Win32's network change notification @@ -108,10 +198,24 @@ */ void (*network_disconnected)(); + /** Called when an error causes a connection to be disconnected. + * Called before #disconnected. This op is intended to replace + * #report_disconnect. If both are implemented, this will be called + * first; however, there's no real reason to implement both. + * @param reason why the connection ended, if known, or + * #PURPLE_CONNECTION_ERROR_OTHER_ERROR, if not. + * @param text a localized message describing the disconnection + * in more detail to the user. + * @see #purple_connection_error_reason + * @since 2.3.0 + */ + void (*report_disconnect_reason)(PurpleConnection *gc, + PurpleConnectionError reason, + const char *text); + void (*_purple_reserved1)(void); void (*_purple_reserved2)(void); void (*_purple_reserved3)(void); - void (*_purple_reserved4)(void); } PurpleConnectionUiOps; struct _PurpleConnection @@ -125,19 +229,23 @@ char *password; /**< The password used. */ int inpa; /**< The input watcher. */ - GSList *buddy_chats; /**< A list of active chats. */ + GSList *buddy_chats; /**< A list of active chats + (#PurpleConversation structs of type + #PURPLE_CONV_TYPE_CHAT). */ void *proto_data; /**< Protocol-specific data. */ char *display_name; /**< How you appear to other people. */ guint keepalive; /**< Keep-alive. */ + /** Wants to Die state. This is set when the user chooses to log out, or + * when the protocol is disconnected and should not be automatically + * reconnected (incorrect password, etc.). prpls should rely on + * purple_connection_error_reason() to set this for them rather than + * setting it themselves. + * @see purple_connection_error_is_fatal + */ + gboolean wants_to_die; - gboolean wants_to_die; /**< Wants to Die state. This is set - when the user chooses to log out, - or when the protocol is - disconnected and should not be - automatically reconnected - (incorrect password, etc.) */ guint disconnect_timeout; /**< Timer used for nasty stack tricks */ }; @@ -209,7 +317,7 @@ /** * Sets the connection state. PRPLs should call this and pass in - * the state "PURPLE_CONNECTED" when the account is completely + * the state #PURPLE_CONNECTED when the account is completely * signed on. What does it mean to be completely signed on? If * the core can call prpl->set_status, and it successfully changes * your status, then the account is online. @@ -302,10 +410,65 @@ * Closes a connection with an error. * * @param gc The connection. - * @param reason The error text. + * @param reason The error text, which may not be @c NULL. + * @deprecated in favour of #purple_connection_error_reason. Calling + * @c purple_connection_error(gc, text) is equivalent to calling + * @c purple_connection_error_reason(gc, reason, text) where @c reason is + * #PURPLE_CONNECTION_ERROR_OTHER_ERROR if @c gc->wants_to_die is @c TRUE, and + * #PURPLE_CONNECTION_ERROR_NETWORK_ERROR if not. (This is to keep + * auto-reconnection behaviour the same when using old prpls which don't use + * reasons yet.) */ void purple_connection_error(PurpleConnection *gc, const char *reason); +/** + * Closes a connection with an error and a human-readable description of the + * error. It also sets @c gc->wants_to_die to the value of + * #purple_connection_error_is_fatal(@a reason), mainly for + * backwards-compatibility. + * + * @param gc the connection which is closing. + * @param reason why the connection is closing. + * @param description a non-@c NULL localized description of the error. + * @since 2.3.0 + */ +void +purple_connection_error_reason (PurpleConnection *gc, + PurpleConnectionError reason, + const char *description); + +/** + * Closes a connection due to an SSL error; this is basically a shortcut to + * turning the #PurpleSslErrorType into a #PurpleConnectionError and a + * human-readable string and then calling purple_connection_error_reason(). + * @since 2.3.0 + */ +void +purple_connection_ssl_error (PurpleConnection *gc, + PurpleSslErrorType ssl_error); + +/** + * Reports whether a disconnection reason is fatal (in which case the account + * should probably not be automatically reconnected) or transient (so + * auto-reconnection is a good idea). + * For instance, #PURPLE_CONNECTION_ERROR_NETWORK_ERROR is a temporary error, + * which might be caused by losing the network connection, so + * purple_connection_error_is_fatal (PURPLE_CONNECTION_ERROR_NETWORK_ERROR) + * is @c FALSE. On the other hand, + * #PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED probably indicates a + * misconfiguration of the account which needs the user to go fix it up, so + * purple_connection_error_is_fatal + * (PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED) is @c TRUE. + * + * (This function is meant to replace checking PurpleConnection.wants_to_die.) + * + * @return @c TRUE if the account should not be automatically reconnected, and + * @c FALSE otherwise. + * @since 2.3.0 + */ +gboolean +purple_connection_error_is_fatal (PurpleConnectionError reason); + /*@}*/ /**************************************************************************/ diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/core.c --- a/libpurple/core.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/core.c Sun Nov 11 14:19:55 2007 +0000 @@ -138,15 +138,17 @@ /* The buddy icon code uses the imgstore, so init it early. */ purple_imgstore_init(); - /* Accounts use status and buddy icons, so initialize these before accounts */ + /* Accounts use status, buddy icons and connection signals, so + * initialize these before accounts + */ purple_status_init(); purple_buddy_icons_init(); + purple_connections_init(); purple_accounts_init(); purple_savedstatuses_init(); purple_notify_init(); purple_certificate_init(); - purple_connections_init(); purple_conversations_init(); purple_blist_init(); purple_log_init(); diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/plugins/signals-test.c --- a/libpurple/plugins/signals-test.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/plugins/signals-test.c Sun Nov 11 14:19:55 2007 +0000 @@ -226,6 +226,18 @@ purple_account_get_username(purple_connection_get_account(gc))); } +static void +connection_error_cb(PurpleConnection *gc, + PurpleConnectionError err, + const gchar *desc, + void *data) +{ + const gchar *username = + purple_account_get_username(purple_connection_get_account(gc)); + purple_debug_misc("signals test", "connection-error (%s, %u, %s)\n", + username, err, desc); +} + /************************************************************************** * Conversation subsystem signal callbacks **************************************************************************/ @@ -626,6 +638,8 @@ plugin, PURPLE_CALLBACK(signing_off_cb), NULL); purple_signal_connect(conn_handle, "signed-off", plugin, PURPLE_CALLBACK(signed_off_cb), NULL); + purple_signal_connect(conn_handle, "connection-error", + plugin, PURPLE_CALLBACK(connection_error_cb), NULL); /* Conversations subsystem signals */ purple_signal_connect(conv_handle, "writing-im-msg", diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/bonjour/bonjour.c --- a/libpurple/protocols/bonjour/bonjour.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/bonjour/bonjour.c Sun Nov 11 14:19:55 2007 +0000 @@ -102,8 +102,8 @@ #ifdef _WIN32 if (!dns_sd_available()) { - gc->wants_to_die = TRUE; - purple_connection_error(gc, + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("The Apple Bonjour For Windows toolkit wasn't found, see the FAQ at: " "http://developer.pidgin.im/wiki/Using%20Pidgin#CanIusePidginforBonjourLink-LocalMessaging" " for more information.")); @@ -121,7 +121,9 @@ if (bonjour_jabber_start(bd->jabber_data) == -1) { /* Send a message about the connection error */ - purple_connection_error(gc, _("Unable to listen for incoming IM connections\n")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unable to listen for incoming IM connections\n")); return; } @@ -146,7 +148,9 @@ bd->dns_sd_data->account = account; if (!bonjour_dns_sd_start(bd->dns_sd_data)) { - purple_connection_error(gc, _("Unable to establish connection with the local mDNS server. Is it running?")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unable to establish connection with the local mDNS server. Is it running?")); return; } diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/bonjour/jabber.c --- a/libpurple/protocols/bonjour/jabber.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/bonjour/jabber.c Sun Nov 11 14:19:55 2007 +0000 @@ -584,7 +584,9 @@ if ((data->socket = socket(PF_INET, SOCK_STREAM, 0)) < 0) { purple_debug_error("bonjour", "Cannot open socket: %s\n", g_strerror(errno)); - purple_connection_error(data->account->gc, _("Cannot open socket")); + purple_connection_error_reason (data->account->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Cannot open socket")); return -1; } @@ -592,7 +594,9 @@ if (setsockopt(data->socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) != 0) { purple_debug_error("bonjour", "Error setting socket options: %s\n", g_strerror(errno)); - purple_connection_error(data->account->gc, _("Error setting socket options")); + purple_connection_error_reason (data->account->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Error setting socket options")); return -1; } @@ -616,7 +620,9 @@ if (!bind_successful) { purple_debug_error("bonjour", "Cannot bind socket: %s\n", g_strerror(errno)); - purple_connection_error(data->account->gc, _("Could not bind socket to port")); + purple_connection_error_reason (data->account->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Could not bind socket to port")); return -1; } @@ -624,7 +630,9 @@ if (listen(data->socket, 10) != 0) { purple_debug_error("bonjour", "Cannot listen on socket: %s\n", g_strerror(errno)); - purple_connection_error(data->account->gc, _("Could not listen on socket")); + purple_connection_error_reason (data->account->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Could not listen on socket")); return -1; } diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/gg/gg.c --- a/libpurple/protocols/gg/gg.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/gg/gg.c Sun Nov 11 14:19:55 2007 +0000 @@ -391,12 +391,16 @@ if (email == NULL || p1 == NULL || p2 == NULL || t == NULL || *email == '\0' || *p1 == '\0' || *p2 == '\0' || *t == '\0') { - purple_connection_error(gc, _("Fill in the registration fields.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_OTHER_ERROR, + _("Fill in the registration fields.")); goto exit_err; } if (g_utf8_collate(p1, p2) != 0) { - purple_connection_error(gc, _("Passwords do not match.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, + _("Passwords do not match.")); goto exit_err; } @@ -404,7 +408,8 @@ token->id, t); h = gg_register3(email, p1, token->id, t, 0); if (h == NULL || !(s = h->data) || !s->success) { - purple_connection_error(gc, + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("Unable to register new account. Error occurred.\n")); goto exit_err; } @@ -1318,7 +1323,9 @@ if (!(ev = gg_watch_fd(info->session))) { purple_debug_error("gg", "ggp_callback_recv: gg_watch_fd failed -- CRITICAL!\n"); - purple_connection_error(gc, _("Unable to read socket")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unable to read socket")); return; } @@ -1471,7 +1478,9 @@ if (!(ev = gg_watch_fd(info->session))) { purple_debug_error("gg", "login_handler: gg_watch_fd failed!\n"); - purple_connection_error(gc, _("Unable to read socket")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unable to read socket")); return; } purple_debug_info("gg", "login_handler: session->fd = %d\n", info->session->fd); @@ -1517,7 +1526,9 @@ case GG_EVENT_CONN_FAILED: purple_input_remove(gc->inpa); gc->inpa = 0; - purple_connection_error(gc, _("Connection failed.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Connection failed.")); break; default: purple_debug_error("gg", "strange event: %d\n", ev->type); @@ -1723,7 +1734,9 @@ info->session = gg_login(glp); if (info->session == NULL) { - purple_connection_error(gc, _("Connection failed.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Connection failed.")); g_free(glp); return; } @@ -2015,7 +2028,9 @@ if (gg_ping(info->session) < 0) { purple_debug_info("gg", "Not connected to the server " "or gg_session is not correct\n"); - purple_connection_error(gc, _("Not connected to the server.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Not connected to the server.")); } } /* }}} */ diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/irc/irc.c --- a/libpurple/protocols/irc/irc.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/irc/irc.c Sun Nov 11 14:19:55 2007 +0000 @@ -123,8 +123,10 @@ if (ret < 0 && errno == EAGAIN) return; else if (ret <= 0) { - purple_connection_error(purple_account_get_connection(irc->account), - _("Server has disconnected")); + PurpleConnection *gc = purple_account_get_connection(irc->account); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Server has disconnected")); return; } @@ -161,8 +163,10 @@ /* purple_debug(PURPLE_DEBUG_MISC, "irc", "sent%s: %s", irc->gsc ? " (ssl)" : "", tosend); */ if (ret <= 0 && errno != EAGAIN) { - purple_connection_error(purple_account_get_connection(irc->account), - _("Server has disconnected")); + PurpleConnection *gc = purple_account_get_connection(irc->account); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Server has disconnected")); } else if (ret < buflen) { if (ret < 0) ret = 0; @@ -295,7 +299,9 @@ gc->flags |= PURPLE_CONNECTION_NO_NEWLINES; if (strpbrk(username, " \t\v\r\n") != NULL) { - purple_connection_error(gc, _("IRC nicks may not contain whitespace")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, + _("IRC nicks may not contain whitespace")); return; } @@ -324,7 +330,9 @@ purple_account_get_int(account, "port", IRC_DEFAULT_SSL_PORT), irc_login_cb_ssl, irc_ssl_connect_failure, gc); } else { - purple_connection_error(gc, _("SSL support unavailable")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, + _("SSL support unavailable")); return; } } @@ -335,7 +343,9 @@ purple_account_get_int(account, "port", IRC_DEFAULT_PORT), irc_login_cb, gc) == NULL) { - purple_connection_error(gc, _("Couldn't create socket")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Couldn't create socket")); return; } } @@ -352,7 +362,6 @@ if (pass && *pass) { buf = irc_format(irc, "vv", "PASS", pass); if (irc_send(irc, buf) < 0) { -/* purple_connection_error(gc, "Error sending password"); */ g_free(buf); return FALSE; } @@ -384,14 +393,12 @@ strlen(realname) ? realname : IRC_DEFAULT_ALIAS); g_free(tmp); if (irc_send(irc, buf) < 0) { -/* purple_connection_error(gc, "Error registering with server");*/ g_free(buf); return FALSE; } g_free(buf); buf = irc_format(irc, "vn", "NICK", purple_connection_get_display_name(gc)); if (irc_send(irc, buf) < 0) { -/* purple_connection_error(gc, "Error sending nickname");*/ g_free(buf); return FALSE; } @@ -418,7 +425,9 @@ struct irc_conn *irc = gc->proto_data; if (source < 0) { - purple_connection_error(gc, _("Couldn't connect to host")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Couldn't connect to host")); return; } @@ -438,7 +447,7 @@ irc->gsc = NULL; - purple_connection_error(gc, purple_ssl_strerror(error)); + purple_connection_ssl_error (gc, error); } static void irc_close(PurpleConnection *gc) @@ -606,10 +615,14 @@ /* Try again later */ return; } else if (len < 0) { - purple_connection_error(gc, _("Read error")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Read error")); return; } else if (len == 0) { - purple_connection_error(gc, _("Server has disconnected")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Server has disconnected")); return; } @@ -631,10 +644,14 @@ if (len < 0 && errno == EAGAIN) { return; } else if (len < 0) { - purple_connection_error(gc, _("Read error")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Read error")); return; } else if (len == 0) { - purple_connection_error(gc, _("Server has disconnected")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Server has disconnected")); return; } diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/irc/msgs.c --- a/libpurple/protocols/irc/msgs.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/irc/msgs.c Sun Nov 11 14:19:55 2007 +0000 @@ -913,9 +913,9 @@ _("Your selected nickname was rejected by the server. It probably contains invalid characters.")); } else { - gc->wants_to_die = TRUE; - purple_connection_error(purple_account_get_connection(irc->account), - _("Your selected account name was rejected by the server. It probably contains invalid characters.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, + _("Your selected account name was rejected by the server. It probably contains invalid characters.")); } } diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/irc/parse.c --- a/libpurple/protocols/irc/parse.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/irc/parse.c Sun Nov 11 14:19:55 2007 +0000 @@ -612,6 +612,7 @@ struct _irc_msg *msgent; char *cur, *end, *tmp, *from, *msgname, *fmt, **args, *msg; guint i; + PurpleConnection *gc = purple_account_get_connection(irc->account); irc->recv_time = time(NULL); @@ -620,7 +621,7 @@ * TODO: It should be passed as an array of bytes and a length * instead of a null terminated string. */ - purple_signal_emit(_irc_plugin, "irc-receiving-text", purple_account_get_connection(irc->account), &input); + purple_signal_emit(_irc_plugin, "irc-receiving-text", gc, &input); if (!strncmp(input, "PING ", 5)) { msg = irc_format(irc, "vv", "PONG", input + 5); @@ -630,10 +631,13 @@ } else if (!strncmp(input, "ERROR ", 6)) { if (g_utf8_validate(input, -1, NULL)) { char *tmp = g_strdup_printf("%s\n%s", _("Disconnected."), input); - purple_connection_error(purple_account_get_connection(irc->account), tmp); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); g_free(tmp); } else - purple_connection_error(purple_account_get_connection(irc->account), _("Disconnected.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Disconnected.")); return; } diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/jabber/adhoccommands.c --- a/libpurple/protocols/jabber/adhoccommands.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/jabber/adhoccommands.c Sun Nov 11 14:19:55 2007 +0000 @@ -138,7 +138,7 @@ const char *type = xmlnode_get_attrib(packet,"type"); if(type && !strcmp(type,"error")) { - char *msg = jabber_parse_error(js, packet); + char *msg = jabber_parse_error(js, packet, NULL); if(!msg) msg = g_strdup(_("Unknown Error")); diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/jabber/auth.c --- a/libpurple/protocols/jabber/auth.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/jabber/auth.c Sun Nov 11 14:19:55 2007 +0000 @@ -50,7 +50,9 @@ "", -1); return TRUE; } else if(xmlnode_get_child(starttls, "required")) { - purple_connection_error(js->gc, _("Server requires TLS/SSL for login. No TLS/SSL support found.")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, + _("Server requires TLS/SSL for login. No TLS/SSL support found.")); return TRUE; } } @@ -113,7 +115,9 @@ static void disallow_plaintext_auth(PurpleAccount *account) { - purple_connection_error(account->gc, _("Server requires plaintext authentication over an unencrypted stream")); + purple_connection_error_reason (account->gc, + PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, + _("Server requires plaintext authentication over an unencrypted stream")); } #ifdef HAVE_CYRUS_SASL @@ -331,7 +335,9 @@ * error here. */ } else { - purple_connection_error(js->gc, _("Server does not use any supported authentication method")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + _("Server does not use any supported authentication method")); return; } /* not reached */ @@ -386,7 +392,9 @@ jabber_send(js, auth); xmlnode_free(auth); } else { - purple_connection_error(js->gc, "SASL authentication failed\n"); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + "SASL authentication failed\n"); } } @@ -459,7 +467,9 @@ mechs = xmlnode_get_child(packet, "mechanisms"); if(!mechs) { - purple_connection_error(js->gc, _("Invalid response from server.")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid response from server.")); return; } @@ -519,7 +529,8 @@ } finish_plaintext_authentication(js); } else { - purple_connection_error(js->gc, + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, _("Server does not use any supported authentication method")); } #endif @@ -532,20 +543,22 @@ if(type && !strcmp(type, "result")) { jabber_stream_set_state(js, JABBER_STREAM_CONNECTED); } else { - char *msg = jabber_parse_error(js, packet); + PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + char *msg = jabber_parse_error(js, packet, &reason); xmlnode *error; const char *err_code; + /* FIXME: Why is this not in jabber_parse_error? */ if((error = xmlnode_get_child(packet, "error")) && (err_code = xmlnode_get_attrib(error, "code")) && !strcmp(err_code, "401")) { - js->gc->wants_to_die = TRUE; + reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; /* Clear the pasword if it isn't being saved */ if (!purple_account_get_remember_password(js->gc->account)) purple_account_set_password(js->gc->account, NULL); } - purple_connection_error(js->gc, msg); + purple_connection_error_reason (js->gc, reason, msg); g_free(msg); } } @@ -558,11 +571,14 @@ const char *pw = purple_connection_get_password(js->gc); if(!type) { - purple_connection_error(js->gc, _("Invalid response from server.")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid response from server.")); return; } else if(!strcmp(type, "error")) { - char *msg = jabber_parse_error(js, packet); - purple_connection_error(js->gc, msg); + PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + char *msg = jabber_parse_error(js, packet, &reason); + purple_connection_error_reason (js->gc, reason, msg); g_free(msg); } else if(!strcmp(type, "result")) { query = xmlnode_get_child(packet, "query"); @@ -606,8 +622,9 @@ } finish_plaintext_authentication(js); } else { - purple_connection_error(js->gc, - _("Server does not use any supported authentication method")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + _("Server does not use any supported authentication method")); return; } } @@ -773,7 +790,9 @@ GHashTable *parts; if(!enc_in) { - purple_connection_error(js->gc, _("Invalid response from server.")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid response from server.")); return; } @@ -794,7 +813,9 @@ "", -1); } else { - purple_connection_error(js->gc, _("Invalid challenge from server")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid challenge from server")); } g_free(js->expected_rspauth); } else { @@ -817,7 +838,9 @@ realm = js->user->domain; if (nonce == NULL || realm == NULL) - purple_connection_error(js->gc, _("Invalid challenge from server")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid challenge from server")); else { GString *response = g_string_new(""); char *a2; @@ -889,7 +912,9 @@ g_free(dec_in); if (js->sasl_state != SASL_CONTINUE && js->sasl_state != SASL_OK) { purple_debug_error("jabber", "Error is %d : %s\n",js->sasl_state,sasl_errdetail(js->sasl)); - purple_connection_error(js->gc, _("SASL error")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("SASL error")); return; } else { response = xmlnode_new("response"); @@ -914,7 +939,9 @@ #endif if(!ns || strcmp(ns, "urn:ietf:params:xml:ns:xmpp-sasl")) { - purple_connection_error(js->gc, _("Invalid response from server.")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid response from server.")); return; } @@ -939,7 +966,9 @@ if (js->sasl_state != SASL_OK) { /* This should never happen! */ - purple_connection_error(js->gc, _("Invalid response from server.")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid response from server.")); } } /* If we've negotiated a security layer, we need to enable it */ @@ -955,12 +984,15 @@ void jabber_auth_handle_failure(JabberStream *js, xmlnode *packet) { - char *msg = jabber_parse_error(js, packet); + PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + char *msg = jabber_parse_error(js, packet, &reason); if(!msg) { - purple_connection_error(js->gc, _("Invalid response from server.")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid response from server.")); } else { - purple_connection_error(js->gc, msg); + purple_connection_error_reason (js->gc, reason, msg); g_free(msg); } } diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/jabber/buddy.c --- a/libpurple/protocols/jabber/buddy.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/jabber/buddy.c Sun Nov 11 14:19:55 2007 +0000 @@ -2344,7 +2344,7 @@ return; if(!(type = xmlnode_get_attrib(packet, "type")) || !strcmp(type, "error")) { - char *msg = jabber_parse_error(js, packet); + char *msg = jabber_parse_error(js, packet, NULL); if(!msg) msg = g_strdup(_("Unknown error")); diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/jabber/chat.c --- a/libpurple/protocols/jabber/chat.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/jabber/chat.c Sun Nov 11 14:19:55 2007 +0000 @@ -391,7 +391,7 @@ } } } else if(!strcmp(type, "error")) { - char *msg = jabber_parse_error(js, packet); + char *msg = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Configuration error"), _("Configuration error"), msg); @@ -465,7 +465,7 @@ const char *type = xmlnode_get_attrib(packet, "type"); if(type && !strcmp(type, "error")) { - char *msg = jabber_parse_error(js, packet); + char *msg = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Registration error"), _("Registration error"), msg); @@ -534,7 +534,7 @@ } } } else if(!strcmp(type, "error")) { - char *msg = jabber_parse_error(js, packet); + char *msg = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Registration error"), _("Registration error"), msg); @@ -673,7 +673,7 @@ return; if(!(type = xmlnode_get_attrib(packet, "type")) || strcmp(type, "result")) { - char *err = jabber_parse_error(js,packet); + char *err = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Error"), _("Error retrieving room list"), err); purple_roomlist_set_in_progress(js->roomlist, FALSE); @@ -684,7 +684,7 @@ } if(!(query = xmlnode_get_child(packet, "query"))) { - char *err = jabber_parse_error(js, packet); + char *err = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Error"), _("Error retrieving room list"), err); purple_roomlist_set_in_progress(js->roomlist, FALSE); diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/jabber/jabber.c --- a/libpurple/protocols/jabber/jabber.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.c Sun Nov 11 14:19:55 2007 +0000 @@ -89,7 +89,9 @@ if(js->unregistration) jabber_unregister_account_cb(js); } else { - purple_connection_error(js->gc, _("Error initializing session")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + ("Error initializing session")); } } @@ -120,15 +122,18 @@ JabberBuddy *my_jb = NULL; jabber_id_free(js->user); if(!(js->user = jabber_id_new(full_jid))) { - purple_connection_error(js->gc, _("Invalid response from server.")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid response from server.")); } if((my_jb = jabber_buddy_find(js, full_jid, TRUE))) my_jb->subscription |= JABBER_SUB_BOTH; g_free(full_jid); } } else { - char *msg = jabber_parse_error(js, packet); - purple_connection_error(js->gc, msg); + PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + char *msg = jabber_parse_error(js, packet, &reason); + purple_connection_error_reason (js->gc, reason, msg); g_free(msg); } @@ -141,8 +146,9 @@ if(jabber_process_starttls(js, packet)) return; } else if(purple_account_get_bool(js->gc->account, "require_tls", FALSE) && !js->gsc) { - js->gc->wants_to_die = TRUE; - purple_connection_error(js->gc, _("You require encryption, but it is not available on this server.")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, + _("You require encryption, but it is not available on this server.")); return; } @@ -173,9 +179,11 @@ static void jabber_stream_handle_error(JabberStream *js, xmlnode *packet) { - char *msg = jabber_parse_error(js, packet); + PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + char *msg = jabber_parse_error(js, packet, &reason); - purple_connection_error(js->gc, msg); + purple_connection_error_reason (js->gc, reason, msg); + g_free(msg); } @@ -256,7 +264,9 @@ if (ret < 0 && errno == EAGAIN) return; else if (ret <= 0) { - purple_connection_error(js->gc, _("Write error")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Write error")); return; } @@ -309,7 +319,9 @@ } if (ret < 0 && errno != EAGAIN) - purple_connection_error(js->gc, _("Write error")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Write error")); else if (ret < olen) { if (ret < 0) ret = 0; @@ -337,7 +349,9 @@ } if (ret < 0 && errno != EAGAIN) - purple_connection_error(js->gc, _("Write error")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Write error")); else if (ret < len) { if (ret < 0) ret = 0; @@ -405,7 +419,9 @@ if(errno == EAGAIN) return; else - purple_connection_error(gc, _("Read Error")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Read Error")); } static void @@ -442,7 +458,9 @@ } else if(errno == EAGAIN) { return; } else { - purple_connection_error(gc, _("Read Error")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Read Error")); } } @@ -481,7 +499,8 @@ gchar *tmp; tmp = g_strdup_printf(_("Could not establish a connection with the server:\n%s"), error); - purple_connection_error(gc, tmp); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); g_free(tmp); return; } @@ -509,7 +528,7 @@ js = gc->proto_data; js->gsc = NULL; - purple_connection_error(gc, purple_ssl_strerror(error)); + purple_connection_ssl_error (gc, error); } static void tls_init(JabberStream *js) @@ -526,7 +545,9 @@ if (purple_proxy_connect(js->gc, js->gc->account, host, port, jabber_login_callback, js->gc) == NULL) - purple_connection_error(js->gc, _("Unable to create socket")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unable to create socket")); } static void srv_resolved_cb(PurpleSrvResponse *resp, int results, gpointer data) @@ -572,12 +593,16 @@ js->old_length = -1; if(!js->user) { - purple_connection_error(gc, _("Invalid XMPP ID")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, + _("Invalid XMPP ID")); return; } if (!js->user->domain || *(js->user->domain) == '\0') { - purple_connection_error(gc, _("Invalid XMPP ID. Domain must be set.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, + _("Invalid XMPP ID. Domain must be set.")); return; } @@ -607,7 +632,9 @@ purple_account_get_int(account, "port", 5223), jabber_login_callback_ssl, jabber_ssl_connect_failure, js->gc); } else { - purple_connection_error(js->gc, _("SSL support unavailable")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, + _("SSL support unavailable")); } } @@ -665,7 +692,7 @@ _("Registration Successful"), buf); g_free(buf); } else { - char *msg = jabber_parse_error(js, packet); + char *msg = jabber_parse_error(js, packet, NULL); if(!msg) msg = g_strdup(_("Unknown Error")); @@ -695,7 +722,7 @@ _("Unregistration Successful"), buf); g_free(buf); } else { - char *msg = jabber_parse_error(js, packet); + char *msg = jabber_parse_error(js, packet, NULL); if(!msg) msg = g_strdup(_("Unknown Error")); @@ -1060,7 +1087,9 @@ js->old_length = -1; if(!js->user) { - purple_connection_error(gc, _("Invalid XMPP ID")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, + _("Invalid XMPP ID")); return; } @@ -1092,7 +1121,9 @@ purple_account_get_int(account, "port", 5222), jabber_login_callback_ssl, jabber_ssl_connect_failure, gc); } else { - purple_connection_error(gc, _("SSL support unavailable")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, + _("SSL support unavailable")); } } @@ -1115,7 +1146,7 @@ PurpleAccount *account = purple_connection_get_account(js->gc); const char *type = xmlnode_get_attrib(packet,"type"); if(!strcmp(type,"error")) { - char *msg = jabber_parse_error(js, packet); + char *msg = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Error unregistering account"), _("Error unregistering account"), msg); @@ -1643,7 +1674,7 @@ purple_account_set_password(js->gc->account, (char *)data); } else { - char *msg = jabber_parse_error(js, packet); + char *msg = jabber_parse_error(js, packet, NULL); purple_notify_error(js->gc, _("Error changing password"), _("Error changing password"), msg); @@ -1807,13 +1838,18 @@ } -char *jabber_parse_error(JabberStream *js, xmlnode *packet) +char *jabber_parse_error(JabberStream *js, + xmlnode *packet, + PurpleConnectionError *reason) { xmlnode *error; const char *code = NULL, *text = NULL; const char *xmlns = xmlnode_get_namespace(packet); char *cdata = NULL; +#define SET_REASON(x) \ + if(reason != NULL) { *reason = x; } + if((error = xmlnode_get_child(packet, "error"))) { cdata = xmlnode_get_data(error); code = xmlnode_get_attrib(error, "code"); @@ -1865,22 +1901,21 @@ text = _("Unknown Error"); } } else if(xmlns && !strcmp(xmlns, "urn:ietf:params:xml:ns:xmpp-sasl")) { + /* Most common reason can be the default */ + SET_REASON(PURPLE_CONNECTION_ERROR_NETWORK_ERROR); if(xmlnode_get_child(packet, "aborted")) { - js->gc->wants_to_die = TRUE; text = _("Authorization Aborted"); } else if(xmlnode_get_child(packet, "incorrect-encoding")) { text = _("Incorrect encoding in authorization"); } else if(xmlnode_get_child(packet, "invalid-authzid")) { - js->gc->wants_to_die = TRUE; text = _("Invalid authzid"); } else if(xmlnode_get_child(packet, "invalid-mechanism")) { - js->gc->wants_to_die = TRUE; text = _("Invalid Authorization Mechanism"); } else if(xmlnode_get_child(packet, "mechanism-too-weak")) { - js->gc->wants_to_die = TRUE; + SET_REASON(PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE); text = _("Authorization mechanism too weak"); } else if(xmlnode_get_child(packet, "not-authorized")) { - js->gc->wants_to_die = TRUE; + SET_REASON(PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED); /* Clear the pasword if it isn't being saved */ if (!purple_account_get_remember_password(js->gc->account)) purple_account_set_password(js->gc->account, NULL); @@ -1888,18 +1923,20 @@ } else if(xmlnode_get_child(packet, "temporary-auth-failure")) { text = _("Temporary Authentication Failure"); } else { - js->gc->wants_to_die = TRUE; + SET_REASON(PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED); text = _("Authentication Failure"); } } else if(!strcmp(packet->name, "stream:error") || (!strcmp(packet->name, "error") && xmlns && !strcmp(xmlns, "http://etherx.jabber.org/streams"))) { + /* Most common reason as default: */ + SET_REASON(PURPLE_CONNECTION_ERROR_NETWORK_ERROR); if(xmlnode_get_child(packet, "bad-format")) { text = _("Bad Format"); } else if(xmlnode_get_child(packet, "bad-namespace-prefix")) { text = _("Bad Namespace Prefix"); } else if(xmlnode_get_child(packet, "conflict")) { - js->gc->wants_to_die = TRUE; + SET_REASON(PURPLE_CONNECTION_ERROR_NAME_IN_USE); text = _("Resource Conflict"); } else if(xmlnode_get_child(packet, "connection-timeout")) { text = _("Connection Timeout"); @@ -1948,6 +1985,8 @@ } } +#undef SET_REASON + if(text || cdata) { char *ret = g_strdup_printf("%s%s%s", code ? code : "", code ? ": " : "", text ? text : cdata); diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/jabber/jabber.h --- a/libpurple/protocols/jabber/jabber.h Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/jabber/jabber.h Sun Nov 11 14:19:55 2007 +0000 @@ -216,7 +216,14 @@ char *jabber_get_next_id(JabberStream *js); -char *jabber_parse_error(JabberStream *js, xmlnode *packet); +/** Parse an error into a human-readable string and optionally a disconnect + * reason. + * @param js the stream on which the error occurred. + * @param packet the error packet + * @param reason where to store the disconnection reason, or @c NULL if you + * don't care or you don't intend to close the connection. + */ +char *jabber_parse_error(JabberStream *js, xmlnode *packet, PurpleConnectionError *reason); void jabber_add_feature(const gchar *shortname, const gchar *namespace, JabberFeatureEnabled cb); /* cb may be NULL */ void jabber_remove_feature(const gchar *shortname); diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/jabber/parser.c --- a/libpurple/protocols/jabber/parser.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/jabber/parser.c Sun Nov 11 14:19:55 2007 +0000 @@ -193,7 +193,9 @@ js->context = xmlCreatePushParserCtxt(&jabber_parser_libxml, js, buf, len, NULL); xmlParseChunk(js->context, "", 0, 0); } else if (xmlParseChunk(js->context, buf, len, 0) < 0) { - purple_connection_error(js->gc, _("XML Parse error")); + purple_connection_error_reason (js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("XML Parse error")); } } diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/jabber/presence.c --- a/libpurple/protocols/jabber/presence.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/jabber/presence.c Sun Nov 11 14:19:55 2007 +0000 @@ -429,7 +429,7 @@ } if(type && !strcmp(type, "error")) { - char *msg = jabber_parse_error(js, packet); + char *msg = jabber_parse_error(js, packet, NULL); state = JABBER_BUDDY_STATE_ERROR; jb->error_msg = msg ? msg : g_strdup(_("Unknown Error in presence")); @@ -561,7 +561,7 @@ char *room_jid = g_strdup_printf("%s@%s", jid->node, jid->domain); if(state == JABBER_BUDDY_STATE_ERROR) { - char *title, *msg = jabber_parse_error(js, packet); + char *title, *msg = jabber_parse_error(js, packet, NULL); if(chat->conv) { title = g_strdup_printf(_("Error in chat %s"), from); diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/msn/contact.c --- a/libpurple/protocols/msn/contact.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/msn/contact.c Sun Nov 11 14:19:55 2007 +0000 @@ -709,7 +709,7 @@ msn_get_address_book(contact, NULL, NULL); */ msn_session_disconnect(session); - purple_connection_error(session->account->gc, _("Unable to retrieve MSN Address Book")); + purple_connection_error_reason(session->account->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to retrieve MSN Address Book")); } } diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/msn/msn.c --- a/libpurple/protocols/msn/msn.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/msn/msn.c Sun Nov 11 14:19:55 2007 +0000 @@ -844,8 +844,8 @@ if (!purple_ssl_is_supported()) { - gc->wants_to_die = TRUE; - purple_connection_error(gc, + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, _("SSL support is needed for MSN. Please install a supported " "SSL library.")); return; @@ -874,7 +874,9 @@ purple_account_set_username(account, username); if (!msn_session_connect(session, host, port, http_method)) - purple_connection_error(gc, _("Failed to connect to server.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Failed to connect to server.")); } static void diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/msn/session.c --- a/libpurple/protocols/msn/session.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/msn/session.c Sun Nov 11 14:19:55 2007 +0000 @@ -333,6 +333,7 @@ const char *info) { PurpleConnection *gc; + PurpleConnectionError reason; char *msg; gc = purple_account_get_connection(session->account); @@ -340,49 +341,56 @@ switch (error) { case MSN_ERROR_SERVCONN: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; msg = g_strdup(info); break; case MSN_ERROR_UNSUPPORTED_PROTOCOL: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; msg = g_strdup(_("Our protocol is not supported by the " "server.")); break; case MSN_ERROR_HTTP_MALFORMED: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; msg = g_strdup(_("Error parsing HTTP.")); break; case MSN_ERROR_SIGN_OTHER: - gc->wants_to_die = TRUE; + reason = PURPLE_CONNECTION_ERROR_NAME_IN_USE; msg = g_strdup(_("You have signed on from another location.")); if (!purple_account_get_remember_password(session->account)) purple_account_set_password(session->account, NULL); break; case MSN_ERROR_SERV_UNAVAILABLE: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; msg = g_strdup(_("The MSN servers are temporarily " "unavailable. Please wait and try " "again.")); break; case MSN_ERROR_SERV_DOWN: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; msg = g_strdup(_("The MSN servers are going down " "temporarily.")); break; case MSN_ERROR_AUTH: - gc->wants_to_die = TRUE; + reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; msg = g_strdup_printf(_("Unable to authenticate: %s"), (info == NULL ) ? _("Unknown error") : info); break; case MSN_ERROR_BAD_BLIST: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; msg = g_strdup(_("Your MSN buddy list is temporarily " "unavailable. Please wait and try " "again.")); break; default: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; msg = g_strdup(_("Unknown error.")); break; } msn_session_disconnect(session); - purple_connection_error(gc, msg); + purple_connection_error_reason (gc, reason, msg); g_free(msg); } diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/myspace/myspace.c --- a/libpurple/protocols/myspace/myspace.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/myspace/myspace.c Sun Nov 11 14:19:55 2007 +0000 @@ -291,8 +291,8 @@ /* Notify an error message also, because this is important! */ purple_notify_error(acct, _("MySpaceIM Error"), str, NULL); - gc->wants_to_die = TRUE; - purple_connection_error(gc, str); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, str); g_free(str); return; } @@ -315,7 +315,9 @@ if (!purple_proxy_connect(gc, acct, host, port, msim_connect_cb, gc)) { /* TODO: try other ports if in auto mode, then save * working port and try that first next time. */ - purple_connection_error(gc, _("Couldn't create socket")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Couldn't create socket")); return; } } @@ -353,7 +355,9 @@ if (nc_len != MSIM_AUTH_CHALLENGE_LENGTH) { purple_debug_info("msim", "bad nc length: %x != 0x%x\n", nc_len, MSIM_AUTH_CHALLENGE_LENGTH); - purple_connection_error(session->gc, _("Unexpected challenge length from server")); + purple_connection_error_reason (session->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unexpected challenge length from server")); return FALSE; } @@ -826,7 +830,6 @@ serv_got_typing_stopped(session->gc, username); g_free(username); - g_free(text); return TRUE; } @@ -1294,7 +1297,8 @@ purple_debug_info("msim", "msim_check_alive: %s > interval of %d, presumed dead\n", errmsg, MSIM_KEEPALIVE_INTERVAL); - purple_connection_error(session->gc, errmsg); + purple_connection_error_reason (session->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, errmsg); purple_notify_error(session->gc, NULL, errmsg, NULL); @@ -1560,7 +1564,8 @@ purple_notify_error(session->account, _("No username set"), _("Please go to http://editprofile.myspace.com/index.cfm?fuseaction=profile.username and choose a username and try to login again."), NULL); - purple_connection_error(session->gc, _("No username set")); + purple_connection_error_reason (session->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("No username set")); return FALSE; } @@ -1796,16 +1801,21 @@ /* Destroy session if fatal. */ if (msim_msg_get(msg, "fatal")) { + PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; purple_debug_info("msim", "fatal error, closing\n"); switch (err) { case 260: /* Incorrect password */ + reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; + if (!purple_account_get_remember_password(session->account)) + purple_account_set_password(session->account, NULL); + break; case 6: /* Logged in elsewhere */ - session->gc->wants_to_die = TRUE; + reason = PURPLE_CONNECTION_ERROR_NAME_IN_USE; if (!purple_account_get_remember_password(session->account)) purple_account_set_password(session->account, NULL); break; } - purple_connection_error(session->gc, full_errmsg); + purple_connection_error_reason (session->gc, reason, full_errmsg); } else { purple_notify_error(session->account, _("MySpaceIM Error"), full_errmsg, NULL); } @@ -1875,7 +1885,6 @@ purple_blist_add_buddy(buddy, NULL, NULL, NULL); user = msim_get_user_from_buddy(buddy); - /* TODO: free user. memory leak? */ /* All buddies on list should have 'uid' integer associated with them. */ purple_blist_node_set_int(&buddy->node, "UserID", msim_msg_get_integer(msg, "f")); @@ -2317,7 +2326,9 @@ /* libpurple/eventloop.h only defines these two */ if (cond != PURPLE_INPUT_READ && cond != PURPLE_INPUT_WRITE) { purple_debug_info("msim_input_cb", "unknown condition=%d\n", cond); - purple_connection_error(gc, _("Invalid input condition")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Invalid input condition")); return; } @@ -2335,7 +2346,9 @@ purple_debug_error("msim", "msim_input_cb: %d-byte read buffer full! rxoff=%d\n", MSIM_READ_BUF_SIZE, session->rxoff); - purple_connection_error(gc, _("Read buffer full")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Read buffer full")); return; } @@ -2354,11 +2367,15 @@ purple_debug_error("msim", "msim_input_cb: read error, ret=%d, " "error=%s, source=%d, fd=%d (%X))\n", n, g_strerror(errno), source, session->fd, session->fd); - purple_connection_error(gc, _("Read error")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Read error")); return; } else if (n == 0) { purple_debug_info("msim", "msim_input_cb: server disconnected\n"); - purple_connection_error(gc, _("Server has disconnected")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Server has disconnected")); return; } @@ -2366,7 +2383,9 @@ purple_debug_info("msim_input_cb", "received %d bytes, pushing rxoff to %d, over buffer size of %d\n", n, n + session->rxoff, MSIM_READ_BUF_SIZE); /* TODO: g_realloc like msn, yahoo, irc, jabber? */ - purple_connection_error(gc, _("Read buffer full")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Read buffer full")); } /* Null terminate */ @@ -2381,7 +2400,9 @@ purple_debug_info("msim", "msim_input_cb: strlen=%d, but read %d bytes" "--null byte encountered?\n", strlen(session->rxbuf + session->rxoff), n); - //purple_connection_error(gc, "Invalid message - null byte on input"); + /*purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + "Invalid message - null byte on input"); */ return; } #endif @@ -2404,7 +2425,9 @@ msg = msim_parse(g_strdup(session->rxbuf)); if (!msg) { purple_debug_info("msim", "msim_input_cb: couldn't parse rxbuf\n"); - purple_connection_error(gc, _("Unparseable message")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unparseable message")); } else { /* Process message and then free it (processing function should * clone message if it wants to keep it afterwards.) */ @@ -2471,9 +2494,9 @@ session = (MsimSession *)gc->proto_data; if (source < 0) { - purple_connection_error(gc, _("Couldn't connect to host")); - purple_connection_error(gc, g_strdup_printf( - _("Couldn't connect to host: %s (%d)"), + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + g_strdup_printf(_("Couldn't connect to host: %s (%d)"), error_message ? error_message : "no message given", source)); return; @@ -2674,7 +2697,6 @@ /* TODO: other fields, store in 'user' */ msim_msg_free(contact_info); - g_free(username); } /** Add first ContactID in contact_info to buddy's list. Used to add diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/novell/novell.c --- a/libpurple/protocols/novell/novell.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/novell/novell.c Sun Nov 11 14:19:55 2007 +0000 @@ -120,21 +120,27 @@ _check_for_disconnect(user, rc); } else { - + PurpleConnectionError reason; char *err = g_strdup_printf(_("Login failed (%s)."), nm_error_to_string (ret_code)); - /* Don't attempt to auto-reconnect if our password - * was invalid. - */ - if (ret_code == NMERR_AUTHENTICATION_FAILED || - ret_code == NMERR_CREDENTIALS_MISSING || - ret_code == NMERR_PASSWORD_INVALID) { - if (!purple_account_get_remember_password(gc->account)) - purple_account_set_password(gc->account, NULL); - gc->wants_to_die = TRUE; + switch (ret_code) { + case NMERR_AUTHENTICATION_FAILED: + case NMERR_CREDENTIALS_MISSING: + case NMERR_PASSWORD_INVALID: + /* Don't attempt to auto-reconnect if our + * password was invalid. + */ + if (!purple_account_get_remember_password(gc->account)) + purple_account_set_password(gc->account, NULL); + reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; + break; + default: + /* FIXME: There are other reasons login could fail */ + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; } - purple_connection_error(gc, err); + + purple_connection_error_reason (gc, reason, err); g_free(err); } } @@ -1120,8 +1126,9 @@ if (_is_disconnect_error(err)) { - purple_connection_error(gc, _("Error communicating with server." - " Closing connection.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Error communicating with server. Closing connection.")); return TRUE; } @@ -1667,7 +1674,7 @@ user = gc->proto_data; user->conn->ssl_conn->data = NULL; - purple_connection_error(gc, _("Unable to make SSL connection to server.")); + purple_connection_ssl_error (gc, error); } static void @@ -1690,9 +1697,9 @@ if (_is_disconnect_error(rc)) { - purple_connection_error(gc, - _("Error communicating with server." - " Closing connection.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Error communicating with server. Closing connection.")); } else { purple_debug(PURPLE_DEBUG_INFO, "novell", "Error processing event or response (%d).\n", rc); @@ -1731,7 +1738,9 @@ conn->connected = TRUE; purple_ssl_input_add(gsc, novell_ssl_recv_cb, gc); } else { - purple_connection_error(gc, _("Unable to connect to server.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unable to connect to server.")); } purple_connection_update_progress(gc, _("Waiting for response..."), @@ -2011,11 +2020,12 @@ gc = purple_account_get_connection(account); if (gc) { - gc->wants_to_die = TRUE; /* we don't want to reconnect in this case */ if (!purple_account_get_remember_password(account)) purple_account_set_password(account, NULL); - purple_connection_error(gc, _("You have been logged out because you" - " logged in at another workstation.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NAME_IN_USE, + _("You have been logged out because you" + " logged in at another workstation.")); } } @@ -2169,9 +2179,10 @@ */ /* ...but for now just error out with a nice message. */ - purple_connection_error(gc, _("Unable to connect to server." - " Please enter the address of the server" - " you wish to connect to.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, + _("Unable to connect to server. Please enter the " + "address of the server you wish to connect to.")); return; } @@ -2197,8 +2208,9 @@ user->conn->addr, user->conn->port, novell_ssl_connected_cb, novell_ssl_connect_error, gc); if (user->conn->ssl_conn->data == NULL) { - purple_connection_error(gc, _("Error." - " SSL support is not installed.")); + purple_connection_error_reason (gc, + PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, + _("Error. SSL support is not installed.")); } } } diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/oscar/flap_connection.c --- a/libpurple/protocols/oscar/flap_connection.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/oscar/flap_connection.c Sun Nov 11 14:19:55 2007 +0000 @@ -380,11 +380,13 @@ { /* No more FLAP connections! Sign off this PurpleConnection! */ gchar *tmp; + PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + if (conn->disconnect_code == 0x0001) { + reason = PURPLE_CONNECTION_ERROR_NAME_IN_USE; tmp = g_strdup(_("You have signed on from another location.")); if (!purple_account_get_remember_password(account)) purple_account_set_password(account, NULL); - od->gc->wants_to_die = TRUE; } else if (conn->disconnect_reason == OSCAR_DISCONNECT_REMOTE_CLOSED) tmp = g_strdup(_("Server closed the connection.")); else if (conn->disconnect_reason == OSCAR_DISCONNECT_LOST_CONNECTION) @@ -404,7 +406,7 @@ if (tmp != NULL) { - purple_connection_error(od->gc, tmp); + purple_connection_error_reason(od->gc, reason, tmp); g_free(tmp); } } diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/oscar/oscar.c --- a/libpurple/protocols/oscar/oscar.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/oscar/oscar.c Sun Nov 11 14:19:55 2007 +0000 @@ -994,7 +994,7 @@ gchar *msg; msg = g_strdup_printf(_("Could not connect to authentication server:\n%s"), error_message); - purple_connection_error(gc, msg); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg); g_free(msg); } else if (conn->type == SNAC_FAMILY_LOCATE) @@ -1002,7 +1002,7 @@ gchar *msg; msg = g_strdup_printf(_("Could not connect to BOS server:\n%s"), error_message); - purple_connection_error(gc, msg); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg); g_free(msg); } else @@ -1260,8 +1260,7 @@ if (!aim_snvalid(purple_account_get_username(account))) { gchar *buf; buf = g_strdup_printf(_("Unable to login: Could not sign on as %s because the screen name is invalid. Screen names must be a valid email address, or start with a letter and contain only letters, numbers and spaces, or contain only numbers."), purple_account_get_username(account)); - gc->wants_to_die = TRUE; - purple_connection_error(gc, buf); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, buf); g_free(buf); return; } @@ -1283,7 +1282,8 @@ connection_established_cb, newconn); if (newconn->connect_data == NULL) { - purple_connection_error(gc, _("Couldn't connect to host")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Couldn't connect to host")); return; } @@ -1344,43 +1344,37 @@ switch (info->errorcode) { case 0x01: /* Unregistered screen name */ - gc->wants_to_die = TRUE; - purple_connection_error(gc, _("Invalid screen name.")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Invalid screen name.")); break; case 0x05: /* Incorrect password */ - gc->wants_to_die = TRUE; if (!purple_account_get_remember_password(account)) purple_account_set_password(account, NULL); - purple_connection_error(gc, _("Incorrect password.")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Incorrect password.")); break; case 0x11: /* Suspended account */ - gc->wants_to_die = TRUE; - purple_connection_error(gc, _("Your account is currently suspended.")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Your account is currently suspended.")); break; case 0x14: /* service temporarily unavailable */ - purple_connection_error(gc, _("The AOL Instant Messenger service is temporarily unavailable.")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("The AOL Instant Messenger service is temporarily unavailable.")); break; case 0x18: /* screen name connecting too frequently */ - gc->wants_to_die = TRUE; - purple_connection_error(gc, _("You have been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer.")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("You have been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer.")); break; case 0x1c: /* client too old */ - gc->wants_to_die = TRUE; g_snprintf(buf, sizeof(buf), _("The client version you are using is too old. Please upgrade at %s"), PURPLE_WEBSITE); - purple_connection_error(gc, buf); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, buf); break; case 0x1d: /* IP address connecting too frequently */ - gc->wants_to_die = TRUE; - purple_connection_error(gc, _("You have been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer.")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("You have been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer.")); break; default: - purple_connection_error(gc, _("Authentication failed")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Authentication failed")); break; } purple_debug_info("oscar", "Login Error Code 0x%04hx\n", info->errorcode); @@ -1410,7 +1404,7 @@ g_free(host); if (newconn->connect_data == NULL) { - purple_connection_error(gc, _("Could Not Connect")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Could Not Connect")); return 0; } @@ -1435,8 +1429,9 @@ PurpleConnection *gc = user_data; /* Disconnect */ - gc->wants_to_die = TRUE; - purple_connection_error(gc, _("The SecurID key entered is invalid.")); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, + _("The SecurID key entered is invalid.")); } static int diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/qq/keep_alive.c --- a/libpurple/protocols/qq/keep_alive.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/qq/keep_alive.c Sun Nov 11 14:19:55 2007 +0000 @@ -84,7 +84,8 @@ /* segments[0] and segment[1] are all 0x30 ("0") */ qd->all_online = strtol(segments[2], NULL, 10); if(0 == qd->all_online) - purple_connection_error(gc, _("Keep alive error")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Keep alive error")); g_free(qd->my_ip); qd->my_ip = g_strdup(segments[3]); qd->my_port = strtol(segments[4], NULL, 10); diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/qq/login_logout.c --- a/libpurple/protocols/qq/login_logout.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/qq/login_logout.c Sun Nov 11 14:19:55 2007 +0000 @@ -405,7 +405,7 @@ ">>> %d bytes -> [default] decrypt and dump\n%s", buf_len, hex_dump); try_dump_as_gbk(buf, buf_len); - purple_connection_error(gc, _("Error requesting login token")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Error requesting login token")); } g_free(hex_dump); } @@ -479,13 +479,14 @@ switch (ret) { case QQ_LOGIN_REPLY_PWD_ERROR: - gc->wants_to_die = TRUE; if (!purple_account_get_remember_password(gc->account)) purple_account_set_password(gc->account, NULL); - purple_connection_error(gc, _("Incorrect password.")); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, _("Incorrect password.")); break; case QQ_LOGIN_REPLY_MISC_ERROR: - purple_connection_error(gc, _("Unable to login, check debug log")); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to login, check debug log")); break; case QQ_LOGIN_REPLY_OK: purple_debug(PURPLE_DEBUG_INFO, "QQ", "Login replys OK, everything is fine\n"); diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/qq/qq.c --- a/libpurple/protocols/qq/qq.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/qq/qq.c Sun Nov 11 14:19:55 2007 +0000 @@ -136,7 +136,8 @@ purple_connection_update_progress(gc, _("Connecting"), 0, QQ_CONNECT_STEPS); if (qq_connect(account, qq_server, strtol(qq_port, NULL, 10), use_tcp, FALSE) < 0) - purple_connection_error(gc, _("Unable to connect.")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unable to connect.")); } /* directly goes for qq_disconnect */ diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/qq/qq_proxy.c --- a/libpurple/protocols/qq/qq_proxy.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/qq/qq_proxy.c Sun Nov 11 14:19:55 2007 +0000 @@ -139,7 +139,7 @@ g_return_if_fail(gc != NULL && gc->proto_data != NULL); if (source < 0) { /* socket returns -1 */ - purple_connection_error(gc, error_message); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_message); return; } @@ -497,7 +497,7 @@ ret = send(qd->fd, data, len, 0); } if (ret == -1) - purple_connection_error(qd->gc, g_strerror(errno)); + purple_connection_error_reason(qd->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, g_strerror(errno)); return ret; } diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/qq/recv_core.c --- a/libpurple/protocols/qq/recv_core.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/qq/recv_core.c Sun Nov 11 14:19:55 2007 +0000 @@ -306,7 +306,8 @@ gc = (PurpleConnection *) data; if(cond != PURPLE_INPUT_READ) { - purple_connection_error(gc, _("Socket error")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Socket error")); return; } @@ -316,7 +317,8 @@ /* here we have UDP proxy suppport */ len = qq_proxy_read(qd, buf, MAX_PACKET_SIZE); if (len <= 0) { - purple_connection_error(gc, _("Unable to read from socket")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unable to read from socket")); return; } else { _qq_packet_process(buf, len, gc); diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/qq/sendqueue.c --- a/libpurple/protocols/qq/sendqueue.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/qq/sendqueue.c Sun Nov 11 14:19:55 2007 +0000 @@ -120,7 +120,8 @@ case QQ_CMD_KEEP_ALIVE: if (qd->logged_in) { purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Connection lost!\n"); - purple_connection_error(gc, _("Connection lost")); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Connection lost")); qd->logged_in = FALSE; } p->resend_times = -1; @@ -128,7 +129,8 @@ case QQ_CMD_LOGIN: case QQ_CMD_REQUEST_LOGIN_TOKEN: if (!qd->logged_in) /* cancel login progress */ - purple_connection_error(gc, _("Login failed, no reply")); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Login failed, no reply")); p->resend_times = -1; break; default:{ diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/sametime/sametime.c --- a/libpurple/protocols/sametime/sametime.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/sametime/sametime.c Sun Nov 11 14:19:55 2007 +0000 @@ -414,7 +414,9 @@ } else if(len > 0) { DEBUG_ERROR("write returned %i, %i bytes left unwritten\n", ret, len); - purple_connection_error(pd->gc, _("Connection closed (writing)")); + purple_connection_error_reason(pd->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Connection closed (writing)")); #if 0 close(pd->socket); @@ -1552,7 +1554,38 @@ if(GPOINTER_TO_UINT(info) & ERR_FAILURE) { char *err = mwError(GPOINTER_TO_UINT(info)); - purple_connection_error(gc, err); + PurpleConnectionError reason; + switch (GPOINTER_TO_UINT(info)) { + case VERSION_MISMATCH: + reason = PURPLE_CONNECTION_ERROR_OTHER_ERROR; + break; + + case USER_RESTRICTED: + case INCORRECT_LOGIN: + case USER_UNREGISTERED: + case GUEST_IN_USE: + reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; + break; + + case ENCRYPT_MISMATCH: + case ERR_ENCRYPT_NO_SUPPORT: + case ERR_NO_COMMON_ENCRYPT: + reason = PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR; + break; + + case VERIFICATION_DOWN: + reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE; + break; + + case MULTI_SERVER_LOGIN: + case MULTI_SERVER_LOGIN2: + reason = PURPLE_CONNECTION_ERROR_NAME_IN_USE; + break; + + default: + reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; + } + purple_connection_error_reason(gc, reason, err); g_free(err); } break; @@ -1698,8 +1731,11 @@ } if(! ret) { + const char *msg = _("Connection reset"); DEBUG_INFO("connection reset\n"); - purple_connection_error(pd->gc, _("Connection reset")); + purple_connection_error_reason(pd->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + msg); } else if(ret < 0) { const gchar *err_str = g_strerror(err); @@ -1708,7 +1744,9 @@ DEBUG_INFO("error in read callback: %s\n", err_str); msg = g_strdup_printf(_("Error reading from socket: %s"), err_str); - purple_connection_error(pd->gc, msg); + purple_connection_error_reason(pd->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + msg); g_free(msg); } } @@ -1730,7 +1768,10 @@ } else { /* this is a regular connect, error out */ - purple_connection_error(pd->gc, _("Unable to connect to host")); + const char *msg = _("Unable to connect to host"); + purple_connection_error_reason(pd->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + msg); } return; @@ -3612,7 +3653,10 @@ static void prompt_host_cancel_cb(PurpleConnection *gc) { - purple_connection_error(gc, _("No Sametime Community Server specified")); + const char *msg = _("No Sametime Community Server specified"); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, + msg); } @@ -3724,7 +3768,8 @@ purple_connection_update_progress(gc, _("Connecting"), 1, MW_CONNECT_STEPS); if (purple_proxy_connect(gc, account, host, port, connect_cb, pd) == NULL) { - purple_connection_error(gc, _("Unable to connect to host")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unable to connect to host")); } } diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/silc/silc.c --- a/libpurple/protocols/silc/silc.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/silc/silc.c Sun Nov 11 14:19:55 2007 +0000 @@ -314,34 +314,40 @@ /* Close the connection */ if (!sg->detaching) - purple_connection_error(gc, _("Disconnected by server")); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Disconnected by server")); else /* TODO: Does this work correctly? Maybe we need to set wants_to_die? */ purple_account_disconnect(purple_connection_get_account(gc)); break; case SILC_CLIENT_CONN_ERROR: - purple_connection_error(gc, _("Error during connecting to SILC Server")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Error during connecting to SILC Server")); g_unlink(silcpurple_session_file(purple_account_get_username(sg->account))); break; case SILC_CLIENT_CONN_ERROR_KE: - purple_connection_error(gc, _("Key Exchange failed")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, + _("Key Exchange failed")); break; case SILC_CLIENT_CONN_ERROR_AUTH: - purple_connection_error(gc, _("Authentication failed")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, + _("Authentication failed")); break; case SILC_CLIENT_CONN_ERROR_RESUME: - purple_connection_error(gc, - _("Resuming detached session failed. " - "Press Reconnect to create new connection.")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, + _("Resuming detached session failed. " + "Press Reconnect to create new connection.")); g_unlink(silcpurple_session_file(purple_account_get_username(sg->account))); break; case SILC_CLIENT_CONN_ERROR_TIMEOUT: - purple_connection_error(gc, _("Connection Timeout")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Connection Timeout")); break; } @@ -408,7 +414,8 @@ sg = gc->proto_data; if (source < 0) { - purple_connection_error(gc, _("Connection failed")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Connection failed")); silc_pkcs_public_key_free(sg->public_key); silc_pkcs_private_key_free(sg->private_key); silc_free(sg); @@ -431,6 +438,7 @@ PurpleAccount *account = purple_connection_get_account(gc); char pkd[256], prd[256]; + /* Progress */ purple_connection_update_progress(gc, _("Connecting to SILC Server"), 1, 5); @@ -441,8 +449,8 @@ (char *)purple_account_get_string(account, "private-key", prd), (gc->password == NULL) ? "" : gc->password, &sg->public_key, &sg->private_key)) { - g_snprintf(pkd, sizeof(pkd), _("Could not load SILC key pair")); - purple_connection_error(gc, pkd); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, + _("Could not load SILC key pair")); gc->proto_data = NULL; silc_free(sg); return; @@ -455,7 +463,8 @@ purple_account_get_int(account, "port", 706), silcpurple_login_connected, gc) == NULL) { - purple_connection_error(gc, _("Unable to create connection")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unable to create connection")); gc->proto_data = NULL; silc_free(sg); return; @@ -484,7 +493,8 @@ /* Allocate SILC client */ client = silc_client_alloc(&ops, ¶ms, gc, NULL); if (!client) { - purple_connection_error(gc, _("Out of memory")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, + _("Out of memory")); return; } @@ -534,8 +544,8 @@ /* Init SILC client */ if (!silc_client_init(client, username, hostname, realname, silcpurple_running, sg)) { - gc->wants_to_die = TRUE; - purple_connection_error(gc, _("Cannot initialize SILC protocol")); + purple_connection_error(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, + _("Cannot initialize SILC protocol")); gc->proto_data = NULL; silc_free(sg); return; @@ -543,8 +553,8 @@ /* Check the ~/.silc dir and create it, and new key pair if necessary. */ if (!silcpurple_check_silc_dir(gc)) { - gc->wants_to_die = TRUE; - purple_connection_error(gc, _("Error loading SILC key pair")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, + _("Error loading SILC key pair")); gc->proto_data = NULL; silc_free(sg); return; diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/silc/util.c --- a/libpurple/protocols/silc/util.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/silc/util.c Sun Nov 11 14:19:55 2007 +0000 @@ -212,7 +212,8 @@ (gc->password == NULL) ? "" : gc->password, NULL, NULL, FALSE)) { - purple_connection_error(gc, _("Cannot create SILC key pair\n")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, + _("Cannot create SILC key pair\n")); return FALSE; } @@ -254,7 +255,8 @@ (gc->password == NULL) ? "" : gc->password, NULL, NULL, FALSE)) { - purple_connection_error(gc, _("Cannot create SILC key pair\n")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, + _("Cannot create SILC key pair\n")); return FALSE; } diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/simple/simple.c --- a/libpurple/protocols/simple/simple.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/simple/simple.c Sun Nov 11 14:19:55 2007 +0000 @@ -414,7 +414,9 @@ written = 0; else if(written <= 0) { /*TODO: do we really want to disconnect on a failure to write?*/ - purple_connection_error(gc, _("Could not write")); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Could not write")); return; } @@ -436,7 +438,9 @@ } if(source < 0) { - purple_connection_error(gc, _("Could not connect")); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Could not connect")); return; } @@ -462,7 +466,7 @@ if(!sip->connecting) { purple_debug_info("simple", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport); if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) { - purple_connection_error(gc, _("Couldn't create socket")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Couldn't create socket")); } sip->connecting = TRUE; } @@ -1101,11 +1105,11 @@ if(sip->registerstatus != SIMPLE_REGISTER_RETRY) { purple_debug_info("simple", "REGISTER retries %d\n", sip->registrar.retries); if(sip->registrar.retries > SIMPLE_REGISTER_RETRY_MAX) { - purple_debug_info("simple", "Setting wants_to_die to true.\n"); - sip->gc->wants_to_die = TRUE; if (!purple_account_get_remember_password(sip->gc->account)) purple_account_set_password(sip->gc->account, NULL); - purple_connection_error(sip->gc, _("Incorrect password.")); + purple_connection_error_reason(sip->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, + _("Incorrect password.")); return TRUE; } tmp = sipmsg_find_header(msg, "WWW-Authenticate"); @@ -1118,8 +1122,9 @@ if (sip->registerstatus != SIMPLE_REGISTER_RETRY) { purple_debug_info("simple", "Unrecognized return code for REGISTER.\n"); if (sip->registrar.retries > SIMPLE_REGISTER_RETRY_MAX) { - sip->gc->wants_to_die = TRUE; - purple_connection_error(sip->gc, _("Unknown server response.")); + purple_connection_error_reason(sip->gc, + PURPLE_CONNECTION_ERROR_OTHER_ERROR, + _("Unknown server response.")); return TRUE; } sip->registerstatus = SIMPLE_REGISTER_RETRY; @@ -1681,7 +1686,9 @@ } if(source < 0) { - purple_connection_error(gc, _("Could not connect")); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Could not connect")); return; } @@ -1715,7 +1722,9 @@ sip->listen_data = NULL; if(listenfd == -1) { - purple_connection_error(sip->gc, _("Could not create listen socket")); + purple_connection_error_reason(sip->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Could not create listen socket")); return; } @@ -1738,7 +1747,9 @@ sip->query_data = NULL; if (!hosts || !hosts->data) { - purple_connection_error(sip->gc, _("Couldn't resolve host")); + purple_connection_error_reason(sip->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Couldn't resolve host")); return; } @@ -1757,7 +1768,9 @@ sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM, simple_udp_host_resolved_listen_cb, sip); if (sip->listen_data == NULL) { - purple_connection_error(sip->gc, _("Could not create listen socket")); + purple_connection_error_reason(sip->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Could not create listen socket")); return; } } @@ -1770,7 +1783,9 @@ sip->listenfd = listenfd; if(sip->listenfd == -1) { - purple_connection_error(sip->gc, _("Could not create listen socket")); + purple_connection_error_reason(sip->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Could not create listen socket")); return; } @@ -1783,7 +1798,9 @@ /* open tcp connection to the server */ if (purple_proxy_connect(sip->gc, sip->account, sip->realhostname, sip->realport, login_cb, sip->gc) == NULL) { - purple_connection_error(sip->gc, _("Couldn't create socket")); + purple_connection_error_reason(sip->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Couldn't create socket")); } } @@ -1821,7 +1838,9 @@ sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM, simple_tcp_connect_listen_cb, sip); if (sip->listen_data == NULL) { - purple_connection_error(sip->gc, _("Could not create listen socket")); + purple_connection_error_reason(sip->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Could not create listen socket")); return; } } else { /* UDP */ @@ -1829,7 +1848,9 @@ sip->query_data = purple_dnsquery_a(hostname, port, simple_udp_host_resolved, sip); if (sip->query_data == NULL) { - purple_connection_error(sip->gc, _("Could not resolve hostname")); + purple_connection_error_reason(sip->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Could not resolve hostname")); } } } @@ -1845,8 +1866,9 @@ gc = purple_account_get_connection(account); if (strpbrk(username, " \t\v\r\n") != NULL) { - gc->wants_to_die = TRUE; - purple_connection_error(gc, _("SIP screen names may not contain whitespaces or @ symbols")); + purple_connection_error_reason(gc, + PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, + _("SIP screen names may not contain whitespaces or @ symbols")); return; } diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/yahoo/yahoo.c --- a/libpurple/protocols/yahoo/yahoo.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo.c Sun Nov 11 14:19:55 2007 +0000 @@ -201,10 +201,10 @@ char *message = NULL; if (pkt->service == YAHOO_SERVICE_LOGOFF && pkt->status == -1) { - gc->wants_to_die = TRUE; if (!purple_account_get_remember_password(account)) purple_account_set_password(account, NULL); - purple_connection_error(gc, _("You have signed on from another location.")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NAME_IN_USE, + _("You have signed on from another location.")); return; } @@ -2139,8 +2139,7 @@ else fullmsg = g_strdup(msg); - gc->wants_to_die = TRUE; - purple_connection_error(gc, fullmsg); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, fullmsg); g_free(msg); g_free(fullmsg); } @@ -2464,11 +2463,12 @@ tmp = g_strdup_printf(_("Lost connection with server:\n%s"), g_strerror(errno)); - purple_connection_error(gc, tmp); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); g_free(tmp); return; } else if (len == 0) { - purple_connection_error(gc, _("Server closed the connection.")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Server closed the connection.")); return; } @@ -2559,7 +2559,7 @@ gchar *tmp; tmp = g_strdup_printf(_("Could not establish a connection with the server:\n%s"), error_message); - purple_connection_error(gc, tmp); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); g_free(tmp); return; } @@ -2591,7 +2591,7 @@ gchar *tmp; tmp = g_strdup_printf(_("Could not establish a connection with the server:\n%s"), error_message); - purple_connection_error(gc, tmp); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); g_free(tmp); return; } @@ -2631,11 +2631,12 @@ tmp = g_strdup_printf(_("Lost connection with server:\n%s"), g_strerror(errno)); - purple_connection_error(gc, tmp); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); g_free(tmp); return; } else if (len == 0) { - purple_connection_error(gc, _("Server closed the connection.")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Server closed the connection.")); return; } @@ -2650,7 +2651,8 @@ if ((strncmp(buf, "HTTP/1.0 302", strlen("HTTP/1.0 302")) && strncmp(buf, "HTTP/1.1 302", strlen("HTTP/1.1 302")))) { - purple_connection_error(gc, _("Received unexpected HTTP response from server.")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Received unexpected HTTP response from server.")); return; } @@ -2672,9 +2674,10 @@ yd->rxlen = 0; /* Now we have our cookies to login with. I'll go get the milk. */ if (purple_proxy_connect(gc, account, "wcs2.msg.dcn.yahoo.com", - purple_account_get_int(account, "port", YAHOO_PAGER_PORT), - yahoo_got_web_connected, gc) == NULL) { - purple_connection_error(gc, _("Connection problem")); + purple_account_get_int(account, "port", YAHOO_PAGER_PORT), + yahoo_got_web_connected, gc) == NULL) { + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Connection problem")); return; } } @@ -2702,7 +2705,7 @@ gc->inpa = 0; tmp = g_strdup_printf(_("Lost connection with %s:\n%s"), "login.yahoo.com:80", g_strerror(errno)); - purple_connection_error(gc, tmp); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); g_free(tmp); return; } @@ -2727,7 +2730,7 @@ gchar *tmp; tmp = g_strdup_printf(_("Could not establish a connection with %s:\n%s"), "login.yahoo.com:80", error_message); - purple_connection_error(gc, tmp); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); g_free(tmp); return; } @@ -2811,7 +2814,8 @@ if (error_message != NULL) { - purple_connection_error(gc, error_message); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + error_message); return; } @@ -2860,7 +2864,8 @@ g_hash_table_destroy(hash); yd->auth = g_string_free(url, FALSE); if (purple_proxy_connect(gc, account, "login.yahoo.com", 80, yahoo_got_cookies, gc) == NULL) { - purple_connection_error(gc, _("Connection problem")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Connection problem")); return; } @@ -2963,7 +2968,8 @@ purple_account_get_int(account, "port", YAHOO_PAGER_PORT), yahoo_got_connected, gc) == NULL) { - purple_connection_error(gc, _("Connection problem")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Connection problem")); return; } } else { @@ -2973,7 +2979,8 @@ purple_account_get_int(account, "port", YAHOO_PAGER_PORT), yahoo_got_connected, gc) == NULL) { - purple_connection_error(gc, _("Connection problem")); + purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Connection problem")); return; } } diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/yahoo/yahoo_packet.c --- a/libpurple/protocols/yahoo/yahoo_packet.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/yahoo/yahoo_packet.c Sun Nov 11 14:19:55 2007 +0000 @@ -303,7 +303,8 @@ return; else if (ret < 0) { /* TODO: what to do here - do we really have to disconnect? */ - purple_connection_error(yd->gc, _("Write Error")); + purple_connection_error_reason(yd->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Write Error")); return; } diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/protocols/yahoo/ycht.c --- a/libpurple/protocols/yahoo/ycht.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/protocols/yahoo/ycht.c Sun Nov 11 14:19:55 2007 +0000 @@ -285,7 +285,8 @@ else if (ret <= 0) { /* TODO: error handling */ /* - purple_connection_error(purple_account_get_connection(irc->account), + purple_connection_error_reason(purple_account_get_connection(irc->account), + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Server has disconnected")); */ return; diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/signals.c --- a/libpurple/signals.c Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/signals.c Sun Nov 11 14:19:55 2007 +0000 @@ -649,6 +649,16 @@ ((void (*)(void *, gint, gint, void *))cb)(arg1, arg2, arg3, data); } +void purple_marshal_VOID__POINTER_INT_POINTER(PurpleCallback cb, va_list args, + void *data, void **return_val) +{ + void *arg1 = va_arg(args, void *); + gint arg2 = va_arg(args, gint); + void *arg3 = va_arg(args, void *); + + ((void (*)(void *, gint, void *, void *))cb)(arg1, arg2, arg3, data); +} + void purple_marshal_VOID__POINTER_POINTER(PurpleCallback cb, va_list args, void *data, void **return_val) diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/signals.h --- a/libpurple/signals.h Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/signals.h Sun Nov 11 14:19:55 2007 +0000 @@ -284,6 +284,8 @@ PurpleCallback cb, va_list args, void *data, void **return_val); void purple_marshal_VOID__POINTER_INT_INT( PurpleCallback cb, va_list args, void *data, void **return_val); +void purple_marshal_VOID__POINTER_INT_POINTER( + PurpleCallback cb, va_list args, void *data, void **return_val); void purple_marshal_VOID__POINTER_POINTER( PurpleCallback cb, va_list args, void *data, void **return_val); void purple_marshal_VOID__POINTER_POINTER_UINT( diff -r 85fbadb8b8fd -r 1ce05db42eb3 libpurple/sslconn.h --- a/libpurple/sslconn.h Sun Nov 11 14:18:23 2007 +0000 +++ b/libpurple/sslconn.h Sun Nov 11 14:19:55 2007 +0000 @@ -26,11 +26,6 @@ #ifndef _PURPLE_SSLCONN_H_ #define _PURPLE_SSLCONN_H_ -#include "certificate.h" -#include "proxy.h" - -#define PURPLE_SSL_DEFAULT_PORT 443 - /** Possible SSL errors. */ typedef enum { @@ -39,6 +34,11 @@ PURPLE_SSL_CERTIFICATE_INVALID = 3 } PurpleSslErrorType; +#include "certificate.h" +#include "proxy.h" + +#define PURPLE_SSL_DEFAULT_PORT 443 + typedef struct _PurpleSslConnection PurpleSslConnection; typedef void (*PurpleSslInputFunction)(gpointer, PurpleSslConnection *, @@ -126,9 +126,9 @@ /** Obtains the certificate chain provided by the peer * * @param gsc Connection context - * @return A newly allocated list of #PurpleCertificate containing the - * certificates the peer provided. - * @see purple_ssl_get_peer_certificates + * @return A newly allocated list containing the certificates + * the peer provided. + * @see PurpleCertificate * @todo Decide whether the ordering of certificates in this * list can be guaranteed. */ diff -r 85fbadb8b8fd -r 1ce05db42eb3 pidgin/Makefile.am --- a/pidgin/Makefile.am Sun Nov 11 14:18:23 2007 +0000 +++ b/pidgin/Makefile.am Sun Nov 11 14:19:55 2007 +0000 @@ -118,7 +118,8 @@ gtkstatusbox.c \ gtkthemes.c \ gtkutils.c \ - gtkwhiteboard.c + gtkwhiteboard.c \ + minidialog.c pidgin_headers = \ eggtrayicon.h \ @@ -170,6 +171,7 @@ gtkthemes.h \ gtkutils.h \ gtkwhiteboard.h \ + minidialog.h \ pidgin.h pidginincludedir=$(includedir)/pidgin diff -r 85fbadb8b8fd -r 1ce05db42eb3 pidgin/gtkblist.c --- a/pidgin/gtkblist.c Sun Nov 11 14:18:23 2007 +0000 +++ b/pidgin/gtkblist.c Sun Nov 11 14:19:55 2007 +0000 @@ -58,6 +58,7 @@ #include "gtkstatusbox.h" #include "gtkscrollbook.h" #include "gtkutils.h" +#include "pidgin/minidialog.h" #include #include @@ -107,6 +108,21 @@ GList *entries; } PidginJoinChatData; +typedef struct +{ + /** PidginScrollBook used to hold error minidialogs. Gets packed + * inside PidginBuddyList.error_buttons + */ + GtkWidget *error_scrollbook; + + /** Pointer to the mini-dialog about having signed on elsewhere, if one + * is showing; @c NULL otherwise. + */ + PidginMiniDialog *signed_on_elsewhere; +} PidginBuddyListPrivate; + +#define PIDGIN_BUDDY_LIST_GET_PRIVATE(list) \ + ((PidginBuddyListPrivate *)((list)->priv)) static GtkWidget *accountmenu = NULL; @@ -4125,10 +4141,13 @@ static void pidgin_blist_new_list(PurpleBuddyList *blist) { PidginBuddyList *gtkblist; + PidginBuddyListPrivate *priv; gtkblist = g_new0(PidginBuddyList, 1); gtkblist->connection_errors = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free); + gtkblist->priv = priv = g_new0(PidginBuddyListPrivate, 1); + blist->ui_data = gtkblist; } @@ -4284,6 +4303,13 @@ widget->allocation.width - 2 - HEADLINE_CLOSE_SIZE, 2, HEADLINE_CLOSE_SIZE, HEADLINE_CLOSE_SIZE, GDK_RGB_DITHER_NONE, 0, 0); +#if 0 + /* The presence of one opening paren in each branch of + * GTK_CHECK_VERSION confuses vim's bracket matching for the + * rest of the file. + */ + ) +#endif gtk_paint_focus(widget->style, widget->window, GTK_STATE_PRELIGHT, NULL, widget, NULL, widget->allocation.width - HEADLINE_CLOSE_SIZE - 3, 1, @@ -4354,124 +4380,363 @@ /* Connection error handling stuff */ /***********************************/ +#define OBJECT_DATA_KEY_ACCOUNT "account" + +static gboolean +find_account_widget(GObject *widget, + PurpleAccount *account) +{ + if (g_object_get_data(widget, OBJECT_DATA_KEY_ACCOUNT) == account) + return 0; /* found */ + else + return 1; +} + static void -ce_modify_account_cb(PurpleAccount *account) +pack_prpl_icon_start(GtkWidget *box, + PurpleAccount *account) +{ + GdkPixbuf *pixbuf; + GtkWidget *image; + + pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL); + if (pixbuf != NULL) { + image = gtk_image_new_from_pixbuf(pixbuf); + g_object_unref(pixbuf); + + gtk_box_pack_start(GTK_BOX(box), image, FALSE, FALSE, 0); + } +} + +static void +add_error_dialog(PidginBuddyList *gtkblist, + GtkWidget *dialog) +{ + PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist); + gtk_container_add(GTK_CONTAINER(priv->error_scrollbook), dialog); + + if (!GTK_WIDGET_HAS_FOCUS(gtkblist->window)) + pidgin_set_urgent(GTK_WINDOW(gtkblist->window), TRUE); +} + +static GtkWidget * +find_child_widget_by_account(GtkContainer *container, + PurpleAccount *account) +{ + GList *l = NULL; + GList *children = gtk_container_get_children(container); + GtkWidget *ret = NULL; + l = g_list_find_custom(children, account, (GCompareFunc) find_account_widget); + if (l) + ret = GTK_WIDGET(l->data); + g_list_free(children); + return ret; +} + +static void +remove_child_widget_by_account(GtkContainer *container, + PurpleAccount *account) +{ + GtkWidget *widget = find_child_widget_by_account(container, account); + if(widget) + gtk_widget_destroy(widget); +} + +/* Generic error buttons */ + +static void +generic_error_modify_cb(PurpleAccount *account) { pidgin_account_dialog_show(PIDGIN_MODIFY_ACCOUNT_DIALOG, account); } static void -ce_enable_account_cb(PurpleAccount *account) +generic_error_enable_cb(PurpleAccount *account) +{ + purple_account_set_enabled(account, purple_core_get_ui(), TRUE); +} + +static void +generic_error_ignore_cb(PurpleAccount *account) +{ + purple_account_clear_current_error(account); +} + +static void +generic_error_destroy_cb(GtkObject *dialog, + PurpleAccount *account) +{ + g_hash_table_remove(gtkblist->connection_errors, account); +} + +static void +add_generic_error_dialog(PurpleAccount *account, + const PurpleConnectionErrorInfo *err) +{ + GtkWidget *mini_dialog; + const char *username = purple_account_get_username(account); + gboolean enabled = + purple_account_get_enabled(account, purple_core_get_ui()); + char *primary; + + if (enabled) + primary = g_strdup_printf(_("%s disconnected"), username); + else + primary = g_strdup_printf(_("%s disabled"), username); + + mini_dialog = pidgin_make_mini_dialog(NULL, PIDGIN_STOCK_DISCONNECT, + primary, err->description, account, + (enabled ? _("Reconnect") : _("Re-enable")), + (enabled ? PURPLE_CALLBACK(purple_account_connect) + : PURPLE_CALLBACK(generic_error_enable_cb)), + _("Modify Account"), PURPLE_CALLBACK(generic_error_modify_cb), + _("Ignore"), PURPLE_CALLBACK(generic_error_ignore_cb), + NULL); + + g_free(primary); + + g_object_set_data(G_OBJECT(mini_dialog), OBJECT_DATA_KEY_ACCOUNT, + account); + + g_signal_connect_after(mini_dialog, "destroy", + (GCallback)generic_error_destroy_cb, + account); + + add_error_dialog(gtkblist, mini_dialog); +} + +static void +remove_generic_error_dialog(PurpleAccount *account) +{ + PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist); + remove_child_widget_by_account(GTK_CONTAINER(priv->error_scrollbook), + account); +} + + +/* Notifications about accounts which were disconnected with + * PURPLE_CONNECTION_ERROR_NAME_IN_USE + */ + +typedef void (*AccountFunction)(PurpleAccount *); + +static void +elsewhere_foreach_account(PidginMiniDialog *mini_dialog, + AccountFunction f) +{ + PurpleAccount *account; + GList *labels = gtk_container_get_children( + GTK_CONTAINER(mini_dialog->contents)); + GList *l; + + for (l = labels; l; l = l->next) { + account = g_object_get_data(G_OBJECT(l->data), OBJECT_DATA_KEY_ACCOUNT); + if (account) + f(account); + else + purple_debug_warning("gtkblist", "mini_dialog's child " + "didn't have an account stored in it!"); + } + g_list_free(labels); +} + +static void +enable_account(PurpleAccount *account) { purple_account_set_enabled(account, purple_core_get_ui(), TRUE); } static void -connection_error_button_clicked_cb(GtkButton *widget, gpointer user_data) -{ - PurpleAccount *account; - char *primary; - const char *text; - gboolean enabled; - GList *list; - - account = user_data; - primary = g_strdup_printf(_("%s disconnected"), - purple_account_get_username(account)); - text = g_hash_table_lookup(gtkblist->connection_errors, account); - - enabled = purple_account_get_enabled(account, purple_core_get_ui()); - purple_request_action_with_hint(account, _("Connection Error"), primary, text, 2, - account, NULL, NULL, - PURPLE_REQUEST_UI_HINT_ACCOUNT, account, 3, - _("OK"), NULL, - _("Modify Account"), PURPLE_CALLBACK(ce_modify_account_cb), - enabled ? _("Connect") : _("Re-enable Account"), - enabled ? PURPLE_CALLBACK(purple_account_connect) : - PURPLE_CALLBACK(ce_enable_account_cb)); - g_free(primary); - gtk_widget_destroy(GTK_WIDGET(widget)); - g_hash_table_remove(gtkblist->connection_errors, account); - if ((list = gtk_container_get_children(GTK_CONTAINER(gtkblist->error_buttons))) == NULL) { - gtk_widget_hide(gtkblist->error_buttons); - } else { - g_list_free(list); - } -} - -/* Add some buttons that show connection errors */ +reconnect_elsewhere_accounts(PidginMiniDialog *mini_dialog, + GtkButton *button, + gpointer unused) +{ + elsewhere_foreach_account(mini_dialog, enable_account); +} + +static void +ignore_elsewhere_accounts(PidginMiniDialog *mini_dialog, + GtkButton *button, + gpointer unused) +{ + elsewhere_foreach_account(mini_dialog, purple_account_clear_current_error); +} + static void -create_connection_error_buttons(gpointer key, gpointer value, - gpointer user_data) -{ - PurpleAccount *account; - gchar *escaped, *text; - GtkWidget *button, *label, *image, *hbox; - GdkPixbuf *pixbuf; - - account = key; - escaped = g_markup_escape_text((const gchar *)value, -1); - text = g_strdup_printf(_("%s disconnected: %s"), - purple_account_get_username(account), - escaped); - g_free(escaped); +ensure_signed_on_elsewhere_minidialog(PidginBuddyList *gtkblist) +{ + PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist); + PidginMiniDialog *mini_dialog; + + if(priv->signed_on_elsewhere) + return; + + mini_dialog = priv->signed_on_elsewhere = + pidgin_mini_dialog_new(NULL, NULL, PIDGIN_STOCK_DISCONNECT); + + pidgin_mini_dialog_add_button(mini_dialog, _("Re-enable"), + reconnect_elsewhere_accounts, NULL); + + pidgin_mini_dialog_add_button(mini_dialog, _("Ignore"), + ignore_elsewhere_accounts, NULL); + + add_error_dialog(gtkblist, GTK_WIDGET(mini_dialog)); + + /* Set priv->signed_on_elsewhere to NULL when the dialog is destroyed */ + g_signal_connect(G_OBJECT(mini_dialog), "destroy", + (GCallback) gtk_widget_destroyed, &(priv->signed_on_elsewhere)); +} + +static void +update_signed_on_elsewhere_minidialog_title(void) +{ + PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist); + PidginMiniDialog *mini_dialog = priv->signed_on_elsewhere; + guint accounts; + char *title; + + if (mini_dialog == NULL) + return; + + accounts = pidgin_mini_dialog_get_num_children(mini_dialog); + + title = g_strdup_printf( + ngettext("%d account was disabled because you signed on from another location.", + "%d accounts were disabled because you signed on from another location.", + accounts), + accounts); + pidgin_mini_dialog_set_title(mini_dialog, title); + g_free(title); +} + +static GtkWidget * +create_account_label(PurpleAccount *account) +{ + GtkWidget *hbox, *label; + const char *username = purple_account_get_username(account); + char *markup; hbox = gtk_hbox_new(FALSE, 6); - - /* Create the icon */ - if (purple_account_get_status_type_with_primitive(account, - PURPLE_STATUS_OFFLINE) != NULL) { - pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL); - if (pixbuf != NULL) { - image = gtk_image_new_from_pixbuf(pixbuf); - g_object_unref(pixbuf); - - gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0); - } - } - - /* Create the text */ - label = gtk_label_new(""); - gtk_label_set_markup(GTK_LABEL(label), text); - g_free(text); + g_object_set_data(G_OBJECT(hbox), OBJECT_DATA_KEY_ACCOUNT, account); + + pack_prpl_icon_start(hbox, account); + + label = gtk_label_new(NULL); + markup = g_strdup_printf("%s", username); + gtk_label_set_markup(GTK_LABEL(label), markup); + g_free(markup); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); #if GTK_CHECK_VERSION(2,6,0) - g_object_set(label, "ellipsize", PANGO_ELLIPSIZE_END, NULL); + g_object_set(G_OBJECT(label), "ellipsize", PANGO_ELLIPSIZE_END, NULL); +#endif +#if GTK_CHECK_VERSION(2,12,0) + { /* avoid unused variable warnings on pre-2.12 Gtk */ + char *description = + purple_account_get_current_error(account)->description; + if (description != NULL && *description != '\0') + gtk_widget_set_tooltip_text(label, description); + } #endif gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); - /* Create the actual button and put the icon and text on it */ - button = gtk_button_new(); - gtk_container_add(GTK_CONTAINER(button), hbox); - g_signal_connect(G_OBJECT(button), "clicked", - G_CALLBACK(connection_error_button_clicked_cb), - account); - gtk_widget_show_all(button); - gtk_box_pack_end(GTK_BOX(gtkblist->error_buttons), button, - FALSE, FALSE, 0); - gtk_widget_show_all(gtkblist->error_buttons); + return hbox; +} + +static void +add_to_signed_on_elsewhere(PurpleAccount *account) +{ + PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist); + PidginMiniDialog *mini_dialog; + GtkWidget *account_label; + + ensure_signed_on_elsewhere_minidialog(gtkblist); + mini_dialog = priv->signed_on_elsewhere; + + if(find_child_widget_by_account(GTK_CONTAINER(mini_dialog->contents), account)) + return; + + account_label = create_account_label(account); + gtk_box_pack_start(mini_dialog->contents, account_label, FALSE, FALSE, 0); + gtk_widget_show_all(account_label); + + update_signed_on_elsewhere_minidialog_title(); + + if (!GTK_WIDGET_HAS_FOCUS(gtkblist->window)) + pidgin_set_urgent(GTK_WINDOW(gtkblist->window), TRUE); +} + +static void +remove_from_signed_on_elsewhere(PurpleAccount *account) +{ + PidginBuddyListPrivate *priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist); + PidginMiniDialog *mini_dialog = priv->signed_on_elsewhere; + if(mini_dialog == NULL) + return; + + remove_child_widget_by_account(GTK_CONTAINER(mini_dialog->contents), account); + + update_signed_on_elsewhere_minidialog_title(); +} + + +/* Call appropriate error notification code based on error types */ +static void +update_account_error_state(PurpleAccount *account, + const PurpleConnectionErrorInfo *old, + const PurpleConnectionErrorInfo *new, + PidginBuddyList *gtkblist) +{ + /* For backwards compatibility: */ + if (new) + pidgin_blist_update_account_error_state(account, new->description); + else + pidgin_blist_update_account_error_state(account, NULL); + + if (old) { + if(old->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE) + remove_from_signed_on_elsewhere(account); + else + remove_generic_error_dialog(account); + } + + if (new) { + if(new->type == PURPLE_CONNECTION_ERROR_NAME_IN_USE) + add_to_signed_on_elsewhere(account); + else + add_generic_error_dialog(account, new); + } +} + +/* In case accounts are loaded before the blist (which they currently are), + * let's call update_account_error_state ourselves on every account's current + * state when the blist starts. + */ +static void +show_initial_account_errors(PidginBuddyList *gtkblist) +{ + GList *l = purple_accounts_get_all(); + PurpleAccount *account; + const PurpleConnectionErrorInfo *err; + + for (; l; l = l->next) + { + account = l->data; + err = purple_account_get_current_error(account); + + update_account_error_state(account, NULL, err, gtkblist); + } } void pidgin_blist_update_account_error_state(PurpleAccount *account, const char *text) { - GList *l; - + /* connection_errors isn't actually used anywhere; it's just kept in + * sync with reality in case a plugin uses it. + */ if (text == NULL) g_hash_table_remove(gtkblist->connection_errors, account); else g_hash_table_insert(gtkblist->connection_errors, account, g_strdup(text)); - - /* Remove the old error buttons */ - for (l = gtk_container_get_children(GTK_CONTAINER(gtkblist->error_buttons)); - l != NULL; - l = g_list_delete_link(l, l)) - { - gtk_widget_destroy(GTK_WIDGET(l->data)); - } - - /* Add new error buttons */ - g_hash_table_foreach(gtkblist->connection_errors, - create_connection_error_buttons, NULL); } static gboolean @@ -4598,6 +4863,7 @@ static void pidgin_blist_show(PurpleBuddyList *list) { + PidginBuddyListPrivate *priv; void *handle; GtkCellRenderer *rend; GtkTreeViewColumn *column; @@ -4624,6 +4890,7 @@ } gtkblist = PIDGIN_BLIST(list); + priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist); gtkblist->empty_avatar = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 32, 32); gdk_pixbuf_fill(gtkblist->empty_avatar, 0x00000000); @@ -4926,11 +5193,19 @@ gtkblist->scrollbook = pidgin_scroll_book_new(); gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->scrollbook, FALSE, FALSE, 0); - /* Create an empty vbox used for showing connection errors */ + /* Create an vbox which holds the scrollbook which is actually used to + * display connection errors. The vbox needs to still exist for + * backwards compatibility. + */ gtkblist->error_buttons = gtk_vbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->error_buttons, FALSE, FALSE, 0); - gtk_container_set_border_width(GTK_CONTAINER(gtkblist->error_buttons), 3); - + gtk_container_set_border_width(GTK_CONTAINER(gtkblist->error_buttons), 0); + + priv->error_scrollbook = pidgin_scroll_book_new(); + gtk_box_pack_start(GTK_BOX(gtkblist->error_buttons), + priv->error_scrollbook, FALSE, FALSE, 0); + + /* Add the statusbox */ gtkblist->statusbox = pidgin_status_box_new(); gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->statusbox, FALSE, TRUE, 0); @@ -5001,43 +5276,56 @@ purple_prefs_connect_callback(handle, PIDGIN_PREFS_ROOT "/sound/method", pidgin_blist_sound_method_pref_cb, NULL); + /* Setup some purple signal handlers. */ - purple_signal_connect(purple_accounts_get_handle(), "account-enabled", - gtkblist, PURPLE_CALLBACK(account_modified), gtkblist); - purple_signal_connect(purple_accounts_get_handle(), "account-disabled", - gtkblist, PURPLE_CALLBACK(account_modified), gtkblist); - purple_signal_connect(purple_accounts_get_handle(), "account-removed", - gtkblist, PURPLE_CALLBACK(account_modified), gtkblist); - purple_signal_connect(purple_accounts_get_handle(), "account-status-changed", - gtkblist, PURPLE_CALLBACK(account_status_changed), gtkblist); - - purple_signal_connect(pidgin_account_get_handle(), "account-modified", - gtkblist, PURPLE_CALLBACK(account_modified), gtkblist); - - purple_signal_connect(purple_connections_get_handle(), "signed-on", - gtkblist, PURPLE_CALLBACK(sign_on_off_cb), list); - purple_signal_connect(purple_connections_get_handle(), "signed-off", - gtkblist, PURPLE_CALLBACK(sign_on_off_cb), list); - - purple_signal_connect(purple_plugins_get_handle(), "plugin-load", - gtkblist, PURPLE_CALLBACK(plugin_changed_cb), NULL); - purple_signal_connect(purple_plugins_get_handle(), "plugin-unload", - gtkblist, PURPLE_CALLBACK(plugin_changed_cb), NULL); - - purple_signal_connect(purple_conversations_get_handle(), "conversation-updated", - gtkblist, PURPLE_CALLBACK(conversation_updated_cb), - gtkblist); - purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation", - gtkblist, PURPLE_CALLBACK(conversation_deleting_cb), - gtkblist); - purple_signal_connect(purple_conversations_get_handle(), "conversation-created", - gtkblist, PURPLE_CALLBACK(conversation_created_cb), - gtkblist); + + handle = purple_accounts_get_handle(); + purple_signal_connect(handle, "account-enabled", gtkblist, + PURPLE_CALLBACK(account_modified), gtkblist); + purple_signal_connect(handle, "account-disabled", gtkblist, + PURPLE_CALLBACK(account_modified), gtkblist); + purple_signal_connect(handle, "account-removed", gtkblist, + PURPLE_CALLBACK(account_modified), gtkblist); + purple_signal_connect(handle, "account-status-changed", gtkblist, + PURPLE_CALLBACK(account_status_changed), + gtkblist); + purple_signal_connect(handle, "account-error-changed", gtkblist, + PURPLE_CALLBACK(update_account_error_state), + gtkblist); + + handle = pidgin_account_get_handle(); + purple_signal_connect(handle, "account-modified", gtkblist, + PURPLE_CALLBACK(account_modified), gtkblist); + + handle = purple_connections_get_handle(); + purple_signal_connect(handle, "signed-on", gtkblist, + PURPLE_CALLBACK(sign_on_off_cb), list); + purple_signal_connect(handle, "signed-off", gtkblist, + PURPLE_CALLBACK(sign_on_off_cb), list); + + handle = purple_plugins_get_handle(); + purple_signal_connect(handle, "plugin-load", gtkblist, + PURPLE_CALLBACK(plugin_changed_cb), NULL); + purple_signal_connect(handle, "plugin-unload", gtkblist, + PURPLE_CALLBACK(plugin_changed_cb), NULL); + + handle = purple_conversations_get_handle(); + purple_signal_connect(handle, "conversation-updated", gtkblist, + PURPLE_CALLBACK(conversation_updated_cb), + gtkblist); + purple_signal_connect(handle, "deleting-conversation", gtkblist, + PURPLE_CALLBACK(conversation_deleting_cb), + gtkblist); + purple_signal_connect(handle, "conversation-created", gtkblist, + PURPLE_CALLBACK(conversation_created_cb), + gtkblist); gtk_widget_hide(gtkblist->headline_hbox); - gtk_widget_hide(gtkblist->error_buttons); + + show_initial_account_errors(gtkblist); /* emit our created signal */ + handle = pidgin_blist_get_handle(); purple_signal_emit(handle, "gtkblist-created", list); } @@ -5660,6 +5948,8 @@ static void pidgin_blist_destroy(PurpleBuddyList *list) { + PidginBuddyListPrivate *priv; + if (!gtkblist) return; @@ -5693,6 +5983,9 @@ gtkblist->hand_cursor = NULL; gtkblist->arrow_cursor = NULL; + priv = PIDGIN_BUDDY_LIST_GET_PRIVATE(gtkblist); + g_free(priv); + g_free(gtkblist); accountmenu = NULL; gtkblist = NULL; diff -r 85fbadb8b8fd -r 1ce05db42eb3 pidgin/gtkblist.h --- a/pidgin/gtkblist.h Sun Nov 11 14:18:23 2007 +0000 +++ b/pidgin/gtkblist.h Sun Nov 11 14:19:55 2007 +0000 @@ -84,7 +84,14 @@ GtkWidget *menutray; /**< The menu tray widget. */ GtkWidget *menutrayicon; /**< The menu tray icon. */ - GHashTable *connection_errors; /**< Caches connection error messages and accounts. */ + /** Caches connection error messages; keys are #PurpleAccount and + * values are non-@c NULL const char *s containing localised + * error messages. (If an account does not have an error, it will not + * appear in the table.) + * @deprecated in favour of purple_account_get_current_error(), which also + * gives you the #PurpleConnectionError value. + */ + GHashTable *connection_errors; guint refresh_timer; /**< The timer for refreshing every 30 seconds */ @@ -119,6 +126,8 @@ GtkWidget *error_buttons; /**< Box containing the connection error buttons */ GtkWidget *statusbox; /**< The status selector dropdown */ GdkPixbuf *empty_avatar; /**< A 32x32 transparent pixbuf */ + + gpointer priv; /**< Pointer to opaque private data */ }; #define PIDGIN_BLIST(list) ((PidginBuddyList *)(list)->ui_data) @@ -339,13 +348,16 @@ void pidgin_append_blist_node_extended_menu(GtkWidget *menu, PurpleBlistNode *node); /** - * Used by the connection API to tell the blist if an account - * has a connection error or no longer has a connection error. + * Was used by the connection API to tell the blist if an account has a + * connection error or no longer has a connection error, but the blist now does + * this itself with the @ref account-error-changed signal. * * @param account The account that either has a connection error * or no longer has a connection error. * @param message The connection error message, or NULL if this * account is no longer in an error state. + * @deprecated There was no good reason for code other than gtkconn to call + * this. */ void pidgin_blist_update_account_error_state(PurpleAccount *account, const char *message); diff -r 85fbadb8b8fd -r 1ce05db42eb3 pidgin/gtkconn.c --- a/pidgin/gtkconn.c Sun Nov 11 14:18:23 2007 +0000 +++ b/pidgin/gtkconn.c Sun Nov 11 14:19:55 2007 +0000 @@ -42,6 +42,7 @@ #define INITIAL_RECON_DELAY_MAX 60000 #define MAX_RECON_DELAY 600000 +#define MAX_RACCOON_DELAY "shorter in urban areas" typedef struct { int delay; @@ -81,8 +82,6 @@ (purple_connections_get_connecting() != NULL)); g_hash_table_remove(auto_reconns, account); - - pidgin_blist_update_account_error_state(account, NULL); } static void @@ -137,7 +136,46 @@ } static void -pidgin_connection_report_disconnect(PurpleConnection *gc, const char *text) +notify_account_disabled(PurpleAccount *account, + PurpleConnectionError reason, + const char *text) +{ + const char *username = purple_account_get_username(account); + const char *alias = purple_account_get_alias(account); + const char *protocol = purple_account_get_protocol_name(account); + char *p, *s, *n; + + if (alias) + n = g_strdup_printf("%s (%s) (%s)", username, alias, protocol); + else + n = g_strdup_printf("%s (%s)", username, protocol); + + p = g_strdup_printf(_("%s disconnected"), n); + + if(reason == PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT) + s = g_strdup_printf( + _("%s\n\n" + "%s will not attempt to reconnect the account until you " + "re-enable the account. See %s for information on how to " + "compile %s with SSL support."), text, PIDGIN_NAME, + "http://developer.pidgin.im/wiki/FAQssl", PIDGIN_NAME); + else + s = g_strdup_printf( + _("%s\n\n" + "%s will not attempt to reconnect the account until you " + "correct the error and re-enable the account."), text, + PIDGIN_NAME); + + purple_notify_error(NULL, NULL, p, s); + g_free(p); + g_free(s); + g_free(n); +} + +static void +pidgin_connection_report_disconnect_reason (PurpleConnection *gc, + PurpleConnectionError reason, + const char *text) { PurpleAccount *account = NULL; PidginAutoRecon *info; @@ -146,8 +184,7 @@ account = purple_connection_get_account(gc); info = g_hash_table_lookup(auto_reconns, account); - pidgin_blist_update_account_error_state(account, text); - if (!gc->wants_to_die) { + if (!purple_connection_error_is_fatal (reason)) { if (info == NULL) { info = g_new0(PidginAutoRecon, 1); g_hash_table_insert(auto_reconns, account, info); @@ -159,32 +196,11 @@ } info->timeout = g_timeout_add(info->delay, do_signon, account); } else { - char *p, *s, *n=NULL ; if (info != NULL) g_hash_table_remove(auto_reconns, account); - if (purple_account_get_alias(account)) - { - n = g_strdup_printf("%s (%s) (%s)", - purple_account_get_username(account), - purple_account_get_alias(account), - purple_account_get_protocol_name(account)); - } - else - { - n = g_strdup_printf("%s (%s)", - purple_account_get_username(account), - purple_account_get_protocol_name(account)); - } - - p = g_strdup_printf(_("%s disconnected"), n); - s = g_strdup_printf(_("%s\n\n" - "%s will not attempt to reconnect the account until you " - "correct the error and re-enable the account."), text, PIDGIN_NAME); - purple_notify_error(NULL, NULL, p, s); - g_free(p); - g_free(s); - g_free(n); + if (reason != PURPLE_CONNECTION_ERROR_NAME_IN_USE) + notify_account_disabled(account, reason, text); /* * TODO: Do we really want to disable the account when it's @@ -259,10 +275,10 @@ pidgin_connection_connected, pidgin_connection_disconnected, pidgin_connection_notice, - pidgin_connection_report_disconnect, + NULL, /* report_disconnect */ pidgin_connection_network_connected, pidgin_connection_network_disconnected, - NULL, + pidgin_connection_report_disconnect_reason, NULL, NULL, NULL @@ -278,8 +294,6 @@ account_removed_cb(PurpleAccount *account, gpointer user_data) { g_hash_table_remove(auto_reconns, account); - - pidgin_blist_update_account_error_state(account, NULL); } diff -r 85fbadb8b8fd -r 1ce05db42eb3 pidgin/gtkutils.c --- a/pidgin/gtkutils.c Sun Nov 11 14:18:23 2007 +0000 +++ b/pidgin/gtkutils.c Sun Nov 11 14:19:55 2007 +0000 @@ -61,6 +61,7 @@ #include "pidginstock.h" #include "gtkthemes.h" #include "gtkutils.h" +#include "pidgin/minidialog.h" typedef struct { GtkWidget *menu; @@ -2910,91 +2911,76 @@ minidialogs = g_slist_remove(minidialogs, widget); } -void *pidgin_make_mini_dialog(PurpleConnection *gc, const char *icon_name, - const char *primary, const char *secondary, - void *user_data, ...) +struct _old_button_clicked_cb_data +{ + PidginUtilMiniDialogCallback cb; + gpointer data; +}; + +static void +old_mini_dialog_button_clicked_cb(PidginMiniDialog *mini_dialog, + GtkButton *button, + gpointer user_data) +{ + struct _old_button_clicked_cb_data *data = user_data; + data->cb(data->data, button); +} + +static void +old_mini_dialog_destroy_cb(GtkWidget *dialog, + GList *cb_datas) { - GtkWidget *vbox; - GtkWidget *hbox; - GtkWidget *hbox2; - GtkWidget *label; - GtkWidget *button; - GtkWidget *img = NULL; - GtkSizeGroup *sg = gtk_size_group_new(GTK_SIZE_GROUP_BOTH); - char label_text[2048]; + while (cb_datas != NULL) + { + g_free(cb_datas->data); + cb_datas = g_list_delete_link(cb_datas, cb_datas); + } +} + +GtkWidget * +pidgin_make_mini_dialog(PurpleConnection *gc, + const char *icon_name, + const char *primary, + const char *secondary, + void *user_data, + ...) +{ + PidginMiniDialog *mini_dialog; const char *button_text; - GCallback callback; - char *primary_esc, *secondary_esc = NULL; + GList *cb_datas = NULL; va_list args; static gboolean first_call = TRUE; - img = gtk_image_new_from_stock(icon_name, gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL)); - gtk_misc_set_alignment(GTK_MISC(img), 0, 0); - - vbox = gtk_vbox_new(FALSE,0); - gtk_container_set_border_width(GTK_CONTAINER(vbox), PIDGIN_HIG_BOX_SPACE); - - g_object_set_data(G_OBJECT(vbox), "gc" ,gc); - minidialogs = g_slist_prepend(minidialogs, vbox); - g_signal_connect(G_OBJECT(vbox), "destroy", G_CALLBACK(alert_killed_cb), NULL); - if (first_call) { first_call = FALSE; purple_signal_connect(purple_connections_get_handle(), "signed-off", - pidgin_utils_get_handle(), - PURPLE_CALLBACK(connection_signed_off_cb), NULL); + pidgin_utils_get_handle(), + PURPLE_CALLBACK(connection_signed_off_cb), NULL); } - hbox = gtk_hbox_new(FALSE, 0); - gtk_container_add(GTK_CONTAINER(vbox), hbox); - - if (img != NULL) - gtk_box_pack_start(GTK_BOX(hbox), img, FALSE, FALSE, 0); - - primary_esc = g_markup_escape_text(primary, -1); - - if (secondary) - secondary_esc = g_markup_escape_text(secondary, -1); - g_snprintf(label_text, sizeof(label_text), - "%s%s%s", - primary_esc, secondary ? "\n" : "", secondary_esc ? secondary_esc : ""); - g_free(primary_esc); - g_free(secondary_esc); - label = gtk_label_new(NULL); - gtk_widget_set_size_request(label, purple_prefs_get_int(PIDGIN_PREFS_ROOT "/blist/width")-25,-1); - gtk_label_set_markup(GTK_LABEL(label), label_text); - gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); - gtk_misc_set_alignment(GTK_MISC(label), 0, 0); - gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); - - hbox2 = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE); - gtk_box_pack_start(GTK_BOX(vbox), hbox2, FALSE, FALSE, 0); + mini_dialog = pidgin_mini_dialog_new(primary, secondary, icon_name); + g_object_set_data(G_OBJECT(mini_dialog), "gc" ,gc); + g_signal_connect(G_OBJECT(mini_dialog), "destroy", + G_CALLBACK(alert_killed_cb), NULL); va_start(args, user_data); while ((button_text = va_arg(args, char*))) { - callback = va_arg(args, GCallback); - button = gtk_button_new(); - - if (callback) - g_signal_connect_swapped(G_OBJECT(button), "clicked", callback, user_data); - g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(gtk_widget_destroy), vbox); - hbox = gtk_hbox_new(FALSE, 0); - gtk_container_add(GTK_CONTAINER(button), hbox); - gtk_container_set_border_width(GTK_CONTAINER(hbox), 3); - g_snprintf(label_text, sizeof(label_text), - "%s", button_text); - label = gtk_label_new(NULL); - gtk_label_set_markup_with_mnemonic(GTK_LABEL(label), label_text); - gtk_misc_set_alignment(GTK_MISC(label), 0.5, 0.5); - gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); - gtk_box_pack_end(GTK_BOX(hbox2), button, FALSE, FALSE, 0); - gtk_size_group_add_widget(sg, button); + PidginUtilMiniDialogCallback callback = + va_arg(args, PidginUtilMiniDialogCallback); + struct _old_button_clicked_cb_data *data = + g_new0(struct _old_button_clicked_cb_data, 1); + data->cb = callback; + data->data = user_data; + pidgin_mini_dialog_add_button(mini_dialog, button_text, + old_mini_dialog_button_clicked_cb, data); + cb_datas = g_list_append(cb_datas, data); } va_end(args); - g_object_unref(sg); - - return vbox; + g_signal_connect(G_OBJECT(mini_dialog), "destroy", + G_CALLBACK(old_mini_dialog_destroy_cb), cb_datas); + + return GTK_WIDGET(mini_dialog); } /* diff -r 85fbadb8b8fd -r 1ce05db42eb3 pidgin/gtkutils.h --- a/pidgin/gtkutils.h Sun Nov 11 14:18:23 2007 +0000 +++ b/pidgin/gtkutils.h Sun Nov 11 14:19:55 2007 +0000 @@ -599,18 +599,38 @@ char *pidgin_make_pretty_arrows(const char *str); /** - * Creates a "mini-dialog" suitable for embedding in the buddy list scrollbook + * The type of callbacks passed to pidgin_make_mini_dialog(). + */ +typedef void (*PidginUtilMiniDialogCallback)(gpointer user_data, GtkButton *); + +/** + * Creates a #PidginMiniDialog, tied to a #PurpleConnection, suitable for + * embedding in the buddy list scrollbook with pidgin_blist_add_alert(). * - * @param handle A handle - * @param stock_id The ID of a stock image to use in the mini dialog + * @param handle The #PurpleConnection to which this mini-dialog + * refers, or @c NULL if it does not refer to a + * connection. If @a handle is supplied, the mini-dialog + * will be automatically removed and destroyed when the + * connection signs off. + * @param stock_id The ID of a stock image to use in the mini dialog. * @param primary The primary text - * @param secondary The secondary text + * @param secondary The secondary text, or @c NULL for no description. * @param user_data Data to pass to the callbacks - * @param ... a NULL-terminated list of button labels and callbacks + * @param ... a NULL-terminated list of button labels + * (char *) and callbacks + * (#PidginUtilMiniDialogCallback). @a user_data will be + * passed as the first argument. (Callbacks may lack a + * second argument, or be @c NULL to take no action when + * the corresponding button is pressed.) When a button is + * pressed, the callback (if any) will be called; when + * the callback returns the dialog will be destroyed. + * @return A #PidginMiniDialog, suitable for passing to + * pidgin_blist_add_alert(). + * @see pidginstock.h */ -void *pidgin_make_mini_dialog(PurpleConnection *handle, const char* stock_id, - const char *primary, const char *secondary, - void *user_data, ...); +GtkWidget *pidgin_make_mini_dialog(PurpleConnection *handle, + const char* stock_id, const char *primary, const char *secondary, + void *user_data, ...); /** * This is a callback function to be used for Ctrl+F searching in treeviews. diff -r 85fbadb8b8fd -r 1ce05db42eb3 pidgin/minidialog.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/minidialog.c Sun Nov 11 14:19:55 2007 +0000 @@ -0,0 +1,386 @@ +/** + * @file minidialog.c Implementation of the #PidginMiniDialog Gtk widget. + * @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 +#include + +#include "libpurple/prefs.h" + +#include "pidgin/minidialog.h" +#include "pidgin/pidgin.h" +#include "pidgin/pidginstock.h" + +G_DEFINE_TYPE (PidginMiniDialog, pidgin_mini_dialog, GTK_TYPE_VBOX) + +enum +{ + PROP_TITLE = 1, + PROP_DESCRIPTION, + PROP_ICON_NAME, + + LAST_PROPERTY +} HazeConnectionProperties; + +typedef struct _PidginMiniDialogPrivate +{ + GtkImage *icon; + GtkBox *title_box; + GtkLabel *title; + GtkLabel *desc; + GtkBox *buttons; +} PidginMiniDialogPrivate; + +#define PIDGIN_MINI_DIALOG_GET_PRIVATE(dialog) \ + ((PidginMiniDialogPrivate *) ((dialog)->priv)) + +PidginMiniDialog * +pidgin_mini_dialog_new(const gchar *title, + const gchar *description, + const gchar *icon_name) +{ + PidginMiniDialog *mini_dialog = g_object_new(PIDGIN_TYPE_MINI_DIALOG, + "title", title, + "description", description, + "icon-name", icon_name, + NULL); + + return mini_dialog; +} + +void +pidgin_mini_dialog_set_title(PidginMiniDialog *mini_dialog, + const char *title) +{ + g_object_set(G_OBJECT(mini_dialog), "title", title, NULL); +} + +void +pidgin_mini_dialog_set_description(PidginMiniDialog *mini_dialog, + const char *description) +{ + g_object_set(G_OBJECT(mini_dialog), "description", description, NULL); +} + +void +pidgin_mini_dialog_set_icon_name(PidginMiniDialog *mini_dialog, + const char *icon_name) +{ + g_object_set(G_OBJECT(mini_dialog), "icon_name", icon_name, NULL); +} + +struct _mini_dialog_button_clicked_cb_data +{ + PidginMiniDialog *mini_dialog; + PidginMiniDialogCallback callback; + gpointer user_data; +}; + +guint +pidgin_mini_dialog_get_num_children(PidginMiniDialog *mini_dialog) +{ + return g_list_length(mini_dialog->contents->children); +} + +static gboolean +idle_destroy_cb(GtkWidget *mini_dialog) +{ + gtk_widget_destroy(mini_dialog); + return FALSE; +} + +static void +mini_dialog_button_clicked_cb(GtkButton *button, + gpointer user_data) +{ + struct _mini_dialog_button_clicked_cb_data *data = user_data; + + data->callback(data->mini_dialog, button, data->user_data); + + g_idle_add((GSourceFunc) idle_destroy_cb, data->mini_dialog); +} + +static void +mini_dialog_button_destroy_cb(GtkButton *button, + gpointer user_data) +{ + struct _mini_dialog_button_clicked_cb_data *data = user_data; + g_free(data); +} + +void +pidgin_mini_dialog_add_button(PidginMiniDialog *self, + const char *text, + PidginMiniDialogCallback clicked_cb, + gpointer user_data) +{ + PidginMiniDialogPrivate *priv = PIDGIN_MINI_DIALOG_GET_PRIVATE(self); + struct _mini_dialog_button_clicked_cb_data *callback_data + = g_new0(struct _mini_dialog_button_clicked_cb_data, 1); + GtkWidget *button = gtk_button_new(); + GtkWidget *label = gtk_label_new(NULL); + char *button_text = + g_strdup_printf("%s", text); + + gtk_label_set_markup_with_mnemonic(GTK_LABEL(label), button_text); + g_free(button_text); + + callback_data->mini_dialog = self; + callback_data->callback = clicked_cb; + callback_data->user_data = user_data; + g_signal_connect(G_OBJECT(button), "clicked", + (GCallback) mini_dialog_button_clicked_cb, callback_data); + g_signal_connect(G_OBJECT(button), "destroy", + (GCallback) mini_dialog_button_destroy_cb, callback_data); + + gtk_misc_set_alignment(GTK_MISC(label), 0.5, 0.5); + gtk_container_add(GTK_CONTAINER(button), label); + + gtk_box_pack_end(GTK_BOX(priv->buttons), button, FALSE, FALSE, + 0); + gtk_widget_show_all(GTK_WIDGET(button)); +} + +static void +pidgin_mini_dialog_get_property(GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + PidginMiniDialog *self = PIDGIN_MINI_DIALOG(object); + PidginMiniDialogPrivate *priv = PIDGIN_MINI_DIALOG_GET_PRIVATE(self); + + switch (property_id) { + case PROP_TITLE: + g_value_set_string(value, gtk_label_get_text(priv->title)); + break; + case PROP_DESCRIPTION: + g_value_set_string(value, gtk_label_get_text(priv->desc)); + break; + case PROP_ICON_NAME: + { + gchar *icon_name = NULL; + GtkIconSize size; + gtk_image_get_stock(priv->icon, &icon_name, &size); + g_value_set_string(value, icon_name); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +mini_dialog_set_title(PidginMiniDialog *self, + const char *title) +{ + PidginMiniDialogPrivate *priv = PIDGIN_MINI_DIALOG_GET_PRIVATE(self); + + char *title_esc = g_markup_escape_text(title, -1); + char *title_markup = g_strdup_printf( + "%s", + title_esc ? title_esc : ""); + + gtk_label_set_markup(priv->title, title_markup); + + g_free(title_esc); + g_free(title_markup); +} + +static void +mini_dialog_set_description(PidginMiniDialog *self, + const char *description) +{ + PidginMiniDialogPrivate *priv = PIDGIN_MINI_DIALOG_GET_PRIVATE(self); + if(description) + { + char *desc_esc = g_markup_escape_text(description, -1); + char *desc_markup = g_strdup_printf( + "%s", desc_esc); + + gtk_label_set_markup(priv->desc, desc_markup); + + g_free(desc_esc); + g_free(desc_markup); + + gtk_widget_show(GTK_WIDGET(priv->desc)); + g_object_set(G_OBJECT(priv->desc), "no-show-all", FALSE, NULL); + } + else + { + gtk_label_set_text(priv->desc, NULL); + gtk_widget_hide(GTK_WIDGET(priv->desc)); + /* make calling show_all() on the minidialog not affect desc + * even though it's packed inside it. + */ + g_object_set(G_OBJECT(priv->desc), "no-show-all", TRUE, NULL); + } +} + +static void +pidgin_mini_dialog_set_property(GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + PidginMiniDialog *self = PIDGIN_MINI_DIALOG(object); + PidginMiniDialogPrivate *priv = PIDGIN_MINI_DIALOG_GET_PRIVATE(self); + + switch (property_id) { + case PROP_TITLE: + mini_dialog_set_title(self, g_value_get_string(value)); + break; + case PROP_DESCRIPTION: + mini_dialog_set_description(self, g_value_get_string(value)); + break; + case PROP_ICON_NAME: + gtk_image_set_from_stock(priv->icon, g_value_get_string(value), + gtk_icon_size_from_name(PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +pidgin_mini_dialog_finalize(GObject *object) +{ + PidginMiniDialog *self = PIDGIN_MINI_DIALOG(object); + PidginMiniDialogPrivate *priv = PIDGIN_MINI_DIALOG_GET_PRIVATE(self); + + g_free(priv); + self->priv = NULL; + + purple_prefs_disconnect_by_handle(self); + + G_OBJECT_CLASS (pidgin_mini_dialog_parent_class)->finalize (object); +} + +static void +pidgin_mini_dialog_class_init(PidginMiniDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + GParamSpec *param_spec; + + object_class->get_property = pidgin_mini_dialog_get_property; + object_class->set_property = pidgin_mini_dialog_set_property; + object_class->finalize = pidgin_mini_dialog_finalize; + + param_spec = g_param_spec_string("title", "title", + "String specifying the mini-dialog's title", NULL, +#if GTK_CHECK_VERSION(2,8,0) + G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | +#endif + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_TITLE, param_spec); + + param_spec = g_param_spec_string("description", "description", + "Description text for the mini-dialog, if desired", NULL, +#if GTK_CHECK_VERSION(2,8,0) + G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | +#endif + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_DESCRIPTION, param_spec); + + param_spec = g_param_spec_string("icon-name", "icon-name", + "String specifying the Gtk stock name of the dialog's icon", + NULL, +#if GTK_CHECK_VERSION(2,8,0) + G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | +#endif + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_ICON_NAME, param_spec); +} + +/* 16 is the width of the icon, due to PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL */ +#define BLIST_WIDTH_OTHER_THAN_LABEL \ + ((PIDGIN_HIG_BOX_SPACE * 3) + 16) + +#define BLIST_WIDTH_PREF \ + (PIDGIN_PREFS_ROOT "/blist/width") + +static void +blist_width_changed_cb(const char *name, + PurplePrefType type, + gconstpointer val, + gpointer data) +{ + PidginMiniDialog *self = PIDGIN_MINI_DIALOG(data); + PidginMiniDialogPrivate *priv = PIDGIN_MINI_DIALOG_GET_PRIVATE(self); + guint blist_width = GPOINTER_TO_INT(val); + guint label_width = blist_width - BLIST_WIDTH_OTHER_THAN_LABEL; + + gtk_widget_set_size_request(GTK_WIDGET(priv->title), label_width, -1); + gtk_widget_set_size_request(GTK_WIDGET(priv->desc), label_width, -1); +} + +static void +pidgin_mini_dialog_init(PidginMiniDialog *self) +{ + GtkBox *self_box = GTK_BOX(self); + guint blist_width = purple_prefs_get_int(BLIST_WIDTH_PREF); + guint label_width = blist_width - BLIST_WIDTH_OTHER_THAN_LABEL; + + PidginMiniDialogPrivate *priv = g_new0(PidginMiniDialogPrivate, 1); + self->priv = priv; + + gtk_container_set_border_width(GTK_CONTAINER(self), PIDGIN_HIG_BOX_SPACE); + + priv->title_box = GTK_BOX(gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE)); + + priv->icon = GTK_IMAGE(gtk_image_new()); + gtk_misc_set_alignment(GTK_MISC(priv->icon), 0, 0); + + priv->title = GTK_LABEL(gtk_label_new(NULL)); + gtk_widget_set_size_request(GTK_WIDGET(priv->title), label_width, -1); + gtk_label_set_line_wrap(priv->title, TRUE); + gtk_misc_set_alignment(GTK_MISC(priv->title), 0, 0); + + gtk_box_pack_start(priv->title_box, GTK_WIDGET(priv->icon), FALSE, FALSE, 0); + gtk_box_pack_start(priv->title_box, GTK_WIDGET(priv->title), TRUE, TRUE, 0); + + priv->desc = GTK_LABEL(gtk_label_new(NULL)); + gtk_widget_set_size_request(GTK_WIDGET(priv->desc), label_width, -1); + gtk_label_set_line_wrap(priv->desc, TRUE); + gtk_misc_set_alignment(GTK_MISC(priv->desc), 0, 0); + /* make calling show_all() on the minidialog not affect desc even though + * it's packed inside it. + */ + g_object_set(G_OBJECT(priv->desc), "no-show-all", TRUE, NULL); + + purple_prefs_connect_callback(self, BLIST_WIDTH_PREF, + blist_width_changed_cb, self); + + self->contents = GTK_BOX(gtk_vbox_new(FALSE, 0)); + + priv->buttons = GTK_BOX(gtk_hbox_new(FALSE, 0)); + + gtk_box_pack_start(self_box, GTK_WIDGET(priv->title_box), FALSE, FALSE, 0); + gtk_box_pack_start(self_box, GTK_WIDGET(priv->desc), FALSE, FALSE, 0); + gtk_box_pack_start(self_box, GTK_WIDGET(self->contents), TRUE, TRUE, 0); + gtk_box_pack_start(self_box, GTK_WIDGET(priv->buttons), FALSE, FALSE, 0); + + gtk_widget_show_all(GTK_WIDGET(self)); +} diff -r 85fbadb8b8fd -r 1ce05db42eb3 pidgin/minidialog.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pidgin/minidialog.h Sun Nov 11 14:19:55 2007 +0000 @@ -0,0 +1,162 @@ +/** + * @file minidialog.h API for the #PidginMiniDialog Gtk widget. + * @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 __PIDGIN_MINI_DIALOG_H__ +#define __PIDGIN_MINI_DIALOG_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define PIDGIN_TYPE_MINI_DIALOG pidgin_mini_dialog_get_type() + +#define PIDGIN_MINI_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + PIDGIN_TYPE_MINI_DIALOG, PidginMiniDialog)) + +#define PIDGIN_MINI_DIALOG_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + PIDGIN_TYPE_MINI_DIALOG, PidginMiniDialogClass)) + +#define PIDGIN_IS_MINI_DIALOG(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + PIDGIN_TYPE_MINI_DIALOG)) + +#define PIDGIN_IS_MINI_DIALOG_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + PIDGIN_TYPE_MINI_DIALOG)) + +#define PIDGIN_MINI_DIALOG_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + PIDGIN_TYPE_MINI_DIALOG, PidginMiniDialogClass)) + +/** + * A widget resembling a diminutive dialog box, designed to be embedded in the + * #PidginBuddyList. Mini-dialogs have titles, optional descriptions, and a row + * of buttons at the bottom; above the buttons is a GtkHBox into which + * you can pack any random widgets you want to add to the dialog. When any of + * the dialog's buttons is clicked, the dialog will be destroyed. + * + * Dialogs have the following GObject properties: + *
+ *
"title" (char *)
+ *
A string to be displayed as the dialog's title.
+ *
"description" (char *)
+ *
A string to be displayed as the dialog's description. If this is @c + * NULL, the description widget will be hidden. + *
+ *
"icon-name" (char *)
+ *
The Gtk stock id of an icon for the dialog, or @c NULL for no icon. + * @see pidginstock.h + *
+ *
+ */ +typedef struct { + GtkVBox parent; + + /** A GtkVBox into which extra widgets for the dialog should be packed. + */ + GtkBox *contents; + + gpointer priv; +} PidginMiniDialog; + +/** The class of #PidginMiniDialog objects. */ +typedef struct { + GtkBoxClass parent_class; + + void (*_purple_reserved1) (void); + void (*_purple_reserved2) (void); + void (*_purple_reserved3) (void); + void (*_purple_reserved4) (void); +} PidginMiniDialogClass; + +/** The type of a callback triggered by a button in a mini-dialog being pressed. + * @param mini_dialog a dialog, one of whose buttons has been pressed. + * @param button the button which was pressed. + * @param user_data arbitrary data, supplied to + * pidgin_mini_dialog_add_button() when the button was + * created. + */ +typedef void (*PidginMiniDialogCallback)(PidginMiniDialog *mini_dialog, + GtkButton *button, gpointer user_data); + +/** Get the GType of #PidginMiniDialog. */ +GType pidgin_mini_dialog_get_type (void); + +/** Creates a new #PidginMiniDialog. This is a shortcut for creating the dialog + * with @c g_object_new() then setting each property yourself. + * @return a new #PidginMiniDialog. + */ +PidginMiniDialog *pidgin_mini_dialog_new(const gchar *title, + const gchar *description, const gchar *icon_name); + +/** Shortcut for setting a mini-dialog's title via GObject properties. + * @param mini_dialog a mini-dialog + * @param title the new title for @a mini_dialog + */ +void pidgin_mini_dialog_set_title(PidginMiniDialog *mini_dialogtitle , + const char *title); + +/** Shortcut for setting a mini-dialog's description via GObject properties. + * @param mini_dialog a mini-dialog + * @param description the new description for @a mini_dialog, or @c NULL to + * hide the description widget. + */ +void pidgin_mini_dialog_set_description(PidginMiniDialog *mini_dialog, + const char *description); + +/** Shortcut for setting a mini-dialog's icon via GObject properties. + * @param mini_dialog a mini-dialog + * @param title the Gtk stock ID of an icon, or @c NULL for no icon. + */ +void pidgin_mini_dialog_set_icon_name(PidginMiniDialog *mini_dialog, + const char *icon_name); + +/** Adds a new button to a mini-dialog, and attaches the supplied callback to + * its clicked signal. After a button is clicked, the dialog is + * destroyed. + * @param mini_dialog a mini-dialog + * @param text the text to display on the new button + * @param clicked_cb the function to call when the button is clicked + * @param user_data arbitrary data to pass to @a clicked_cb when it is + * called. + */ +void pidgin_mini_dialog_add_button(PidginMiniDialog *mini_dialog, + const char *text, PidginMiniDialogCallback clicked_cb, + gpointer user_data); + +/** Gets the number of widgets packed into PidginMiniDialog.contents. + * @param mini_dialog a mini-dialog + * @return the number of widgets in @a mini_dialog->contents. + */ +guint pidgin_mini_dialog_get_num_children(PidginMiniDialog *mini_dialog); + +G_END_DECLS + +#endif /* __PIDGIN_MINI_DIALOG_H__ */