[evolution-data-server] Introduce evolution-user-prompter service



commit ff581ea9040a44b632d0239123dd8b4e597499bf
Author: Milan Crha <mcrha redhat com>
Date:   Fri Dec 7 22:48:39 2012 +0100

    Introduce evolution-user-prompter service
    
    This is used to prompt users for input, mainly from book or calendar
    backends. The service is extensible, thus the backend can provide its
    own dialogs and use them.

 configure.ac                                       |   11 +-
 libebackend/Makefile.am                            |    6 +
 libebackend/e-extensible.c                         |    6 +-
 libebackend/e-user-prompter-server-extension.c     |  181 ++++++
 libebackend/e-user-prompter-server-extension.h     |   89 +++
 libebackend/e-user-prompter-server.c               |  547 +++++++++++++++++
 libebackend/e-user-prompter-server.h               |  100 +++
 libebackend/e-user-prompter.c                      |  635 ++++++++++++++++++++
 libebackend/e-user-prompter.h                      |  106 ++++
 libebackend/libebackend.h                          |    3 +
 libedataserver/e-data-server-util.c                |  249 ++++++++
 libedataserver/e-data-server-util.h                |   19 +
 libedataserver/e-marshal.list                      |    1 +
 modules/Makefile.am                                |    1 +
 modules/trust-prompt/Makefile.am                   |   32 +
 modules/trust-prompt/module-trust-prompt.c         |  406 +++++++++++++
 po/POTFILES.in                                     |    3 +
 private/Makefile.am                                |   16 +
 ...org.gnome.evolution.dataserver.UserPrompter.xml |   89 +++
 services/Makefile.am                               |    1 +
 services/evolution-user-prompter/Makefile.am       |   36 ++
 .../evolution-user-prompter.c                      |  139 +++++
 ...me.evolution.dataserver.UserPrompter.service.in |    3 +
 tests/libedataserver/Makefile.am                   |    8 +
 tests/libedataserver/e-user-prompter-test.c        |  273 +++++++++
 25 files changed, 2956 insertions(+), 4 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 69170d5..a9e4d2d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -71,6 +71,7 @@ dnl ******************************
 ADDRESS_BOOK_DBUS_SERVICE_NAME="org.gnome.evolution.dataserver.AddressBook4"
 CALENDAR_DBUS_SERVICE_NAME="org.gnome.evolution.dataserver.Calendar3"
 SOURCES_DBUS_SERVICE_NAME="org.gnome.evolution.dataserver.Sources1"
