Index: shell/main.c
===================================================================
--- shell/main.c	(revision 35390)
+++ shell/main.c	(working copy)
@@ -95,6 +95,7 @@
 #include <pthread.h>
 
 #include "e-util/e-plugin.h"
+#include "e-util/e-plugin-ui.h"
 
 #define SKIP_WARNING_DIALOG_KEY \
 	"/apps/evolution/shell/skip_warning_dialog"
@@ -760,6 +761,7 @@ main (int argc, char **argv)
 #endif
 		e_plugin_hook_register_type(e_plugin_type_hook_get_type());
 		e_plugin_hook_register_type(e_import_hook_get_type());
+		e_plugin_hook_register_type(E_TYPE_PLUGIN_UI_HOOK);
 		e_plugin_load_plugins();
 	}
 
Index: plugins/face/face.c
===================================================================
--- plugins/face/face.c	(revision 35390)
+++ plugins/face/face.c	(working copy)
@@ -33,16 +33,16 @@
 
 #define d(x) x
 
-void org_gnome_composer_face (EPlugin * ep, EMMenuTargetWidget * target);
-int e_plugin_lib_configure (EPlugin * ep);
+gboolean	e_plugin_ui_init		(GtkUIManager *manager,
+						 EMsgComposer *composer);
 
-void org_gnome_composer_face (EPlugin * ep, EMMenuTargetWidget * t)
+static void
+action_face_cb (GtkAction *action,
+                EMsgComposer *composer)
 {
-	EMsgComposer *composer;
 	gchar *filename, *file_contents;
 	GError *error = NULL;
 
-	composer = (EMsgComposer *) t->target.widget;
 	filename = g_build_filename (e_get_user_data_dir (), "faces", NULL);
 	g_file_get_contents (filename, &file_contents, NULL, &error);
 
@@ -117,3 +117,29 @@ void org_gnome_composer_face (EPlugin * 
 	}
 	e_msg_composer_modify_header (composer, "Face", file_contents);
 }
+
+static GtkActionEntry entries[] = {
+
+	{ "face",
+	  NULL,
+	  N_("_Face"),
+	  NULL,
+	  NULL,
+	  G_CALLBACK (action_face_cb) }
+};
+
+gboolean
+e_plugin_ui_init (GtkUIManager *manager,
+                  EMsgComposer *composer)
+{
+	GtkhtmlEditor *editor;
+
+	editor = GTKHTML_EDITOR (composer);
+
+	/* Add actions to the "composer" action group. */
+	gtk_action_group_add_actions (
+		gtkhtml_editor_get_action_group (editor, "composer"),
+		entries, G_N_ELEMENTS (entries), composer);
+
+	return TRUE;
+}
Index: plugins/face/org-gnome-face-ui.xml
===================================================================
--- plugins/face/org-gnome-face-ui.xml	(revision 35390)
+++ plugins/face/org-gnome-face-ui.xml	(working copy)
@@ -1,12 +0,0 @@
-<Root>
-  <commands>
-  	<cmd name="Face" _label="_Face"/>
-  </commands>
-  <menu>
- 	<submenu name="Insert">
-  		<placeholder name="Component">
-  			<menuitem name="Face" verb="" />
-	  	</placeholder>
-	</submenu>
-  </menu>
-</Root>
Index: plugins/face/org-gnome-face.eplug.xml
===================================================================
--- plugins/face/org-gnome-face.eplug.xml	(revision 35390)
+++ plugins/face/org-gnome-face.eplug.xml	(working copy)
@@ -6,17 +6,12 @@
 		<author name="Sankar P" email="psankar@novell.com"/>
 		<_description>Attach Face header to outgoing messages. First time the user needs to configure a 48*48 png image. It is base64 encoded and stored in ~/.evolution/faces  This will be used in messages that are sent further.</_description>
 
-		<hook class="org.gnome.evolution.mail.bonobomenu:1.0">
-			<menu id="org.gnome.evolution.mail.composer" target="widget">
-				<ui file="@PLUGINDIR@/org-gnome-face-ui.xml"/>
-				<item
-					type="item"
-					verb="Face"
-					path="/commands/Face"			   
-					activate="org_gnome_composer_face"
-					enable="one"
-				/>
-			</menu>
+		<hook class="org.gnome.evolution.ui:1.0">
+			<ui-manager id="org.gnome.evolution.composer">
+				<placeholder name="insert-menu-top">
+					<menuitem action="face"/>
+				</placeholder>
+			</ui-manager>
 		</hook>
 	</e-plugin>
 </e-plugin-list>
