changeset 21421:be50064a83e0

propagate from branch 'im.pidgin.pidgin' (head 0f99eebc17d3efab8dac3d72feb857ab25b06cb7) to branch 'im.pidgin.cpw.resiak.disconnectreason' (head 5536c47fe349c077e16ffe50897bd3757b5df106)
author Will Thompson <will.thompson@collabora.co.uk>
date Sun, 11 Nov 2007 17:01:59 +0000
parents 48c6c89a8158 (current diff) 1ce05db42eb3 (diff)
children 56a608b80fda
files libpurple/protocols/gg/gg.c
diffstat 55 files changed, 2213 insertions(+), 526 deletions(-) [+]
line wrap: on
line diff
--- a/COPYRIGHT	Sun Nov 11 16:56:03 2007 +0000
+++ b/COPYRIGHT	Sun Nov 11 17:01:59 2007 +0000
@@ -80,6 +80,7 @@
 Graham Cole
 Jono Cole
 Lorenzo Colitti
+Collabora Ltd.
 Jeff Connelly
 Nathan Conrad
 Felipe Contreras
--- a/ChangeLog.API	Sun Nov 11 16:56:03 2007 +0000
+++ b/ChangeLog.API	Sun Nov 11 17:01:59 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
--- a/doc/account-signals.dox	Sun Nov 11 16:56:03 2007 +0000
+++ b/doc/account-signals.dox	Sun Nov 11 17:01:59 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
--- a/doc/connection-signals.dox	Sun Nov 11 16:56:03 2007 +0000
+++ b/doc/connection-signals.dox	Sun Nov 11 17:01:59 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
--- a/libpurple/account.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/account.c	Sun Nov 11 17:01:59 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);
 }
--- a/libpurple/account.h	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/account.h	Sun Nov 11 17:01:59 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);
+
 /*@}*/
 
 /**************************************************************************/
--- a/libpurple/connection.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/connection.c	Sun Nov 11 17:01:59 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
--- a/libpurple/connection.h	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/connection.h	Sun Nov 11 17:01:59 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 <time.h>
 
 #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 <tt>
+ * purple_connection_error_is_fatal (PURPLE_CONNECTION_ERROR_NETWORK_ERROR)</tt>
+ * 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
+ * <tt> purple_connection_error_is_fatal
+ * (PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED)</tt> 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);
+
 /*@}*/
 
 /**************************************************************************/
--- a/libpurple/core.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/core.c	Sun Nov 11 17:01:59 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();
--- a/libpurple/plugins/signals-test.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/plugins/signals-test.c	Sun Nov 11 17:01:59 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",
--- a/libpurple/protocols/bonjour/bonjour.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/bonjour/bonjour.c	Sun Nov 11 17:01:59 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;
 	}
 
--- a/libpurple/protocols/bonjour/jabber.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/bonjour/jabber.c	Sun Nov 11 17:01:59 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;
 	}
 
--- a/libpurple/protocols/gg/gg.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/gg/gg.c	Sun Nov 11 17:01:59 2007 +0000
@@ -377,12 +377,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;
 	}
 
@@ -390,7 +394,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;
 	}
@@ -1304,7 +1309,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;
 	}
 
@@ -1457,7 +1464,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);
@@ -1503,7 +1512,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);
@@ -1709,7 +1720,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;
 	}
@@ -2001,7 +2014,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."));
 	}
 }
 /* }}} */
--- a/libpurple/protocols/irc/irc.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/irc/irc.c	Sun Nov 11 17:01:59 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;
 	}
 
--- a/libpurple/protocols/irc/msgs.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/irc/msgs.c	Sun Nov 11 17:01:59 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."));
 	}
 }
 
--- a/libpurple/protocols/irc/parse.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/irc/parse.c	Sun Nov 11 17:01:59 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;
 	}
 
--- a/libpurple/protocols/jabber/adhoccommands.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/jabber/adhoccommands.c	Sun Nov 11 17:01:59 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"));
 		
--- a/libpurple/protocols/jabber/auth.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/jabber/auth.c	Sun Nov 11 17:01:59 2007 +0000
@@ -50,7 +50,9 @@
 					"<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>", -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 @@
 						"<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />",
 						-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);
 	}
 }
--- a/libpurple/protocols/jabber/buddy.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/jabber/buddy.c	Sun Nov 11 17:01:59 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"));
--- a/libpurple/protocols/jabber/chat.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/jabber/chat.c	Sun Nov 11 17:01:59 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);
--- a/libpurple/protocols/jabber/jabber.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.c	Sun Nov 11 17:01:59 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);
--- a/libpurple/protocols/jabber/jabber.h	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/jabber/jabber.h	Sun Nov 11 17:01:59 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);
--- a/libpurple/protocols/jabber/parser.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/jabber/parser.c	Sun Nov 11 17:01:59 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"));
 	}
 }
 
--- a/libpurple/protocols/jabber/presence.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/jabber/presence.c	Sun Nov 11 17:01:59 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);
--- a/libpurple/protocols/msn/contact.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/msn/contact.c	Sun Nov 11 17:01:59 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"));
 	}
 }
 
--- a/libpurple/protocols/msn/msn.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/msn/msn.c	Sun Nov 11 17:01:59 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
--- a/libpurple/protocols/msn/session.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/msn/session.c	Sun Nov 11 17:01:59 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);
 }
--- a/libpurple/protocols/myspace/myspace.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/myspace/myspace.c	Sun Nov 11 17:01:59 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
--- a/libpurple/protocols/novell/novell.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/novell/novell.c	Sun Nov 11 17:01:59 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."));
 		}
 	}
 }