+USER_PROMPTER_DBUS_SERVICE_NAME="org.gnome.evolution.dataserver.UserPrompter0"
 
 AC_DEFINE_UNQUOTED(
        ADDRESS_BOOK_DBUS_SERVICE_NAME,
@@ -87,9 +88,15 @@ AC_DEFINE_UNQUOTED(
 	["$SOURCES_DBUS_SERVICE_NAME"],
 	[D-Bus service name for the source registry])
 
+AC_DEFINE_UNQUOTED(
+	USER_PROMPTER_DBUS_SERVICE_NAME,
+	["$USER_PROMPTER_DBUS_SERVICE_NAME"],
+	[D-Bus service name for the user prompter])
+
 AC_SUBST(ADDRESS_BOOK_DBUS_SERVICE_NAME)
 AC_SUBST(CALENDAR_DBUS_SERVICE_NAME)
 AC_SUBST(SOURCES_DBUS_SERVICE_NAME)
+AC_SUBST(USER_PROMPTER_DBUS_SERVICE_NAME)
 
 dnl ******************************
 dnl Libtool versioning
@@ -1494,7 +1501,7 @@ dnl *******************
 dnl D-BUS service stuff
 dnl *******************
 m4_pattern_allow([AM_V_GEN])
-EVO_SUBST_SERVICE_RULE='%.service: %.service.in Makefile ; $(AM_V_GEN) sed -e "s|\ libexecdir\@|$(libexecdir)|" -e s"|\ ADDRESS_BOOK_DBUS_SERVICE_NAME\@|$(ADDRESS_BOOK_DBUS_SERVICE_NAME)|" -e "s|\ CALENDAR_DBUS_SERVICE_NAME\@|$(CALENDAR_DBUS_SERVICE_NAME)|" -e "s|\ SOURCES_DBUS_SERVICE_NAME\@|$(SOURCES_DBUS_SERVICE_NAME)|" $< > $@'
+EVO_SUBST_SERVICE_RULE='%.service: %.service.in Makefile ; $(AM_V_GEN) sed -e "s|\ libexecdir\@|$(libexecdir)|" -e s"|\ ADDRESS_BOOK_DBUS_SERVICE_NAME\@|$(ADDRESS_BOOK_DBUS_SERVICE_NAME)|" -e "s|\ CALENDAR_DBUS_SERVICE_NAME\@|$(CALENDAR_DBUS_SERVICE_NAME)|" -e "s|\ SOURCES_DBUS_SERVICE_NAME\@|$(SOURCES_DBUS_SERVICE_NAME)|" -e "s|\ USER_PROMPTER_DBUS_SERVICE_NAME\@|$(USER_PROMPTER_DBUS_SERVICE_NAME)|" $< > $@'
 AC_SUBST(EVO_SUBST_SERVICE_RULE)
 
 dnl ******************************
@@ -1616,12 +1623,14 @@ modules/Makefile
 modules/cache-reaper/Makefile
 modules/gnome-online-accounts/Makefile
 modules/google-backend/Makefile
+modules/trust-prompt/Makefile
 modules/yahoo-backend/Makefile
 private/Makefile
 services/Makefile
 services/evolution-addressbook-factory/Makefile
 services/evolution-calendar-factory/Makefile
 services/evolution-source-registry/Makefile
+services/evolution-user-prompter/Makefile
 tests/Makefile
 tests/libebook/Makefile
 tests/libebook/client/Makefile
diff --git a/libebackend/Makefile.am b/libebackend/Makefile.am
index 35e8f5e..a8ac830 100644
--- a/libebackend/Makefile.am
+++ b/libebackend/Makefile.am
@@ -52,6 +52,9 @@ libebackend_1_2_la_SOURCES =		\
 	e-server-side-source.c		\
 	e-source-registry-server.c	\
 	e-sqlite3-vfs.c			\
+	e-user-prompter.c		\
+	e-user-prompter-server.c	\
+	e-user-prompter-server-extension.c \
 	e-file-cache.c
 
 libebackend_1_2_la_LIBADD = 				\
@@ -92,6 +95,9 @@ libebackendinclude_HEADERS =		\
 	e-server-side-source.h		\
 	e-source-registry-server.h	\
 	e-sqlite3-vfs.h			\
+	e-user-prompter.h		\
+	e-user-prompter-server.h	\
+	e-user-prompter-server-extension.h \
 	e-file-cache.h
 
 %-$(API_VERSION).pc: %.pc
diff --git a/libebackend/e-extensible.c b/libebackend/e-extensible.c
index 27a832d..f7f59dd 100644
--- a/libebackend/e-extensible.c
+++ b/libebackend/e-extensible.c
@@ -30,7 +30,7 @@
  *
  * <informalexample>
  * <programlisting>
- * #include <libebackend/e-extensible.h>
+ * #include <libebackend/libebackend.h>
  *
  * G_DEFINE_TYPE_WITH_CODE (
  *         ECustomWidget, e_custom_widget, GTK_TYPE_WIDGET,
@@ -46,9 +46,9 @@
  * <informalexample>
  * <programlisting>
  * static void
- * e_custom_widget_init (ECustomWidget *widget)
+ * e_custom_widget_constructed (ECustomWidget *widget)
  * {
- *         Initialization code goes here...
+ *         Construction code goes here, same as call to parent's 'constructed'...
  *
  *         e_extensible_load_extensions (E_EXTENSIBLE (widget));
  * }
diff --git a/libebackend/e-user-prompter-server-extension.c b/libebackend/e-user-prompter-server-extension.c
new file mode 100644
index 0000000..acaa203
--- /dev/null
+++ b/libebackend/e-user-prompter-server-extension.c
@@ -0,0 +1,181 @@
+/*
+ * e-user-prompter-server-extension.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/**
+ * SECTION: e-user-prompter-server-extension
+ * @short_description: Extension for a server-side user prompter
+ *
+ * The #EUserPrompterServerExtension is a base struct for extension
+ * of EUserPrompterServer, to provide customized or specialized dialog
+ * prompts.
+ *
+ * A descendant defines two virtual functions,
+ * the EUserPrompterServerExtensionClass::register_dialogs which is used as
+ * a convenient function, where the descendant registers all the dialogs it
+ * provides on the server with e_user_prompter_server_register().
+ *
+ * The next function is EUserPrompterServerExtensionClass::prompt, which is
+ * used to initiate user prompt. The implementor should not block main thread
+ * with this function, because this is treated fully asynchronously.
+ * User's response is passed to the server with
+ * e_user_prompter_server_extension_response() call.
+ **/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+
+#include "e-user-prompter-server-extension.h"
+
+#define E_USER_PROMPTER_SERVER_EXTENSION_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_USER_PROMPTER_SERVER_EXTENSION, EUserPrompterServerExtensionPrivate))
+
+struct _EUserPrompterServerExtensionPrivate {
+	gint dummy; /* not used */
+};
+
+G_DEFINE_ABSTRACT_TYPE (EUserPrompterServerExtension, e_user_prompter_server_extension, E_TYPE_EXTENSION)
+
+static void
+user_prompter_server_extension_constructed (GObject *object)
+{
+	EExtensible *extensible;
+	EUserPrompterServer *server;
+	EExtension *extension;
+	EUserPrompterServerExtensionClass *klass;
+
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_user_prompter_server_extension_parent_class)->constructed (object);
+
+	g_return_if_fail (E_IS_USER_PROMPTER_SERVER_EXTENSION (object));
+
+	extension = E_EXTENSION (object);
+	g_return_if_fail (extension != NULL);
+
+	extensible = e_extension_get_extensible (extension);
+	g_return_if_fail (E_IS_USER_PROMPTER_SERVER (extensible));
+
+	server = E_USER_PROMPTER_SERVER (extensible);
+
+	klass = E_USER_PROMPTER_SERVER_EXTENSION_GET_CLASS (extension);
+	g_return_if_fail (klass->register_dialogs);
+
+	klass->register_dialogs (extension, server);
+}
+
+static void
+e_user_prompter_server_extension_class_init (EUserPrompterServerExtensionClass *class)
+{
+	GObjectClass *object_class;
+	EExtensionClass *extension_class;
+
+	g_type_class_add_private (class, sizeof (EUserPrompterServerExtensionPrivate));
+
+	class->register_dialogs = NULL;
+	class->prompt = NULL;
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->constructed = user_prompter_server_extension_constructed;
+
+	extension_class = E_EXTENSION_CLASS (class);
+	extension_class->extensible_type = E_TYPE_USER_PROMPTER_SERVER;
+}
+
+static void
+e_user_prompter_server_extension_init (EUserPrompterServerExtension *extension)
+{
+	extension->priv = E_USER_PROMPTER_SERVER_EXTENSION_GET_PRIVATE (extension);
+}
+
+/**
+ * e_user_prompter_server_extension_prompt:
+ * @extension: an #EUserPrompterServerExtension
+ * @prompt_id: Prompt identificator, which is used in call to e_user_prompter_server_extension_response()
+ * @dialog_name: Name of a dialog to run
+ * @parameters: (allow-none): Optional extension parameters for the dialog, as passed by a caller
+ *
+ * Instructs extension to show dialog @dialog_name. If it cannot be found,
+ * or any error, then return %FALSE. The caller can pass optional @parameters,
+ * if @extension uses any. Meaning of @parameters is known only to the caller
+ * and to the dialog implementor, it's not interpretted nor checked for correctness
+ * in any way in #EUserPrompterServer. The only limitation of @parameters is that
+ * the array elements are strings.
+ *
+ * The @prompt_id is used as an identificator of the prompt itself,
+ * and is used in e_user_prompter_server_extension_response() call,
+ * which finishes the prompt.
+ *
+ * Note: The function call should not block main loop, it should
+ * just show dialog and return.
+ *
+ * Returns: Whether dialog was found and shown.
+ *
+ * Since: 3.8
+ **/
+gboolean
+e_user_prompter_server_extension_prompt (EUserPrompterServerExtension *extension,
+					 gint prompt_id,
+					 const gchar *dialog_name,
+					 const ENamedParameters *parameters)
+{
+	EUserPrompterServerExtensionClass *klass;
+
+	g_return_val_if_fail (E_IS_USER_PROMPTER_SERVER_EXTENSION (extension), FALSE);
+
+	klass = E_USER_PROMPTER_SERVER_EXTENSION_GET_CLASS (extension);
+	g_return_val_if_fail (klass->prompt != NULL, FALSE);
+
+	return klass->prompt (extension, prompt_id, dialog_name, parameters);
+}
+
+/**
+ * e_user_prompter_server_extension_response:
+ * @extension: an #EUserPrompterServerExtension
+ * @prompt_id: Prompt identificator
+ * @response: Response of the prompt
+ * @values: (allow-none): Additional response values, if extension defines any
+ *
+ * A conveniente wrapper function around e_user_prompter_server_response(),
+ * which ends previous call of e_user_prompter_server_extension_prompt().
+ * The @response and @values is known only to the caller and to the dialog implementor,
+ * it's not interpretted nor checked for correctness in any way in #EUserPrompterServer.
+ * The only limitation of @values is that the array elements are strings.
+ *
+ * Since: 3.8
+ **/
+void
+e_user_prompter_server_extension_response (EUserPrompterServerExtension *extension,
+					   gint prompt_id,
+					   gint response,
+					   const ENamedParameters *values)
+{
+	EExtensible *extensible;
+	EUserPrompterServer *server;
+
+	g_return_if_fail (E_IS_USER_PROMPTER_SERVER_EXTENSION (extension));
+
+	extensible = e_extension_get_extensible (E_EXTENSION (extension));
+	g_return_if_fail (E_IS_USER_PROMPTER_SERVER (extensible));
+
+	server = E_USER_PROMPTER_SERVER (extensible);
+
+	e_user_prompter_server_response (server, prompt_id, response, values);
+}
diff --git a/libebackend/e-user-prompter-server-extension.h b/libebackend/e-user-prompter-server-extension.h
new file mode 100644
index 0000000..dc4568a
--- /dev/null
+++ b/libebackend/e-user-prompter-server-extension.h
@@ -0,0 +1,89 @@
+/*
+ * e-user-prompter-server-extension.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_USER_PROMPTER_SERVER_EXTENSION_H
+#define E_USER_PROMPTER_SERVER_EXTENSION_H
+
+#include <libebackend/libebackend.h>
+
+/* Standard GObject macros */
+#define E_TYPE_USER_PROMPTER_SERVER_EXTENSION \
+	(e_user_prompter_server_extension_get_type ())
+#define E_USER_PROMPTER_SERVER_EXTENSION(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_USER_PROMPTER_SERVER_EXTENSION, EUserPrompterServerExtension))
+#define E_USER_PROMPTER_SERVER_EXTENSION_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_USER_PROMPTER_SERVER_EXTENSION, EUserPrompterServerExtensionClass))
+#define E_IS_USER_PROMPTER_SERVER_EXTENSION(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_USER_PROMPTER_SERVER_EXTENSION))
+#define E_IS_USER_PROMPTER_SERVER_EXTENSION_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_USER_PROMPTER_SERVER_EXTENSION))
+#define E_USER_PROMPTER_SERVER_EXTENSION_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_USER_PROMPTER_SERVER_EXTENSION, EUserPrompterServerExtensionClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EUserPrompterServerExtension EUserPrompterServerExtension;
+typedef struct _EUserPrompterServerExtensionClass EUserPrompterServerExtensionClass;
+typedef struct _EUserPrompterServerExtensionPrivate EUserPrompterServerExtensionPrivate;
+
+/* Forward declaration for EUserPrompterServer object */
+struct _EUserPrompterServer;
+
+/**
+ * EUserPrompterServerExtension:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.8
+ **/
+struct _EUserPrompterServerExtension {
+	EExtension parent;
+	EUserPrompterServerExtensionPrivate *priv;
+};
+
+struct _EUserPrompterServerExtensionClass {
+	EExtensionClass parent_class;
+
+	/* virtual methods */
+	void		(*register_dialogs)	(EExtension *extension,
+						 struct _EUserPrompterServer *server); 
+	gboolean	(*prompt)		(EUserPrompterServerExtension *extension,
+						 gint prompt_id,
+						 const gchar *dialog_name,
+						 const ENamedParameters *parameters);
+};
+
+GType		e_user_prompter_server_extension_get_type	(void);
+gboolean	e_user_prompter_server_extension_prompt		(EUserPrompterServerExtension *extension,
+								 gint prompt_id,
+								 const gchar *dialog_name,
+								 const ENamedParameters *parameters);
+void		e_user_prompter_server_extension_response	(EUserPrompterServerExtension *extension,
+								 gint prompt_id,
+								 gint response,
+								 const ENamedParameters *values);
+
+G_END_DECLS
+
+#endif /* E_USER_PROMPTER_SERVER_EXTENSION_H */
diff --git a/libebackend/e-user-prompter-server.c b/libebackend/e-user-prompter-server.c
new file mode 100644
index 0000000..3324e4e
--- /dev/null
+++ b/libebackend/e-user-prompter-server.c
@@ -0,0 +1,547 @@
+/*
+ * e-user-prompter-server.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/**
+ * SECTION: e-user-prompter-server
+ * @short_description: Server-side user prompter
+ *
+ * The #EUserPrompterServer is the heart of the user prompter D-Bus service.
+ * Acting as a global singleton for user prompts from backends.
+ **/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include <glib/gi18n-lib.h>
+
+#include <libedataserver/libedataserver.h>
+
+/* Private D-Bus classes. */
+#include "e-dbus-user-prompter.h"
+
+#include "libedataserver/e-marshal.h"
+
+#include "e-user-prompter-server.h"
+
+#define E_USER_PROMPTER_SERVER_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_USER_PROMPTER_SERVER, EUserPrompterServerPrivate))
+
+struct _EUserPrompterServerPrivate {
+	EDBusUserPrompter *dbus_prompter;
+
+	GHashTable *extensions;
+
+	GRecMutex lock;
+	guint schedule_id;
+	GSList *prompts;
+	gint last_prompt_id;
+};
+
+enum {
+	PROMPT,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE_WITH_CODE (EUserPrompterServer, e_user_prompter_server, E_TYPE_DBUS_SERVER,
+			 G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
+
+typedef struct _PromptRequest {
+	gint id;
+	gboolean is_extension_prompt;
+
+	/* 'Prompt' properties */
+	gchar *type;
+	gchar *title;
+	gchar *primary_text;
+	gchar *secondary_text;
+	gboolean use_markup;
+	GSList *button_captions;
+
+	/* 'ExtensionPrompt' properties */
+	gchar *dialog_name;
+	ENamedParameters *parameters;
+} PromptRequest;
+
+static void
+prompt_request_free (gpointer data)
+{
+	PromptRequest *pr = data;
+
+	if (pr) {
+		g_free (pr->type);
+		g_free (pr->title);
+		g_free (pr->primary_text);
+		g_free (pr->secondary_text);
+		g_slist_free_full (pr->button_captions, g_free);
+
+		g_free (pr->dialog_name);
+		e_named_parameters_free (pr->parameters);
+
+		g_free (pr);
+	}
+}
+
+static gint
+add_prompt (EUserPrompterServer *server,
+	    gboolean is_extension_prompt,
+	    const gchar *type,
+	    const gchar *title,
+	    const gchar *primary_text,
+	    const gchar *secondary_text,
+	    gboolean use_markup,
+	    const gchar *const *button_captions,
+	    const gchar *dialog_name,
+	    const gchar *const *parameters)
+{
+	PromptRequest *pr;
+	gint id;
+
+	g_return_val_if_fail (E_IS_USER_PROMPTER_SERVER (server), -1);
+
+	g_rec_mutex_lock (&server->priv->lock);
+
+	server->priv->last_prompt_id++;
+
+	pr = g_new0 (PromptRequest, 1);
+	pr->is_extension_prompt = is_extension_prompt;
+	pr->id = server->priv->last_prompt_id;
+	pr->type = g_strdup (type);
+	pr->title = g_strdup (title);
+	pr->primary_text = g_strdup (primary_text);
+	pr->secondary_text = g_strdup (secondary_text);
+	pr->use_markup = use_markup;
+	pr->button_captions = e_util_strv_to_slist (button_captions);
+	pr->dialog_name = g_strdup (dialog_name);
+	pr->parameters = parameters ? e_named_parameters_new_strv (parameters) : NULL;
+
+	server->priv->prompts = g_slist_append (server->priv->prompts, pr);
+
+	id = pr->id;
+
+	e_dbus_server_hold (E_DBUS_SERVER (server));
+
+	g_rec_mutex_unlock (&server->priv->lock);
+
+	return id;
+}
+
+static gboolean
+remove_prompt (EUserPrompterServer *server,
+	       gint prompt_id,
+	       gboolean *is_extension_prompt)
+{
+	GSList *iter;
+
+	g_return_val_if_fail (E_IS_USER_PROMPTER_SERVER (server), FALSE);
+
+	g_rec_mutex_lock (&server->priv->lock);
+
+	for (iter = server->priv->prompts; iter; iter = g_slist_next (iter)) {
+		PromptRequest *pr = iter->data;
+
+		if (pr && pr->id == prompt_id) {
+			server->priv->prompts = g_slist_remove (server->priv->prompts, pr);
+
+			if (is_extension_prompt)
+				*is_extension_prompt = pr->is_extension_prompt;
+
+			prompt_request_free (pr);
+			e_dbus_server_release (E_DBUS_SERVER (server));
+
+			g_rec_mutex_unlock (&server->priv->lock);
+			return TRUE;
+		}
+	}
+
+	g_rec_mutex_unlock (&server->priv->lock);
+
+	g_warn_if_reached ();
+
+	return FALSE;
+}
+
+static void
+do_show_prompt (EUserPrompterServer *server)
+{
+	PromptRequest *pr;
+
+	g_return_if_fail (server->priv->prompts != NULL);
+
+	pr = server->priv->prompts->data;
+	g_return_if_fail (pr != NULL);
+
+	if (pr->is_extension_prompt) {
+		EUserPrompterServerExtension *extension;
+
+		extension = g_hash_table_lookup (server->priv->extensions, pr->dialog_name);
+		g_return_if_fail (extension != NULL);
+
+		if (!e_user_prompter_server_extension_prompt (extension, pr->id, pr->dialog_name, pr->parameters)) {
+			e_user_prompter_server_response (server, pr->id, -1, NULL);
+		}
+	} else {
+		g_signal_emit (server, signals[PROMPT], 0, pr->id, pr->type, pr->title, pr->primary_text, pr->secondary_text, pr->use_markup, pr->button_captions);
+	}
+}
+
+static gboolean
+show_prompt_idle_cb (gpointer user_data)
+{
+	EUserPrompterServer *server = user_data;
+
+	g_return_val_if_fail (E_IS_USER_PROMPTER_SERVER (server), FALSE);
+
+	g_rec_mutex_lock (&server->priv->lock);
+	if (server->priv->prompts) {
+		do_show_prompt (server);
+		/* keep the schedule_id set, until user responds */
+	} else {
+		server->priv->schedule_id = 0;
+	}
+	g_rec_mutex_unlock (&server->priv->lock);
+
+	return FALSE;
+}
+
+static void
+maybe_schedule_prompt (EUserPrompterServer *server)
+{
+	g_return_if_fail (E_IS_USER_PROMPTER_SERVER (server));
+
+	g_rec_mutex_lock (&server->priv->lock);
+	if (!server->priv->schedule_id && server->priv->prompts)
+		server->priv->schedule_id = g_idle_add (show_prompt_idle_cb, server);
+	g_rec_mutex_unlock (&server->priv->lock);
+}
+
+static gboolean
+user_prompter_server_prompt_cb (EDBusUserPrompter *dbus_prompter,
+				GDBusMethodInvocation *invocation,
+				const gchar *type,
+				const gchar *title,
+				const gchar *primary_text,
+				const gchar *secondary_text,
+				gboolean use_markup,
+				const gchar *const *button_captions,
+				EUserPrompterServer *server)
+{
+	gint id;
+
+	g_rec_mutex_lock (&server->priv->lock);
+
+	id = add_prompt (server, FALSE, type, title, primary_text, secondary_text, use_markup, button_captions, NULL, NULL);
+
+	e_dbus_user_prompter_complete_prompt (dbus_prompter, invocation, id);
+
+	maybe_schedule_prompt (server);
+
+	g_rec_mutex_unlock (&server->priv->lock);
+
+	return TRUE;
+}
+
+static gboolean
+user_prompter_server_extension_prompt_cb (EDBusUserPrompter *dbus_prompter,
+					  GDBusMethodInvocation *invocation,
+					  const gchar *dialog_name,
+					  const gchar *const *parameters,
+					  EUserPrompterServer *server)
+{
+	gint id;
+
+	g_rec_mutex_lock (&server->priv->lock);
+
+	if (!dialog_name || !g_hash_table_lookup (server->priv->extensions, dialog_name)) {
+		g_rec_mutex_unlock (&server->priv->lock);
+
+		g_dbus_method_invocation_return_error (invocation, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
+			_("Extension dialog '%s' not found."), dialog_name ? dialog_name : "[null]");
+
+		return TRUE;
+	}
+
+	id = add_prompt (server, TRUE, NULL, NULL, NULL, NULL, FALSE, NULL, dialog_name, parameters);
+
+	e_dbus_user_prompter_complete_extension_prompt (dbus_prompter, invocation, id);
+
+	maybe_schedule_prompt (server);
+
+	g_rec_mutex_unlock (&server->priv->lock);
+
+	return TRUE;
+}
+
+static void
+user_prompter_server_constructed (GObject *object)
+{
+	/* Chain up to parent's constructed() method. */
+	G_OBJECT_CLASS (e_user_prompter_server_parent_class)->constructed (object);
+
+	e_extensible_load_extensions (E_EXTENSIBLE (object));
+}
+
+static void
+user_prompter_server_dispose (GObject *object)
+{
+	EUserPrompterServerPrivate *priv;
+
+	priv = E_USER_PROMPTER_SERVER_GET_PRIVATE (object);
+
+	if (priv->dbus_prompter != NULL) {
+		g_object_unref (priv->dbus_prompter);
+		priv->dbus_prompter = NULL;
+	}
+
+	g_slist_free_full (priv->prompts, prompt_request_free);
+	g_hash_table_remove_all (priv->extensions);
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (e_user_prompter_server_parent_class)->dispose (object);
+}
+
+static void
+user_prompter_server_finalize (GObject *object)
+{
+	EUserPrompterServerPrivate *priv;
+
+	priv = E_USER_PROMPTER_SERVER_GET_PRIVATE (object);
+
+	g_rec_mutex_clear (&priv->lock);
+	g_hash_table_destroy (priv->extensions);
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_user_prompter_server_parent_class)->finalize (object);
+}
+
+static void
+user_prompter_server_bus_acquired (EDBusServer *server,
+                                     GDBusConnection *connection)
+{
+	EUserPrompterServerPrivate *priv;
+	GError *error = NULL;
+
+	priv = E_USER_PROMPTER_SERVER_GET_PRIVATE (server);
+
+	g_dbus_interface_skeleton_export (
+		G_DBUS_INTERFACE_SKELETON (priv->dbus_prompter),
+		connection, E_USER_PROMPTER_SERVER_OBJECT_PATH, &error);
+
+	/* Terminate the server if we can't export the interface. */
+	if (error != NULL) {
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		e_dbus_server_quit (server, E_DBUS_SERVER_EXIT_NORMAL);
+		g_error_free (error);
+	}
+
+	/* Chain up to parent's bus_acquired() method. */
+	E_DBUS_SERVER_CLASS (e_user_prompter_server_parent_class)->
+		bus_acquired (server, connection);
+}
+
+static void
+user_prompter_server_quit_server (EDBusServer *server,
+				  EDBusServerExitCode code)
+{
+	EUserPrompterServerPrivate *priv;
+
+	priv = E_USER_PROMPTER_SERVER_GET_PRIVATE (server);
+
+	g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (priv->dbus_prompter));
+
+	/* Chain up to parent's quit_server() method. */
+	E_DBUS_SERVER_CLASS (e_user_prompter_server_parent_class)->quit_server (server, code);
+}
+
+static void
+e_user_prompter_server_class_init (EUserPrompterServerClass *class)
+{
+	GObjectClass *object_class;
+	EDBusServerClass *dbus_server_class;
+
+	g_type_class_add_private (class, sizeof (EUserPrompterServerPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->constructed = user_prompter_server_constructed;
+	object_class->dispose = user_prompter_server_dispose;
+	object_class->finalize = user_prompter_server_finalize;
+
+	dbus_server_class = E_DBUS_SERVER_CLASS (class);
+	dbus_server_class->bus_name = USER_PROMPTER_DBUS_SERVICE_NAME;
+	dbus_server_class->module_directory = MODULE_DIRECTORY;
+	dbus_server_class->bus_acquired = user_prompter_server_bus_acquired;
+	dbus_server_class->quit_server = user_prompter_server_quit_server;
+
+	signals[PROMPT] = g_signal_new (
+		"prompt",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_LAST,
+		G_STRUCT_OFFSET (EUserPrompterServerClass, prompt),
+		NULL, NULL,
+		e_marshal_VOID__INT_STRING_STRING_STRING_STRING_BOOLEAN_POINTER,
+		G_TYPE_NONE, 7,
+		G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
+}
+
+static void
+e_user_prompter_server_init (EUserPrompterServer *server)
+{
+	server->priv = E_USER_PROMPTER_SERVER_GET_PRIVATE (server);
+	server->priv->dbus_prompter = e_dbus_user_prompter_skeleton_new ();
+	server->priv->prompts = NULL;
+	server->priv->extensions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+
+	g_rec_mutex_init (&server->priv->lock);
+
+	g_signal_connect (server->priv->dbus_prompter, "handle-prompt",
+		G_CALLBACK (user_prompter_server_prompt_cb), server);
+
+	g_signal_connect (server->priv->dbus_prompter, "handle-extension-prompt",
+		G_CALLBACK (user_prompter_server_extension_prompt_cb), server);
+}
+
+/**
+ * e_user_prompter_server_new:
+ *
+ * Creates a new instance of #EUserPrompterServer.
+ *
+ * Returns: a new instance of #EUserPrompterServer
+ *
+ * Since: 3.8
+ **/
+EDBusServer *
+e_user_prompter_server_new (void)
+{
+	return g_object_new (E_TYPE_USER_PROMPTER_SERVER, NULL);
+}
+
+/**
+ * e_user_prompter_server_response:
+ * @server: an #EUserPrompterServer
+ * @prompt_id: Id of a prompt, which was responded
+ * @response: Response of the prompt
+ * @extension_values: (allow-none): For extension prompts can pass extra return values
+ *
+ * Finishes prompt initiated by a "prompt" signal or an extension prompt.
+ * The @response for non-extension prompts is a 0-based index of a button
+ * used to close the prompt.
+ *
+ * The @extension_values is ignored for non-extension prompts.
+ *
+ * Since: 3.8
+ **/
+void
+e_user_prompter_server_response (EUserPrompterServer *server,
+				 gint prompt_id,
+				 gint response,
+				 const ENamedParameters *extension_values)
+{
+	gboolean is_extension_prompt = FALSE;
+
+	g_return_if_fail (E_IS_USER_PROMPTER_SERVER (server));
+	g_return_if_fail (server->priv->schedule_id != 0);
+
+	g_rec_mutex_lock (&server->priv->lock);
+
+	if (!server->priv->prompts || server->priv->schedule_id == 0) {
+		g_rec_mutex_unlock (&server->priv->lock);
+		g_return_if_reached ();
+		return;
+	}
+
+	if (remove_prompt (server, prompt_id, &is_extension_prompt)) {
+		if (is_extension_prompt) {
+			GPtrArray *values = NULL;
+
+			if (!extension_values || !extension_values->len ||
+			    extension_values->pdata[extension_values->len - 1] != NULL) {
+				gint ii;
+
+				values = g_ptr_array_new ();
+				for (ii = 0; extension_values && ii < extension_values->len; ii++) {
+					g_ptr_array_add (values, extension_values->pdata[ii]);
+				}
+				g_ptr_array_add (values, NULL);
+				extension_values = values;
+			}
+
+			e_dbus_user_prompter_emit_extension_response (server->priv->dbus_prompter, prompt_id, response,
+				(const gchar * const *) extension_values->pdata);
+
+			if (values)
+				g_ptr_array_free (values, TRUE);
+		} else {
+			e_dbus_user_prompter_emit_response (server->priv->dbus_prompter, prompt_id, response);
+		}
+	}
+
+	if (server->priv->prompts) {
+		do_show_prompt (server);
+	} else {
+		server->priv->schedule_id = 0;
+	}
+
+	g_rec_mutex_unlock (&server->priv->lock);
+}
+
+/**
+ * e_user_prompter_server_register:
+ * @server: an #EUserPrompterServer
+ * @extension: an #EUserPrompterServerExtension descendant
+ * @dialog_name: name of a dialog, which the @extensions implement
+ *
+ * Registers @extension as a provider of @dialog_name dialog. The names
+ * are compared case sensitively and two extensions cannot provide
+ * the same dialog. If the function succeeds, then it adds its own
+ * reference on the @extension.
+ *
+ * Extensions providing multiple dialogs call this function multiple
+ * times, for each dialog name separately.
+ *
+ * Returns: Whether properly registered @extension
+ *
+ * Since: 3.8
+ **/
+gboolean
+e_user_prompter_server_register (EUserPrompterServer *server,
+				 EExtension *extension,
+				 const gchar *dialog_name)
+{
+	g_return_val_if_fail (E_IS_USER_PROMPTER_SERVER (server), FALSE);
+	g_return_val_if_fail (E_IS_USER_PROMPTER_SERVER_EXTENSION (extension), FALSE);
+	g_return_val_if_fail (dialog_name != NULL, FALSE);
+	g_return_val_if_fail (*dialog_name != '\0', FALSE);
+
+	g_rec_mutex_lock (&server->priv->lock);
+
+	if (g_hash_table_lookup (server->priv->extensions, dialog_name)) {
+		g_rec_mutex_unlock (&server->priv->lock);
+		return FALSE;
+	}
+
+	g_print ("Registering %s for dialog '%s'\n", G_OBJECT_TYPE_NAME (extension), dialog_name);
+	g_hash_table_insert (server->priv->extensions, g_strdup (dialog_name), g_object_ref (extension));
+
+	g_rec_mutex_unlock (&server->priv->lock);
+
+	return TRUE;
+}
diff --git a/libebackend/e-user-prompter-server.h b/libebackend/e-user-prompter-server.h
new file mode 100644
index 0000000..e1cca20
--- /dev/null
+++ b/libebackend/e-user-prompter-server.h
@@ -0,0 +1,100 @@
+/*
+ * e-user-prompter-server.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_USER_PROMPTER_SERVER_H
+#define E_USER_PROMPTER_SERVER_H
+
+#include <libebackend/libebackend.h>
+#include <libedataserver/libedataserver.h>
+
+/* Standard GObject macros */
+#define E_TYPE_USER_PROMPTER_SERVER \
+	(e_user_prompter_server_get_type ())
+#define E_USER_PROMPTER_SERVER(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_USER_PROMPTER_SERVER, EUserPrompterServer))
+#define E_USER_PROMPTER_SERVER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_USER_PROMPTER_SERVER, EUserPrompterServerClass))
+#define E_IS_USER_PROMPTER_SERVER(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_USER_PROMPTER_SERVER))
+#define E_IS_USER_PROMPTER_SERVER_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_USER_PROMPTER_SERVER))
+#define E_USER_PROMPTER_SERVER_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_USER_PROMPTER_SERVER, EUserPrompterServerClass))
+
+/**
+ * E_USER_PROMPTER_SERVER_OBJECT_PATH:
+ *
+ * D-Bus object path of the user prompter.
+ *
+ * Since: 3.8
+ **/
+#define E_USER_PROMPTER_SERVER_OBJECT_PATH \
+	"/org/gnome/evolution/dataserver/UserPrompter"
+
+G_BEGIN_DECLS
+
+typedef struct _EUserPrompterServer EUserPrompterServer;
+typedef struct _EUserPrompterServerClass EUserPrompterServerClass;
+typedef struct _EUserPrompterServerPrivate EUserPrompterServerPrivate;
+
+/**
+ * EUserPrompterServer:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.8
+ **/
+struct _EUserPrompterServer {
+	EDBusServer parent;
+	EUserPrompterServerPrivate *priv;
+};
+
+struct _EUserPrompterServerClass {
+	EDBusServerClass parent_class;
+
+	/* signals */
+	void (* prompt) (EUserPrompterServer *server,
+			 gint id,
+			 const gchar *type,
+			 const gchar *title,
+			 const gchar *primary_text,
+			 const gchar *secondary_text,
+			 gboolean use_markup,
+			 const GSList *button_captions);
+};
+
+GType		e_user_prompter_server_get_type	(void);
+EDBusServer *	e_user_prompter_server_new	(void);
+void		e_user_prompter_server_response	(EUserPrompterServer *server,
+						 gint prompt_id,
+						 gint button_index,
+						 const ENamedParameters *extension_values);
+
+gboolean	e_user_prompter_server_register	(EUserPrompterServer *server,
+						 EExtension *extension,
+						 const gchar *dialog_name);
+
+G_END_DECLS
+
+#endif /* E_USER_PROMPTER_SERVER_H */
diff --git a/libebackend/e-user-prompter.c b/libebackend/e-user-prompter.c
new file mode 100644
index 0000000..fea0e74
--- /dev/null
+++ b/libebackend/e-user-prompter.c
@@ -0,0 +1,635 @@
+/*
+ * e-user-prompter.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/**
+ * SECTION: e-user-prompter
+ * @include: libebackend/libebackend.h
+ * @short_description: Manages user prompts over DBus
+ *
+ * Use this to initiate a user prompt from an #EBackend descendant.
+ **/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <libedataserver/libedataserver.h>
+
+#include "e-dbus-user-prompter.h"
+#include "e-user-prompter.h"
+
+#define E_USER_PROMPTER_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_USER_PROMPTER, EUserPrompterPrivate))
+
+struct _EUserPrompterPrivate {
+	gint dummy; /* not used */
+};
+
+G_DEFINE_TYPE (EUserPrompter, e_user_prompter, G_TYPE_OBJECT)
+
+static void
+e_user_prompter_class_init (EUserPrompterClass *class)
+{
+	g_type_class_add_private (class, sizeof (EUserPrompterPrivate));
+}
+
+static void
+e_user_prompter_init (EUserPrompter *prompter)
+{
+	prompter->priv = E_USER_PROMPTER_GET_PRIVATE (prompter);
+}
+
+/**
+ * e_user_prompter_new:
+ *
+ * Creates a new instance of #EUserPrompter.
+ *
+ * Returns: a new instance of #EUserPrompter
+ *
+ * Since: 3.8
+ **/
+EUserPrompter *
+e_user_prompter_new (void)
+{
+	return g_object_new (E_TYPE_USER_PROMPTER, NULL);
+}
+
+typedef struct _PrompterAsyncData {
+	/* Prompt data */
+	gchar *type;
+	gchar *title;
+	gchar *primary_text;
+	gchar *secondary_text;
+	gboolean use_markup;
+	GSList *button_captions;
+
+	/* ExtensionPrompt data */
+	gchar *dialog_name;
+	ENamedParameters *in_parameters;
+	ENamedParameters *out_values;
+
+	/* common data */
+	gint response_button;
+
+	/* callbacks */
+	gchar *response_signal_name;
+	GCallback response_callback;
+	gboolean (* invoke) (EDBusUserPrompter *dbus_prompter,
+			     struct _PrompterAsyncData *async_data,
+			     GCancellable *cancellable,
+			     GError **error);
+	
+	/* Internal data */
+	gint prompt_id;
+	GMainLoop *main_loop; /* not owned by the structure */
+} PrompterAsyncData;
+
+static void
+prompter_async_data_free (PrompterAsyncData *async_data)
+{
+	if (!async_data)
+		return;
+
+	g_free (async_data->type);
+	g_free (async_data->title);
+	g_free (async_data->primary_text);
+	g_free (async_data->secondary_text);
+	g_slist_free_full (async_data->button_captions, g_free);
+
+	g_free (async_data->dialog_name);
+	e_named_parameters_free (async_data->in_parameters);
+	e_named_parameters_free (async_data->out_values);
+
+	g_free (async_data->response_signal_name);
+
+	g_free (async_data);
+}
+
+static void
+user_prompter_response_cb (EDBusUserPrompter *dbus_prompter,
+			   gint prompt_id,
+			   gint response_button,
+			   PrompterAsyncData *async_data)
+{
+	g_return_if_fail (async_data != NULL);
+
+	if (async_data->prompt_id == prompt_id) {
+		async_data->response_button = response_button;
+		g_main_loop_quit (async_data->main_loop);
+	}
+}
+
+static gboolean
+user_prompter_prompt_invoke (EDBusUserPrompter *dbus_prompter,
+			     struct _PrompterAsyncData *async_data,
+			     GCancellable *cancellable,
+			     GError **error)
+{
+	GPtrArray *captions;
+	GSList *iter;
+	gboolean success;
+
+	g_return_val_if_fail (dbus_prompter != NULL, FALSE);
+	g_return_val_if_fail (async_data != NULL, FALSE);
+
+	captions = g_ptr_array_new ();
+	for (iter = async_data->button_captions; iter; iter = g_slist_next (iter)) {
+		gchar *caption = iter->data;
+
+		g_ptr_array_add (captions, caption ? caption : (gchar *) "");
+	}
+
+	/* NULL-terminated array */
+	g_ptr_array_add (captions, NULL);
+
+	success = e_dbus_user_prompter_call_prompt_sync (dbus_prompter,
+		async_data->type ? async_data->type : "",
+		async_data->title ? async_data->title : "",
+		async_data->primary_text ? async_data->primary_text : "",
+		async_data->secondary_text ? async_data->secondary_text : "",
+		async_data->use_markup,
+		(const gchar *const *) captions->pdata,
+		&async_data->prompt_id,
+		cancellable,
+		error);
+
+	g_ptr_array_free (captions, TRUE);
+
+	return success;
+}
+
+static void
+user_prompter_extension_response_cb (EDBusUserPrompter *dbus_prompter,
+				     gint prompt_id,
+				     gint response_button,
+				     const gchar * const *arg_values,
+				     PrompterAsyncData *async_data)
+{
+	g_return_if_fail (async_data != NULL);
+
+	if (async_data->prompt_id == prompt_id) {
+		async_data->response_button = response_button;
+		if (arg_values)
+			async_data->out_values = e_named_parameters_new_strv (arg_values);
+		g_main_loop_quit (async_data->main_loop);
+	}
+}
+
+static gboolean
+user_prompter_extension_prompt_invoke (EDBusUserPrompter *dbus_prompter,
+				       struct _PrompterAsyncData *async_data,
+				       GCancellable *cancellable,
+				       GError **error)
+{
+	gboolean success;
+	GPtrArray *params;
+	gint ii;
+
+	g_return_val_if_fail (dbus_prompter != NULL, FALSE);
+	g_return_val_if_fail (async_data != NULL, FALSE);
+
+	params = g_ptr_array_new ();
+	for (ii = 0; async_data->in_parameters && ii < async_data->in_parameters->len; ii++) {
+		gchar *param = async_data->in_parameters->pdata[ii];
+
+		if (param)
+			g_ptr_array_add (params, param);
+	}
+
+	/* NULL-terminated array */
+	g_ptr_array_add (params, NULL);
+
+	success = e_dbus_user_prompter_call_extension_prompt_sync (dbus_prompter,
+		async_data->dialog_name,
+		(const gchar *const *) params->pdata,
+		&async_data->prompt_id,
+		cancellable,
+		error);
+
+	g_ptr_array_free (params, TRUE);
+
+	return success;
+}
+
+static void
+user_prompter_prompt_thread (GSimpleAsyncResult *simple,
+			     GObject *object,
+			     GCancellable *cancellable)
+{
+	EDBusUserPrompter *dbus_prompter;
+	PrompterAsyncData *async_data;
+	GMainContext *main_context;
+	GError *local_error = NULL;
+	gulong handler_id;
+
+	g_return_if_fail (E_IS_USER_PROMPTER (object));
+
+	async_data = g_simple_async_result_get_op_res_gpointer (simple);
+	g_return_if_fail (async_data != NULL);
+	g_return_if_fail (async_data->response_signal_name != NULL);
+	g_return_if_fail (async_data->response_callback != NULL);
+	g_return_if_fail (async_data->invoke != NULL);
+
+	main_context = g_main_context_new ();
+	/* this way the Response signal is delivered here, not to the main thread's context,
+	   which can be blocked by the e_user_prompter_prompt_sync() call anyway */
+	g_main_context_push_thread_default (main_context);
+
+	dbus_prompter = e_dbus_user_prompter_proxy_new_for_bus_sync (
+		G_BUS_TYPE_SESSION,
+		G_DBUS_PROXY_FLAGS_NONE,
+		USER_PROMPTER_DBUS_SERVICE_NAME,
+		"/org/gnome/evolution/dataserver/UserPrompter",
+		cancellable,
+		&local_error);
+
+	if (!dbus_prompter) {
+		g_main_context_pop_thread_default (main_context);
+		g_main_context_unref (main_context);
+		g_dbus_error_strip_remote_error (local_error);
+		g_simple_async_result_take_error (simple, local_error);
+		return;
+	}
+
+	handler_id = g_signal_connect (dbus_prompter, async_data->response_signal_name,
+		async_data->response_callback, async_data);
+
+	if (!async_data->invoke (dbus_prompter, async_data, cancellable, &local_error)) {
+		g_main_context_pop_thread_default (main_context);
+		g_main_context_unref (main_context);
+		g_dbus_error_strip_remote_error (local_error);
+		g_simple_async_result_take_error (simple, local_error);
+		g_signal_handler_disconnect (dbus_prompter, handler_id);
+		g_object_unref (dbus_prompter);
+		return;
+	}
+
+	async_data->main_loop = g_main_loop_new (main_context, FALSE);
+
+	g_main_loop_run (async_data->main_loop);
+
+	g_main_loop_unref (async_data->main_loop);
+	async_data->main_loop = NULL;
+
+	g_signal_handler_disconnect (dbus_prompter, handler_id);
+	g_object_unref (dbus_prompter);
+
+	g_main_context_pop_thread_default (main_context);
+	g_main_context_unref (main_context);
+}
+
+/**
+ * e_user_prompter_prompt:
+ * @prompter: an #EUserPrompter
+ * @type: (allow-none): type of the prompt; can be %NULL
+ * @title: (allow-none): window title of the prompt; can be %NULL
+ * @primary_text: (allow-none): primary text of the prompt; can be %NULL
+ * @secondary_text: (allow-none): secondary text of the prompt; can be %NULL
+ * @use_markup: whether both texts are with markup
+ * @button_captions: (allow-none): captions of buttons to use in the message; can be %NULL
+ * @cancellable: (allow-none): optional #GCancellable object, or %NULL
+ * @callback: (scope async): a #GAsyncReadyCallback to call when the request
+ *            is satisfied
+ * @user_data: (closure): data to pass to the callback function
+ *
+ * Asynchronously prompt a user for a decision.
+ *
+ * The @type can be one of "info", "warning", "question" or "error", to include
+ * an icon in the message prompt; anything else results in no icon in the message.
+ *
+ * If @button_captions is %NULL or empty list, then only one button is shown in
+ * the prompt, a "Dismiss" button.
+ *
+ * When the operation is finished, @callback will be called.  You can then
+ * call e_user_prompter_prompt_finish() to get the result of the operation.
+ *
+ * Since: 3.8
+ **/
+void
+e_user_prompter_prompt (EUserPrompter *prompter,
+			const gchar *type,
+			const gchar *title,
+			const gchar *primary_text,
+			const gchar *secondary_text,
+			gboolean use_markup,
+			const GSList *button_captions,
+			GCancellable *cancellable,
+			GAsyncReadyCallback callback,
+			gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	PrompterAsyncData *async_data;
+	GSList *iter;
+
+	g_return_if_fail (E_IS_USER_PROMPTER (prompter));
+	g_return_if_fail (callback != NULL);
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (prompter), callback, user_data,
+		e_user_prompter_prompt);
+
+	async_data = g_new0 (PrompterAsyncData, 1);
+	async_data->type = g_strdup (type);
+	async_data->title = g_strdup (title);
+	async_data->primary_text = g_strdup (primary_text);
+	async_data->secondary_text = g_strdup (secondary_text);
+	async_data->use_markup = use_markup;
+	async_data->button_captions = g_slist_copy ((GSList *) button_captions);
+	async_data->prompt_id = -1;
+	async_data->response_button = -1;
+
+	async_data->response_signal_name = g_strdup ("response");
+	async_data->response_callback = G_CALLBACK (user_prompter_response_cb);
+	async_data->invoke = user_prompter_prompt_invoke;
+
+	for (iter = async_data->button_captions; iter; iter = g_slist_next (iter)) {
+		iter->data = g_strdup (iter->data);
+	}
+
+	g_simple_async_result_set_op_res_gpointer (simple, async_data, (GDestroyNotify) prompter_async_data_free);
+	g_simple_async_result_run_in_thread (simple, user_prompter_prompt_thread, G_PRIORITY_DEFAULT, cancellable);
+
+	g_object_unref (simple);
+}
+
+/**
+ * e_user_prompter_prompt_finish:
+ * @prompter: an #EUserPrompter
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with e_user_prompter_prompt().
+ *
+ * If an error occurred, the function sets @error and returns -1.
+ *
+ * Returns: 0-based index of a button being used by a user as a response,
+ *   corresponding to 'button_captions' from e_user_prompter_prompt() call.
+ *
+ * Since: 3.8
+ **/
+gint
+e_user_prompter_prompt_finish (EUserPrompter *prompter,
+			       GAsyncResult *result,
+			       GError **error)
+{
+	GSimpleAsyncResult *simple;
+	PrompterAsyncData *async_data;
+
+	g_return_val_if_fail (E_IS_USER_PROMPTER (prompter), -1);
+	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (prompter), e_user_prompter_prompt), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+	async_data = g_simple_async_result_get_op_res_gpointer (simple);
+
+	if (g_simple_async_result_propagate_error (simple, error))
+		return -1;
+
+	g_return_val_if_fail (async_data != NULL, -1);
+
+	return async_data->response_button;
+}
+
+/**
+ * e_user_prompter_prompt_sync:
+ * @prompter: an #EUserPrompter
+ * @type: (allow-none): type of the prompt; can be %NULL
+ * @title: (allow-none): window title of the prompt; can be %NULL
+ * @primary_text: (allow-none): primary text of the prompt; can be %NULL
+ * @secondary_text: (allow-none): secondary text of the prompt; can be %NULL
+ * @use_markup: whether both texts are with markup
+ * @button_captions: (allow-none): captions of buttons to use in the message; can be %NULL
+ * @cancellable: (allow-none): optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Prompts a user for a decision.
+ *
+ * The @type can be one of "info", "warning", "question" or "error", to include
+ * an icon in the message prompt; anything else results in no icon in the message.
+ *
+ * If @button_captions is %NULL or empty list, then only one button is shown in
+ * the prompt, a "Dismiss" button.
+ *
+ * If an error occurred, the function sets @error and returns -1.
+ *
+ * Returns: 0-based index of a button being used by a user as a response,
+ *   corresponding to @button_captions list.
+ *
+ * Since: 3.8
+ **/
+gint
+e_user_prompter_prompt_sync (EUserPrompter *prompter,
+			     const gchar *type,
+			     const gchar *title,
+			     const gchar *primary_text,
+			     const gchar *secondary_text,
+			     gboolean use_markup,
+			     const GSList *button_captions,
+			     GCancellable *cancellable,
+			     GError **error)
+{
+	EAsyncClosure *closure;
+	GAsyncResult *result;
+	gint response_button;
+
+	g_return_val_if_fail (E_IS_USER_PROMPTER (prompter), -1);
+
+	closure = e_async_closure_new ();
+
+	e_user_prompter_prompt (prompter, type, title, primary_text, secondary_text, use_markup, button_captions,
+		cancellable, e_async_closure_callback, closure);
+
+	result = e_async_closure_wait (closure);
+
+	response_button = e_user_prompter_prompt_finish (prompter, result, error);
+
+	e_async_closure_free (closure);
+
+	return response_button;
+}
+
+/**
+ * e_user_prompter_extension_prompt:
+ * @prompter: an #EUserPrompter
+ * @dialog_name: name of a dialog to invoke
+ * @in_parameters: (allow-none): optional parameters to pass to extension; can be %NULL
+ * @cancellable: (allow-none): optional #GCancellable object, or %NULL
+ * @callback: (scope async): a #GAsyncReadyCallback to call when the request
+ *            is satisfied
+ * @user_data: (closure): data to pass to the callback function
+ *
+ * Asynchronously prompt a user for a decision on an extension-provided dialog.
+ * The caller usually provides an extension for #EUserPrompterServer, a descendant
+ * of #EUserPrompterServerExtension, which registers itself as a dialog provider.
+ * The extension defines @dialog_name, same as meaning of @in_parameters;
+ * only the extension and the caller know about meaning of these.
+ *
+ * When the operation is finished, @callback will be called.  You can then
+ * call e_user_prompter_extension_prompt_finish() to get the result of the operation.
+ * If there is no extension providing given dialog name, the operation finishes with
+ * a G_IO_ERROR, G_IO_ERROR_NOT_FOUND #GError.
+ *
+ * Since: 3.8
+ **/
+void
+e_user_prompter_extension_prompt (EUserPrompter *prompter,
+				  const gchar *dialog_name,
+				  const ENamedParameters *in_parameters,
+				  GCancellable *cancellable,
+				  GAsyncReadyCallback callback,
+				  gpointer user_data)
+{
+	GSimpleAsyncResult *simple;
+	PrompterAsyncData *async_data;
+
+	g_return_if_fail (E_IS_USER_PROMPTER (prompter));
+	g_return_if_fail (dialog_name != NULL);
+	g_return_if_fail (callback != NULL);
+
+	simple = g_simple_async_result_new (
+		G_OBJECT (prompter), callback, user_data,
+		e_user_prompter_extension_prompt);
+
+	async_data = g_new0 (PrompterAsyncData, 1);
+	async_data->dialog_name = g_strdup (dialog_name);
+	if (in_parameters) {
+		async_data->in_parameters = e_named_parameters_new ();
+		e_named_parameters_assign (async_data->in_parameters, in_parameters);
+	} else {
+		async_data->in_parameters = NULL;
+	}
+
+	async_data->prompt_id = -1;
+	async_data->response_button = -1;
+	async_data->out_values = NULL;
+
+	async_data->response_signal_name = g_strdup ("extension-response");
+	async_data->response_callback = G_CALLBACK (user_prompter_extension_response_cb);
+	async_data->invoke = user_prompter_extension_prompt_invoke;
+
+	g_simple_async_result_set_op_res_gpointer (simple, async_data, (GDestroyNotify) prompter_async_data_free);
+	g_simple_async_result_run_in_thread (simple, user_prompter_prompt_thread, G_PRIORITY_DEFAULT, cancellable);
+
+	g_object_unref (simple);
+}
+
+/**
+ * e_user_prompter_extension_prompt_finish:
+ * @prompter: an #EUserPrompter
+ * @result: a #GAsyncResult
+ * @out_values: (allow-none): Where to store values from the extension, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with e_user_prompter_extension_prompt().
+ * Caller can provide @out_values to get additional values provided by the extension.
+ * In case the caller is not interested in additional values, it can pass %NULL @out_values.
+ * The @out_values will be cleared first, then any values will be added there.
+ * Only the caller and the extension know about meaning of the result code and
+ * additional values.
+ *
+ * If an error occurred, the function sets @error and returns -1. If there is
+ * no extension providing given dialog name, the operation finishes with
+ * a G_IO_ERROR, G_IO_ERROR_NOT_FOUND @error.
+ *
+ * Returns: Result code of the prompt, as defined by the extension, or -1 on error.
+ *
+ * Since: 3.8
+ **/
+gint
+e_user_prompter_extension_prompt_finish (EUserPrompter *prompter,
+					 GAsyncResult *result,
+					 ENamedParameters *out_values,
+					 GError **error)
+{
+	GSimpleAsyncResult *simple;
+	PrompterAsyncData *async_data;
+
+	g_return_val_if_fail (E_IS_USER_PROMPTER (prompter), -1);
+	g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (prompter), e_user_prompter_extension_prompt), FALSE);
+
+	simple = G_SIMPLE_ASYNC_RESULT (result);
+	async_data = g_simple_async_result_get_op_res_gpointer (simple);
+
+	if (g_simple_async_result_propagate_error (simple, error))
+		return -1;
+
+	g_return_val_if_fail (async_data != NULL, -1);
+
+	if (out_values)
+		e_named_parameters_assign (out_values, async_data->out_values);
+
+	return async_data->response_button;
+}
+
+/**
+ * e_user_prompter_extension_prompt:
+ * @prompter: an #EUserPrompter
+ * @dialog_name: name of a dialog to invoke
+ * @in_parameters: (allow-none): optional parameters to pass to extension; can be %NULL
+ * @out_values: (allow-none): Where to store values from the extension, or %NULL
+ * @cancellable: (allow-none): optional #GCancellable object, or %NULL
+ * @error: return location for a #GError, or %NULL
+ *
+ * Synchronously prompt a user for a decision on an extension-provided dialog.
+ * The caller usually provides an extension for #EUserPrompterServer, a descendant
+ * of #EUserPrompterServerExtension, which registers itself as a dialog provider.
+ * The extension defines @dialog_name, same as meaning of @in_parameters;
+ * only the extension and the caller know about meaning of these.
+ *
+ * Caller can provide @out_values to get additional values provided by the extension.
+ * In case the caller is not interested in additional values, it can pass %NULL @out_values.
+ * The @out_values will be cleared first, then any values will be added there.
+ * Only the caller and the extension know about meaning of the result code and
+ * additional values.
+ *
+ * If an error occurred, the function sets @error and returns -1. If there is
+ * no extension providing given dialog name, the operation finishes with
+ * a G_IO_ERROR, G_IO_ERROR_NOT_FOUND @error.
+ *
+ * Returns: Result code of the prompt, as defined by the extension, or -1 on error.
+ *
+ * Since: 3.8
+ **/
+gint
+e_user_prompter_extension_prompt_sync (EUserPrompter *prompter,
+				       const gchar *dialog_name,
+				       const ENamedParameters *in_parameters,
+				       ENamedParameters *out_values,
+				       GCancellable *cancellable,
+				       GError **error)
+{
+	EAsyncClosure *closure;
+	GAsyncResult *result;
+	gint response_button;
+
+	g_return_val_if_fail (E_IS_USER_PROMPTER (prompter), -1);
+	g_return_val_if_fail (dialog_name != NULL, -1);
+
+	closure = e_async_closure_new ();
+
+	e_user_prompter_extension_prompt (prompter, dialog_name, in_parameters,
+		cancellable, e_async_closure_callback, closure);
+
+	result = e_async_closure_wait (closure);
+
+	response_button = e_user_prompter_extension_prompt_finish (prompter, result, out_values, error);
+
+	e_async_closure_free (closure);
+
+	return response_button;
+}
diff --git a/libebackend/e-user-prompter.h b/libebackend/e-user-prompter.h
new file mode 100644
index 0000000..c595f79
--- /dev/null
+++ b/libebackend/e-user-prompter.h
@@ -0,0 +1,106 @@
+/*
+ * e-user-prompter.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#if !defined (__LIBEBACKEND_H_INSIDE__) && !defined (LIBEBACKEND_COMPILATION)
+#error "Only <libebackend/libebackend.h> should be included directly."
+#endif
+
+#ifndef E_USER_PROMPTER_H
+#define E_USER_PROMPTER_H
+
+#include <glib.h>
+#include <libebackend/libebackend.h>
+#include <libedataserver/libedataserver.h>
+
+/* Standard GObject macros */
+#define E_TYPE_USER_PROMPTER \
+	(e_user_prompter_get_type ())
+#define E_USER_PROMPTER(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_USER_PROMPTER, EUserPrompter))
+#define E_IS_USER_PROMPTER(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_USER_PROMPTER))
+#define E_USER_PROMPTER_GET_INTERFACE(obj) \
+	(G_TYPE_INSTANCE_GET_INTERFACE \
+	((obj), E_TYPE_USER_PROMPTER, EUserPrompterInterface))
+
+G_BEGIN_DECLS
+
+typedef struct _EUserPrompter EUserPrompter;
+typedef struct _EUserPrompterClass EUserPrompterClass;
+typedef struct _EUserPrompterPrivate EUserPrompterPrivate;
+
+/**
+ * EUserPrompter:
+ *
+ * Since: 3.8
+ **/
+struct _EUserPrompter {
+	GObject parent;
+	EUserPrompterPrivate *priv;
+};
+
+struct _EUserPrompterClass {
+	GObjectClass parent;
+};
+
+GType		e_user_prompter_get_type		(void);
+EUserPrompter *	e_user_prompter_new			(void);
+void		e_user_prompter_prompt			(EUserPrompter *prompter,
+							 const gchar *type,
+							 const gchar *title,
+							 const gchar *primary_text,
+							 const gchar *secondary_text,
+							 gboolean use_markup,
+							 const GSList *button_captions,
+							 GCancellable *cancellable,
+							 GAsyncReadyCallback callback,
+							 gpointer user_data);
+gint		e_user_prompter_prompt_finish		(EUserPrompter *prompter,
+							 GAsyncResult *result,
+							 GError **error);
+gint		e_user_prompter_prompt_sync		(EUserPrompter *prompter,
+							 const gchar *type,
+							 const gchar *title,
+							 const gchar *primary_text,
+							 const gchar *secondary_text,
+							 gboolean use_markup,
+							 const GSList *button_captions,
+							 GCancellable *cancellable,
+							 GError **error);
+void		e_user_prompter_extension_prompt	(EUserPrompter *prompter,
+							 const gchar *dialog_name,
+							 const ENamedParameters *in_parameters,
+							 GCancellable *cancellable,
+							 GAsyncReadyCallback callback,
+							 gpointer user_data);
+gint		e_user_prompter_extension_prompt_finish	(EUserPrompter *prompter,
+							 GAsyncResult *result,
+							 ENamedParameters *out_values,
+							 GError **error);
+gint		e_user_prompter_extension_prompt_sync	(EUserPrompter *prompter,
+							 const gchar *dialog_name,
+							 const ENamedParameters *in_parameters,
+							 ENamedParameters *out_values,
+							 GCancellable *cancellable,
+							 GError **error);
+
+G_END_DECLS
+
+#endif /* E_USER_PROMPTER_H */
diff --git a/libebackend/libebackend.h b/libebackend/libebackend.h
index 99947b1..b0b9d3d 100644
--- a/libebackend/libebackend.h
+++ b/libebackend/libebackend.h
@@ -43,6 +43,9 @@
 #include <libebackend/e-server-side-source.h>
 #include <libebackend/e-source-registry-server.h>
 #include <libebackend/e-sqlite3-vfs.h>