Index: plugins/face/Makefile.am
===================================================================
--- plugins/face/Makefile.am	(revision 35390)
+++ plugins/face/Makefile.am	(working copy)
@@ -13,8 +13,7 @@ INCLUDES = -I. 						\
 @EVO_PLUGIN_RULE@
 
 plugin_DATA =	\
-	org-gnome-face.eplug	\
-	org-gnome-face-ui.xml  
+	org-gnome-face.eplug
 
 plugin_LTLIBRARIES = liborg-gnome-face.la
 
@@ -36,7 +35,6 @@ errordir = $(privdatadir)/errors
 
 EXTRA_DIST = 						\
 	org-gnome-face.eplug.xml			\
-	org-gnome-face-ui.xml				\
 	$(error_DATA)
 
 BUILT_SOURCES = 				\
Index: composer/e-msg-composer.c
===================================================================
--- composer/e-msg-composer.c	(revision 35390)
+++ composer/e-msg-composer.c	(working copy)
@@ -69,6 +69,7 @@
 #include "misc/e-charset-picker.h"
 #include "misc/e-expander.h"
 #include "e-util/e-error.h"
+#include "e-util/e-plugin-ui.h"
 #include "e-util/e-util-private.h"
 #include "e-util/e-util.h"
 #include <mail/em-event.h>
@@ -2182,14 +2183,6 @@ msg_composer_destroy (GtkObject *object)
 
 	all_composers = g_slist_remove (all_composers, object);
 