--- a/libpurple/protocols/oscar/flap_connection.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/oscar/flap_connection.c	Sun Nov 11 17:01:59 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);
 		}
 	}
--- a/libpurple/protocols/oscar/oscar.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/oscar/oscar.c	Sun Nov 11 17:01:59 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
--- a/libpurple/protocols/qq/keep_alive.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/qq/keep_alive.c	Sun Nov 11 17:01:59 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);
--- a/libpurple/protocols/qq/login_logout.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/qq/login_logout.c	Sun Nov 11 17:01:59 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");
--- a/libpurple/protocols/qq/qq.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/qq/qq.c	Sun Nov 11 17:01:59 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 */
--- a/libpurple/protocols/qq/qq_proxy.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/qq/qq_proxy.c	Sun Nov 11 17:01:59 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;
 }
--- a/libpurple/protocols/qq/recv_core.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/qq/recv_core.c	Sun Nov 11 17:01:59 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);
--- a/libpurple/protocols/qq/sendqueue.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/qq/sendqueue.c	Sun Nov 11 17:01:59 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:{
--- a/libpurple/protocols/sametime/sametime.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/sametime/sametime.c	Sun Nov 11 17:01:59 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"));
   }
 }
 
--- a/libpurple/protocols/silc/silc.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/silc/silc.c	Sun Nov 11 17:01:59 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, &params, 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;
--- a/libpurple/protocols/silc/util.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/silc/util.c	Sun Nov 11 17:01:59 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;
 			}
 
--- a/libpurple/protocols/simple/simple.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/simple/simple.c	Sun Nov 11 17:01:59 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;
 	}
 
--- a/libpurple/protocols/yahoo/yahoo.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo.c	Sun Nov 11 17:01:59 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;
 		}
 	}
--- a/libpurple/protocols/yahoo/yahoo_packet.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/yahoo/yahoo_packet.c	Sun Nov 11 17:01:59 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;
 	}
 
--- a/libpurple/protocols/yahoo/ycht.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/protocols/yahoo/ycht.c	Sun Nov 11 17:01:59 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;
--- a/libpurple/signals.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/signals.c	Sun Nov 11 17:01:59 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)
--- a/libpurple/signals.h	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/signals.h	Sun Nov 11 17:01:59 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(
--- a/libpurple/sslconn.h	Sun Nov 11 16:56:03 2007 +0000
+++ b/libpurple/sslconn.h	Sun Nov 11 17:01:59 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.
 	 */
--- a/pidgin/Makefile.am	Sun Nov 11 16:56:03 2007 +0000
+++ b/pidgin/Makefile.am	Sun Nov 11 17:01:59 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
--- a/pidgin/gtkblist.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/pidgin/gtkblist.c	Sun Nov 11 17:01:59 2007 +0000
@@ -58,6 +58,7 @@
 #include "gtkstatusbox.h"
 #include "gtkscrollbook.h"
 #include "gtkutils.h"
+#include "pidgin/minidialog.h"
 
 #include <gdk/gdkkeysyms.h>
 #include <gtk/gtk.h>
@@ -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(_("<span color=\"red\">%s disconnected: %s</span>"),
-	                       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("<span size=\"smaller\">%s</span>", 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;
--- a/pidgin/gtkblist.h	Sun Nov 11 16:56:03 2007 +0000
+++ b/pidgin/gtkblist.h	Sun Nov 11 17:01:59 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 <tt>const char *</tt>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);
 
--- a/pidgin/gtkconn.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/pidgin/gtkconn.c	Sun Nov 11 17:01:59 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);
 }
 
 
--- a/pidgin/gtkutils.c	Sun Nov 11 16:56:03 2007 +0000
+++ b/pidgin/gtkutils.c	Sun Nov 11 17:01:59 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),
-		   "<span weight=\"bold\" size=\"smaller\">%s</span>%s<span size=\"smaller\">%s</span>",
-		   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),
-			   "<span size=\"smaller\">%s</span>", 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);
 }
 
 /*
--- a/pidgin/gtkutils.h	Sun Nov 11 16:56:03 2007 +0000
+++ b/pidgin/gtkutils.h	Sun Nov 11 17:01:59 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 <tt>NULL</tt>-terminated list of button labels
+ *                       (<tt>char *</tt>) 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.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/minidialog.c	Sun Nov 11 17:01:59 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 <gtk/gtkhbox.h>
+#include <gtk/gtkbutton.h>
+
+#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("<span size=\"smaller\">%s</span>", 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(
+		"<span weight=\"bold\" size=\"smaller\">%s</span>",
+		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(
+			"<span size=\"smaller\">%s</span>", 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));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pidgin/minidialog.h	Sun Nov 11 17:01:59 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 <glib-object.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtklabel.h>
+
+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 <tt>GtkHBox</tt> 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:
+ * <dl>
+ *   <dt><tt>"title"</tt> (<tt>char *</tt>)</dt>
+ *   <dd>A string to be displayed as the dialog's title.</dd>
+ *   <dt><tt>"description"</tt> (<tt>char *</tt>)</dt>
+ *   <dd>A string to be displayed as the dialog's description.  If this is @c
+ *       NULL, the description widget will be hidden.
+ *   </dd>
+ *   <dt><tt>"icon-name"</tt> (<tt>char *</tt>)</dt>
+ *   <dd>The Gtk stock id of an icon for the dialog, or @c NULL for no icon.
+ *       @see pidginstock.h
+ *   </dd>
+ * </dl>
+ */
+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 <tt>clicked</tt> 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__ */