+#include <libebackend/e-user-prompter.h>
+#include <libebackend/e-user-prompter-server.h>
+#include <libebackend/e-user-prompter-server-extension.h>
 
 #undef __LIBEBACKEND_H_INSIDE__
 
diff --git a/libedataserver/e-data-server-util.c b/libedataserver/e-data-server-util.c
index 06508ca..cce8cad 100644
--- a/libedataserver/e-data-server-util.c
+++ b/libedataserver/e-data-server-util.c
@@ -1704,3 +1704,252 @@ e_data_server_util_get_dbus_call_timeout (void)
 {
 	return default_dbus_timeout;
 }
+
+/**
+ * e_named_parameters_new:
+ *
+ * Creates a new instance of an #ENamedParameters. This should be freed
+ * with e_named_parameters_free(), when no longer needed. Names are
+ * compared case insensitively.
+ *
+ * The structure is not thread safe, if the caller requires thread safety,
+ * then it should provide it on its own.
+ *
+ * Returns: newly allocated #ENamedParameters
+ *
+ * Since: 3.8
+ **/
+ENamedParameters *
+e_named_parameters_new (void)
+{
+	return g_ptr_array_new_with_free_func (g_free);
+}
+
+/**
+ * e_named_parameters_new_strv:
+ * @strv: NULL-terminated string array to be used as a content of a newly
+ *     created #ENamedParameters
+ *
+ * Creates a new instance of an #ENamedParameters, with initial content
+ * being taken from @strv. This should be freed with e_named_parameters_free(),
+ * when no longer needed. Names are compared case insensitively.
+ *
+ * The structure is not thread safe, if the caller requires thread safety,
+ * then it should provide it on its own.
+ *
+ * Returns: newly allocated #ENamedParameters
+ *
+ * Since: 3.8
+ **/
+ENamedParameters *
+e_named_parameters_new_strv (const gchar * const *strv)
+{
+	ENamedParameters *parameters;
+	gint ii;
+
+	g_return_val_if_fail (strv != NULL, NULL);
+
+	parameters = e_named_parameters_new ();
+	for (ii = 0; strv[ii]; ii++) {
+		g_ptr_array_add (parameters, g_strdup (strv[ii]));
+	}
+
+	return parameters;
+}
+
+/**
+ * e_named_parameters_free:
+ * @parameters: an #ENamedParameters
+ *
+ * Frees an instance of #ENamedParameters, previously allocated
+ * with e_named_parameters_new(). Function does nothing, if
+ * @parameters is %NULL.
+ *
+ * Since: 3.8
+ **/
+void
+e_named_parameters_free (ENamedParameters *parameters)
+{
+	if (!parameters)
+		return;
+
+	g_ptr_array_free (parameters, TRUE);
+}
+
+/**
+ * e_named_parameters_clear:
+ * @parameters: an #ENamedParameters
+ *
+ * Removes all stored parameters from @parameters.
+ *
+ * Since: 3.8
+ **/
+void
+e_named_parameters_clear (ENamedParameters *parameters)
+{
+	g_return_if_fail (parameters != NULL);
+
+	if (parameters->len)
+		g_ptr_array_remove_range (parameters, 0, parameters->len);
+}
+
+/**
+ * e_named_parameters_assign:
+ * @parameters: an #ENamedParameters to assign values to
+ * @from: (allow-none): an #ENamedParameters to get values from, or %NULL
+ *
+ * Makes content of the @parameters the same as @from.
+ * Functions clears content of @parameters if @from is %NULL.
+ *
+ * Since: 3.8
+ **/
+void
+e_named_parameters_assign (ENamedParameters *parameters,
+			   const ENamedParameters *from)
+{
+	g_return_if_fail (parameters != NULL);
+
+	e_named_parameters_clear (parameters);
+
+	if (from) {
+		gint ii;
+
+		for (ii = 0; ii < from->len; ii++) {
+			g_ptr_array_add (parameters, g_strdup (from->pdata[ii]));
+		}
+	}
+}
+
+static gint
+get_parameter_index (const ENamedParameters *parameters,
+		     const gchar *name)
+{
+	gint ii, name_len;
+
+	g_return_val_if_fail (parameters != NULL, -1);
+	g_return_val_if_fail (name != NULL, -1);
+
+	name_len = strlen (name);
+
+	for (ii = 0; ii < parameters->len; ii++) {
+		const gchar *name_and_value = g_ptr_array_index (parameters, ii);
+
+		if (name_and_value && g_ascii_strncasecmp (name_and_value, name, name_len) == 0 &&
+		    name_and_value[name_len] == ':')
+			return ii;
+	}
+
+	return -1;
+}
+
+/**
+ * e_named_parameters_set:
+ * @parameters: an #ENamedParameters
+ * @name: name of a parameter to set
+ * @value: (allow-none): value to set, or %NULL to unset
+ *
+ * Sets parameter named @name to value @value. If @value is NULL,
+ * then the parameter is removed. @value can be an empty string.
+ *
+ * Note: There is a restriction on parameter names, it cannot be empty or
+ * contain a colon character (':'), otherwise it can be pretty much anything.
+ *
+ * Since: 3.8
+ **/
+void
+e_named_parameters_set (ENamedParameters *parameters,
+			const gchar *name,
+			const gchar *value)
+{
+	gint index;
+	gchar *name_and_value;
+
+	g_return_if_fail (parameters != NULL);
+	g_return_if_fail (name != NULL);
+	g_return_if_fail (strchr (name, ':') == NULL);
+	g_return_if_fail (*name != '\0');
+
+	index = get_parameter_index (parameters, name);
+	if (!value) {
+		if (index != -1)
+			g_ptr_array_remove_index (parameters, index);
+		return;
+	}
+
+	name_and_value = g_strconcat (name, ":", value, NULL);
+	if (index != -1) {
+		g_free (parameters->pdata[index]);
+		parameters->pdata[index] = name_and_value;
+	} else {
+		g_ptr_array_add (parameters, name_and_value);
+	}
+}
+
+/**
+ * e_named_parameters_get:
+ * @parameters: an #ENamedParameters
+ * @name: name of a parameter to get
+ *
+ * Returns current value of a parameter with name @name. If not such
+ * exists, then returns %NULL.
+ *
+ * Returns: value of a parameter named @name, or %NULL.
+ *
+ * Since: 3.8
+ **/
+const gchar *
+e_named_parameters_get (const ENamedParameters *parameters,
+			const gchar *name)
+{
+	gint index;
+	const gchar *name_and_value;
+
+	g_return_val_if_fail (parameters != NULL, NULL);
+	g_return_val_if_fail (name != NULL, NULL);
+
+	index = get_parameter_index (parameters, name);
+	if (index == -1)
+		return NULL;
+
+	name_and_value = g_ptr_array_index (parameters, index);
+
+	return name_and_value + strlen (name) + 1;
+}
+
+/**
+ * e_named_parameters_test:
+ * @parameters: an #ENamedParameters
+ * @name: name of a parameter to test
+ * @value: value to test
+ * @case_sensitively: whether to compare case sensitively
+ *
+ * Compares current value of parameter named @name with given @value
+ * and returns whether they are equal, either case sensitively or
+ * insensitively, based on @case_sensitively argument. Function
+ * returns %FALSE, if no such parameter exists.
+ *
+ * Returns: Whether parameter of given name has stored value of given value.
+ *
+ * Since: 3.8
+ **/
+gboolean
+e_named_parameters_test (const ENamedParameters *parameters,
+			 const gchar *name,
+			 const gchar *value,
+			 gboolean case_sensitively)
+{
+	const gchar *stored_value;
+
+	g_return_val_if_fail (parameters != NULL, FALSE);
+	g_return_val_if_fail (name != NULL, FALSE);
+	g_return_val_if_fail (value != NULL, FALSE);
+
+	stored_value = e_named_parameters_get (parameters, name);
+	if (!stored_value)
+		return FALSE;
+
+	if (case_sensitively)
+		return strcmp (stored_value, value) == 0;
+
+	return g_ascii_strcasecmp (stored_value, value) == 0;
+}
diff --git a/libedataserver/e-data-server-util.h b/libedataserver/e-data-server-util.h
index ab33083..5fdca11 100644
--- a/libedataserver/e-data-server-util.h
+++ b/libedataserver/e-data-server-util.h
@@ -134,6 +134,25 @@ gint		e_data_server_util_get_dbus_call_timeout
 void		e_data_server_util_set_dbus_call_timeout
 						(gint timeout_msec);
 