-#if 0 /* GTKHTML-EDITOR */
-	if (composer->priv->menu) {
-		e_menu_update_target ((EMenu *)composer->priv->menu, NULL);
-		g_object_unref (composer->priv->menu);
-		composer->priv->menu = NULL;
-	}
-#endif
-
 	if (composer->priv->address_dialog != NULL) {
 		gtk_widget_destroy (composer->priv->address_dialog);
 		composer->priv->address_dialog = NULL;
@@ -2723,17 +2716,18 @@ static void
 msg_composer_init (EMsgComposer *composer)
 {
 	EComposerHeaderTable *table;
-#if 0 /* GTKHTML-EDITOR */
-	EMMenuTargetWidget *target;
-#endif
+	GtkUIManager *manager;
+	GtkhtmlEditor *editor;
 	GtkHTML *html;
 
 	composer->priv = E_MSG_COMPOSER_GET_PRIVATE (composer);
 
 	e_composer_private_init (composer);
 
+	editor = GTKHTML_EDITOR (composer);
+	html = gtkhtml_editor_get_html (editor);
+	manager = gtkhtml_editor_get_ui_manager (editor);
 	all_composers = g_slist_prepend (all_composers, composer);
-	html = gtkhtml_editor_get_html (GTKHTML_EDITOR (composer));
 	table = E_COMPOSER_HEADER_TABLE (composer->priv->header_table);
 
 	gtk_window_set_title (GTK_WINDOW (composer), _("Compose Message"));
@@ -2750,24 +2744,6 @@ msg_composer_init (EMsgComposer *compose
 		html, "drag-data-received",
 		G_CALLBACK (msg_composer_drag_data_received), NULL);
 
-	/* Plugin Support */
-
-#if 0 /* GTKHTML-EDITOR */
-	/** @HookPoint-EMMenu: Main Mail Menu
-	 * @Id: org.gnome.evolution.mail.composer
-	 * @Class: org.gnome.evolution.mail.bonobomenu:1.0
-	 * @Target: EMMenuTargetWidget
-	 *
-	 * The main menu of the composer window.  The widget of the
-	 * target will point to the EMsgComposer object.
-	 */
-	composer->priv->menu = em_menu_new ("org.gnome.evolution.mail.composer");
-	target = em_menu_target_new_widget (p->menu, (GtkWidget *)composer);
-	e_menu_update_target ((EMenu *)p->menu, target);
-	e_menu_activate ((EMenu *)p->menu, p->uic, TRUE);
-
-#endif
-
 	/* Configure Headers */
 
 	e_composer_header_table_set_account_list (
@@ -2823,7 +2799,10 @@ msg_composer_init (EMsgComposer *compose
 	e_composer_autosave_register (composer);
 
 	/* Initialization may have tripped the "changed" state. */
-	gtkhtml_editor_set_changed (GTKHTML_EDITOR (composer), FALSE);
+	gtkhtml_editor_set_changed (editor, FALSE);
+
+	e_plugin_ui_register_manager (
+		"org.gnome.evolution.composer", manager, composer);
 }
 
 GType
Index: e-util/e-plugin.c
===================================================================
--- e-util/e-plugin.c	(revision 35390)
+++ e-util/e-plugin.c	(working copy)
@@ -697,6 +697,27 @@ e_plugin_invoke(EPlugin *ep, const char 
 }
 
 /**
+ * e_plugin_get_symbol:
+ * @ep: an #EPlugin
+ * @name: The name of the symbol to fetch. The format of this name
+ * will depend on the EPlugin type and its language conventions.
+ *
+ * Helper to fetch a symbol name from a plugin.
+ *
+ * Return value: the symbol value, or %NULL if not found
+ **/
+void *
+e_plugin_get_symbol(EPlugin *ep, const char *name)
+{
+	EPluginClass *class;
+
+	class = (EPluginClass *) G_OBJECT_GET_CLASS (ep);
+	g_return_val_if_fail (class->get_symbol != NULL, NULL);
+
+	return class->get_symbol (ep, name);
+}
+
+/**
  * e_plugin_enable:
  * @ep:
  * @state:
@@ -927,6 +948,20 @@ epl_invoke(EPlugin *ep, const char *name
 	return cb(epl, data);
 }
 
+static void *
+epl_get_symbol(EPlugin *ep, const gchar *name)
+{
+	gpointer symbol;
+
+	if (epl_loadmodule(ep) != 0)
+		return NULL;
+
+	if (!g_module_symbol (epl->module, name, &symbol))
+		return NULL;
+
+	return symbol;
+}
+
 static int
 epl_construct(EPlugin *ep, xmlNodePtr root)
 {
@@ -1029,6 +1064,7 @@ epl_class_init(EPluginClass *klass)
 	((GObjectClass *)klass)->finalize = epl_finalise;
 	klass->construct = epl_construct;
 	klass->invoke = epl_invoke;
+	klass->get_symbol = epl_get_symbol;
 	klass->enable = epl_enable;
 	klass->get_configure_widget = epl_get_configure_widget;
 	klass->type = "shlib";
Index: e-util/e-plugin.h
===================================================================
--- e-util/e-plugin.h	(revision 35390)
+++ e-util/e-plugin.h	(working copy)
@@ -86,6 +86,7 @@ struct _EPluginClass {
 	const char *type;
 
 	int (*construct)(EPlugin *, xmlNodePtr root);
+	void *(*get_symbol)(EPlugin *, const char *name);
 	void *(*invoke)(EPlugin *, const char *name, void *data);
 	void (*enable)(EPlugin *, int state);
 	GtkWidget *(*get_configure_widget)(EPlugin *);
@@ -100,6 +101,7 @@ GSList * e_plugin_list_plugins(void);
 
 void e_plugin_register_type(GType type);
 
+void *e_plugin_get_symbol(EPlugin *ep, const char *name);
 void *e_plugin_invoke(EPlugin *ep, const char *name, void *data);
 void e_plugin_enable(EPlugin *eph, int state);
 
Index: e-util/e-plugin-ui.c
===================================================================
--- e-util/e-plugin-ui.c	(revision 0)
+++ e-util/e-plugin-ui.c	(revision 0)
@@ -0,0 +1,443 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2008 Novell, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "e-plugin-ui.h"
+
+#include <string.h>
+
+/* XXX These should moved to e-plugin.h */
+#define E_TYPE_PLUGIN_HOOK \
+	(e_plugin_hook_get_type ())
+#define E_PLUGIN_HOOK(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_PLUGIN_HOOK, EPluginHook))
+#define E_PLUGIN_HOOK_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_PLUGIN_HOOK, EPluginHookClass))
+#define E_IS_PLUGIN_HOOK(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_PLUGIN_HOOK))
+#define E_IS_PLUGIN_HOOK_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_PLUGIN_HOOK))
+#define E_PLUGIN_HOOK_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_PLUGIN_HOOK, EPluginHookClass))
+
+
+#define E_PLUGIN_UI_HOOK_GET_PRIVATE(obj) \
+	(G_TYPE_INSTANCE_GET_PRIVATE \
+	((obj), E_TYPE_PLUGIN_UI_HOOK, EPluginUIHookPrivate))
+
+#define E_PLUGIN_UI_INIT_FUNC		"e_plugin_ui_init"
+#define E_PLUGIN_UI_HOOK_CLASS_ID       "org.gnome.evolution.ui:1.0"
+#define E_PLUGIN_UI_MANAGER_ID_KEY	"e-plugin-ui-manager-id"
+
+struct _EPluginUIHookPrivate {
+
+	/* Table of GtkUIManager ID's to UI definitions. 
+	 *
+	 * For example:
+	 *
+	 *     <ui-manager id="org.gnome.evolution.sample">
+ 	 *             ... UI definition ...
+	 *     </ui-manager>
+	 *
+	 * Results in:
+	 *
+	 *     g_hash_table_insert (
+	 *             ui_definitions,
+	 *             "org.gnome.evolution.sample",
+	 *             "... UI definition ...");
+	 *
+	 * See http://library.gnome.org/devel/gtk/unstable/GtkUIManager.html
+	 * for more information about UI definitions.  Note: the <ui> tag is
+	 * optional.
+	 */
+	GHashTable *ui_definitions;
+};
+
+/* The registry is a hash table of hash tables.  It maps
+ *
+ *    EPluginUIHook instance --> GtkUIManager instance --> UI merge id
+ *
+ * GtkUIManager instances are automatically removed when finalized.
+ */
+static GHashTable *registry;
+static gpointer parent_class;
+
+static void
+plugin_ui_registry_remove (EPluginUIHook *hook,
+                           GtkUIManager *manager)
+{
+	GHashTable *hash_table;
+
+	/* Note: Manager may already be finalized. */
+
+	hash_table = g_hash_table_lookup (registry, hook);
+	g_return_if_fail (hash_table != NULL);
+
+	g_hash_table_remove (hash_table, manager);
+	if (g_hash_table_size (hash_table) == 0)
+		g_hash_table_remove (registry, hook);
+}
+
+static void
+plugin_ui_registry_insert (EPluginUIHook *hook,
+                           GtkUIManager *manager,
+                           guint merge_id)
+{
+	GHashTable *hash_table;
+
+	if (registry == NULL)
+		registry = g_hash_table_new_full (
+			g_direct_hash, g_direct_equal,
+			(GDestroyNotify) NULL,
+			(GDestroyNotify) g_hash_table_destroy);
+
+	hash_table = g_hash_table_lookup (registry, hook);
+	if (hash_table == NULL) {
+		hash_table = g_hash_table_new (g_direct_hash, g_direct_equal);
+		g_hash_table_insert (registry, hook, hash_table);
+	}
+
+	g_object_weak_ref (
+		G_OBJECT (manager), (GWeakNotify)
+		plugin_ui_registry_remove, hook);
+
+	g_hash_table_insert (hash_table, manager, GUINT_TO_POINTER (merge_id));
+}
+
+/* Helper for plugin_ui_hook_merge_ui() */
+static void
+plugin_ui_hook_merge_foreach (GtkUIManager *manager,
+                              const gchar *ui_definition,
+                              GHashTable *hash_table)
+{
+	guint merge_id;
+	GError *error = NULL;
+
+	/* Merge the UI definition into the manager. */
+	merge_id = gtk_ui_manager_add_ui_from_string (
+		manager, ui_definition, -1, &error);
+	gtk_ui_manager_ensure_update (manager);
+	if (error != NULL) {
+		g_warning ("%s", error->message);
+		g_error_free (error);
+	}
+
+	/* Merge ID will be 0 on error, which is what we want. */
+	g_hash_table_insert (hash_table, manager, GUINT_TO_POINTER (merge_id));
+}
+
+static void
+plugin_ui_hook_merge_ui (EPluginUIHook *hook)
+{
+	GHashTable *old_merge_ids;
+	GHashTable *new_merge_ids;
+	GHashTable *intermediate;
+	GList *keys;
+
+	old_merge_ids = g_hash_table_lookup (registry, hook);
+	if (old_merge_ids == NULL)
+		return;
+
+	/* The GtkUIManager instances and UI definitions live in separate
+	 * tables, so we need to build an intermediate table that we can
+	 * easily iterate over. */
+	keys = g_hash_table_get_keys (old_merge_ids);
+	intermediate = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+	while (keys != NULL) {
+		GtkUIManager *manager = keys->data;
+		gchar *ui_definition;
+
+		ui_definition = g_hash_table_lookup (
+			hook->priv->ui_definitions,
+			e_plugin_ui_get_manager_id (manager));
+
+		g_hash_table_insert (intermediate, manager, ui_definition);
+
+		keys = g_list_delete_link (keys, keys);
+	}
+
+	new_merge_ids = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+	g_hash_table_foreach (
+		intermediate, (GHFunc)
+		plugin_ui_hook_merge_foreach, new_merge_ids);
+
+	g_hash_table_insert (registry, hook, new_merge_ids);
+
+	g_hash_table_destroy (intermediate);
+}
+
+/* Helper for plugin_ui_hook_unmerge_ui() */
+static void
+plugin_ui_hook_unmerge_foreach (GtkUIManager *manager,
+                                gpointer value,
+                                GHashTable *hash_table)
+{
+	guint merge_id;
+
+	merge_id = GPOINTER_TO_UINT (value);
+	gtk_ui_manager_remove_ui (manager, merge_id);
+
+	g_hash_table_insert (hash_table, manager, GUINT_TO_POINTER (0));
+}
+
+static void
+plugin_ui_hook_unmerge_ui (EPluginUIHook *hook)
+{
+	GHashTable *old_merge_ids;
+	GHashTable *new_merge_ids;
+
+	old_merge_ids = g_hash_table_lookup (registry, hook);
+	if (old_merge_ids == NULL)
+		return;
+
+	new_merge_ids = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+	g_hash_table_foreach (
+		old_merge_ids, (GHFunc)
+		plugin_ui_hook_unmerge_foreach, new_merge_ids);
+
+	g_hash_table_insert (registry, hook, new_merge_ids);
+}
+
+static void
+plugin_ui_hook_register_manager (EPluginUIHook *hook,
+                                 GtkUIManager *manager,
+                                 const gchar *ui_definition,
+                                 gpointer user_data)
+{
+	EPlugin *plugin;
+	EPluginUIInitFunc func;
+	guint merge_id = 0;
+
+	plugin = ((EPluginHook *) hook)->plugin;
+	func = e_plugin_get_symbol (plugin, E_PLUGIN_UI_INIT_FUNC);
+
+	/* Pass the manager and user_data to the plugin's e_plugin_ui_init()
+	 * function (if it defined one).  The plugin should install whatever
+	 * GtkActions and GtkActionGroups are neccessary to implement the
+	 * action names in its UI definition. */
+	if (func != NULL && !func (manager, user_data))
+		return;
+
+	if (plugin->enabled) {
+		GError *error = NULL;
+
+		/* Merge the UI definition into the manager. */
+		merge_id = gtk_ui_manager_add_ui_from_string (
+			manager, ui_definition, -1, &error);
+		gtk_ui_manager_ensure_update (manager);
+		if (error != NULL) {
+			g_warning ("%s", error->message);
+			g_error_free (error);
+		}
+	}
+
+	/* Save merge ID's for later use. */
+	plugin_ui_registry_insert (hook, manager, merge_id);
+}
+
+static void
+plugin_ui_hook_finalize (GObject *object)
+{
+	EPluginUIHookPrivate *priv;
+
+	priv = E_PLUGIN_UI_HOOK_GET_PRIVATE (object);
+
+	g_hash_table_destroy (priv->ui_definitions);
+
+	/* Chain up to parent's dispose() method. */
+	G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static gint
+plugin_ui_hook_construct (EPluginHook *hook,
+                          EPlugin *plugin,
+                          xmlNodePtr node)
+{
+	EPluginUIHookPrivate *priv;
+
+	priv = E_PLUGIN_UI_HOOK_GET_PRIVATE (hook);
+
+	/* XXX The EPlugin should be a property of EPluginHookClass.
+	 *     Then it could be passed directly to g_object_new() and
+	 *     we wouldn't have to chain up here. */
+
+	/* Chain up to parent's construct() method. */
+	E_PLUGIN_HOOK_CLASS (parent_class)->construct (hook, plugin, node);
+
+	for (node = node->children; node != NULL; node = node->next) {
+		xmlNodePtr child;
+		xmlBufferPtr buffer;
+		const gchar *content;
+		gchar *id;
+
+		if (strcmp ((gchar *) node->name, "ui-manager") != 0)
+			continue;
+
+		id = e_plugin_xml_prop (node, "id");
+		if (id == NULL) {
+			g_warning ("<ui-manager> requires 'id' property");
+			continue;
+		}
+
+		/* Extract the XML content below <ui-manager> */
+		buffer = xmlBufferCreate ();
+		child = node->children;
+		while (child != NULL && xmlNodeIsText (child))
+			child = child->next;
+		if (child != NULL)
+			xmlNodeDump (buffer, node->doc, child, 2, 1);
+		content = (const gchar *) xmlBufferContent (buffer);
+
+		g_hash_table_insert (
+			priv->ui_definitions,
+			id, g_strdup (content));
+
+		xmlBufferFree (buffer);
+	}
+
+	return 0;
+}
+
+static void
+plugin_ui_hook_enable (EPluginHook *hook,
+                       gint state)
+{
+	if (state)
+		plugin_ui_hook_merge_ui (E_PLUGIN_UI_HOOK (hook));
+	else
+		plugin_ui_hook_unmerge_ui (E_PLUGIN_UI_HOOK (hook));
+}
+
+static void
+plugin_ui_hook_class_init (EPluginUIHookClass *class)
+{
+	GObjectClass *object_class;
+	EPluginHookClass *plugin_hook_class;
+
+	parent_class = g_type_class_peek_parent (class);
+	g_type_class_add_private (class, sizeof (EPluginUIHookPrivate));
+
+	object_class = G_OBJECT_CLASS (class);
+	object_class->finalize = plugin_ui_hook_finalize;
+
+	plugin_hook_class = E_PLUGIN_HOOK_CLASS (class);
+	plugin_hook_class->id = E_PLUGIN_UI_HOOK_CLASS_ID;
+	plugin_hook_class->construct = plugin_ui_hook_construct;
+	plugin_hook_class->enable = plugin_ui_hook_enable;
+}
+
+static void
+plugin_ui_hook_init (EPluginUIHook *hook)
+{
+	GHashTable *ui_definitions;
+
+	ui_definitions = g_hash_table_new_full (
+		g_str_hash, g_str_equal,
+		(GDestroyNotify) g_free,
+		(GDestroyNotify) g_free);
+
+	hook->priv = E_PLUGIN_UI_HOOK_GET_PRIVATE (hook);
+	hook->priv->ui_definitions = ui_definitions;
+}
+
+GType
+e_plugin_ui_hook_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0)) {
+		static const GTypeInfo type_info = {
+			sizeof (EPluginUIHookClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) plugin_ui_hook_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL,  /* class_data */
+			sizeof (EPluginUIHook),
+			0,     /* n_preallocs */
+			(GInstanceInitFunc) plugin_ui_hook_init,
+			NULL   /* value_table */
+		};
+
+		type = g_type_register_static (
+			E_TYPE_PLUGIN_HOOK, "EPluginUIHook", &type_info, 0);
+	}
+
+	return type;
+}
+
+void
+e_plugin_ui_register_manager (const gchar *id,
+                              GtkUIManager *manager,
+                              gpointer user_data)
+{
+	const gchar *key = E_PLUGIN_UI_MANAGER_ID_KEY;
+	GSList *plugin_list;
+
+	g_return_if_fail (id != NULL);
+	g_return_if_fail (GTK_IS_UI_MANAGER (manager));
+
+	g_object_set_data (G_OBJECT (manager), key, (gpointer) id);
+
+	/* Loop over all installed plugins. */
+	plugin_list = e_plugin_list_plugins ();
+	while (plugin_list != NULL) {
+		EPlugin *plugin = plugin_list->data;
+		GSList *iter;
+
+		/* Look for hooks of type EPluginUIHook. */
+		for (iter = plugin->hooks; iter != NULL; iter = iter->next) {
+			EPluginUIHook *hook = iter->data;
+			const gchar *ui_definition;
+
+			if (!E_IS_PLUGIN_UI_HOOK (hook))
+				continue;
+
+			/* Check if the hook has a UI definition
+			 * for the GtkUIManager being registered. */
+			ui_definition = g_hash_table_lookup (
+				hook->priv->ui_definitions, id);
+			if (ui_definition == NULL)
+				continue;
+
+			/* Register the manager with the hook. */
+			plugin_ui_hook_register_manager (
+				hook, manager, ui_definition, user_data);
+		}
+
+		plugin_list = g_slist_next (plugin_list);
+	}
+}
+
+const gchar *
+e_plugin_ui_get_manager_id (GtkUIManager *manager)
+{
+	const gchar *key = E_PLUGIN_UI_MANAGER_ID_KEY;
+
+	g_return_val_if_fail (GTK_IS_UI_MANAGER (manager), NULL);
+
+	return g_object_get_data (G_OBJECT (manager), key);
+}
Index: e-util/e-plugin-ui.h
===================================================================
--- e-util/e-plugin-ui.h	(revision 0)
+++ e-util/e-plugin-ui.h	(revision 0)
@@ -0,0 +1,74 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2008 Novell, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef E_PLUGIN_UI_H
+#define E_PLUGIN_UI_H
+
+#include <gtk/gtk.h>
+#include "e-plugin.h"
+
+/* Standard GObject macros */
+#define E_TYPE_PLUGIN_UI_HOOK \
+	(e_plugin_ui_hook_get_type ())
+#define E_PLUGIN_UI_HOOK(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST \
+	((obj), E_TYPE_PLUGIN_UI_HOOK, EPluginUIHook))
+#define E_PLUGIN_UI_HOOK_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_CAST \
+	((cls), E_TYPE_PLUGIN_UI_HOOK, EPluginUIHookClass))
+#define E_IS_PLUGIN_UI_HOOK(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE \
+	((obj), E_TYPE_PLUGIN_UI_HOOK))
+#define E_IS_PLUGIN_UI_HOOK_CLASS(cls) \
+	(G_TYPE_CHECK_CLASS_TYPE \
+	((cls), E_TYPE_PLUGIN_UI_HOOK))
+#define E_PLUGIN_UI_HOOK_GET_CLASS(obj) \
+	(G_TYPE_INSTANCE_GET_CLASS \
+	((obj), E_TYPE_PLUGIN_UI_HOOK, EPluginUIHookClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EPluginUIHook EPluginUIHook;
+typedef struct _EPluginUIHookClass EPluginUIHookClass;
+typedef struct _EPluginUIHookPrivate EPluginUIHookPrivate;
+
+struct _EPluginUIHook {
+	EPluginHook parent;
+	EPluginUIHookPrivate *priv;
+};
+
+struct _EPluginUIHookClass {
+	EPluginHookClass parent_class;
+};
+
+/* Plugins with "org.gnome.evolution.ui" hooks should define a
+ * function named e_plugin_ui_init() having this signature. */
+typedef gboolean	(*EPluginUIInitFunc)	(GtkUIManager *manager,
+						 gpointer user_data);
+
+GType		e_plugin_ui_hook_get_type	(void);
+
+void		e_plugin_ui_register_manager	(const gchar *id,
+						 GtkUIManager *manager,
+						 gpointer user_data);
+const gchar *	e_plugin_ui_get_manager_id	(GtkUIManager *manager);
+
+G_END_DECLS
+
+#endif /* E_PLUGIN_UI_H */
Index: e-util/Makefile.am
===================================================================
--- e-util/Makefile.am	(revision 35390)
+++ e-util/Makefile.am	(working copy)
@@ -61,6 +61,7 @@ eutilinclude_HEADERS = 				\
 	e-mktemp.h				\
 	e-print.h				\
 	e-plugin.h				\
+	e-plugin-ui.h				\
 	e-popup.h				\
 	e-profile-event.h			\
 	e-request.h				\
@@ -100,6 +101,7 @@ libeutil_la_SOURCES =				\
 	e-menu.c				\
 	e-mktemp.c				\
 	e-plugin.c				\
+	e-plugin-ui.c				\
 	e-popup.c				\
 	e-print.c				\
 	e-profile-event.c			\