+/* utility functions for easier processing of named parameters */
+typedef GPtrArray ENamedParameters;
+
+ENamedParameters *	e_named_parameters_new		(void);
+ENamedParameters *	e_named_parameters_new_strv	(const gchar * const *strv);
+void			e_named_parameters_free		(ENamedParameters *parameters);
+void			e_named_parameters_clear	(ENamedParameters *parameters);
+void			e_named_parameters_assign	(ENamedParameters *parameters,
+							 const ENamedParameters *from);
+void			e_named_parameters_set		(ENamedParameters *parameters,
+							 const gchar *name,
+							 const gchar *value);
+const gchar *		e_named_parameters_get		(const ENamedParameters *parameters,
+							 const gchar *name);
+gboolean		e_named_parameters_test		(const ENamedParameters *parameters,
+							 const gchar *name,
+							 const gchar *value,
+							 gboolean case_sensitively);
+
 G_END_DECLS
 
 #endif /* E_DATA_SERVER_UTIL_H */
diff --git a/libedataserver/e-marshal.list b/libedataserver/e-marshal.list
index 4abc0be..2739b41 100644
--- a/libedataserver/e-marshal.list
+++ b/libedataserver/e-marshal.list
@@ -1 +1,2 @@
 NONE:OBJECT,BOXED
+NONE:INT,STRING,STRING,STRING,STRING,BOOLEAN,POINTER
diff --git a/modules/Makefile.am b/modules/Makefile.am
index 1aeca75..21e7252 100644
--- a/modules/Makefile.am
+++ b/modules/Makefile.am
@@ -7,6 +7,7 @@ endif
 SUBDIRS = \
 	cache-reaper \
 	google-backend \
+	trust-prompt \
 	yahoo-backend \
 	$(GNOME_ONLINE_ACCOUNTS_DIR) \
 	$(NULL)
diff --git a/modules/trust-prompt/Makefile.am b/modules/trust-prompt/Makefile.am
new file mode 100644
index 0000000..1dfed75
--- /dev/null
+++ b/modules/trust-prompt/Makefile.am
@@ -0,0 +1,32 @@
+NULL =
+
+module_LTLIBRARIES = module-trust-prompt.la
+
+module_trust_prompt_la_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-I$(top_srcdir) \
+	-DG_LOG_DOMAIN=\"module-trust-prompt\" \
+	$(E_BACKEND_CFLAGS) \
+	$(E_DATA_SERVER_CFLAGS) \
+	$(CAMEL_CFLAGS) \
+	$(GNOME_PLATFORM_CFLAGS) \
+	$(NULL)
+
+module_trust_prompt_la_SOURCES = \
+	module-trust-prompt.c \
+	$(NULL)
+
+module_trust_prompt_la_LIBADD = \
+	$(top_builddir)/libebackend/libebackend-1.2.la \
+	$(top_builddir)/libedataserver/libedataserver-1.2.la \
+	$(E_BACKEND_LIBS) \
+	$(E_DATA_SERVER_LIBS) \
+	$(CAMEL_LIBS) \
+	$(GNOME_PLATFORM_LIBS) \
+	$(NULL)
+
+module_trust_prompt_la_LDFLAGS = \
+	-module -avoid-version $(NO_UNDEFINED) \
+	$(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/modules/trust-prompt/module-trust-prompt.c b/modules/trust-prompt/module-trust-prompt.c
new file mode 100644
index 0000000..a818424
--- /dev/null
+++ b/modules/trust-prompt/module-trust-prompt.c
@@ -0,0 +1,406 @@
+/*
+ * module-trust-prompt.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <gtk/gtk.h>
+#include <glib/gi18n-lib.h>
+
+#include <cert.h>
+
+#include <libebackend/libebackend.h>
+
+/* Standard GObject macros */
+#define E_TYPE_TRUST_PROMPT (e_trust_prompt_get_type ())
+#define E_TRUST_PROMPT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_TRUST_PROMPT, ETrustPrompt))
+
+typedef struct _ETrustPrompt ETrustPrompt;
+typedef struct _ETrustPromptClass ETrustPromptClass;
+
+struct _ETrustPrompt {
+	EUserPrompterServerExtension parent;
+
+	gboolean nss_initialized;
+};
+
+struct _ETrustPromptClass {
+	EUserPrompterServerExtensionClass parent_class;
+};
+
+/* Module Entry Points */
+void e_module_load (GTypeModule *type_module);
+void e_module_unload (GTypeModule *type_module);
+
+/* Forward Declarations */
+GType e_trust_prompt_get_type (void);
+
+G_DEFINE_DYNAMIC_TYPE (ETrustPrompt, e_trust_prompt, E_TYPE_USER_PROMPTER_SERVER_EXTENSION)
+
+static gboolean trust_prompt_show_trust_prompt (EUserPrompterServerExtension *extension,
+						gint prompt_id,
+						const ENamedParameters *parameters);
+#define TRUST_PROMPT_DIALOG "ETrustPrompt::trust-prompt"
+
+static void
+trust_prompt_register_dialogs (EExtension *extension,
+			       EUserPrompterServer *server)
+{
+	ETrustPrompt *trust_prompt = E_TRUST_PROMPT (extension);
+
+	if (!trust_prompt->nss_initialized) {
+		trust_prompt->nss_initialized = TRUE;
+
+		/* Use camel_init() to initialise NSS consistently... */
+		camel_init (e_get_user_data_dir (), TRUE);
+	}
+
+	e_user_prompter_server_register (server, extension, TRUST_PROMPT_DIALOG);
+}
+
+static gboolean
+trust_prompt_prompt (EUserPrompterServerExtension *extension,
+		     gint prompt_id,
+		     const gchar *dialog_name,
+		     const ENamedParameters *parameters)
+{
+	if (g_strcmp0 (dialog_name, TRUST_PROMPT_DIALOG) == 0)
+		return trust_prompt_show_trust_prompt (extension, prompt_id, parameters);
+
+	return FALSE;
+}
+
+static void
+trust_prompt_finalize (GObject *object)
+{
+	ETrustPrompt *trust_prompt = E_TRUST_PROMPT (object);
+
+	if (trust_prompt->nss_initialized)
+		camel_shutdown ();
+
+	/* Chain up to parent's finalize() method. */
+	G_OBJECT_CLASS (e_trust_prompt_parent_class)->finalize (object);
+}
+
+static void
+e_trust_prompt_class_init (ETrustPromptClass *class)
+{
+	GObjectClass *object_class;
+	EUserPrompterServerExtensionClass *server_extension_class;
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = trust_prompt_finalize;
+
+	server_extension_class = E_USER_PROMPTER_SERVER_EXTENSION_CLASS (class);
+	server_extension_class->register_dialogs = trust_prompt_register_dialogs;
+	server_extension_class->prompt = trust_prompt_prompt;
+}
+
+static void
+e_trust_prompt_class_finalize (ETrustPromptClass *class)
+{
+}
+
+static void
+e_trust_prompt_init (ETrustPrompt *trust_prompt)
+{
+	trust_prompt->nss_initialized = FALSE;
+}
+
+G_MODULE_EXPORT void
+e_module_load (GTypeModule *type_module)
+{
+	e_trust_prompt_register_type (type_module);
+}
+
+G_MODULE_EXPORT void
+e_module_unload (GTypeModule *type_module)
+{
+}
+
+/* dialog definitions */
+
+/* ETrustPrompt::trust-prompt
+   The dialog expects these parameters:
+      "host" - host from which the certificate is received
+      "certificate" - a base64-encoded DER certificate, for which ask on trust
+      "certificate-errors" - a hexa-decimal integer (as string) corresponding to GTlsCertificateFlags
+
+   Result of the dialog is:
+      0 - reject
+      1 - accept permanently
+      2 - accept temporarily
+     -1 - user didn't choose any of the above
+
+   The dialog doesn't provide any additional values in the response.
+ */
+
+static gchar *
+cert_fingerprint (CERTCertificate *cert)
+{
+	GChecksum *checksum;
+	guint8 *digest;
+	gsize length;
+	guchar fingerprint[50], *f;
+	gint i;
+	const gchar tohex[16] = "0123456789abcdef";
+
+	length = g_checksum_type_get_length (G_CHECKSUM_MD5);
+	digest = g_alloca (length);
+
+	checksum = g_checksum_new (G_CHECKSUM_MD5);
+	g_checksum_update (checksum, cert->derCert.data, cert->derCert.len);
+	g_checksum_get_digest (checksum, digest, &length);
+	g_checksum_free (checksum);
+
+	for (i = 0,f = fingerprint; i < length; i++) {
+		guint c = digest[i];
+
+		*f++ = tohex[(c >> 4) & 0xf];
+		*f++ = tohex[c & 0xf];
+		*f++ = ':';
+	}
+
+	fingerprint[47] = 0;
+
+	return g_strdup ((gchar *) fingerprint);
+}
+
+static gchar *
+cert_errors_to_reason (GTlsCertificateFlags flags)
+{
+	struct _convert_table {
+		GTlsCertificateFlags flag;
+		const gchar *description;
+	} convert_table[] = {
+		{ G_TLS_CERTIFICATE_UNKNOWN_CA,
+		  N_("The signing certificate authority is not known.") },
+		{ G_TLS_CERTIFICATE_BAD_IDENTITY,
+		  N_("The certificate does not match the expected identity of the site that it was retrieved from.") },
+		{ G_TLS_CERTIFICATE_NOT_ACTIVATED,
+		  N_("The certificate's activation time is still in the future.") },
+		{ G_TLS_CERTIFICATE_EXPIRED,
+		  N_("The certificate has expired.") },
+		{ G_TLS_CERTIFICATE_REVOKED,
+		  N_("The certificate has been revoked according to the connection's certificate revocation list.") },
+		{ G_TLS_CERTIFICATE_INSECURE,
+		  N_("The certificate's algorithm is considered insecure.") }
+	};
+
+	GString *reason = g_string_new ("");
+	gint ii;
+
+	for (ii = 0; ii < G_N_ELEMENTS (convert_table); ii++) {
+		if ((flags & convert_table[ii].flag) != 0) {
+			if (reason->len > 0)
+				g_string_append (reason, "\n");
+
+			g_string_append (reason, _(convert_table[ii].description));
+		}
+	}
+
+	return g_string_free (reason, FALSE);
+}
+
+static void
+trust_prompt_add_info_line (GtkGrid *grid,
+			    const gchar *label_text,
+			    const gchar *value_text,
+			    gboolean ellipsize,
+			    gint *at_row)
+{
+	GtkWidget *widget;
+	PangoAttribute *attr;
+	PangoAttrList *bold;
+
+	g_return_if_fail (grid != NULL);
+	g_return_if_fail (label_text != NULL);
+	g_return_if_fail (at_row != NULL);
+
+	if (!value_text || !*value_text)
+		return;
+
+	bold = pango_attr_list_new ();
+	attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
+	pango_attr_list_insert (bold, attr);
+
+	widget = gtk_label_new (label_text);
+	gtk_misc_set_padding (GTK_MISC (widget), 12, 0);
+	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.0);
+
+	gtk_grid_attach (grid, widget, 1, *at_row, 1, 1);
+
+	widget = gtk_label_new (value_text);
+	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.0);
+	g_object_set (G_OBJECT (widget),
+		"hexpand", TRUE,
+		"halign", GTK_ALIGN_FILL,
+		"justify", GTK_JUSTIFY_LEFT,
+		"attributes", bold,
+		"selectable", TRUE,
+		"ellipsize", ellipsize ? PANGO_ELLIPSIZE_END : PANGO_ELLIPSIZE_NONE,
+		NULL);
+
+	gtk_grid_attach (grid, widget, 2, *at_row, 1, 1);
+
+	*at_row = (*at_row) + 1;
+
+	pango_attr_list_unref (bold);
+}
+
+#define TRUST_PROMP_ID_KEY "ETrustPrompt::prompt-id-key"
+
+static void
+trust_prompt_response_cb (GtkWidget *dialog,
+			  gint response,
+			  EUserPrompterServerExtension *extension)
+{
+	gint prompt_id;
+
+	if (response == GTK_RESPONSE_HELP) {
+		/* view certificate */
+		return;
+	}
+
+	prompt_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dialog), TRUST_PROMP_ID_KEY));
+	gtk_widget_destroy (dialog);
+
+	if (response == GTK_RESPONSE_REJECT)
+		response = 0;
+	else if (response == GTK_RESPONSE_ACCEPT)
+		response = 1;
+	else if (response == GTK_RESPONSE_YES)
+		response = 2;
+	else
+		response = -1;
+
+	e_user_prompter_server_extension_response (extension, prompt_id, response, NULL);
+}
+
+static gboolean
+trust_prompt_show_trust_prompt (EUserPrompterServerExtension *extension,
+				gint prompt_id,
+				const ENamedParameters *parameters)
+{
+	const gchar *host, *base64_cert, *cert_errs_str;
+	gchar *tmp, *reason, *issuer, *subject;
+	gint row = 0;
+	gint64 cert_errs;
+	GtkWidget *dialog, *widget;
+	GtkGrid *grid;
+	CERTCertDBHandle *certdb;
+	CERTCertificate *cert;
+	SECItem derCert;
+	gsize derCert_len = 0;
+
+	g_return_val_if_fail (extension != NULL, FALSE);
+	g_return_val_if_fail (parameters != NULL, FALSE);
+
+	host = e_named_parameters_get (parameters, "host");
+	base64_cert = e_named_parameters_get (parameters, "certificate");
+	cert_errs_str = e_named_parameters_get (parameters, "certificate-errors");
+
+	g_return_val_if_fail (host != NULL, FALSE);
+	g_return_val_if_fail (base64_cert != NULL, FALSE);
+	g_return_val_if_fail (cert_errs_str != NULL, FALSE);
+
+	derCert.type = siDERCertBuffer;
+	derCert.data = g_base64_decode (base64_cert, &derCert_len);
+	g_return_val_if_fail (derCert.data != NULL, FALSE);
+	derCert.len = derCert_len;
+
+	certdb = CERT_GetDefaultCertDB ();
+	cert = CERT_NewTempCertificate (certdb, &derCert, NULL, PR_FALSE, PR_TRUE);
+	g_return_val_if_fail (cert != NULL, FALSE);
+
+	cert_errs = g_ascii_strtoll (cert_errs_str, NULL, 16);
+
+	dialog = gtk_dialog_new_with_buttons (_("Certificate trust..."), NULL, 0,
+		_("_View Certificate"), GTK_RESPONSE_HELP,
+		_("_Reject"), GTK_RESPONSE_REJECT,
+		_("Accept _Temporarily"), GTK_RESPONSE_YES,
+		_("_Accept Permanently"), GTK_RESPONSE_ACCEPT,
+		NULL);
+
+	gtk_window_set_icon_name (GTK_WINDOW (dialog), "evolution");
+	gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_HELP, FALSE);
+	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_YES);
+
+	grid = g_object_new (GTK_TYPE_GRID,
+		"orientation", GTK_ORIENTATION_HORIZONTAL,
+		"row-homogeneous", FALSE,
+		"row-spacing", 2,
+		"column-homogeneous", FALSE,
+		"column-spacing", 6,
+		"hexpand", TRUE,
+		"halign", GTK_ALIGN_FILL,
+		"vexpand", TRUE,
+		"valign", GTK_ALIGN_FILL,
+		"border-width", 12,
+		NULL);
+
+	widget = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+	gtk_container_add (GTK_CONTAINER (widget), GTK_WIDGET (grid));
+
+	widget = gtk_image_new_from_stock (GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_DIALOG);
+	g_object_set (G_OBJECT (widget),
+		"vexpand", FALSE,
+		"valign", GTK_ALIGN_START,
+		"xpad", 6,
+		NULL);
+	gtk_grid_attach (grid, widget, 0, row, 1, 3);
+
+	reason = g_strconcat ("<b>", host, "</b>", NULL);
+	tmp = g_strdup_printf (_("SSL Certificate for '%s' is not trusted. Do you wish to accept it?\n\n"
+				    "Detailed information about the certificate:"), reason);
+	g_free (reason);
+	widget = gtk_label_new (NULL);
+	gtk_label_set_markup (GTK_LABEL (widget), tmp);
+	gtk_misc_set_alignment (GTK_MISC (widget), 0.0, 0.0);
+	g_free (tmp);
+
+	gtk_grid_attach (grid, widget, 1, row, 2, 1);
+	row++;
+
+	issuer = CERT_NameToAscii (&cert->issuer);
+	subject = CERT_NameToAscii (&cert->subject);
+	reason = cert_errors_to_reason ((GTlsCertificateFlags) cert_errs);
+	tmp = cert_fingerprint (cert);
+
+	trust_prompt_add_info_line (grid, _("Issuer:"), issuer, TRUE, &row);
+	trust_prompt_add_info_line (grid, _("Subject:"), subject, TRUE, &row);
+	trust_prompt_add_info_line (grid, _("Fingerprint:"), tmp, TRUE, &row);
+	trust_prompt_add_info_line (grid, _("Reason:"), reason, FALSE, &row);
+
+	PORT_Free (issuer);
+	PORT_Free (subject);
+	g_free (reason);
+	g_free (tmp);
+
+	g_object_set_data (G_OBJECT (dialog), TRUST_PROMP_ID_KEY, GINT_TO_POINTER (prompt_id));
+	g_signal_connect (dialog, "response", G_CALLBACK (trust_prompt_response_cb), extension);
+
+	gtk_widget_show_all (GTK_WIDGET (grid));
+	gtk_widget_show (dialog);
+
+	CERT_DestroyCertificate (cert);
+	g_free (derCert.data);
+
+	return TRUE;
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index be557f1..a66d380 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -205,6 +205,7 @@ libebackend/e-backend.c
 libebackend/e-collection-backend.c
 libebackend/e-server-side-source.c
 libebackend/e-source-registry-server.c
+libebackend/e-user-prompter-server.c
 libedataserver/e-categories.c
 libedataserver/e-client.c
 libedataserver/e-source-authenticator.c
@@ -229,7 +230,9 @@ libedataserverui/e-passwords-win32.c
 libedataserverui/e-source-selector-dialog.c
 modules/gnome-online-accounts/goaewsclient.c
 modules/google-backend/module-google-backend.c
+modules/trust-prompt/module-trust-prompt.c
 modules/yahoo-backend/module-yahoo-backend.c
 services/evolution-addressbook-factory/evolution-addressbook-factory.c
 services/evolution-calendar-factory/evolution-calendar-factory.c
+services/evolution-user-prompter/evolution-user-prompter.c
 tests/libedataserverui/evolution-source-viewer.c
diff --git a/private/Makefile.am b/private/Makefile.am
index ccf8b6b..b2138ca 100644
--- a/private/Makefile.am
+++ b/private/Makefile.am
@@ -28,6 +28,15 @@ $(GENERATED_DBUS_AUTHENTICATOR) : Makefile.am org.gnome.evolution.dataserver.Aut
 	$(top_srcdir)/private/org.gnome.evolution.dataserver.Authenticator.xml \
 	$(NULL)
 
+$(GENERATED_DBUS_USER_PROMPTER) : Makefile.am org.gnome.evolution.dataserver.UserPrompter.xml
+	$(AM_V_GEN) gdbus-codegen \
+	--interface-prefix org.gnome.evolution.dataserver. \
+	--c-namespace E_DBus \
+	--generate-c-code e-dbus-user-prompter \
+	--generate-docbook e-dbus-user-prompter \
+	$(top_srcdir)/private/org.gnome.evolution.dataserver.UserPrompter.xml \
+	$(NULL)
+
 GENERATED_DBUS_SOURCE = \
 	e-dbus-source.c \
 	e-dbus-source.h \
@@ -43,10 +52,16 @@ GENERATED_DBUS_AUTHENTICATOR = \
 	e-dbus-authenticator.h \
 	$(NULL)
 
+GENERATED_DBUS_USER_PROMPTER = \
+	e-dbus-user-prompter.c \
+	e-dbus-user-prompter.h \
+	$(NULL)
+
 BUILT_SOURCES = \
 	$(GENERATED_DBUS_SOURCE) \
 	$(GENERATED_DBUS_SOURCE_MANAGER) \
 	$(GENERATED_DBUS_AUTHENTICATOR) \
+	$(GENERATED_DBUS_USER_PROMPTER) \
 	$(NULL)
 
 noinst_LTLIBRARIES = libedbus-private.la
@@ -78,6 +93,7 @@ EXTRA_DIST = \
 	org.gnome.evolution.dataserver.Source.xml \
 	org.gnome.evolution.dataserver.SourceManager.xml \
 	org.gnome.evolution.dataserver.Authenticator.xml \
+	org.gnome.evolution.dataserver.UserPrompter.xml \
 	$(NULL)
 
 CLEANFILES = \
diff --git a/private/org.gnome.evolution.dataserver.UserPrompter.xml b/private/org.gnome.evolution.dataserver.UserPrompter.xml
new file mode 100644
index 0000000..84a96a4
--- /dev/null
+++ b/private/org.gnome.evolution.dataserver.UserPrompter.xml
@@ -0,0 +1,89 @@
+<!DOCTYPE node PUBLIC
+"-//freedesktop//DTD D-Bus Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd";>
+<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd";>
+
+<!--
+    org.gnome.evolution.dataserver.UserPrompter:
+    @short_description: UserPrompter interface
+
+    Interface for user prompts.
+-->
+<interface name="org.gnome.evolution.dataserver.UserPrompter">
+  <!--
+      Prompt:
+      @type: Type of the prompt, can be one of GtkMessageType enum values
+      @title: Window title
+      @primary_text: The primary text of the prompt
+      @secondary_text: The secondary text of the prompt
+      @use_markup: whether the text uses markup - this applies to both texts
+      @button_captions: Array of button captions, choices for a user
+      @id: Prompt identificator, used in 'Response' signal
+
+      Shows a prompt (#GtkMessageDialog) to a user of given @type with @primary_text
+      and @secondary_text, either as plain text or with markup, according to @use_markup.
+      The @button_captions can be an empty array, in which case only
+      one button will be shown, with "Dismiss" caption.
+  -->
+  <method name="Prompt">
+    <arg name="type" direction="in" type="s"/>
+    <arg name="title" direction="in" type="s"/>
+    <arg name="primary_text" direction="in" type="s"/>
+    <arg name="secondary_text" direction="in" type="s"/>
+    <arg name="use_markup" direction="in" type="b"/>
+    <arg name="button_captions" direction="in" type="as"/>
+    <arg name="id" direction="out" type="i"/>
+  </method>
+
+  <!--
+      Response:
+
+      Emitted when user responded to a Prompt.
+
+      @id: An identificator of the prompt, as returned by Prompt method
+      @response_button: Which button index was used to close the prompt
+
+      Index in the @response_button corresponds to 'button_captions' index
+      from the 'Prompt' call. If none button caption was gived, then 0 is returned.
+  -->
+  <signal name="Response">
+    <arg name="id" type="i"/>
+    <arg name="response_button" type="i"/>
+  </signal>
+
+  <!--
+      ExtensionPrompt:
+      @dialog_name: Dialog name, as defined by an extension, to show
+      @parameter: Optional parameters for the extension
+      @id: Prompt identificator, used in 'ExtensionResponse' signal
+
+      Shows a dialog provided by an extension to a user. Dialog names are
+      case sesitive. Extension can define some parameters, which are passed
+      to it within @parameters. Parameters content is not checked or otherwise
+      interpretted by the UserPrompter, all this is left to the extension itself.
+  -->
+  <method name="ExtensionPrompt">
+    <arg name="dialog_name" direction="in" type="s"/>
+    <arg name="parameters" direction="in" type="as"/>
+    <arg name="id" direction="out" type="i"/>
+  </method>
+
+  <!--
+      ExtensionResponse:
+
+      Emitted when user responded to an ExtensionPrompt.
+
+      @id: An identificator of the prompt, as returned by ExtensionPrompt method
+      @response: Generic response, as defined by the extension
+      @values: Additional values returned by the extension
+
+      Extension can return additional @values, which are not interpretted or
+      otherwise checked by the UserPrompter, all this is left to the extension
+      and its caller.
+  -->
+  <signal name="ExtensionResponse">
+    <arg name="id" type="i"/>
+    <arg name="response" type="i"/>
+    <arg name="values" type="as"/>
+  </signal>
+</interface>
diff --git a/services/Makefile.am b/services/Makefile.am
index 551a440..f3c8447 100644
--- a/services/Makefile.am
+++ b/services/Makefile.am
@@ -4,6 +4,7 @@ SUBDIRS = \
 	evolution-addressbook-factory \
 	evolution-calendar-factory \
 	evolution-source-registry \
+	evolution-user-prompter \
 	$(NULL)
 
 -include $(top_srcdir)/git.mk
diff --git a/services/evolution-user-prompter/Makefile.am b/services/evolution-user-prompter/Makefile.am
new file mode 100644
index 0000000..9064171
--- /dev/null
+++ b/services/evolution-user-prompter/Makefile.am
@@ -0,0 +1,36 @@
+NULL =
+
+service_in_files = org.gnome.evolution.dataserver.UserPrompter.service.in
+servicedir = $(datadir)/dbus-1/services
+service_DATA = $(service_in_files:.service.in=.service)
+ EVO_SUBST_SERVICE_RULE@
+
+CLEANFILES = $(service_DATA)
+EXTRA_DIST = $(service_in_files)
+
+libexec_PROGRAMS = evolution-user-prompter
+
+evolution_user_prompter_CPPFLAGS = \
+	$(AM_CPPFLAGS) \
+	-I$(top_srcdir) \
+	-I$(top_srcdir)/private \
+	-I$(top_builddir) \
+	-I$(top_builddir)/private \
+	-DG_LOG_DOMAIN=\"evolution-user-prompter\" \
+	-DLOCALEDIR=\"$(localedir)\" \
+	$(GNOME_PLATFORM_CFLAGS) \
+	$(E_DATA_SERVER_CFLAGS) \
+	$(NULL)
+
+evolution_user_prompter_SOURCES = \
+	evolution-user-prompter.c \
+	$(NULL)
+
+evolution_user_prompter_LDADD = \
+	$(top_builddir)/libebackend/libebackend-1.2.la \
+	$(top_builddir)/libedataserver/libedataserver-1.2.la \
+	$(GNOME_PLATFORM_LIBS) \
+	$(E_DATA_SERVER_LIBS) \
+	$(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/services/evolution-user-prompter/evolution-user-prompter.c b/services/evolution-user-prompter/evolution-user-prompter.c
new file mode 100644
index 0000000..375ced0
--- /dev/null
+++ b/services/evolution-user-prompter/evolution-user-prompter.c
@@ -0,0 +1,139 @@
+/*
+ * evolution-user-prompter.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <locale.h>
+#include <stdlib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include <libebackend/libebackend.h>
+
+#define E_USER_PROMPTER_ID_KEY "e-user-prompter-id"
+
+static void
+message_response_cb (GtkWidget *dialog,
+		     gint button,
+		     EUserPrompterServer *server)
+{
+	gint prompt_id;
+
+	prompt_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (dialog), E_USER_PROMPTER_ID_KEY));
+
+	gtk_widget_destroy (dialog);
+
+	g_return_if_fail (E_IS_USER_PROMPTER_SERVER (server));
+
+	e_user_prompter_server_response	(server, prompt_id, button, NULL);
+}
+
+static void
+prompt_cb (EUserPrompterServer *server,
+	   gint id,
+	   const gchar *type,
+	   const gchar *title,
+	   const gchar *primary_text,
+	   const gchar *secondary_text,
+	   gboolean use_markup,
+	   const GSList *button_captions)
+{
+	GtkMessageType ntype = GTK_MESSAGE_OTHER;
+	GtkWidget *message;
+	gint index;
+	const GSList *iter;
+
+	g_return_if_fail (server != NULL);
+
+	if (type) {
+		if (g_ascii_strcasecmp (type, "info") == 0)
+			ntype = GTK_MESSAGE_INFO;
+		else if (g_ascii_strcasecmp (type, "warning") == 0)
+			ntype = GTK_MESSAGE_WARNING;
+		else if (g_ascii_strcasecmp (type, "question") == 0)
+			ntype = GTK_MESSAGE_QUESTION;
+		else if (g_ascii_strcasecmp (type, "error") == 0)
+			ntype = GTK_MESSAGE_ERROR;
+	}
+
+	if (use_markup)
+		message = gtk_message_dialog_new_with_markup (NULL, 0, ntype, GTK_BUTTONS_NONE,
+			"%s", primary_text ? primary_text : "");
+	else
+		message = gtk_message_dialog_new (NULL, 0, ntype, GTK_BUTTONS_NONE,
+			"%s", primary_text ? primary_text : "");
+
+	/* To show dialog on a taskbar */
+	gtk_window_set_skip_taskbar_hint (GTK_WINDOW (message), FALSE);
+	gtk_window_set_title (GTK_WINDOW (message), title ? title : "");
+	gtk_window_set_icon_name (GTK_WINDOW (message), "evolution");
+
+	if (secondary_text && *secondary_text) {
+		if (use_markup)
+			gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (message),
+				"%s", secondary_text);
+		else
+			gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (message),
+				"%s", secondary_text);
+	}
+
+	g_object_set (message, "resizable", TRUE, NULL);
+
+	for (index = 0, iter = button_captions; iter; index++, iter = iter->next) {
+		gtk_dialog_add_button (GTK_DIALOG (message), iter->data, index);
+	}
+
+	if (index == 0)
+		gtk_dialog_add_button (GTK_DIALOG (message), _("_Dismiss"), index);
+
+	g_object_set_data (G_OBJECT (message), E_USER_PROMPTER_ID_KEY, GINT_TO_POINTER (id));
+
+	g_signal_connect (message, "response", G_CALLBACK (message_response_cb), server);
+
+	gtk_widget_show (message);
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+	EDBusServer *server;
+
+	setlocale (LC_ALL, "");
+	bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+	gtk_init_check (&argc, &argv);
+
+	e_gdbus_templates_init_main_thread ();
+
+	server = e_user_prompter_server_new ();
+	g_signal_connect (server, "prompt", G_CALLBACK (prompt_cb), NULL);
+
+	g_print ("Prompter is up and running...\n");
+
+	e_dbus_server_run (server, TRUE);
+
+	g_object_unref (server);
+
+	g_print ("Bye.\n");
+
+	return 0;
+}
diff --git a/services/evolution-user-prompter/org.gnome.evolution.dataserver.UserPrompter.service.in b/services/evolution-user-prompter/org.gnome.evolution.dataserver.UserPrompter.service.in
new file mode 100644
index 0000000..5fd25e2
--- /dev/null
+++ b/services/evolution-user-prompter/org.gnome.evolution.dataserver.UserPrompter.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name= USER_PROMPTER_DBUS_SERVICE_NAME@
+Exec= libexecdir@/evolution-user-prompter
diff --git a/tests/libedataserver/Makefile.am b/tests/libedataserver/Makefile.am
index 283d4ac..efc7d9a 100644
--- a/tests/libedataserver/Makefile.am
+++ b/tests/libedataserver/Makefile.am
@@ -5,6 +5,7 @@ NULL =
 TESTS = \
 	e-source-test \
 	e-source-registry-test \
+	e-user-prompter-test \
 	$(NULL)
 
 noinst_PROGRAMS = $(TESTS)
@@ -34,10 +35,17 @@ e_source_registry_test_SOURCES = \
 	e-test-dbus-utils.h \
 	$(NULL)
 
+e_user_prompter_test_SOURCES = \
+	e-user-prompter-test.c \
+	$(NULL)
+
 e_source_test_CPPFLAGS = $(test_CPPFLAGS)
 e_source_test_LDADD = $(test_LDADD)
 
 e_source_registry_test_CPPFLAGS = $(test_CPPFLAGS)
 e_source_registry_test_LDADD = $(test_LDADD)
 
+e_user_prompter_test_CPPFLAGS = $(test_CPPFLAGS)
+e_user_prompter_test_LDADD = $(top_builddir)/libebackend/libebackend-1.2.la $(test_LDADD)
+
 -include $(top_srcdir)/git.mk
diff --git a/tests/libedataserver/e-user-prompter-test.c b/tests/libedataserver/e-user-prompter-test.c
new file mode 100644
index 0000000..8ad04cb
--- /dev/null
+++ b/tests/libedataserver/e-user-prompter-test.c
@@ -0,0 +1,273 @@
+/*
+ * e-user-prompter-test.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) version 3.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include <libebackend/libebackend.h>
+
+typedef struct _TestClosure TestClosure;
+typedef struct _TestFixture TestFixture;
+
+struct _TestClosure {
+};
+
+struct _TestFixture {
+	EUserPrompter *prompter;
+	GMainLoop *main_loop;
+};
+
+static void
+test_fixture_setup_session (TestFixture *fixture,
+                            gconstpointer user_data)
+{
+	g_assert (fixture->prompter == NULL);
+	g_assert (fixture->main_loop == NULL);
+
+	fixture->prompter = e_user_prompter_new ();
+	g_assert (fixture->prompter != NULL);
+
+	fixture->main_loop = g_main_loop_new (NULL, FALSE);
+}
+
+static void
+test_fixture_teardown_session (TestFixture *fixture,
+                               gconstpointer user_data)
+{
+	g_object_unref (fixture->prompter);
+	fixture->prompter = NULL;
+
+	g_main_loop_unref (fixture->main_loop);
+	fixture->main_loop = NULL;
+}
+
+static void
+test_trust_prompt (EUserPrompter *prompter)
+{
+	const gchar *der_certificate =
+		"MIIIKzCCBxOgAwIBAgIDAMP1MA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJTDEWMBQGA1UEChM"
+		"NU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZz"
+		"E4MDYGA1UEAxMvU3RhcnRDb20gQ2xhc3MgMiBQcmltYXJ5IEludGVybWVkaWF0ZSBTZXJ2ZXIgQ0EwH"
+		"hcNMTIwNDEwMDIyNzU2WhcNMTQwNDExMTQzNjUyWjCBqTEZMBcGA1UEDRMQbGZVMVd1OUVydm9EOW0z"
+		"MDELMAkGA1UEBhMCVVMxFjAUBgNVBAgTDU1hc3NhY2h1c2V0dHMxDzANBgNVBAcTBkJvc3RvbjEZMBc"
+		"GA1UEChMQR05PTUUgRm91bmRhdGlvbjEWMBQGA1UEAxMNd3d3Lmdub21lLm9yZzEjMCEGCSqGSIb3DQ"
+		"EJARYUaG9zdG1hc3RlckBnbm9tZS5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIG"
+		"1jjf88ZZw/oUIpXHj75jV8Fn5QrnKkWZzFzs9oIHwIhjJC/A0W1zweUzxZQZuj/r8Pn7RLj86NU8qfX"
+		"UijnXmqpNd71iapUbnmpAbkD7FGDT0Z8ekzzMQmEI1qhZt3emJUgMCqU/OFAO8ooID0oYLw6VeXFZNK"
+		"hOxNifWGSFor77r8GbeDTkIoLdm3YlaIwuiKCzA2ti/SCveFyu/oSrXw0XOJGHc1XSfpHrrQ0xHPKeH"
+		"hzpsOlTBZHZHR0fGpHpyuqhKfCA8Mp0HIJ05IeJwC4Cg41ZnPA2y1sIeos0CbNvrUHPNd+WPdZR/qdR"
+		"phh1OQgeB1bv/AnZ8pUzu9LAgMBAAGjggR1MIIEcTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIDqDAdBgNV"
+		"HSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwHQYDVR0OBBYEFDPkRLQJulDChRFa7dLD7DARlxdVMB8"
+		"GA1UdIwQYMBaAFBHbI0X9VMxqcW+EigPXvvcBLyaGMIHlBgNVHREEgd0wgdqCDXd3dy5nbm9tZS5vcm"
+		"eCCWdub21lLm9yZ4IObGl2ZS5nbm9tZS5vcmeCDHJ0Lmdub21lLm9yZ4ISYnVnemlsbGEuZ25vbWUub"
+		"3Jngg52b3RlLmdub21lLm9yZ4IObWFpbC5nbm9tZS5vcmeCDmxkYXAuZ25vbWUub3JnghFtZW51YmFy"
+		"Lmdub21lLm9yZ4IRd2ViYXBwcy5nbm9tZS5vcmeCD2xhYmVsLmdub21lLm9yZ4IPbWFuZ28uZ25vbWU"
+		"ub3JnghRleHRlbnNpb25zLmdub21lLm9yZzCCAiEGA1UdIASCAhgwggIUMIICEAYLKwYBBAGBtTcBAg"
+		"IwggH/MC4GCCsGAQUFBwIBFiJodHRwOi8vd3d3LnN0YXJ0c3NsLmNvbS9wb2xpY3kucGRmMDQGCCsGA"
+		"QUFBwIBFihodHRwOi8vd3d3LnN0YXJ0c3NsLmNvbS9pbnRlcm1lZGlhdGUucGRmMIH3BggrBgEFBQcC"
+		"AjCB6jAnFiBTdGFydENvbSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTADAgEBGoG+VGhpcyBjZXJ0aWZ"
+		"pY2F0ZSB3YXMgaXNzdWVkIGFjY29yZGluZyB0byB0aGUgQ2xhc3MgMiBWYWxpZGF0aW9uIHJlcXVpcm"
+		"VtZW50cyBvZiB0aGUgU3RhcnRDb20gQ0EgcG9saWN5LCByZWxpYW5jZSBvbmx5IGZvciB0aGUgaW50Z"
+		"W5kZWQgcHVycG9zZSBpbiBjb21wbGlhbmNlIG9mIHRoZSByZWx5aW5nIHBhcnR5IG9ibGlnYXRpb25z"
+		"LjCBnAYIKwYBBQUHAgIwgY8wJxYgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwAwIBAhp"
+		"kTGlhYmlsaXR5IGFuZCB3YXJyYW50aWVzIGFyZSBsaW1pdGVkISBTZWUgc2VjdGlvbiAiTGVnYWwgYW"
+		"5kIExpbWl0YXRpb25zIiBvZiB0aGUgU3RhcnRDb20gQ0EgcG9saWN5LjA1BgNVHR8ELjAsMCqgKKAmh"
+		"iRodHRwOi8vY3JsLnN0YXJ0c3NsLmNvbS9jcnQyLWNybC5jcmwwgY4GCCsGAQUFBwEBBIGBMH8wOQYI"
+		"KwYBBQUHMAGGLWh0dHA6Ly9vY3NwLnN0YXJ0c3NsLmNvbS9zdWIvY2xhc3MyL3NlcnZlci9jYTBCBgg"
+		"rBgEFBQcwAoY2aHR0cDovL2FpYS5zdGFydHNzbC5jb20vY2VydHMvc3ViLmNsYXNzMi5zZXJ2ZXIuY2"
+		"EuY3J0MCMGA1UdEgQcMBqGGGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tLzANBgkqhkiG9w0BAQUFAAOCA"
+		"QEAZ+mc7DDWtNBPiWmM62/rWv6c6l77CMLfAfWks8YDf8T69/agJfA+I7tJyP0WoviyVHaoD2LS8FcU"
+		"RQNFErk+8ry50d3NFFZaAfKRMFkob6gBx19YdQ64n0ZvS/0+a+ye6NL/yttjLE0ynei8nPmmgzaf5M3"
+		"3zOMCaTr7Cq6SJqnlrYUYbdBkobjadcfG2eAKfbhOiVGVEOee4O6JJ+nCrqXpqj42EGuQ8mKvl7Kao+"
+		"xerxctag0jzlLRFWJ69l7DZZyyFzY+/I9IWSVj8i0VCz0FkulK9adKeYD4E4BAOQvDFY4ED2FckW3AZ"
+		"zVueeiqTSIKwkDFhSDwTJsIfsOaEQ==";
+	ENamedParameters *parameters;
+	GError *error = NULL;
+	gint result;
+
+	g_return_if_fail (prompter != NULL);
+
+	parameters = e_named_parameters_new ();
+
+	e_named_parameters_set (parameters, "host", "https://bugzilla.gnome.org/";);
+	e_named_parameters_set (parameters, "certificate", der_certificate);
+	e_named_parameters_set (parameters, "certificate-errors", "007f");
+
+	result = e_user_prompter_extension_prompt_sync (prompter, "ETrustPrompt::trust-prompt", parameters, NULL, NULL, &error);
+
+	g_print ("Trust prompt result: %s (%d)%s%s\n", result == 0 ? "Reject" :
+						   result == 1 ? "Accept permanently" :
+						   result == 2 ? "Accept temporarily" : "Unknown",
+						   result,
+						   error ? ", error: " : "",
+						   error ? error->message : "");
+	g_assert_no_error (error);
+
+	e_named_parameters_free (parameters);
+
+	/* test for an unknown dialog prompt */
+	result = e_user_prompter_extension_prompt_sync (prompter, "Uknown-Dialog-Prompt", NULL, NULL, NULL, &error);
+
+	g_print ("Unknown dialog prompt, result:%d, error: %s\n", result, error ? error->message : "None");
+
+	g_assert (result == -1);
+	g_assert (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND));
+
+	g_clear_error (&error);
+}
+
+struct _Prompts {
+	const gchar *type;
+	const gchar *primary;
+	const gchar *secondary;
+	gboolean use_markup;
+	const gchar *buttons;
+} prompts[] = {
+	{ "info",     "%d) <u>info</u> primary text", "info <i>secondary</i> text\nmarkup", TRUE, NULL },
+	{ "warning",  "%d) warning primary text", "warning secondary text", FALSE, NULL },
+	{ "question", "%d) <u>question</u> primary text", "question <i>secondary</i> text\nmarkup text, but not used as markup", FALSE, NULL },
+	{ "error",    "%d) error primary text", "error <i>secondary</i> text\nmarkup", TRUE, NULL },
+	{ "other",    "%d) other primary text", "other <i>secondary</i> text\nmarkup", TRUE, NULL },
+	{ "#$% $#%",  "%d) totally unknown type primary text", "totally unknown type secondary text\nmarkup without markup texts", TRUE, NULL },
+	{ "",  	      NULL, "%d) a very long secondary text, with no primary text and no icon,"
+			    " which should wrap ideally, and be on multiple lines, like one may"
+			    " expect for such long messages, even without markup", FALSE, NULL },
+	{ "",  	      "%d) a very long primary text, with no secondary text and no icon,"
+			" which should wrap ideally, and be on multiple lines, like one may"
+			" expect for such long messages, even without markup", NULL, FALSE, NULL },
+	{ "",  	      "%d) This one has primary text...", "...and secondary text, and 5 buttons", FALSE, "1st button:2nd button:3rd button:4th button:5th button" }
+};
+
+static void
+user_prompt_respond_cb (GObject *source,
+			GAsyncResult *result,
+			gpointer user_data)
+{
+	gint result_button;
+	GError *error = NULL;
+
+	result_button = e_user_prompter_prompt_finish (E_USER_PROMPTER (source), result, &error);
+
+	g_print ("   Prompt [%d] returned %d%s%s\n", GPOINTER_TO_INT (user_data), result_button,
+		error ? ", error: " : "", error ? error->message : "");
+
+	g_assert_no_error (error);
+}
+
+static gboolean
+quit_main_loop_cb (gpointer main_loop)
+{
+	g_main_loop_quit (main_loop);
+	return FALSE;
+}
+
+static gboolean
+test_user_prompts_idle_cb (gpointer user_data)
+{
+	TestFixture *fixture = user_data;
+	gint ii, sz;
+	GMainContext *main_context = g_main_loop_get_context (fixture->main_loop);
+	GSource *source;
+	GError *error = NULL;
+
+	/* all but the last run asynchronously, to test they will come
+	   in the right order and only one at a time, and then run
+	   the last synchronously, to wait for the result */
+	sz = G_N_ELEMENTS (prompts);
+	for (ii = 0; ii < sz && !error; ii++) {
+		gchar *title, *primary, *secondary, **buttons = NULL;
+		GSList *captions = NULL;
+
+		title = g_strdup_printf ("Prompt %d...", ii);
+		primary = g_strdup_printf (prompts[ii].primary, ii);
+		secondary = g_strdup_printf (prompts[ii].secondary, ii);
+		if (prompts[ii].buttons) {
+			gint jj;
+
+			buttons = g_strsplit (prompts[ii].buttons, ":", -1);
+			for (jj = 0; buttons[jj]; jj++) {
+				captions = g_slist_append (captions, buttons[jj]);
+			}							
+		}
+
+		if (ii + 1 == sz) {
+			gint result_button;
+
+			result_button = e_user_prompter_prompt_sync (fixture->prompter,
+				prompts[ii].type, title, primary, secondary, prompts[ii].use_markup, captions,
+				NULL, &error);
+			g_print ("   Prompt [%d] (sync) returned %d%s%s\n", ii, result_button,
+				error ? ", error: " : "", error ? error->message : "");
+		} else {
+			e_user_prompter_prompt (fixture->prompter,
+				prompts[ii].type, title, primary, secondary, prompts[ii].use_markup, captions,
+				NULL, user_prompt_respond_cb, GINT_TO_POINTER (ii));
+
+			/* give it a chance to be delivered in this order */
+			g_main_context_iteration (main_context, FALSE);
+
+			g_usleep (G_USEC_PER_SEC);
+		}
+
+		g_free (title);
+		g_free (primary);
+		g_free (secondary);
+		g_strfreev (buttons);
+		g_slist_free (captions);
+	}
+
+	g_assert_no_error (error);
+
+	test_trust_prompt (fixture->prompter);
+
+	g_print ("Waiting 5 seconds for response deliveries...\n");
+	source = g_timeout_source_new_seconds (5);
+	g_source_set_callback (source, quit_main_loop_cb, fixture->main_loop, NULL);
+	g_source_attach (source, main_context);
+
+	return FALSE;
+}
+
+static void
+test_user_prompts (TestFixture *fixture,
+		   gconstpointer user_data)
+{
+	g_idle_add (test_user_prompts_idle_cb, fixture);
+	g_main_loop_run (fixture->main_loop);
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+	TestClosure closure;
+	gint retval;
+
+	g_type_init ();
+
+	g_test_init (&argc, &argv, NULL);
+	g_test_bug_base ("http://bugzilla.gnome.org/";);
+
+	g_test_add (
+		"/e-user-prompter-test/UserPrompts",
+		TestFixture, &closure,
+		test_fixture_setup_session,
+		test_user_prompts,
+		test_fixture_teardown_session);
+
+	retval = g_test_run ();
+
+	return retval;
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]