[evolution-data-server/mmeeks-gdbus-import] configure cleanups import and rename of edbus - see libedbus/README for it's origins.



commit 10e99aeaeaa7d8136465cc59f246b27f9aa8b0cb
Author: Michael Meeks <michael meeks novell com>
Date:   Fri Feb 26 10:18:23 2010 +0000

    configure cleanups
    import and rename of edbus - see libedbus/README for it's origins.

 Makefile.am                      |    4 +-
 configure.ac                     |   20 +-
 libedbus/example-own-name.c      |   77 +
 libedbus/example-server.c        |  320 +++
 libedbus/example-subtree.c       |  388 ++++
 libedbus/example-watch-name.c    |   80 +
 libedbus/example-watch-proxy.c   |  177 ++
 libedbus/gbitlock.c              |  298 +++
 libedbus/gbitlock.h              |   43 +
 libedbus/gdbus-marshal.c         |   89 +
 libedbus/gdbus-marshal.h         |   20 +
 libedbus/gdbus-marshal.list      |    1 +
 libedbus/gdbus.h                 |   44 +
 libedbus/gdbusconnection.c       | 4228 ++++++++++++++++++++++++++++++++++++++
 libedbus/gdbusconnection.h       |  392 ++++
 libedbus/gdbusconversion.c       |  509 +++++
 libedbus/gdbusconversion.h       |   45 +
 libedbus/gdbusenums.h            |  277 +++
 libedbus/gdbusenumtypes.c        |  258 +++
 libedbus/gdbusenumtypes.h        |   37 +
 libedbus/gdbuserror.c            |  822 ++++++++
 libedbus/gdbuserror.h            |   74 +
 libedbus/gdbusintrospection.c    | 1826 ++++++++++++++++
 libedbus/gdbusintrospection.h    |  209 ++
 libedbus/gdbusmethodinvocation.c |  721 +++++++
 libedbus/gdbusmethodinvocation.h |  119 ++
 libedbus/gdbusnameowning.c       |  679 ++++++
 libedbus/gdbusnameowning.h       |   77 +
 libedbus/gdbusnamewatching.c     |  537 +++++
 libedbus/gdbusnamewatching.h     |   71 +
 libedbus/gdbusprivate.c          |  779 +++++++
 libedbus/gdbusprivate.h          |   52 +
 libedbus/gdbusproxy.c            | 1311 ++++++++++++
 libedbus/gdbusproxy.h            |  141 ++
 libedbus/gdbusproxywatching.c    |  431 ++++
 libedbus/gdbusproxywatching.h    |   80 +
 libedbus/gdbusserver.c           |  336 +++
 libedbus/gdbusserver.h           |   93 +
 libedbus/gdbustypes.h            |   53 +
 libedbus/gvariant-core.c         | 1509 ++++++++++++++
 libedbus/gvariant-printer.c      |  318 +++
 libedbus/gvariant-private.h      |   43 +
 libedbus/gvariant-serialiser.c   | 1653 +++++++++++++++
 libedbus/gvariant-serialiser.h   |   75 +
 libedbus/gvariant-util.c         | 1902 +++++++++++++++++
 libedbus/gvariant-valist.c       | 1572 ++++++++++++++
 libedbus/gvariant.h              |  307 +++
 libedbus/gvarianttype.c          | 1470 +++++++++++++
 libedbus/gvarianttype.h          |  304 +++
 libedbus/gvarianttypeinfo.c      |  854 ++++++++
 libedbus/gvarianttypeinfo.h      |  159 ++
 51 files changed, 25872 insertions(+), 12 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 5d06719..acef370 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -6,8 +6,8 @@ if ENABLE_CALENDAR
 CALENDAR_DIR = calendar
 endif
 
-SUBDIRS = win32 libedataserver libebackend servers camel addressbook $(CALENDAR_DIR) libedataserverui docs art po
-DIST_SUBDIRS = win32 libedataserver libebackend servers camel addressbook calendar libedataserverui docs art po
+SUBDIRS = win32 libedataserver libebackend servers camel addressbook $(CALENDAR_DIR) libedataserverui docs art po libedbus
+DIST_SUBDIRS = win32 libedataserver libebackend servers camel addressbook calendar libedataserverui docs art po libedbus
 
 changelogs =			\
 	ChangeLog
diff --git a/configure.ac b/configure.ac
index f01fa36..ce64a0f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -42,7 +42,6 @@ m4_define([sqlite_minimum_version], [3.5])
 m4_define([gweather_minimum_version], [2.25.4])
 m4_define([libical_minimum_version], [0.43])
 m4_define([dbus_glib_minimum_version], [0.6])
-m4_define([gdbus_standalone_minimum_version], [0.1])
 
 AC_SUBST([BASE_VERSION],[base_version])
 AC_SUBST([API_VERSION],[api_version])
@@ -1143,9 +1142,6 @@ fi
 DBUS_GLIB_REQUIRED=dbus_glib_minimum_version
 AC_SUBST(DBUS_GLIB_REQUIRED)
 
-GDBUS_STANDALONE_REQUIRED=gdbus_standalone_minimum_version
-AC_SUBST(GDBUS_STANDALONE_REQUIRED)
-
 dnl ******************************
 dnl GObject marshalling
 dnl ******************************
@@ -1185,10 +1181,6 @@ PKG_CHECK_MODULES(DBUS_GLIB, dbus-glib-1 >= dbus_glib_minimum_version)
 AC_SUBST(DBUS_GLIB_CFLAGS)
 AC_SUBST(DBUS_GLIB_LIBS)
 
-PKG_CHECK_MODULES(GDBUS_STANDALONE, gdbus-standalone >= gdbus_standalone_minimum_version)
-AC_SUBST(GDBUS_STANDALONE_CFLAGS)
-AC_SUBST(GDBUS_STANDALONE_LIBS)
-
 dnl ***********************
 dnl Gnome keyring support
 dnl ***********************
@@ -1247,17 +1239,21 @@ AC_SUBST(E_BACKEND_LIBS)
 dnl ******************************
 dnl evolution-addressbook flags
 dnl ******************************
-EVOLUTION_ADDRESSBOOK_DEPS="libxml-2.0 dbus-glib-1 gdbus-standalone gobject-2.0 gthread-2.0 gconf-2.0"
+EVOLUTION_ADDRESSBOOK_DEPS="libxml-2.0 dbus-glib-1 gobject-2.0 gthread-2.0 gconf-2.0"
 
 EVO_SET_COMPILE_FLAGS(EVOLUTION_ADDRESSBOOK, $EVOLUTION_ADDRESSBOOK_DEPS)
+EVOLUTION_ADDRESSBOOK_CFLAGS="$EVOLUTION_ADDRESSBOOK_CFLAGS -I$(top_srcdir)/libedbus"
+EVOLUTION_ADDRESSBOOK_LIBS="$EVOLUTION_ADDRESSBOOK_LIBS $(top_builddir)/libedbus/libedbus.la"
+
 AC_SUBST(EVOLUTION_ADDRESSBOOK_CFLAGS)
 AC_SUBST(EVOLUTION_ADDRESSBOOK_LIBS)
 
+
 dnl ******************************
 dnl evolution-calendar flags
 dnl ******************************
 if test "x${enable_calendar}" = "xyes"; then
-	EVOLUTION_CALENDAR_DEPS="libical >= libical_minimum_version libxml-2.0 dbus-glib-1 gdbus-standalone gio-2.0 gobject-2.0 gthread-2.0 gconf-2.0"
+	EVOLUTION_CALENDAR_DEPS="libical >= libical_minimum_version libxml-2.0 dbus-glib-1 gio-2.0 gobject-2.0 gthread-2.0 gconf-2.0"
 
 dnl *****
 dnl libical.pc from libical-0.43 has a bug in it's CFlags.
@@ -1272,6 +1268,9 @@ dnl *****
 	AC_SUBST(LIBICAL_EXTRA_LIBS)
 
 	EVO_SET_COMPILE_FLAGS(EVOLUTION_CALENDAR, $EVOLUTION_CALENDAR_DEPS, $LIBICAL_EXTRA_CFLAGS, $LIBICAL_EXTRA_LIBS)
+	EVOLUTION_CALENDAR_CFLAGS="$EVOLUTION_CALENDAR_CFLAGS -I$(top_srcdir)/libedbus"
+	EVOLUTION_CALENDAR_LIBS="$EVOLUTION_CALENDAR_LIBS $(top_builddir)/libedbus/libedbus.la"
+
 	AC_SUBST(EVOLUTION_CALENDAR_CFLAGS)
 	AC_SUBST(EVOLUTION_CALENDAR_LIBS)
 fi
@@ -1495,6 +1494,7 @@ camel/tests/smime/Makefile
 camel/tests/stream/Makefile
 camel/camel.pc
 camel/camel-provider.pc
+libedbus/Makefile
 libebackend/Makefile
 libebackend/libebackend.pc
 libedataserver/Makefile
diff --git a/libedbus/example-own-name.c b/libedbus/example-own-name.c
new file mode 100644
index 0000000..a3d12a7
--- /dev/null
+++ b/libedbus/example-own-name.c
@@ -0,0 +1,77 @@
+#include <gdbus/gdbus.h>
+
+static void
+on_name_acquired (EDBusConnection *connection,
+                  const gchar     *name,
+                  gpointer         user_data)
+{
+  g_print ("Acquired the name %s on the session bus\n", name);
+}
+
+static void
+on_name_lost (EDBusConnection *connection,
+              const gchar     *name,
+              gpointer         user_data)
+{
+  g_print ("Lost the name %s on the session bus\n", name);
+}
+
+int
+main (int argc, char *argv[])
+{
+  guint owner_id;
+  GMainLoop *loop;
+  GBusNameOwnerFlags flags;
+  gboolean opt_replace;
+  gboolean opt_allow_replacement;
+  gchar *opt_name;
+  GOptionContext *opt_context;
+  GError *error;
+  GOptionEntry opt_entries[] =
+    {
+      { "replace", 'r', 0, G_OPTION_ARG_NONE, &opt_replace, "Replace existing name if possible", NULL },
+      { "allow-replacement", 'a', 0, G_OPTION_ARG_NONE, &opt_allow_replacement, "Allow replacement", NULL },
+      { "name", 'n', 0, G_OPTION_ARG_STRING, &opt_name, "Name to acquire", NULL },
+      { NULL}
+    };
+
+  g_type_init ();
+
+  error = NULL;
+  opt_name = NULL;
+  opt_replace = FALSE;
+  opt_allow_replacement = FALSE;
+  opt_context = g_option_context_new ("e_bus_own_name() example");
+  g_option_context_add_main_entries (opt_context, opt_entries, NULL);
+  if (!g_option_context_parse (opt_context, &argc, &argv, &error))
+    {
+      g_printerr ("Error parsing options: %s", error->message);
+      return 1;
+    }
+  if (opt_name == NULL)
+    {
+      g_printerr ("Incorrect usage, try --help.\n");
+      return 1;
+    }
+
+  flags = G_BUS_NAME_OWNER_FLAGS_NONE;
+  if (opt_replace)
+    flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE;
+  if (opt_allow_replacement)
+    flags |= G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT;
+
+  owner_id = e_bus_own_name (G_BUS_TYPE_SESSION,
+                             opt_name,
+                             flags,
+                             on_name_acquired,
+                             on_name_lost,
+                             NULL,
+                             NULL);
+
+  loop = g_main_loop_new (NULL, FALSE);
+  g_main_loop_run (loop);
+
+  e_bus_unown_name (owner_id);
+
+  return 0;
+}
diff --git a/libedbus/example-server.c b/libedbus/example-server.c
new file mode 100644
index 0000000..df3eeec
--- /dev/null
+++ b/libedbus/example-server.c
@@ -0,0 +1,320 @@
+
+#include <gdbus/gdbus.h>
+#include <stdlib.h>
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static EDBusNodeInfo *introspection_data = NULL;
+
+/* Introspection data for the service we are exporting */
+static const gchar introspection_xml[] =
+  "<node>"
+  "  <interface name='org.gtk.EDBus.TestInterface'>"
+  "    <method name='HelloWorld'>"
+  "      <arg type='s' name='greeting' direction='in'/>"
+  "      <arg type='s' name='response' direction='out'/>"
+  "    </method>"
+  "    <method name='EmitSignal'>"
+  "      <arg type='d' name='speed_in_mph' direction='in'/>"
+  "    </method>"
+  "    <signal name='VelocityChanged'>"
+  "      <arg type='d' name='speed_in_mph'/>"
+  "      <arg type='s' name='speed_as_string'/>"
+  "    </signal>"
+  "    <property type='s' name='FluxCapicitorName' access='read'/>"
+  "    <property type='s' name='Title' access='readwrite'/>"
+  "    <property type='s' name='ReadingAlwaysThrowsError' access='read'/>"
+  "    <property type='s' name='WritingAlwaysThrowsError' access='readwrite'/>"
+  "    <property type='s' name='OnlyWritable' access='write'/>"
+  "    <property type='s' name='Foo' access='read'/>"
+  "    <property type='s' name='Bar' access='read'/>"
+  "  </interface>"
+  "</node>";
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+handle_method_call (EDBusConnection       *connection,
+                    gpointer               user_data,
+                    const gchar           *sender,
+                    const gchar           *object_path,
+                    const gchar           *interface_name,
+                    const gchar           *method_name,
+                    EVariant              *parameters,
+                    EDBusMethodInvocation *invocation)
+{
+  if (g_strcmp0 (method_name, "HelloWorld") == 0)
+    {
+      const gchar *greeting;
+
+      e_variant_get (parameters, "(s)", &greeting);
+
+      if (g_strcmp0 (greeting, "Return Unregistered") == 0)
+        {
+          e_dbus_method_invocation_return_error (invocation,
+                                                 G_IO_ERROR,
+                                                 G_IO_ERROR_FAILED_HANDLED,
+                                                 "As requested, here's a GError not registered (G_IO_ERROR_FAILED_HANDLED)");
+        }
+      else if (g_strcmp0 (greeting, "Return Registered") == 0)
+        {
+          e_dbus_method_invocation_return_error (invocation,
+                                                 E_DBUS_ERROR,
+                                                 E_DBUS_ERROR_MATCH_RULE_NOT_FOUND,
+                                                 "As requested, here's a GError that is registered (E_DBUS_ERROR_MATCH_RULE_NOT_FOUND)");
+        }
+      else if (g_strcmp0 (greeting, "Return Raw") == 0)
+        {
+          e_dbus_method_invocation_return_dbus_error (invocation,
+                                                      "org.gtk.EDBus.SomeErrorName",
+                                                      "As requested, here's a raw D-Bus error");
+        }
+      else
+        {
+          gchar *response;
+          response = g_strdup_printf ("You greeted me with '%s'. Thanks!", greeting);
+          e_dbus_method_invocation_return_value (invocation,
+                                                 e_variant_new ("(s)", response));
+          g_free (response);
+        }
+    }
+  else if (g_strcmp0 (method_name, "EmitSignal") == 0)
+    {
+      GError *local_error;
+      gdouble speed_in_mph;
+      gchar *speed_as_string;
+
+      e_variant_get (parameters, "(d)", &speed_in_mph);
+      speed_as_string = g_strdup_printf ("%g mph!", speed_in_mph);
+
+      local_error = NULL;
+      e_dbus_connection_emit_signal (connection,
+                                     NULL,
+                                     object_path,
+                                     interface_name,
+                                     "VelocityChanged",
+                                     e_variant_new ("(ds)",
+                                                    speed_in_mph,
+                                                    speed_as_string),
+                                     &local_error);
+      g_assert_no_error (local_error);
+      g_free (speed_as_string);
+
+      e_dbus_method_invocation_return_value (invocation, NULL);
+    }
+}
+
+static gchar *_global_title = NULL;
+
+static gboolean swap_a_and_b = FALSE;
+
+static EVariant *
+handle_get_property (EDBusConnection  *connection,
+                     gpointer          user_data,
+                     const gchar      *sender,
+                     const gchar      *object_path,
+                     const gchar      *interface_name,
+                     const gchar      *property_name,
+                     GError          **error)
+{
+  EVariant *ret;
+
+  ret = NULL;
+  if (g_strcmp0 (property_name, "FluxCapicitorName") == 0)
+    {
+      ret = e_variant_new_string ("DeLorean");
+    }
+  else if (g_strcmp0 (property_name, "Title") == 0)
+    {
+      if (_global_title == NULL)
+        _global_title = g_strdup ("Back To C!");
+      ret = e_variant_new_string (_global_title);
+    }
+  else if (g_strcmp0 (property_name, "ReadingAlwaysThrowsError") == 0)
+    {
+      g_set_error (error,
+                   E_DBUS_ERROR,
+                   E_DBUS_ERROR_FAILED,
+                   "Hello %s. I thought I said reading this property "
+                   "always results in an error. kthxbye",
+                   sender);
+    }
+  else if (g_strcmp0 (property_name, "WritingAlwaysThrowsError") == 0)
+    {
+      ret = e_variant_new_string ("There's no home like home");
+    }
+  else if (g_strcmp0 (property_name, "Foo") == 0)
+    {
+      ret = e_variant_new_string (swap_a_and_b ? "Tock" : "Tick");
+    }
+  else if (g_strcmp0 (property_name, "Bar") == 0)
+    {
+      ret = e_variant_new_string (swap_a_and_b ? "Tick" : "Tock");
+    }
+
+  return ret;
+}
+
+static gboolean
+handle_set_property (EDBusConnection  *connection,
+                     gpointer          user_data,
+                     const gchar      *sender,
+                     const gchar      *object_path,
+                     const gchar      *interface_name,
+                     const gchar      *property_name,
+                     EVariant         *value,
+                     GError          **error)
+{
+  if (g_strcmp0 (property_name, "Title") == 0)
+    {
+      if (g_strcmp0 (_global_title, e_variant_get_string (value, NULL)) != 0)
+        {
+          EVariantBuilder *builder;
+          GError *local_error;
+
+          g_free (_global_title);
+          _global_title = e_variant_dup_string (value, NULL);
+
+          local_error = NULL;
+          builder = e_variant_builder_new (E_VARIANT_TYPE_ARRAY);
+          e_variant_builder_add (builder,
+                                 "{sv}",
+                                 "Title",
+                                 e_variant_new_string (_global_title));
+          e_dbus_connection_emit_signal (connection,
+                                         NULL,
+                                         object_path,
+                                         "org.freedesktop.DBus.Properties",
+                                         "PropertiesChanged",
+                                         e_variant_new ("(sa{sv})",
+                                                        interface_name,
+                                                        builder),
+                                         &local_error);
+          g_assert_no_error (local_error);
+        }
+    }
+  else if (g_strcmp0 (property_name, "ReadingAlwaysThrowsError") == 0)
+    {
+      /* do nothing - they can't read it after all! */
+    }
+  else if (g_strcmp0 (property_name, "WritingAlwaysThrowsError") == 0)
+    {
+      g_set_error (error,
+                   E_DBUS_ERROR,
+                   E_DBUS_ERROR_FAILED,
+                   "Hello AGAIN %s. I thought I said writing this property "
+                   "always results in an error. kthxbye",
+                   sender);
+    }
+
+  return *error == NULL;
+}
+
+
+/* for now */
+static const EDBusInterfaceVTable interface_vtable =
+{
+  handle_method_call,
+  handle_get_property,
+  handle_set_property
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+on_timeout_cb (gpointer user_data)
+{
+  EDBusConnection *connection = E_DBUS_CONNECTION (user_data);
+  EVariantBuilder *builder;
+  GError *error;
+
+  swap_a_and_b = !swap_a_and_b;
+
+  error = NULL;
+  builder = e_variant_builder_new (E_VARIANT_TYPE_ARRAY);
+  e_variant_builder_add (builder,
+                         "{sv}",
+                         "Foo",
+                         e_variant_new_string (swap_a_and_b ? "Tock" : "Tick"));
+  e_variant_builder_add (builder,
+                         "{sv}",
+                         "Bar",
+                         e_variant_new_string (swap_a_and_b ? "Tick" : "Tock"));
+  e_dbus_connection_emit_signal (connection,
+                                 NULL,
+                                 "/org/gtk/EDBus/TestObject",
+                                 "org.freedesktop.DBus.Properties",
+                                 "PropertiesChanged",
+                                 e_variant_new ("(sa{sv})",
+                                                "org.gtk.EDBus.TestInterface",
+                                                builder),
+                                 &error);
+  g_assert_no_error (error);
+
+
+  return TRUE;
+}
+
+static void
+on_name_acquired (EDBusConnection *connection,
+                  const gchar     *name,
+                  gpointer         user_data)
+{
+  guint registration_id;
+
+  registration_id = e_dbus_connection_register_object (connection,
+                                                       "/org/gtk/EDBus/TestObject",
+                                                       "org.gtk.EDBus.TestInterface",
+                                                       &introspection_data->interfaces[0],
+                                                       &interface_vtable,
+                                                       NULL,  /* user_data */
+                                                       NULL,  /* user_data_free_func */
+                                                       NULL); /* GError** */
+  g_assert (registration_id > 0);
+
+  /* swap value of properties Foo and Bar every two seconds */
+  g_timeout_add_seconds (2,
+                         on_timeout_cb,
+                         connection);
+}
+
+static void
+on_name_lost (EDBusConnection *connection,
+              const gchar     *name,
+              gpointer         user_data)
+{
+  exit (1);
+}
+
+int
+main (int argc, char *argv[])
+{
+  guint owner_id;
+  GMainLoop *loop;
+
+  g_type_init ();
+
+  /* We are lazy here - we don't want to manually provide
+   * the introspection data structures - so we just build
+   * them from XML.
+   */
+  introspection_data = e_dbus_node_info_new_for_xml (introspection_xml, NULL);
+  g_assert (introspection_data != NULL);
+
+  owner_id = e_bus_own_name (G_BUS_TYPE_SESSION,
+                             "org.gtk.EDBus.TestServer",
+                             G_BUS_NAME_OWNER_FLAGS_NONE,
+                             on_name_acquired,
+                             on_name_lost,
+                             NULL,
+                             NULL);
+
+  loop = g_main_loop_new (NULL, FALSE);
+  g_main_loop_run (loop);
+
+  e_bus_unown_name (owner_id);
+
+  e_dbus_node_info_free (introspection_data);
+
+  return 0;
+}
diff --git a/libedbus/example-subtree.c b/libedbus/example-subtree.c
new file mode 100644
index 0000000..7b50ecf
--- /dev/null
+++ b/libedbus/example-subtree.c
@@ -0,0 +1,388 @@
+
+#include <gdbus/gdbus.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static EDBusNodeInfo *introspection_data = NULL;
+static const EDBusInterfaceInfo *manager_interface_info = NULL;
+static const EDBusInterfaceInfo *block_interface_info = NULL;
+static const EDBusInterfaceInfo *partition_interface_info = NULL;
+
+/* Introspection data for the service we are exporting */
+static const gchar introspection_xml[] =
+  "<node>"
+  "  <interface name='org.gtk.EDBus.Example.Manager'>"
+  "    <method name='Hello'>"
+  "      <arg type='s' name='greeting' direction='in'/>"
+  "      <arg type='s' name='response' direction='out'/>"
+  "    </method>"
+  "  </interface>"
+  "  <interface name='org.gtk.EDBus.Example.Block'>"
+  "    <method name='Hello'>"
+  "      <arg type='s' name='greeting' direction='in'/>"
+  "      <arg type='s' name='response' direction='out'/>"
+  "    </method>"
+  "    <property type='i' name='Major' access='read'/>"
+  "    <property type='i' name='Minor' access='read'/>"
+  "    <property type='s' name='Notes' access='readwrite'/>"
+  "  </interface>"
+  "  <interface name='org.gtk.EDBus.Example.Partition'>"
+  "    <method name='Hello'>"
+  "      <arg type='s' name='greeting' direction='in'/>"
+  "      <arg type='s' name='response' direction='out'/>"
+  "    </method>"
+  "    <property type='i' name='PartitionNumber' access='read'/>"
+  "    <property type='s' name='Notes' access='readwrite'/>"
+  "  </interface>"
+  "</node>";
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+manager_method_call (EDBusConnection       *connection,
+                     gpointer               user_data,
+                     const gchar           *sender,
+                     const gchar           *object_path,
+                     const gchar           *interface_name,
+                     const gchar           *method_name,
+                     EVariant              *parameters,
+                     EDBusMethodInvocation *invocation)
+{
+  const gchar *greeting;
+  gchar *response;
+
+  g_assert_cmpstr (interface_name, ==, "org.gtk.EDBus.Example.Manager");
+  g_assert_cmpstr (method_name, ==, "Hello");
+
+  e_variant_get (parameters, "(s)", &greeting);
+
+  response = g_strdup_printf ("Method %s.%s with user_data `%s' on object path %s called with arg '%s'",
+                              interface_name,
+                              method_name,
+                              (const gchar *) user_data,
+                              object_path,
+                              greeting);
+  e_dbus_method_invocation_return_value (invocation,
+                                         e_variant_new ("(s)", response));
+  g_free (response);
+}
+
+const EDBusInterfaceVTable manager_vtable =
+{
+  manager_method_call,
+  NULL,                 /* get_property */
+  NULL                  /* set_property */
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+block_method_call (EDBusConnection       *connection,
+                   gpointer               user_data,
+                   const gchar           *sender,
+                   const gchar           *object_path,
+                   const gchar           *interface_name,
+                   const gchar           *method_name,
+                   EVariant              *parameters,
+                   EDBusMethodInvocation *invocation)
+{
+  g_assert_cmpstr (interface_name, ==, "org.gtk.EDBus.Example.Block");
+
+  if (g_strcmp0 (method_name, "Hello") == 0)
+    {
+      const gchar *greeting;
+      gchar *response;
+
+      e_variant_get (parameters, "(s)", &greeting);
+
+      response = g_strdup_printf ("Method %s.%s with user_data `%s' on object path %s called with arg '%s'",
+                                  interface_name,
+                                  method_name,
+                                  (const gchar *) user_data,
+                                  object_path,
+                                  greeting);
+      e_dbus_method_invocation_return_value (invocation,
+                                             e_variant_new ("(s)", response));
+      g_free (response);
+    }
+  else if (g_strcmp0 (method_name, "DoStuff") == 0)
+    {
+      e_dbus_method_invocation_return_dbus_error (invocation,
+                                                  "org.gtk.EDBus.TestSubtree.Error.Failed",
+                                                  "This method intentionally always fails");
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
+}
+
+static EVariant *
+block_get_property (EDBusConnection  *connection,
+                    gpointer          user_data,
+                    const gchar      *sender,
+                    const gchar      *object_path,
+                    const gchar      *interface_name,
+                    const gchar      *property_name,
+                    GError          **error)
+{
+  EVariant *ret;
+  const gchar *node;
+  gint major;
+  gint minor;
+
+  node = strrchr (object_path, '/') + 1;
+  if (g_str_has_prefix (node, "sda"))
+    major = 8;
+  else
+    major = 9;
+  if (strlen (node) == 4)
+    minor = node[3] - '0';
+
+  ret = NULL;
+  if (g_strcmp0 (property_name, "Major") == 0)
+    {
+      ret = e_variant_new_int32 (major);
+    }
+  else if (g_strcmp0 (property_name, "Minor") == 0)
+    {
+      ret = e_variant_new_int32 (minor);
+    }
+  else if (g_strcmp0 (property_name, "Notes") == 0)
+    {
+      g_set_error (error,
+                   E_DBUS_ERROR,
+                   E_DBUS_ERROR_FAILED,
+                   "Hello %s. I thought I said reading this property "
+                   "always results in an error. kthxbye",
+                   sender);
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
+
+  return ret;
+}
+
+static gboolean
+block_set_property (EDBusConnection  *connection,
+                    gpointer          user_data,
+                    const gchar      *sender,
+                    const gchar      *object_path,
+                    const gchar      *interface_name,
+                    const gchar      *property_name,
+                    EVariant         *value,
+                    GError          **error)
+{
+  /* TODO */
+  g_assert_not_reached ();
+}
+
+const EDBusInterfaceVTable block_vtable =
+{
+  block_method_call,
+  block_get_property,
+  block_set_property,
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+partition_method_call (EDBusConnection       *connection,
+                       gpointer               user_data,
+                       const gchar           *sender,
+                       const gchar           *object_path,
+                       const gchar           *interface_name,
+                       const gchar           *method_name,
+                       EVariant              *parameters,
+                       EDBusMethodInvocation *invocation)
+{
+  const gchar *greeting;
+  gchar *response;
+
+  g_assert_cmpstr (interface_name, ==, "org.gtk.EDBus.Example.Partition");
+  g_assert_cmpstr (method_name, ==, "Hello");
+
+  e_variant_get (parameters, "(s)", &greeting);
+
+  response = g_strdup_printf ("Method %s.%s with user_data `%s' on object path %s called with arg '%s'",
+                              interface_name,
+                              method_name,
+                              (const gchar *) user_data,
+                              object_path,
+                              greeting);
+  e_dbus_method_invocation_return_value (invocation,
+                                         e_variant_new ("(s)", response));
+  g_free (response);
+}
+
+const EDBusInterfaceVTable partition_vtable =
+{
+  partition_method_call,
+  //partition_get_property,
+  //partition_set_property
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar **
+subtree_enumerate (EDBusConnection       *connection,
+                   gpointer               user_data,
+                   const gchar           *sender,
+                   const gchar           *object_path)
+{
+  gchar **nodes;
+  GPtrArray *p;
+
+  p = g_ptr_array_new ();
+  g_ptr_array_add (p, g_strdup ("sda"));
+  g_ptr_array_add (p, g_strdup ("sda1"));
+  g_ptr_array_add (p, g_strdup ("sda2"));
+  g_ptr_array_add (p, g_strdup ("sda3"));
+  g_ptr_array_add (p, g_strdup ("sdb"));
+  g_ptr_array_add (p, g_strdup ("sdb1"));
+  g_ptr_array_add (p, g_strdup ("sdc"));
+  g_ptr_array_add (p, g_strdup ("sdc1"));
+  g_ptr_array_add (p, NULL);
+  nodes = (gchar **) g_ptr_array_free (p, FALSE);
+
+  return nodes;
+}
+
+static GPtrArray *
+subtree_introspect (EDBusConnection       *connection,
+                    gpointer               user_data,
+                    const gchar           *sender,
+                    const gchar           *object_path,
+                    const gchar           *node)
+{
+  GPtrArray *p;
+
+  p = g_ptr_array_new ();
+  if (g_strcmp0 (node, "/") == 0)
+    {
+      g_ptr_array_add (p, (gpointer) manager_interface_info);
+    }
+  else
+    {
+      g_ptr_array_add (p, (gpointer) block_interface_info);
+      if (strlen (node) == 4)
+        g_ptr_array_add (p, (gpointer) partition_interface_info);
+    }
+
+  return p;
+}
+
+static const EDBusInterfaceVTable *
+subtree_dispatch (EDBusConnection             *connection,
+                  gpointer                     user_data,
+                  const gchar                 *sender,
+                  const gchar                 *object_path,
+                  const gchar                 *interface_name,
+                  const gchar                 *node,
+                  gpointer                    *out_user_data)
+{
+  const EDBusInterfaceVTable *vtable_to_return;
+  gpointer user_data_to_return;
+
+  if (g_strcmp0 (interface_name, "org.gtk.EDBus.Example.Manager") == 0)
+    {
+      user_data_to_return = "The Root";
+      vtable_to_return = &manager_vtable;
+    }
+  else
+    {
+      if (strlen (node) == 4)
+        user_data_to_return = "A partition";
+      else
+        user_data_to_return = "A block device";
+
+      if (g_strcmp0 (interface_name, "org.gtk.EDBus.Example.Block") == 0)
+        vtable_to_return = &block_vtable;
+      else if (g_strcmp0 (interface_name, "org.gtk.EDBus.Example.Partition") == 0)
+        vtable_to_return = &partition_vtable;
+      else
+        g_assert_not_reached ();
+    }
+
+  *out_user_data = user_data_to_return;
+
+  return vtable_to_return;
+}
+
+const EDBusSubtreeVTable subtree_vtable =
+{
+  subtree_enumerate,
+  subtree_introspect,
+  subtree_dispatch
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_name_acquired (EDBusConnection *connection,
+                  const gchar     *name,
+                  gpointer         user_data)
+{
+  guint registration_id;
+
+  registration_id = e_dbus_connection_register_subtree (connection,
+                                                       "/org/gtk/EDBus/TestSubtree/Devices",
+                                                        &subtree_vtable,
+                                                        E_DBUS_SUBTREE_FLAGS_NONE,
+                                                        NULL,  /* user_data */
+                                                        NULL,  /* user_data_free_func */
+                                                        NULL); /* GError** */
+  g_assert (registration_id > 0);
+}
+
+static void
+on_name_lost (EDBusConnection *connection,
+              const gchar     *name,
+              gpointer         user_data)
+{
+  exit (1);
+}
+
+int
+main (int argc, char *argv[])
+{
+  guint owner_id;
+  GMainLoop *loop;
+
+  g_type_init ();
+
+  /* We are lazy here - we don't want to manually provide
+   * the introspection data structures - so we just build
+   * them from XML.
+   */
+  introspection_data = e_dbus_node_info_new_for_xml (introspection_xml, NULL);
+  g_assert (introspection_data != NULL);
+
+  manager_interface_info = e_dbus_node_info_lookup_interface (introspection_data, "org.gtk.EDBus.Example.Manager");
+  block_interface_info = e_dbus_node_info_lookup_interface (introspection_data, "org.gtk.EDBus.Example.Block");
+  partition_interface_info = e_dbus_node_info_lookup_interface (introspection_data, "org.gtk.EDBus.Example.Partition");
+  g_assert (manager_interface_info != NULL);
+  g_assert (block_interface_info != NULL);
+  g_assert (partition_interface_info != NULL);
+
+  owner_id = e_bus_own_name (G_BUS_TYPE_SESSION,
+                             "org.gtk.EDBus.TestSubtree",
+                             G_BUS_NAME_OWNER_FLAGS_NONE,
+                             on_name_acquired,
+                             on_name_lost,
+                             NULL,
+                             NULL);
+
+  loop = g_main_loop_new (NULL, FALSE);
+  g_main_loop_run (loop);
+
+  e_bus_unown_name (owner_id);
+
+  e_dbus_node_info_free (introspection_data);
+
+  return 0;
+}
diff --git a/libedbus/example-watch-name.c b/libedbus/example-watch-name.c
new file mode 100644
index 0000000..63a74c9
--- /dev/null
+++ b/libedbus/example-watch-name.c
@@ -0,0 +1,80 @@
+#include <gdbus/gdbus.h>
+
+static gchar *opt_name         = NULL;
+static gboolean opt_system_bus = FALSE;
+
+static GOptionEntry opt_entries[] =
+{
+  { "name", 'n', 0, G_OPTION_ARG_STRING, &opt_name, "Name to watch", NULL },
+  { "system-bus", 's', 0, G_OPTION_ARG_NONE, &opt_system_bus, "Use the system-bus instead of the session-bus", NULL },
+  { NULL}
+};
+
+static void
+on_name_appeared (EDBusConnection *connection,
+                  const gchar     *name,
+                  const gchar     *name_owner,
+                  gpointer         user_data)
+{
+  g_print ("Name %s on %s is owned by %s\n",
+           name,
+           opt_system_bus ? "the system bus" : "the session bus",
+           name_owner);
+}
+
+static void
+on_name_vanished (EDBusConnection *connection,
+                  const gchar     *name,
+                  gpointer         user_data)
+{
+  g_print ("Name %s does not exist on %s\n",
+           name,
+           opt_system_bus ? "the system bus" : "the session bus");
+}
+
+int
+main (int argc, char *argv[])
+{
+  guint watcher_id;
+  GMainLoop *loop;
+  GOptionContext *opt_context;
+  GError *error;
+
+  g_type_init ();
+
+  error = NULL;
+  opt_context = g_option_context_new ("e_bus_watch_name() example");
+  g_option_context_set_summary (opt_context,
+                                "Example: to watch the power manager on the session bus, use:\n"
+                                "\n"
+                                "  ./example-watch-name -n org.gnome.PowerManager");
+  g_option_context_add_main_entries (opt_context, opt_entries, NULL);
+  if (!g_option_context_parse (opt_context, &argc, &argv, &error))
+    {
+      g_printerr ("Error parsing options: %s", error->message);
+      goto out;
+    }
+  if (opt_name == NULL)
+    {
+      g_printerr ("Incorrect usage, try --help.\n");
+      goto out;
+    }
+
+  watcher_id = e_bus_watch_name (opt_system_bus ? G_BUS_TYPE_SYSTEM : G_BUS_TYPE_SESSION,
+                                 opt_name,
+                                 on_name_appeared,
+                                 on_name_vanished,
+                                 NULL,
+                                 NULL);
+
+  loop = g_main_loop_new (NULL, FALSE);
+  g_main_loop_run (loop);
+
+  e_bus_unwatch_name (watcher_id);
+
+ out:
+  g_option_context_free (opt_context);
+  g_free (opt_name);
+
+  return 0;
+}
diff --git a/libedbus/example-watch-proxy.c b/libedbus/example-watch-proxy.c
new file mode 100644
index 0000000..151e4cd
--- /dev/null
+++ b/libedbus/example-watch-proxy.c
@@ -0,0 +1,177 @@
+#include <gdbus/gdbus.h>
+
+static gchar *opt_name         = NULL;
+static gchar *opt_object_path  = NULL;
+static gchar *opt_interface    = NULL;
+static gboolean opt_system_bus = FALSE;
+
+static GOptionEntry opt_entries[] =
+{
+  { "name", 'n', 0, G_OPTION_ARG_STRING, &opt_name, "Name of the remote object to watch", NULL },
+  { "object-path", 'o', 0, G_OPTION_ARG_STRING, &opt_object_path, "Object path of the remote object", NULL },
+  { "interface", 'i', 0, G_OPTION_ARG_STRING, &opt_interface, "D-Bus interface of remote object", NULL },
+  { "system-bus", 's', 0, G_OPTION_ARG_NONE, &opt_system_bus, "Use the system-bus instead of the session-bus", NULL },
+  { NULL}
+};
+
+static void
+print_properties (EDBusProxy *proxy)
+{
+  gchar **property_names;
+  guint n;
+
+  g_print ("    properties:\n");
+
+  property_names = e_dbus_proxy_get_cached_property_names (proxy, NULL);
+  for (n = 0; property_names != NULL && property_names[n] != NULL; n++)
+    {
+      const gchar *key = property_names[n];
+      EVariant *value;
+      gchar *value_str;
+      value = e_dbus_proxy_get_cached_property (proxy, key, NULL);
+      value_str = e_variant_print (value, TRUE);
+      g_print ("      %s -> %s\n", key, value_str);
+      e_variant_unref (value);
+      g_free (value_str);
+    }
+  g_strfreev (property_names);
+}
+
+static void
+on_properties_changed (EDBusProxy *proxy,
+                       GHashTable *changed_properties,
+                       gpointer    user_data)
+{
+  GHashTableIter iter;
+  const gchar *key;
+  EVariant *value;
+
+  g_print (" *** Properties Changed:\n");
+
+  g_hash_table_iter_init (&iter, changed_properties);
+  while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &value))
+    {
+      gchar *value_str;
+      value_str = e_variant_print (value, TRUE);
+      g_print ("      %s -> %s\n", key, value_str);
+      g_free (value_str);
+    }
+}
+
+static void
+on_signal (EDBusProxy *proxy,
+           gchar      *sender_name,
+           gchar      *signal_name,
+           EVariant   *parameters,
+           gpointer    user_data)
+{
+  gchar *parameters_str;
+
+  parameters_str = e_variant_print (parameters, TRUE);
+  g_print (" *** Received Signal: %s: %s\n",
+           signal_name,
+           parameters_str);
+  g_free (parameters_str);
+}
+
+static void
+on_proxy_appeared (EDBusConnection *connection,
+                   const gchar     *name,
+                   const gchar     *name_owner,
+                   EDBusProxy      *proxy,
+                   gpointer         user_data)
+{
+  g_print ("+++ Acquired proxy object for remote object owned by %s\n"
+           "    bus:          %s\n"
+           "    name:         %s\n"
+           "    object path:  %s\n"
+           "    interface:    %s\n",
+           name_owner,
+           opt_system_bus ? "System Bus" : "Session Bus",
+           opt_name,
+           opt_object_path,
+           opt_interface);
+
+  print_properties (proxy);
+
+  g_signal_connect (proxy,
+                    "g-properties-changed",
+                    G_CALLBACK (on_properties_changed),
+                    NULL);
+
+  g_signal_connect (proxy,
+                    "g-signal",
+                    G_CALLBACK (on_signal),
+                    NULL);
+}
+
+static void
+on_proxy_vanished (EDBusConnection *connection,
+                   const gchar     *name,
+                   gpointer         user_data)
+{
+  g_print ("--- Cannot create proxy object for\n"
+           "    bus:          %s\n"
+           "    name:         %s\n"
+           "    object path:  %s\n"
+           "    interface:    %s\n",
+           opt_system_bus ? "System Bus" : "Session Bus",
+           opt_name,
+           opt_object_path,
+           opt_interface);
+}
+
+int
+main (int argc, char *argv[])
+{
+  guint watcher_id;
+  GMainLoop *loop;
+  GOptionContext *opt_context;
+  GError *error;
+
+  g_type_init ();
+
+  opt_context = g_option_context_new ("e_bus_watch_proxy() example");
+  g_option_context_set_summary (opt_context,
+                                "Example: to watch the object of example-server, use:\n"
+                                "\n"
+                                "  ./example-watch-proxy -n org.gtk.EDBus.TestServer  \\\n"
+                                "                        -o /org/gtk/EDBus/TestObject \\\n"
+                                "                        -i org.gtk.EDBus.TestInterface");
+  g_option_context_add_main_entries (opt_context, opt_entries, NULL);
+  error = NULL;
+  if (!g_option_context_parse (opt_context, &argc, &argv, &error))
+    {
+      g_printerr ("Error parsing options: %s", error->message);
+      goto out;
+    }
+  if (opt_name == NULL || opt_object_path == NULL || opt_interface == NULL)
+    {
+      g_printerr ("Incorrect usage, try --help.\n");
+      goto out;
+    }
+
+  watcher_id = e_bus_watch_proxy (opt_system_bus ? G_BUS_TYPE_SYSTEM : G_BUS_TYPE_SESSION,
+                                  opt_name,
+                                  opt_object_path,
+                                  opt_interface,
+                                  E_TYPE_DBUS_PROXY,
+                                  E_DBUS_PROXY_FLAGS_NONE,
+                                  on_proxy_appeared,
+                                  on_proxy_vanished,
+                                  NULL,
+                                  NULL);
+
+  loop = g_main_loop_new (NULL, FALSE);
+  g_main_loop_run (loop);
+
+  e_bus_unwatch_proxy (watcher_id);
+
+ out:
+  g_option_context_free (opt_context);
+  g_free (opt_name);
+  g_free (opt_object_path);
+  g_free (opt_interface);
+
+  return 0;
+}
diff --git a/libedbus/gbitlock.c b/libedbus/gbitlock.c
new file mode 100644
index 0000000..745aed1
--- /dev/null
+++ b/libedbus/gbitlock.c
@@ -0,0 +1,298 @@
+/*
+ * Copyright © 2008 Ryan Lortie
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library 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 licence, or (at your option) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#include "gbitlock.h"
+
+#include <glib/gatomic.h>
+#include <glib/gslist.h>
+#include <glib/gthread.h>
+
+// #include "gthreadprivate.h"
+#include "config.h"
+
+#ifdef G_BIT_LOCK_FORCE_FUTEX_EMULATION
+#undef HAVE_FUTEX
+#endif
+
+#ifndef HAVE_FUTEX
+static GSList *g_futex_address_list = NULL;
+static GMutex *g_futex_mutex = NULL;
+#endif
+
+#warning Need to initialize me ...
+extern void _g_futex_thread_init(void);
+
+void
+_g_futex_thread_init (void) {
+#ifndef HAVE_FUTEX
+  g_futex_mutex = g_mutex_new ();
+#endif
+}
+
+#ifdef HAVE_FUTEX
+/*
+ * We have headers for futex(2) on the build machine.  This does not
+ * imply that every system that ever runs the resulting glib will have
+ * kernel support for futex, but you'd have to have a pretty old
+ * kernel in order for that not to be the case.
+ *
+ * If anyone actually gets bit by this, please file a bug. :)
+ */
+#include <linux/futex.h>
+#include <syscall.h>
+#include <unistd.h>
+
+/* < private >
+ * g_futex_wait:
+ * @address: a pointer to an integer
+ * @value: the value that should be at @address
+ *
+ * Atomically checks that the value stored at @address is equal to
+ * @value and then blocks.  If the value stored at @address is not
+ * equal to @value then this function returns immediately.
+ *
+ * To unblock, call g_futex_wake() on @address.
+ *
+ * This call may spuriously unblock (for example, in response to the
+ * process receiving a signal) but this is not guaranteed.  Unlike the
+ * Linux system call of a similar name, there is no guarantee that a
+ * waiting process will unblock due to a g_futex_wake() call in a
+ * separate process.
+ */
+static void
+g_futex_wait (const volatile gint *address,
+              gint                 value)
+{
+  syscall (SYS_futex, address, (gsize) FUTEX_WAIT, (gsize) value, NULL);
+}
+
+/* < private >
+ * g_futex_wake:
+ * @address: a pointer to an integer
+ *
+ * Nominally, wakes one thread that is blocked in g_futex_wait() on
+ * @address (if any thread is currently waiting).
+ *
+ * As mentioned in the documention for g_futex_wait(), spurious
+ * wakeups may occur.  As such, this call may result in more than one
+ * thread being woken up.
+ */
+static void
+g_futex_wake (const volatile gint *address)
+{
+  syscall (SYS_futex, address, (gsize) FUTEX_WAKE, (gsize) 1, NULL);
+}
+
+#else
+
+/* emulate futex(2) */
+typedef struct
+{
+  const volatile gint *address;
+  gint                 ref_count;
+  GCond               *wait_queue;
+} WaitAddress;
+
+static WaitAddress *
+g_futex_find_address (const volatile gint *address)
+{
+  GSList *node;
+
+  for (node = g_futex_address_list; node; node = node->next)
+    {
+      WaitAddress *waiter = node->data;
+
+      if (waiter->address == address)
+        return waiter;
+    }
+
+  return NULL;
+}
+
+static void
+g_futex_wait (const volatile gint *address,
+              gint                 value)
+{
+  g_mutex_lock (g_futex_mutex);
+  if G_LIKELY (g_atomic_int_get (address) == value)
+    {
+      WaitAddress *waiter;
+
+      if ((waiter = g_futex_find_address (address)) == NULL)
+        {
+          waiter = g_slice_new (WaitAddress);
+          waiter->address = address;
+          waiter->wait_queue = g_cond_new ();
+          waiter->ref_count = 0;
+          g_futex_address_list =
+            g_slist_prepend (g_futex_address_list, waiter);
+        }
+
+      waiter->ref_count++;
+      g_cond_wait (waiter->wait_queue, g_futex_mutex);
+
+      if (!--waiter->ref_count)
+        {
+          g_futex_address_list =
+            g_slist_remove (g_futex_address_list, waiter);
+          g_cond_free (waiter->wait_queue);
+          g_slice_free (WaitAddress, waiter);
+        }
+    }
+  g_mutex_unlock (g_futex_mutex);
+}
+
+static void
+g_futex_wake (const volatile gint *address)
+{
+  WaitAddress *waiter;
+
+  /* need to lock here for two reasons:
+   *   1) need to acquire/release lock to ensure waiter is not in
+   *      the process of registering a wait
+   *   2) need to -stay- locked until the end to ensure a wake()
+   *      in another thread doesn't cause 'waiter' to stop existing
+   */
+  g_mutex_lock (g_futex_mutex);
+  if ((waiter = g_futex_find_address (address)))
+    g_cond_signal (waiter->wait_queue);
+  g_mutex_unlock (g_futex_mutex);
+}
+#endif
+
+#define CONTENTION_CLASSES 11
+static volatile gint e_bit_lock_contended[CONTENTION_CLASSES];
+
+/**
+ * e_bit_lock:
+ * @address: a pointer to an integer
+ * @lock_bit: a bit value between 0 and 31
+ *
+ * Sets the indicated @lock_bit in @address.  If the bit is already
+ * set, this call will block until e_bit_unlock() unsets the
+ * corresponding bit.
+ *
+ * Attempting to lock on two different bits within the same integer is
+ * not supported and will very probably cause deadlocks.
+ *
+ * The value of the bit that is set is (1u << @bit).  If @bit is not
+ * between 0 and 31 then the result is undefined.
+ *
+ * This function accesses @address atomically.  All other accesses to
+ * @address must be atomic in order for this function to work
+ * reliably.
+ *
+ * Since: 2.24
+ **/
+void
+e_bit_lock (volatile gint *address,
+            gint           lock_bit)
+{
+  guint v;
+
+ retry:
+  v = g_atomic_int_get (address);
+  if (v & (1u << lock_bit))
+    /* already locked */
+    {
+      guint class = ((gsize) address) % G_N_ELEMENTS (e_bit_lock_contended);
+
+      g_atomic_int_add (&e_bit_lock_contended[class], +1);
+      g_futex_wait (address, v);
+      g_atomic_int_add (&e_bit_lock_contended[class], -1);
+
+      goto retry;
+    }
+
+  if (!g_atomic_int_compare_and_exchange (address, v, v | (1u << lock_bit)))
+    goto retry;
+}
+
+/**
+ * e_bit_trylock:
+ * @address: a pointer to an integer
+ * @lock_bit: a bit value between 0 and 31
+ * @returns: %TRUE if the lock was acquired
+ *
+ * Sets the indicated @lock_bit in @address, returning %TRUE if
+ * successful.  If the bit is already set, returns %FALSE immediately.
+ *
+ * Attempting to lock on two different bits within the same integer is
+ * not supported.
+ *
+ * The value of the bit that is set is (1u << @bit).  If @bit is not
+ * between 0 and 31 then the result is undefined.
+ *
+ * This function accesses @address atomically.  All other accesses to
+ * @address must be atomic in order for this function to work
+ * reliably.
+ *
+ * Since: 2.24
+ **/
+gboolean
+e_bit_trylock (volatile gint *address,
+               gint           lock_bit)
+{
+  guint v;
+
+ retry:
+  v = g_atomic_int_get (address);
+  if (v & (1u << lock_bit))
+    /* already locked */
+    return FALSE;
+
+  if (!g_atomic_int_compare_and_exchange (address, v, v | (1u << lock_bit)))
+    goto retry;
+
+  return TRUE;
+}
+
+/**
+ * e_bit_unlock:
+ * @address: a pointer to an integer
+ * @lock_bit: a bit value between 0 and 31
+ *
+ * Clears the indicated @lock_bit in @address.  If another thread is
+ * currently blocked in e_bit_lock() on this same bit then it will be
+ * woken up.
+ *
+ * This function accesses @address atomically.  All other accesses to
+ * @address must be atomic in order for this function to work
+ * reliably.
+ *
+ * Since: 2.24
+ **/
+void
+e_bit_unlock (volatile gint *address,
+              gint           lock_bit)
+{
+  guint class = ((gsize) address) % G_N_ELEMENTS (e_bit_lock_contended);
+  guint v;
+
+ retry:
+  v = g_atomic_int_get (address);
+  if (!g_atomic_int_compare_and_exchange (address, v, v & ~(1u << lock_bit)))
+    goto retry;
+
+  if (g_atomic_int_get (&e_bit_lock_contended[class]))
+    g_futex_wake (address);
+}
diff --git a/libedbus/gbitlock.h b/libedbus/gbitlock.h
new file mode 100644
index 0000000..3ea9898
--- /dev/null
+++ b/libedbus/gbitlock.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2008 Ryan Lortie
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library 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 licence, or (at your option) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#ifndef __G_BITLOCK_H__
+#define __G_BITLOCK_H__
+
+#include <glib/gtypes.h>
+
+#if defined(G_DISABLE_SINGLE_INCLUDES) && !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION)
+#error "Only <glib.h> can be included directly."
+#endif
+
+G_BEGIN_DECLS
+
+void      e_bit_lock                      (volatile gint *address,
+                                           gint           lock_bit);
+gboolean  e_bit_trylock                   (volatile gint *address,
+                                           gint           lock_bit);
+void      e_bit_unlock                    (volatile gint *address,
+                                           gint           lock_bit);
+
+G_END_DECLS
+
+#endif /* __G_BITLOCK_H_ */
diff --git a/libedbus/gdbus-marshal.c b/libedbus/gdbus-marshal.c
new file mode 100644
index 0000000..732f9fd
--- /dev/null
+++ b/libedbus/gdbus-marshal.c
@@ -0,0 +1,89 @@
+#include "gdbus-marshal.h"
+
+#include	<glib-object.h>
+
+
+#ifdef G_ENABLE_DEBUG
+#define g_marshal_value_peek_boolean(v)  g_value_get_boolean (v)
+#define g_marshal_value_peek_char(v)     g_value_get_char (v)
+#define g_marshal_value_peek_uchar(v)    g_value_get_uchar (v)
+#define g_marshal_value_peek_int(v)      g_value_get_int (v)
+#define g_marshal_value_peek_uint(v)     g_value_get_uint (v)
+#define g_marshal_value_peek_long(v)     g_value_get_long (v)
+#define g_marshal_value_peek_ulong(v)    g_value_get_ulong (v)
+#define g_marshal_value_peek_int64(v)    g_value_get_int64 (v)
+#define g_marshal_value_peek_uint64(v)   g_value_get_uint64 (v)
+#define g_marshal_value_peek_enum(v)     g_value_get_enum (v)
+#define g_marshal_value_peek_flags(v)    g_value_get_flags (v)
+#define g_marshal_value_peek_float(v)    g_value_get_float (v)
+#define g_marshal_value_peek_double(v)   g_value_get_double (v)
+#define g_marshal_value_peek_string(v)   (char*) g_value_get_string (v)
+#define g_marshal_value_peek_param(v)    g_value_get_param (v)
+#define g_marshal_value_peek_boxed(v)    g_value_get_boxed (v)
+#define g_marshal_value_peek_pointer(v)  g_value_get_pointer (v)
+#define g_marshal_value_peek_object(v)   g_value_get_object (v)
+#else /* !G_ENABLE_DEBUG */
+/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API.
+ *          Do not access GValues directly in your code. Instead, use the
+ *          g_value_get_*() functions
+ */
+#define g_marshal_value_peek_boolean(v)  (v)->data[0].v_int
+#define g_marshal_value_peek_char(v)     (v)->data[0].v_int
+#define g_marshal_value_peek_uchar(v)    (v)->data[0].v_uint
+#define g_marshal_value_peek_int(v)      (v)->data[0].v_int
+#define g_marshal_value_peek_uint(v)     (v)->data[0].v_uint
+#define g_marshal_value_peek_long(v)     (v)->data[0].v_long
+#define g_marshal_value_peek_ulong(v)    (v)->data[0].v_ulong
+#define g_marshal_value_peek_int64(v)    (v)->data[0].v_int64
+#define g_marshal_value_peek_uint64(v)   (v)->data[0].v_uint64
+#define g_marshal_value_peek_enum(v)     (v)->data[0].v_long
+#define g_marshal_value_peek_flags(v)    (v)->data[0].v_ulong
+#define g_marshal_value_peek_float(v)    (v)->data[0].v_float
+#define g_marshal_value_peek_double(v)   (v)->data[0].v_double
+#define g_marshal_value_peek_string(v)   (v)->data[0].v_pointer
+#define g_marshal_value_peek_param(v)    (v)->data[0].v_pointer
+#define g_marshal_value_peek_boxed(v)    (v)->data[0].v_pointer
+#define g_marshal_value_peek_pointer(v)  (v)->data[0].v_pointer
+#define g_marshal_value_peek_object(v)   (v)->data[0].v_pointer
+#endif /* !G_ENABLE_DEBUG */
+
+
+/* VOID:STRING,STRING,BOXED (./gdbus-marshal.list:1) */
+void
+_gdbus_marshal_VOID__STRING_STRING_BOXED (GClosure     *closure,
+                                          GValue       *return_value G_GNUC_UNUSED,
+                                          guint         n_param_values,
+                                          const GValue *param_values,
+                                          gpointer      invocation_hint G_GNUC_UNUSED,
+                                          gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__STRING_STRING_BOXED) (gpointer     data1,
+                                                          gpointer     arg_1,
+                                                          gpointer     arg_2,
+                                                          gpointer     arg_3,
+                                                          gpointer     data2);
+  register GMarshalFunc_VOID__STRING_STRING_BOXED callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 4);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__STRING_STRING_BOXED) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_string (param_values + 1),
+            g_marshal_value_peek_string (param_values + 2),
+            g_marshal_value_peek_boxed (param_values + 3),
+            data2);
+}
+
diff --git a/libedbus/gdbus-marshal.h b/libedbus/gdbus-marshal.h
new file mode 100644
index 0000000..fa40318
--- /dev/null
+++ b/libedbus/gdbus-marshal.h
@@ -0,0 +1,20 @@
+
+#ifndef ___gdbus_marshal_MARSHAL_H__
+#define ___gdbus_marshal_MARSHAL_H__
+
+#include	<glib-object.h>
+
+G_BEGIN_DECLS
+
+/* VOID:STRING,STRING,BOXED (./gdbus-marshal.list:1) */
+G_GNUC_INTERNAL void _gdbus_marshal_VOID__STRING_STRING_BOXED (GClosure     *closure,
+                                                               GValue       *return_value,
+                                                               guint         n_param_values,
+                                                               const GValue *param_values,
+                                                               gpointer      invocation_hint,
+                                                               gpointer      marshal_data);
+
+G_END_DECLS
+
+#endif /* ___gdbus_marshal_MARSHAL_H__ */
+
diff --git a/libedbus/gdbus-marshal.list b/libedbus/gdbus-marshal.list
new file mode 100644
index 0000000..e42df79
--- /dev/null
+++ b/libedbus/gdbus-marshal.list
@@ -0,0 +1 @@
+VOID:STRING,STRING,BOXED
diff --git a/libedbus/gdbus.h b/libedbus/gdbus.h
new file mode 100644
index 0000000..84a33a4
--- /dev/null
+++ b/libedbus/gdbus.h
@@ -0,0 +1,44 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __E_DBUS_H__
+#define __E_DBUS_H__
+
+#define __E_DBUS_H_INSIDE__
+
+#include <gdbus/gdbustypes.h>
+#include <gdbus/gdbusenumtypes.h>
+#include <gdbus/gdbusconnection.h>
+#include <gdbus/gdbuserror.h>
+#include <gdbus/gdbusnameowning.h>
+#include <gdbus/gdbusnamewatching.h>
+#include <gdbus/gdbusproxywatching.h>
+#include <gdbus/gdbusproxy.h>
+#include <gdbus/gdbusintrospection.h>
+#include <gdbus/gdbusmethodinvocation.h>
+#include <gdbus/gdbusserver.h>
+
+extern void e_dbus_threads_init (void);
+
+#undef __E_DBUS_H_INSIDE__
+
+#endif /* __E_DBUS_H__ */
diff --git a/libedbus/gdbusconnection.c b/libedbus/gdbusconnection.c
new file mode 100644
index 0000000..3025073
--- /dev/null
+++ b/libedbus/gdbusconnection.c
@@ -0,0 +1,4228 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbusconnection.h"
+#include "gdbuserror.h"
+#include "gdbusenumtypes.h"
+#include "gdbusconversion.h"
+#include "gdbusintrospection.h"
+#include "gdbusmethodinvocation.h"
+#include "gdbusprivate.h"
+
+/**
+ * SECTION:gdbusconnection
+ * @short_description: D-Bus Connections
+ * @include: gdbus/gdbus.h
+ *
+ * <para><note>
+ * This class is rarely used directly. If you are writing an application, it is often
+ * easier to use the e_bus_own_name() or e_bus_watch_name() APIs.
+ * </note></para>
+ * #EDBusConnection is a thin wrapper class for the #DBusConnection
+ * type that integrates with the GLib type system.
+ *
+ * TODO: stuff about caching unix_process_id etc. when we add that.
+ */
+
+struct _EDBusConnectionPrivate
+{
+  DBusConnection *dbus_1_connection;
+
+  /* construct properties */
+  GBusType        bus_type;
+  gchar          *address;
+  gboolean        is_private;
+
+  gboolean is_initialized;
+  GError *initialization_error;
+
+  /* unfortunately there is no dbus_connection_get_exit_on_disconnect() so we need to track this ourselves */
+  gboolean exit_on_disconnect;
+
+  /* Maps used for signal subscription */
+  GHashTable *map_rule_to_signal_data;
+  GHashTable *map_id_to_signal_data;
+  GHashTable *map_sender_to_signal_data_array;
+
+  /* Maps used for exporting interfaces */
+  GHashTable *map_object_path_to_eo; /* gchar* -> ExportedObject* */
+  GHashTable *map_id_to_ei;          /* guint -> ExportedInterface* */
+
+  /* Map used for exporting subtrees */
+  GHashTable *map_object_path_to_es; /* gchar* -> ExportedSubtree* */
+  GHashTable *map_id_to_es;          /* guint  -> ExportedSubtree* */
+};
+
+static void         e_dbus_connection_send_dbus_1_message_with_reply           (EDBusConnection    *connection,
+                                                                                DBusMessage        *message,
+                                                                                gint                timeout_msec,
+                                                                                GCancellable       *cancellable,
+                                                                                GAsyncReadyCallback callback,
+                                                                                gpointer            user_data);
+static DBusMessage *e_dbus_connection_send_dbus_1_message_with_reply_finish    (EDBusConnection    *connection,
+                                                                                GAsyncResult       *res,
+                                                                                GError            **error);
+static DBusMessage *e_dbus_connection_send_dbus_1_message_with_reply_sync      (EDBusConnection    *connection,
+                                                                                DBusMessage        *message,
+                                                                                gint                timeout_msec,
+                                                                                GCancellable       *cancellable,
+                                                                                GError            **error);
+
+typedef struct ExportedObject ExportedObject;
+static void exported_object_free (ExportedObject *eo);
+
+typedef struct ExportedSubtree ExportedSubtree;
+static void exported_subtree_free (ExportedSubtree *es);
+
+enum
+{
+  DISCONNECTED_SIGNAL,
+  LAST_SIGNAL,
+};
+
+enum
+{
+  PROP_0,
+  PROP_BUS_TYPE,
+  PROP_ADDRESS,
+  PROP_IS_PRIVATE,
+  PROP_UNIQUE_NAME,
+  PROP_IS_DISCONNECTED,
+  PROP_EXIT_ON_DISCONNECT,
+};
+
+static void distribute_signals (EDBusConnection *connection,
+                                DBusMessage     *message);
+
+static void purge_all_signal_subscriptions (EDBusConnection *connection);
+
+G_LOCK_DEFINE_STATIC (connection_lock);
+
+static EDBusConnection *the_session_bus = NULL;
+static EDBusConnection *the_system_bus = NULL;
+
+static GObject *e_dbus_connection_constructor (GType                  type,
+                                               guint                  n_construct_properties,
+                                               GObjectConstructParam *construct_properties);
+
+static DBusHandlerResult
+filter_function (DBusConnection *dbus_1_connection,
+                 DBusMessage    *message,
+                 void           *user_data);
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void e_dbus_connection_set_dbus_1_connection (EDBusConnection *connection,
+                                                     DBusConnection  *dbus_1_connection);
+
+static void initable_iface_init       (GInitableIface *initable_iface);
+static void async_initable_iface_init (GAsyncInitableIface *async_initable_iface);
+
+G_DEFINE_TYPE_WITH_CODE (EDBusConnection, e_dbus_connection, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)
+                         G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
+                         );
+
+static void
+e_dbus_connection_dispose (GObject *object)
+{
+  EDBusConnection *connection = E_DBUS_CONNECTION (object);
+
+  G_LOCK (connection_lock);
+  if (connection == the_session_bus)
+    {
+      the_session_bus = NULL;
+    }
+  else if (connection == the_system_bus)
+    {
+      the_system_bus = NULL;
+    }
+  G_UNLOCK (connection_lock);
+
+  if (G_OBJECT_CLASS (e_dbus_connection_parent_class)->dispose != NULL)
+    G_OBJECT_CLASS (e_dbus_connection_parent_class)->dispose (object);
+}
+
+static void
+e_dbus_connection_finalize (GObject *object)
+{
+  EDBusConnection *connection = E_DBUS_CONNECTION (object);
+
+  e_dbus_connection_set_dbus_1_connection (connection, NULL);
+
+  if (connection->priv->initialization_error != NULL)
+    g_error_free (connection->priv->initialization_error);
+
+  g_free (connection->priv->address);
+
+  purge_all_signal_subscriptions (connection);
+  g_hash_table_unref (connection->priv->map_rule_to_signal_data);
+  g_hash_table_unref (connection->priv->map_id_to_signal_data);
+  g_hash_table_unref (connection->priv->map_sender_to_signal_data_array);
+
+  g_hash_table_unref (connection->priv->map_id_to_ei);
+  g_hash_table_unref (connection->priv->map_object_path_to_eo);
+  g_hash_table_unref (connection->priv->map_id_to_es);
+  g_hash_table_unref (connection->priv->map_object_path_to_es);
+
+  if (G_OBJECT_CLASS (e_dbus_connection_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (e_dbus_connection_parent_class)->finalize (object);
+}
+
+static void
+e_dbus_connection_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  EDBusConnection *connection = E_DBUS_CONNECTION (object);
+
+  switch (prop_id)
+    {
+    case PROP_BUS_TYPE:
+      g_value_set_enum (value, e_dbus_connection_get_bus_type (connection));
+      break;
+
+    case PROP_ADDRESS:
+      g_value_set_string (value, e_dbus_connection_get_address (connection));
+      break;
+
+    case PROP_IS_PRIVATE:
+      g_value_set_boolean (value, e_dbus_connection_get_is_private (connection));
+      break;
+
+    case PROP_UNIQUE_NAME:
+      g_value_set_string (value, e_dbus_connection_get_unique_name (connection));
+      break;
+
+    case PROP_IS_DISCONNECTED:
+      g_value_set_boolean (value, e_dbus_connection_get_is_disconnected (connection));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+e_dbus_connection_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  EDBusConnection *connection = E_DBUS_CONNECTION (object);
+
+  switch (prop_id)
+    {
+    case PROP_BUS_TYPE:
+      connection->priv->bus_type = g_value_get_enum (value);
+      break;
+
+    case PROP_ADDRESS:
+      connection->priv->address = g_value_dup_string (value);
+      break;
+
+    case PROP_IS_PRIVATE:
+      connection->priv->is_private = g_value_get_boolean (value);
+      break;
+
+    case PROP_EXIT_ON_DISCONNECT:
+      e_dbus_connection_set_exit_on_disconnect (connection, g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+e_dbus_connection_class_init (EDBusConnectionClass *klass)
+{
+  GObjectClass *gobject_class;
+
+  g_type_class_add_private (klass, sizeof (EDBusConnectionPrivate));
+
+  gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->constructor  = e_dbus_connection_constructor;
+  gobject_class->finalize     = e_dbus_connection_finalize;
+  gobject_class->dispose      = e_dbus_connection_dispose;
+  gobject_class->set_property = e_dbus_connection_set_property;
+  gobject_class->get_property = e_dbus_connection_get_property;
+
+  /**
+   * EDBusConnection:bus-type:
+   *
+   * When constructing an object, set this to the type of the message bus
+   * the connection is for or #G_BUS_TYPE_NONE if the connection is not
+   * a message bus connection.
+   *
+   * When reading, this property is never #G_BUS_TYPE_STARTER - if #G_BUS_TYPE_STARTER
+   * was passed as a construction property, then this property will be either #G_BUS_TYPE_SESSION
+   * or #G_BUS_TYPE_SYSTEM depending on what message bus activated the process.
+   *
+   * This property must be unset on construction if
+   * #EDBusConnection:address is set upon construction.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_BUS_TYPE,
+                                   g_param_spec_enum ("bus-type",
+                                                      _("bus-type"),
+                                                      _("The type of message bus, if any, the connection is for"),
+                                                      G_TYPE_BUS_TYPE,
+                                                      G_BUS_TYPE_NONE,
+                                                      G_PARAM_READABLE |
+                                                      G_PARAM_WRITABLE |
+                                                      G_PARAM_CONSTRUCT_ONLY |
+                                                      G_PARAM_STATIC_NAME |
+                                                      G_PARAM_STATIC_BLURB |
+                                                      G_PARAM_STATIC_NICK));
+
+  /**
+   * EDBusConnection:address:
+   *
+   * The address of the connection or %NULL if @connection is a
+   * message bus connection.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_ADDRESS,
+                                   g_param_spec_string ("address",
+                                                        _("Address"),
+                                                        _("The address of the connection"),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * EDBusConnection:is-private:
+   *
+   * When constructing an object and #EDBusConnection:bus-type is set to something
+   * other than #G_BUS_TYPE_NONE, specifies whether the connection to the requested
+   * message bus should be a private connection.
+   * This property is ignored if #EDBusConnection:dbus-1-connection is set upon construction.
+   *
+   * When reading, specifies if connection to the message bus is
+   * private or shared with others.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_IS_PRIVATE,
+                                   g_param_spec_boolean ("is-private",
+                                                         _("is-private"),
+                                                         _("Whether the connection to the message bus is private"),
+                                                         FALSE,
+                                                         G_PARAM_READABLE |
+                                                         G_PARAM_WRITABLE |
+                                                         G_PARAM_CONSTRUCT_ONLY |
+                                                         G_PARAM_STATIC_NAME |
+                                                         G_PARAM_STATIC_BLURB |
+                                                         G_PARAM_STATIC_NICK));
+
+  /**
+   * EDBusConnection:unique-name:
+   *
+   * The unique name as assigned by the message bus or %NULL if the
+   * connection is not open or not a message bus connection.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_UNIQUE_NAME,
+                                   g_param_spec_string ("unique-name",
+                                                        _("unique-name"),
+                                                        _("Unique name of bus connection"),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * EDBusConnection:is-disconnected:
+   *
+   * A boolean specifying whether the connection has been disconnected.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_IS_DISCONNECTED,
+                                   g_param_spec_boolean ("is-disconnected",
+                                                         _("is-disconnected"),
+                                                         _("Whether the connection has been disconnected"),
+                                                         FALSE,
+                                                         G_PARAM_READABLE |
+                                                         G_PARAM_STATIC_NAME |
+                                                         G_PARAM_STATIC_BLURB |
+                                                         G_PARAM_STATIC_NICK));
+
+  /**
+   * EDBusConnection:exit-on-disconnect:
+   *
+   * A boolean specifying whether _exit() should be called when the
+   * connection has been disconnected by the other end.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_EXIT_ON_DISCONNECT,
+                                   g_param_spec_boolean ("exit-on-disconnect",
+                                                         _("exit-on-disconnect"),
+                                                         _("Whether _exit() is called when the connection has been disconnected"),
+                                                         TRUE,
+                                                         G_PARAM_WRITABLE |
+                                                         G_PARAM_CONSTRUCT |
+                                                         G_PARAM_STATIC_NAME |
+                                                         G_PARAM_STATIC_BLURB |
+                                                         G_PARAM_STATIC_NICK));
+
+
+  /**
+   * EDBusConnection::disconnected:
+   * @connection: The #EDBusConnection emitting the signal.
+   *
+   * Emitted when the connection has been disconnected. You should
+   * give up your reference to @connection when receiving this signal.
+   *
+   * You are guaranteed that this signal is emitted only once.
+   **/
+  signals[DISCONNECTED_SIGNAL] = g_signal_new ("disconnected",
+                                               E_TYPE_DBUS_CONNECTION,
+                                               G_SIGNAL_RUN_LAST,
+                                               G_STRUCT_OFFSET (EDBusConnectionClass, disconnected),
+                                               NULL,
+                                               NULL,
+                                               g_cclosure_marshal_VOID__VOID,
+                                               G_TYPE_NONE,
+                                               0);
+}
+
+static void
+e_dbus_connection_init (EDBusConnection *connection)
+{
+  connection->priv = G_TYPE_INSTANCE_GET_PRIVATE (connection, E_TYPE_DBUS_CONNECTION, EDBusConnectionPrivate);
+
+  connection->priv->map_rule_to_signal_data = g_hash_table_new (g_str_hash,
+                                                                g_str_equal);
+  connection->priv->map_id_to_signal_data = g_hash_table_new (g_direct_hash,
+                                                              g_direct_equal);
+  connection->priv->map_sender_to_signal_data_array = g_hash_table_new_full (g_str_hash,
+                                                                             g_str_equal,
+                                                                             g_free,
+                                                                             NULL);
+
+  connection->priv->map_object_path_to_eo = g_hash_table_new_full (g_str_hash,
+                                                                   g_str_equal,
+                                                                   NULL,
+                                                                   (GDestroyNotify) exported_object_free);
+
+  connection->priv->map_id_to_ei = g_hash_table_new (g_direct_hash,
+                                                     g_direct_equal);
+
+  connection->priv->map_object_path_to_es = g_hash_table_new_full (g_str_hash,
+                                                                   g_str_equal,
+                                                                   NULL,
+                                                                   (GDestroyNotify) exported_subtree_free);
+
+  connection->priv->map_id_to_es = g_hash_table_new (g_direct_hash,
+                                                     g_direct_equal);
+}
+
+/**
+ * e_dbus_connection_get_bus_type:
+ * @connection: A #EDBusConnection.
+ *
+ * Gets the type of message bus connection, if any.
+ *
+ * This will never return #G_BUS_TYPE_STARTER. If
+ * #G_BUS_TYPE_STARTER was passed to e_dbus_connection_bus_get()
+ * then the return value will be either #G_BUS_TYPE_SESSION or
+ * #G_BUS_TYPE_SYSTEM depending on what bus started the
+ * process.
+ *
+ * Returns: Type type of the message bus the connection is for or
+ * #G_BUS_TYPE_NONE if the connection is not to a message
+ * bus.
+ **/
+GBusType
+e_dbus_connection_get_bus_type (EDBusConnection *connection)
+{
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), G_BUS_TYPE_NONE);
+
+  return connection->priv->bus_type;
+}
+
+/**
+ * e_dbus_connection_get_address:
+ * @connection: A #EDBusConnection.
+ *
+ * Gets the address that @connection was constructed with.
+ *
+ * Returns: The address that @connection was constructed with or %NULL
+ * if a message bus connection. Do not free this string, it is owned
+ * by @connection.
+ */
+const gchar *
+e_dbus_connection_get_address (EDBusConnection *connection)
+{
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+
+  return connection->priv->address;
+}
+
+/**
+ * e_dbus_connection_get_is_disconnected:
+ * @connection: A #EDBusConnection.
+ *
+ * Gets whether a connection has been disconnected.
+ *
+ * Returns: %TRUE if the connection is open, %FALSE otherwise.
+ **/
+gboolean
+e_dbus_connection_get_is_disconnected (EDBusConnection *connection)
+{
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+
+  return connection->priv->dbus_1_connection == NULL;
+}
+
+/**
+ * e_dbus_connection_get_is_private:
+ * @connection: A #EDBusConnection.
+ *
+ * Gets whether the connection is private.
+ *
+ * Returns: %TRUE if the connection is private, %FALSE otherwise.
+ **/
+gboolean
+e_dbus_connection_get_is_private (EDBusConnection *connection)
+{
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+
+  return connection->priv->is_private;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+#define PRINT_MESSAGE(message)                          \
+  do {                                                  \
+    const gchar *message_type;                          \
+    switch (dbus_message_get_type (message))            \
+      {                                                 \
+      case DBUS_MESSAGE_TYPE_METHOD_CALL:               \
+        message_type = "method_call";                   \
+        break;                                          \
+      case DBUS_MESSAGE_TYPE_METHOD_RETURN:             \
+        message_type = "method_return";                 \
+        break;                                          \
+      case DBUS_MESSAGE_TYPE_ERROR:                     \
+        message_type = "error";                         \
+        break;                                          \
+      case DBUS_MESSAGE_TYPE_SIGNAL:                    \
+        message_type = "signal";                        \
+        break;                                          \
+      case DBUS_MESSAGE_TYPE_INVALID:                   \
+        message_type = "invalid";                       \
+        break;                                          \
+      default:                                          \
+        message_type = "unknown";                       \
+        break;                                          \
+      }                                                 \
+    g_print ("new message:\n"                           \
+             " type:         %s\n"                      \
+             " sender:       %s\n"                      \
+             " destination:  %s\n"                      \
+             " path:         %s\n"                      \
+             " interface:    %s\n"                      \
+             " member:       %s\n"                      \
+             " signature:    %s\n",                     \
+             message_type,                              \
+             dbus_message_get_sender (message),         \
+             dbus_message_get_destination (message),    \
+             dbus_message_get_path (message),           \
+             dbus_message_get_interface (message),      \
+             dbus_message_get_member (message),         \
+             dbus_message_get_signature (message));     \
+  } while (FALSE)
+
+static void
+process_message (EDBusConnection *connection,
+                 DBusMessage *message)
+{
+  DBusError dbus_error;
+
+  //g_debug ("in filter_function for dbus_1_connection %p", connection);
+  //PRINT_MESSAGE (message);
+
+  dbus_error_init (&dbus_error);
+  /* check if we are disconnected from the bus */
+  if (dbus_message_is_signal (message,
+                              DBUS_INTERFACE_LOCAL,
+                              "Disconnected") &&
+      dbus_message_get_sender (message) == NULL &&
+      dbus_message_get_destination (message) == NULL &&
+      g_strcmp0 (dbus_message_get_path (message), DBUS_PATH_LOCAL) == 0)
+    {
+      if (connection->priv->dbus_1_connection != NULL)
+        {
+          e_dbus_connection_set_dbus_1_connection (connection, NULL);
+
+          g_object_notify (G_OBJECT (connection), "is-disconnected");
+          g_signal_emit (connection, signals[DISCONNECTED_SIGNAL], 0);
+        }
+    }
+  else
+    {
+      /* distribute to signal subscribers */
+      distribute_signals (connection, message);
+    }
+}
+
+static DBusHandlerResult
+filter_function (DBusConnection *dbus_1_connection,
+                 DBusMessage    *message,
+                 void           *user_data)
+{
+  EDBusConnection *connection = E_DBUS_CONNECTION (user_data);
+
+  //PRINT_MESSAGE (message);
+  process_message (connection, message);
+
+  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+/* must be called with lock held */
+static void
+e_dbus_connection_set_dbus_1_connection (EDBusConnection *connection,
+                                         DBusConnection  *dbus_1_connection)
+{
+  g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+
+  if (connection->priv->dbus_1_connection != NULL)
+    {
+      dbus_connection_remove_filter (connection->priv->dbus_1_connection,
+                                     filter_function,
+                                     connection);
+      _e_dbus_unintegrate_dbus_1_connection (connection->priv->dbus_1_connection);
+      if (connection->priv->is_private)
+        {
+          dbus_connection_close (connection->priv->dbus_1_connection);
+        }
+      else
+        {
+          /* shared connections must not be closed */
+        }
+      dbus_connection_unref (connection->priv->dbus_1_connection);
+    }
+
+  if (dbus_1_connection != NULL)
+    {
+      connection->priv->dbus_1_connection = dbus_connection_ref (dbus_1_connection);
+      _e_dbus_integrate_dbus_1_connection (connection->priv->dbus_1_connection, NULL);
+      if (!dbus_connection_add_filter (connection->priv->dbus_1_connection,
+                                       filter_function,
+                                       connection,
+                                       NULL))
+        _e_dbus_oom ();
+      dbus_connection_set_exit_on_disconnect (connection->priv->dbus_1_connection,
+                                              connection->priv->exit_on_disconnect);
+    }
+  else
+    {
+      connection->priv->dbus_1_connection = NULL;
+    }
+}
+
+/**
+ * e_dbus_connection_disconnect:
+ * @connection: A #EDBusConnection.
+ *
+ * Disconnects @connection. Note that this never causes the process to
+ * exit, #EDBusConnection:exit-on-disconnect is only used if the other
+ * end of the connection disconnects.
+ *
+ * If @connection is already disconnected, this method does nothing.
+ */
+void
+e_dbus_connection_disconnect (EDBusConnection *connection)
+{
+  gboolean emit_signal;
+
+  g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+
+  G_LOCK (connection_lock);
+  emit_signal = FALSE;
+  if (connection->priv->dbus_1_connection != NULL)
+    {
+      e_dbus_connection_set_dbus_1_connection (connection, NULL);
+      emit_signal = TRUE;
+    }
+  G_UNLOCK (connection_lock);
+
+  if (emit_signal)
+    g_signal_emit (connection, signals[DISCONNECTED_SIGNAL], 0);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GObject *
+e_dbus_connection_constructor (GType                  type,
+                               guint                  n_construct_properties,
+                               GObjectConstructParam *construct_properties)
+{
+  EDBusConnection **singleton;
+  gboolean is_private;
+  GObject *object;
+  guint n;
+
+  object = NULL;
+  singleton = NULL;
+  is_private = FALSE;
+
+  G_LOCK (connection_lock);
+
+  for (n = 0; n < n_construct_properties; n++)
+    {
+      if (g_strcmp0 (construct_properties[n].pspec->name, "bus-type") == 0)
+        {
+          GBusType bus_type;
+          const gchar *starter_bus;
+
+          bus_type = g_value_get_enum (construct_properties[n].value);
+          switch (bus_type)
+            {
+            case G_BUS_TYPE_NONE:
+              /* do nothing */
+              break;
+
+            case G_BUS_TYPE_SESSION:
+              singleton = &the_session_bus;
+              break;
+
+            case G_BUS_TYPE_SYSTEM:
+              singleton = &the_system_bus;
+              break;
+
+            case G_BUS_TYPE_STARTER:
+              starter_bus = g_getenv ("DBUS_STARTER_BUS_TYPE");
+              if (g_strcmp0 (starter_bus, "session") == 0)
+                {
+                  g_value_set_enum (construct_properties[n].value, G_BUS_TYPE_SESSION);
+                  singleton = &the_session_bus;
+                }
+              else if (g_strcmp0 (starter_bus, "system") == 0)
+                {
+                  g_value_set_enum (construct_properties[n].value, G_BUS_TYPE_SYSTEM);
+                  singleton = &the_system_bus;
+                }
+              else
+                {
+                  g_critical (_("Cannot construct a EDBusConnection object with bus_type G_BUS_TYPE_STARTER "
+                                "because the DBUS_STARTER_BUS_TYPE environment variable is not set. "
+                                "This is an error in the application or library using EDBus."));
+                  goto out;
+                }
+              break;
+
+            default:
+              g_assert_not_reached ();
+              break;
+            }
+        }
+      else if (g_strcmp0 (construct_properties[n].pspec->name, "is-private") == 0)
+        {
+          is_private = g_value_get_boolean (construct_properties[n].value);
+        }
+    }
+
+  if (is_private)
+    singleton = NULL;
+
+  if (singleton != NULL && *singleton != NULL)
+    {
+      object = g_object_ref (*singleton);
+      goto out;
+    }
+
+  object = G_OBJECT_CLASS (e_dbus_connection_parent_class)->constructor (type,
+                                                                         n_construct_properties,
+                                                                         construct_properties);
+
+  if (singleton != NULL)
+    *singleton = E_DBUS_CONNECTION (object);
+
+ out:
+  G_UNLOCK (connection_lock);
+  return object;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+initable_init (GInitable       *initable,
+               GCancellable    *cancellable,
+               GError         **error)
+{
+  EDBusConnection *connection = E_DBUS_CONNECTION (initable);
+  DBusConnection *dbus_1_connection;
+  DBusError dbus_error;
+  gboolean ret;
+
+  G_LOCK (connection_lock);
+
+  ret = FALSE;
+
+  if (connection->priv->is_initialized)
+    {
+      if (connection->priv->dbus_1_connection != NULL)
+        {
+          ret = TRUE;
+        }
+      else
+        {
+          g_assert (connection->priv->initialization_error != NULL);
+          g_propagate_error (error, g_error_copy (connection->priv->initialization_error));
+        }
+      goto out;
+    }
+
+  g_assert (connection->priv->dbus_1_connection == NULL);
+  g_assert (connection->priv->initialization_error == NULL);
+
+  dbus_connection_set_change_sigpipe (TRUE);
+
+  dbus_error_init (&dbus_error);
+  if (connection->priv->address != NULL)
+    {
+      g_assert (connection->priv->bus_type == G_BUS_TYPE_NONE); /* API contract */
+
+      if (connection->priv->is_private)
+        {
+          dbus_1_connection = dbus_connection_open_private (connection->priv->address,
+                                                            &dbus_error);
+        }
+      else
+        {
+          dbus_1_connection = dbus_connection_open (connection->priv->address,
+                                                    &dbus_error);
+        }
+    }
+  else
+    {
+      g_assert (connection->priv->bus_type != G_BUS_TYPE_NONE); /* API contract */
+      if (connection->priv->is_private)
+        {
+          dbus_1_connection = dbus_bus_get_private (connection->priv->bus_type,
+                                                    &dbus_error);
+        }
+      else
+        {
+          dbus_1_connection = dbus_bus_get (connection->priv->bus_type,
+                                            &dbus_error);
+        }
+    }
+
+  if (dbus_1_connection != NULL)
+    {
+      e_dbus_connection_set_dbus_1_connection (connection, dbus_1_connection);
+      dbus_connection_unref (dbus_1_connection);
+      ret = TRUE;
+    }
+  else
+    {
+      e_dbus_error_set_dbus_error (&connection->priv->initialization_error,
+                                   dbus_error.name,
+                                   dbus_error.message,
+                                   NULL);
+      /* this is a locally generated error so strip the remote part */
+      e_dbus_error_strip_remote_error (connection->priv->initialization_error);
+      dbus_error_free (&dbus_error);
+      g_propagate_error (error, g_error_copy (connection->priv->initialization_error));
+    }
+
+  connection->priv->is_initialized = TRUE;
+
+ out:
+  G_UNLOCK (connection_lock);
+  return ret;
+}
+
+static void
+initable_iface_init (GInitableIface *initable_iface)
+{
+  initable_iface->init = initable_init;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+async_initable_init_async (GAsyncInitable     *initable,
+                           gint                io_priority,
+                           GCancellable       *cancellable,
+                           GAsyncReadyCallback callback,
+                           gpointer            user_data)
+{
+  GSimpleAsyncResult *simple;
+  GError *error;
+
+  simple = g_simple_async_result_new (G_OBJECT (initable),
+                                      callback,
+                                      user_data,
+                                      async_initable_init_async);
+
+  /* for now, we just do this asynchronously and complete in idle since libdbus has no way
+   * to do it asynchronously
+   */
+  error = NULL;
+  if (!initable_init (G_INITABLE (initable),
+                      cancellable,
+                      &error))
+    {
+      g_simple_async_result_set_from_error (simple, error);
+      g_error_free (error);
+    }
+
+  g_simple_async_result_complete_in_idle (simple);
+  g_object_unref (simple);
+}
+
+static gboolean
+async_initable_init_finish (GAsyncInitable  *initable,
+                            GAsyncResult    *res,
+                            GError         **error)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+
+  g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == async_initable_init_async);
+
+  if (g_simple_async_result_propagate_error (simple, error))
+    return FALSE;
+  return TRUE;
+}
+
+static void
+async_initable_iface_init (GAsyncInitableIface *async_initable_iface)
+{
+  async_initable_iface->init_async = async_initable_init_async;
+  async_initable_iface->init_finish = async_initable_init_finish;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+EDBusConnection *
+_e_dbus_connection_new_for_dbus_1_connection (DBusConnection  *dbus_1_connection)
+{
+  EDBusConnection *connection;
+
+  connection = E_DBUS_CONNECTION (g_object_new (E_TYPE_DBUS_CONNECTION,
+                                                "is-private", TRUE,
+                                                "exit-on-disconnect", FALSE,
+                                                NULL));
+
+  /* TODO: set address? */
+
+  e_dbus_connection_set_dbus_1_connection (connection, dbus_connection_ref (dbus_1_connection));
+  connection->priv->is_initialized = TRUE;
+
+  return connection;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_connection_bus_get_sync:
+ * @bus_type: A #GBusType.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously connects to the message bus specified by @bus_type.
+ * Note that the returned object may shared with other callers,
+ * e.g. if two separate parts of a process calls this function with
+ * the same @bus_type, they will share the same object.
+ *
+ * Use e_dbus_connection_bus_get_private_sync() to get a private
+ * connection.
+ *
+ * This is a synchronous failable constructor. See
+ * e_dbus_connection_bus_get() and e_dbus_connection_bus_get_finish()
+ * for the asynchronous version.
+ *
+ * Returns: A #EDBusConnection or %NULL if @error is set. Free with g_object_unref().
+ **/
+EDBusConnection *
+e_dbus_connection_bus_get_sync (GBusType            bus_type,
+                                GCancellable       *cancellable,
+                                GError            **error)
+{
+  GInitable *initable;
+
+  initable = g_initable_new (E_TYPE_DBUS_CONNECTION,
+                             cancellable,
+                             error,
+                             "bus-type", bus_type,
+                             NULL);
+
+  if (initable != NULL)
+    return E_DBUS_CONNECTION (initable);
+  else
+    return NULL;
+}
+
+/**
+ * e_dbus_connection_bus_get:
+ * @bus_type: A #GBusType.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied.
+ * @user_data: The data to pass to @callback.
+ *
+ * Asynchronously connects to the message bus specified by @bus_type.
+ *
+ * When the operation is finished, @callback will be invoked. You can
+ * then call e_dbus_connection_bus_get_finish() to get the result of
+ * the operation.
+ *
+ * Use e_dbus_connection_bus_get_private() to get a private
+ * connection.
+ *
+ * This is a asynchronous failable constructor. See
+ * e_dbus_connection_bus_get_sync() for the synchronous version.
+ **/
+void
+e_dbus_connection_bus_get (GBusType             bus_type,
+                           GCancellable        *cancellable,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
+{
+  g_async_initable_new_async (E_TYPE_DBUS_CONNECTION,
+                              G_PRIORITY_DEFAULT,
+                              cancellable,
+                              callback,
+                              user_data,
+                              "bus-type", bus_type,
+                              NULL);
+}
+
+/**
+ * e_dbus_connection_bus_get_finish:
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to e_dbus_connection_bus_get().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with e_dbus_connection_bus_get().
+ *
+ * Note that the returned object may shared with other callers,
+ * e.g. if two separate parts of a process calls this function with
+ * the same @bus_type, they will share the same object.
+ *
+ * Returns: A #EDBusConnection or %NULL if @error is set. Free with g_object_unref().
+ **/
+EDBusConnection *
+e_dbus_connection_bus_get_finish (GAsyncResult  *res,
+                                  GError       **error)
+{
+  GObject *object;
+  GObject *source_object;
+
+  source_object = g_async_result_get_source_object (res);
+  g_assert (source_object != NULL);
+
+  object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
+                                        res,
+                                        error);
+  g_object_unref (source_object);
+
+  if (object != NULL)
+    return E_DBUS_CONNECTION (object);
+  else
+    return NULL;
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_connection_bus_get_private_sync:
+ * @bus_type: A #GBusType.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Like e_dbus_connection_bus_get_sync() but gets a connection that is not
+ * shared with other callers.
+ *
+ * Returns: A #EDBusConnection. Free with g_object_unref().
+ **/
+EDBusConnection *
+e_dbus_connection_bus_get_private_sync (GBusType        bus_type,
+                                        GCancellable   *cancellable,
+                                        GError        **error)
+{
+  GInitable *initable;
+
+  initable = g_initable_new (E_TYPE_DBUS_CONNECTION,
+                             cancellable,
+                             error,
+                             "bus-type", bus_type,
+                             "is-private", TRUE,
+                             NULL);
+
+  if (initable != NULL)
+    return E_DBUS_CONNECTION (initable);
+  else
+    return NULL;
+}
+
+/**
+ * e_dbus_connection_bus_get_private:
+ * @bus_type: A #GBusType.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied.
+ * @user_data: The data to pass to @callback.
+ *
+ * Asynchronously connects to the message bus specified by @bus_type
+ * using a private connection.
+ *
+ * When the operation is finished, @callback will be invoked. You can
+ * then call e_dbus_connection_bus_get_finish() to get the result of
+ * the operation.
+ *
+ * Use e_dbus_connection_bus_get() to get a shared connection.
+ *
+ * This is a asynchronous failable constructor. See
+ * e_dbus_connection_bus_get_private_sync() for the synchronous
+ * version.
+ **/
+void
+e_dbus_connection_bus_get_private (GBusType             bus_type,
+                                   GCancellable        *cancellable,
+                                   GAsyncReadyCallback  callback,
+                                   gpointer             user_data)
+{
+  g_async_initable_new_async (E_TYPE_DBUS_CONNECTION,
+                              G_PRIORITY_DEFAULT,
+                              cancellable,
+                              callback,
+                              user_data,
+                              "bus-type", bus_type,
+                              "is-private", TRUE,
+                              NULL);
+}
+
+/**
+ * e_dbus_connection_bus_get_private_finish:
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to e_dbus_connection_bus_get_private().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with e_dbus_connection_bus_get_private().
+ *
+ * The returned object is never shared with other callers.
+ *
+ * Returns: A #EDBusConnection or %NULL if @error is set. Free with g_object_unref().
+ **/
+EDBusConnection *
+e_dbus_connection_bus_get_private_finish (GAsyncResult  *res,
+                                          GError       **error)
+{
+  GObject *object;
+  GObject *source_object;
+
+  source_object = g_async_result_get_source_object (res);
+  g_assert (source_object != NULL);
+
+  object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
+                                        res,
+                                        error);
+  g_object_unref (source_object);
+
+  if (object != NULL)
+    return E_DBUS_CONNECTION (object);
+  else
+    return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_connection_new:
+ * @address: The address to connect to. See the D-Bus specification for details on address formats.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied.
+ * @user_data: The data to pass to @callback.
+ *
+ * Asynchronously creates a private connection to @address.
+ *
+ * When the operation is finished, @callback will be invoked. You can
+ * then call e_dbus_connection_new_finish() to get the result of the
+ * operation.
+ *
+ * This is a asynchronous failable constructor. See
+ * e_dbus_connection_new_sync() for the synchronous
+ * version.
+ */
+void
+e_dbus_connection_new (const gchar         *address,
+                       GCancellable        *cancellable,
+                       GAsyncReadyCallback  callback,
+                       gpointer             user_data)
+{
+  g_async_initable_new_async (E_TYPE_DBUS_CONNECTION,
+                              G_PRIORITY_DEFAULT,
+                              cancellable,
+                              callback,
+                              user_data,
+                              "address", address,
+                              "is-private", TRUE,
+                              "exit-on-disconnect", FALSE,
+                              NULL);
+}
+
+/**
+ * e_dbus_connection_new_finish:
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to e_dbus_connection_new().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with e_dbus_connection_new().
+ *
+ * The returned #EDBusConnection object will have
+ * #EDBusConnection:exit-on-disconnect set to %FALSE.
+ *
+ * Returns: A #EDBusConnection or %NULL if @error is set. Free with g_object_unref().
+ */
+EDBusConnection *
+e_dbus_connection_new_finish (GAsyncResult        *res,
+                              GError             **error)
+{
+  GObject *object;
+  GObject *source_object;
+
+  source_object = g_async_result_get_source_object (res);
+  g_assert (source_object != NULL);
+
+  object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
+                                        res,
+                                        error);
+  g_object_unref (source_object);
+
+  if (object != NULL)
+    return E_DBUS_CONNECTION (object);
+  else
+    return NULL;
+}
+
+/**
+ * e_dbus_connection_new_sync:
+ * @address: The address to connect to. See the D-Bus specification for details on address formats.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously creates a private connection to @address.
+ *
+ * This is a synchronous failable constructor. See
+ * e_dbus_connection_new() for the asynchronous version.
+ *
+ * The returned #EDBusConnection object will have
+ * #EDBusConnection:exit-on-disconnect set to %FALSE.
+ *
+ * Returns: A #EDBusConnection or %NULL if @error is set. Free with g_object_unref().
+ */
+EDBusConnection *
+e_dbus_connection_new_sync (const gchar         *address,
+                            GCancellable       *cancellable,
+                            GError            **error)
+{
+  GInitable *initable;
+
+  initable = g_initable_new (E_TYPE_DBUS_CONNECTION,
+                             cancellable,
+                             error,
+                             "address", address,
+                             "is-private", TRUE,
+                             "exit-on-disconnect", FALSE,
+                             NULL);
+
+  if (initable != NULL)
+    return E_DBUS_CONNECTION (initable);
+  else
+    return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_connection_set_exit_on_disconnect:
+ * @connection: A #EDBusConnection.
+ * @exit_on_disconnect: Whether _exit() should be called when @connection is
+ * disconnected by the other end.
+ *
+ * Sets whether _exit() should be called when @connection is
+ * disconnected by the other end.
+ **/
+void
+e_dbus_connection_set_exit_on_disconnect (EDBusConnection *connection,
+                                          gboolean         exit_on_disconnect)
+{
+  g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+
+  connection->priv->exit_on_disconnect = exit_on_disconnect;
+  if (connection->priv->dbus_1_connection != NULL)
+    dbus_connection_set_exit_on_disconnect (connection->priv->dbus_1_connection,
+                                            connection->priv->exit_on_disconnect);
+}
+
+/**
+ * e_dbus_connection_get_unique_name:
+ * @connection: A #EDBusConnection.
+ *
+ * Gets the unique name of @connection as assigned by the message bus.
+ *
+ * Returns: The unique name or %NULL if the connection is disconnected
+ * or @connection is not a message bus connection. Do not free this
+ * string, it is owned by @connection.
+ **/
+const gchar *
+e_dbus_connection_get_unique_name (EDBusConnection *connection)
+{
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+
+  if (connection->priv->bus_type == G_BUS_TYPE_NONE)
+    return NULL;
+
+  if (connection->priv->dbus_1_connection != NULL)
+    return dbus_bus_get_unique_name (connection->priv->dbus_1_connection);
+  else
+    return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+void
+_e_dbus_connection_send_dbus_1_message (EDBusConnection    *connection,
+                                        DBusMessage        *message)
+{
+  g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+  g_return_if_fail (message != NULL);
+
+  if (connection->priv->dbus_1_connection == NULL)
+    goto out;
+
+  if (!dbus_connection_send (connection->priv->dbus_1_connection,
+                             message,
+                             NULL))
+    _e_dbus_oom ();
+
+ out:
+  ;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+send_dbus_1_message_with_reply_cb (DBusPendingCall *pending_call,
+                                   void            *user_data)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
+  EDBusConnection *connection;
+  GCancellable *cancellable;
+  gulong cancellable_handler_id;
+  DBusMessage *reply;
+
+  G_LOCK (connection_lock);
+  cancellable_handler_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (simple), "cancellable-handler-id"));
+  connection = E_DBUS_CONNECTION (g_async_result_get_source_object (G_ASYNC_RESULT (simple)));
+  G_UNLOCK (connection_lock);
+
+  cancellable = g_object_get_data (G_OBJECT (simple), "cancellable");
+
+  if (cancellable_handler_id > 0)
+    g_cancellable_disconnect (cancellable, cancellable_handler_id);
+
+  if (pending_call == NULL)
+    {
+      g_simple_async_result_set_error (simple,
+                                       E_DBUS_ERROR,
+                                       E_DBUS_ERROR_CANCELLED,
+                                       _("Operation was cancelled"));
+    }
+  else
+    {
+      reply = dbus_pending_call_steal_reply (pending_call);
+      g_assert (reply != NULL);
+      g_simple_async_result_set_op_res_gpointer (simple, reply, (GDestroyNotify) dbus_message_unref);
+    }
+  g_simple_async_result_complete_in_idle (simple);
+  g_object_unref (connection);
+  g_object_unref (simple);
+}
+
+static gboolean
+send_dbus_1_message_with_reply_cancelled_in_idle (gpointer user_data)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
+  send_dbus_1_message_with_reply_cb (NULL, simple);
+  return FALSE;
+}
+
+static void
+send_dbus_1_message_with_reply_cancelled_cb (GCancellable *cancellable,
+                                             gpointer      user_data)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
+  DBusPendingCall *pending_call;
+
+  pending_call = g_object_get_data (G_OBJECT (simple), "dbus-1-pending-call");
+  dbus_pending_call_cancel (pending_call);
+
+  g_idle_add (send_dbus_1_message_with_reply_cancelled_in_idle, simple);
+}
+
+static void
+e_dbus_connection_send_dbus_1_message_with_reply (EDBusConnection    *connection,
+                                                  DBusMessage        *message,
+                                                  gint                timeout_msec,
+                                                  GCancellable       *cancellable,
+                                                  GAsyncReadyCallback callback,
+                                                  gpointer            user_data)
+{
+  GSimpleAsyncResult *simple;
+  DBusPendingCall *pending_call;
+  gulong cancellable_handler_id;
+
+  g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+  g_return_if_fail (callback != NULL);
+  g_return_if_fail (message != NULL);
+  g_return_if_fail (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_METHOD_CALL);
+
+  G_LOCK (connection_lock);
+
+  simple = g_simple_async_result_new (G_OBJECT (connection),
+                                      callback,
+                                      user_data,
+                                      e_dbus_connection_send_dbus_1_message_with_reply);
+
+  /* don't even send a message if already cancelled */
+  if (g_cancellable_is_cancelled (cancellable))
+    {
+      g_simple_async_result_set_error (simple,
+                                       E_DBUS_ERROR,
+                                       E_DBUS_ERROR_CANCELLED,
+                                       _("Operation was cancelled"));
+      g_simple_async_result_complete_in_idle (simple);
+      g_object_unref (simple);
+      goto out;
+    }
+
+  if (connection->priv->dbus_1_connection == NULL)
+    {
+      g_simple_async_result_set_error (simple,
+                                       E_DBUS_ERROR,
+                                       E_DBUS_ERROR_DISCONNECTED,
+                                       _("Not connected"));
+      g_simple_async_result_complete_in_idle (simple);
+      g_object_unref (simple);
+      goto out;
+    }
+
+  if (!dbus_connection_send_with_reply (connection->priv->dbus_1_connection,
+                                        message,
+                                        &pending_call,
+                                        timeout_msec))
+    _e_dbus_oom ();
+
+  if (pending_call == NULL)
+    {
+      g_simple_async_result_set_error (simple,
+                                       E_DBUS_ERROR,
+                                       E_DBUS_ERROR_FAILED,
+                                       _("Not connected"));
+      g_simple_async_result_complete_in_idle (simple);
+      g_object_unref (simple);
+      goto out;
+    }
+
+  g_object_set_data_full (G_OBJECT (simple),
+                          "dbus-1-pending-call",
+                          pending_call,
+                          (GDestroyNotify) dbus_pending_call_unref);
+
+  g_object_set_data (G_OBJECT (simple),
+                     "cancellable",
+                     cancellable);
+
+  dbus_pending_call_set_notify (pending_call,
+                                send_dbus_1_message_with_reply_cb,
+                                simple,
+                                NULL);
+
+  cancellable_handler_id = 0;
+  if (cancellable != NULL)
+    {
+      /* use the lock to ensure cancellable-handler-id is set on simple before trying to get it
+       * in send_dbus_1_message_with_reply_cb()
+       */
+      cancellable_handler_id = g_cancellable_connect (cancellable,
+                                                      G_CALLBACK (send_dbus_1_message_with_reply_cancelled_cb),
+                                                      simple,
+                                                      NULL);
+      g_object_set_data (G_OBJECT (simple),
+                         "cancellable-handler-id",
+                         GUINT_TO_POINTER (cancellable_handler_id));
+    }
+
+ out:
+  G_UNLOCK (connection_lock);
+}
+
+static DBusMessage *
+e_dbus_connection_send_dbus_1_message_with_reply_finish (EDBusConnection   *connection,
+                                                         GAsyncResult      *res,
+                                                         GError           **error)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+  DBusMessage *reply;
+
+  g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == e_dbus_connection_send_dbus_1_message_with_reply);
+
+  reply = NULL;
+  if (g_simple_async_result_propagate_error (simple, error))
+    goto out;
+
+  reply = dbus_message_ref (g_simple_async_result_get_op_res_gpointer (simple));
+
+ out:
+  return reply;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+send_dbus_1_message_with_reply_sync_cancelled_cb (GCancellable *cancellable,
+                                                  gpointer      user_data)
+{
+  DBusPendingCall *pending_call = user_data;
+
+  dbus_pending_call_cancel (pending_call);
+}
+
+static DBusMessage *
+e_dbus_connection_send_dbus_1_message_with_reply_sync (EDBusConnection    *connection,
+                                                       DBusMessage        *message,
+                                                       gint                timeout_msec,
+                                                       GCancellable       *cancellable,
+                                                       GError            **error)
+{
+  gulong cancellable_handler_id;
+  DBusMessage *result;
+  DBusPendingCall *pending_call;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0);
+  g_return_val_if_fail (message != NULL, 0);
+  g_return_val_if_fail (dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_METHOD_CALL, 0);
+
+  result = NULL;
+
+  G_LOCK (connection_lock);
+
+  /* don't even send a message if already cancelled */
+  if (g_cancellable_is_cancelled (cancellable))
+    {
+      g_set_error (error,
+                   E_DBUS_ERROR,
+                   E_DBUS_ERROR_CANCELLED,
+                   _("Operation was cancelled"));
+      G_UNLOCK (connection_lock);
+      goto out;
+    }
+
+  if (connection->priv->dbus_1_connection == NULL)
+    {
+      g_set_error (error,
+                   E_DBUS_ERROR,
+                   E_DBUS_ERROR_FAILED,
+                   _("Not connected"));
+      G_UNLOCK (connection_lock);
+      goto out;
+    }
+
+  if (!dbus_connection_send_with_reply (connection->priv->dbus_1_connection,
+                                        message,
+                                        &pending_call,
+                                        timeout_msec))
+    _e_dbus_oom ();
+
+  if (pending_call == NULL)
+    {
+      g_set_error (error,
+                   E_DBUS_ERROR,
+                   E_DBUS_ERROR_FAILED,
+                   _("Not connected"));
+      G_UNLOCK (connection_lock);
+      goto out;
+    }
+
+  cancellable_handler_id = 0;
+  if (cancellable != NULL)
+    {
+      /* use the lock to ensure cancellable-handler-id is set on simple before trying to get it
+       * in send_dbus_1_message_with_reply_cb()
+       */
+      cancellable_handler_id = g_cancellable_connect (cancellable,
+                                                      G_CALLBACK (send_dbus_1_message_with_reply_sync_cancelled_cb),
+                                                      pending_call,
+                                                      NULL);
+    }
+
+  G_UNLOCK (connection_lock);
+
+  /* block without holding the lock */
+  dbus_pending_call_block (pending_call);
+
+  if (cancellable_handler_id > 0)
+    {
+      g_cancellable_disconnect (cancellable,
+                                cancellable_handler_id);
+    }
+
+  result = dbus_pending_call_steal_reply (pending_call);
+  if (pending_call == NULL)
+    {
+      g_set_error (error,
+                   E_DBUS_ERROR,
+                   E_DBUS_ERROR_CANCELLED,
+                   _("Operation was cancelled"));
+    }
+
+  dbus_pending_call_unref (pending_call);
+
+ out:
+  return result;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  gchar *rule;
+  gchar *sender;
+  gchar *interface_name;
+  gchar *member;
+  gchar *object_path;
+  gchar *arg0;
+  GArray *subscribers;
+} SignalData;
+
+typedef struct
+{
+  EDBusSignalCallback callback;
+  gpointer user_data;
+  GDestroyNotify user_data_free_func;
+  guint id;
+  GMainContext *context;
+} SignalSubscriber;
+
+static void
+signal_data_free (SignalData *data)
+{
+  g_free (data->rule);
+  g_free (data->sender);
+  g_free (data->interface_name);
+  g_free (data->member);
+  g_free (data->object_path);
+  g_free (data->arg0);
+  g_array_free (data->subscribers, TRUE);
+  g_free (data);
+}
+
+static gchar *
+args_to_rule (const gchar         *sender,
+              const gchar         *interface_name,
+              const gchar         *member,
+              const gchar         *object_path,
+              const gchar         *arg0)
+{
+  GString *rule;
+
+  rule = g_string_new ("type='signal'");
+  if (sender != NULL)
+    g_string_append_printf (rule, ",sender='%s'", sender);
+  if (interface_name != NULL)
+    g_string_append_printf (rule, ",interface='%s'", interface_name);
+  if (member != NULL)
+    g_string_append_printf (rule, ",member='%s'", member);
+  if (object_path != NULL)
+    g_string_append_printf (rule, ",path='%s'", object_path);
+  if (arg0 != NULL)
+    g_string_append_printf (rule, ",arg0='%s'", arg0);
+
+  return g_string_free (rule, FALSE);
+}
+
+static guint _global_subscriber_id = 1;
+static guint _global_registration_id = 1;
+static guint _global_subtree_registration_id = 1;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+add_match_cb (DBusPendingCall *pending_call,
+              void            *user_data)
+{
+  DBusMessage *reply;
+  DBusError dbus_error;
+
+  reply = dbus_pending_call_steal_reply (pending_call);
+  g_assert (reply != NULL);
+
+  dbus_error_init (&dbus_error);
+  if (dbus_set_error_from_message (&dbus_error, reply))
+    {
+      if (g_strcmp0 (dbus_error.name, "org.freedesktop.DBus.Error.OOM") == 0)
+        {
+          g_critical ("Message bus reported OOM when trying to add match rule: %s: %s",
+                      dbus_error.name,
+                      dbus_error.message);
+          _e_dbus_oom ();
+        }
+
+      /* Don't report other errors; the bus might have gone away while sending the message
+       * so @dbus_error might be a locally generated error.
+       */
+
+      dbus_error_free (&dbus_error);
+    }
+}
+
+static void
+add_match_rule (EDBusConnection *connection,
+                const gchar     *match_rule)
+{
+  DBusMessage *message;
+  DBusPendingCall *pending_call;
+
+  if ((message = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
+                                               DBUS_PATH_DBUS,
+                                               DBUS_INTERFACE_DBUS,
+                                               "AddMatch")) == NULL)
+    _e_dbus_oom ();
+  if (!dbus_message_append_args (message,
+                                 DBUS_TYPE_STRING, &match_rule,
+                                 DBUS_TYPE_INVALID))
+    _e_dbus_oom ();
+
+  /* don't use e_dbus_connection_send_dbus_1_message_with_reply() since we don't want to ref @connection */
+  if (!dbus_connection_send_with_reply (connection->priv->dbus_1_connection,
+                                        message,
+                                        &pending_call,
+                                        -1))
+    _e_dbus_oom ();
+
+  dbus_pending_call_set_notify (pending_call,
+                                add_match_cb,
+                                NULL,
+                                NULL);
+
+  dbus_message_unref (message);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+remove_match_cb (DBusPendingCall *pending_call,
+                 void            *user_data)
+{
+  DBusMessage *reply;
+  DBusError dbus_error;
+
+  reply = dbus_pending_call_steal_reply (pending_call);
+  g_assert (reply != NULL);
+
+  dbus_error_init (&dbus_error);
+  if (dbus_set_error_from_message (&dbus_error, reply))
+    {
+      if (g_strcmp0 (dbus_error.name, "org.freedesktop.DBus.Error.MatchRuleNotFound") == 0)
+        {
+          g_warning ("Message bus reported error removing match rule: %s: %s\n"
+                     "This is a bug in EDBus.",
+                     dbus_error.name,
+                     dbus_error.message);
+        }
+      dbus_error_free (&dbus_error);
+    }
+}
+
+static void
+remove_match_rule (EDBusConnection *connection,
+                   const gchar     *match_rule)
+{
+  DBusMessage *message;
+  DBusPendingCall *pending_call;
+
+  if ((message = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
+                                               DBUS_PATH_DBUS,
+                                               DBUS_INTERFACE_DBUS,
+                                               "RemoveMatch")) == NULL)
+    _e_dbus_oom ();
+  if (!dbus_message_append_args (message,
+                                 DBUS_TYPE_STRING, &match_rule,
+                                 DBUS_TYPE_INVALID))
+    _e_dbus_oom ();
+
+  /* don't use e_dbus_connection_send_dbus_1_message_with_reply() since we don't want to ref @connection */
+  if (!dbus_connection_send_with_reply (connection->priv->dbus_1_connection,
+                                        message,
+                                        &pending_call,
+                                        -1))
+    _e_dbus_oom ();
+
+  dbus_pending_call_set_notify (pending_call,
+                                remove_match_cb,
+                                NULL,
+                                NULL);
+
+  dbus_message_unref (message);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_signal_data_for_name_lost_or_acquired (SignalData *signal_data)
+{
+  return g_strcmp0 (signal_data->sender, DBUS_SERVICE_DBUS) == 0 &&
+    g_strcmp0 (signal_data->interface_name, DBUS_INTERFACE_DBUS) == 0 &&
+    g_strcmp0 (signal_data->object_path, DBUS_PATH_DBUS) == 0 &&
+    (g_strcmp0 (signal_data->member, "NameLost") == 0 ||
+     g_strcmp0 (signal_data->member, "NameAcquired") == 0);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_connection_signal_subscribe:
+ * @connection: A #EDBusConnection.
+ * @sender: Sender name to match on. Must be either <literal>org.freedesktop.DBus</literal> (for listening to signals from the message bus daemon) or a unique name or %NULL to listen from all senders.
+ * @interface_name: D-Bus interface name to match on or %NULL to match on all interfaces.
+ * @member: D-Bus signal name to match on or %NULL to match on all signals.
+ * @object_path: Object path to match on or %NULL to match on all object paths.
+ * @arg0: Contents of first string argument to match on or %NULL to match on all kinds of arguments.
+ * @callback: Callback to invoke when there is a signal matching the requested data.
+ * @user_data: User data to pass to @callback.
+ * @user_data_free_func: Function to free @user_data when subscription is removed or %NULL.
+ *
+ * Subscribes to signals on @connection and invokes @callback with a
+ * whenever the signal is received. Note that @callback
+ * will be invoked in the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link> of the thread you are calling this method from.
+ *
+ * It is considered a programming error to use this function if @connection has been disconnected.
+ *
+ * Note that if @sender is not <literal>org.freedesktop.DBus</literal> (for listening to signals from the
+ * message bus daemon), then it needs to be a unique bus name or %NULL (for listening to signals from any
+ * name) - you cannot pass a name like <literal>com.example.MyApp</literal>.
+ * Use e.g. e_bus_watch_name() to find the unique name for the owner of the name you are interested in. Also note
+ * that this function does not remove a subscription if @sender vanishes from the bus. You have to manually
+ * call e_dbus_connection_signal_unsubscribe() to remove a subscription.
+ *
+ * Returns: A subscription identifier that can be used with e_dbus_connection_signal_unsubscribe().
+ **/
+guint
+e_dbus_connection_signal_subscribe (EDBusConnection     *connection,
+                                    const gchar         *sender,
+                                    const gchar         *interface_name,
+                                    const gchar         *member,
+                                    const gchar         *object_path,
+                                    const gchar         *arg0,
+                                    EDBusSignalCallback  callback,
+                                    gpointer             user_data,
+                                    GDestroyNotify       user_data_free_func)
+{
+  gchar *rule;
+  SignalData *signal_data;
+  SignalSubscriber subscriber;
+  GPtrArray *signal_data_array;
+
+  /* Right now we abort if AddMatch() fails since it can only fail with the bus being in
+   * an OOM condition. We might want to change that but that would involve making
+   * e_dbus_connection_signal_subscribe() asynchronous and having the call sites
+   * handle that. And there's really no sensible way of handling this short of retrying
+   * to add the match rule... and then there's the little thing that, hey, maybe there's
+   * a reason the bus in an OOM condition.
+   *
+   * Doable, but not really sure it's worth it...
+   */
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0);
+  g_return_val_if_fail (!e_dbus_connection_get_is_disconnected (connection), 0);
+  g_return_val_if_fail (sender == NULL || ((strcmp (sender, DBUS_SERVICE_DBUS) == 0 || sender[0] == ':') &&
+                                           connection->priv->bus_type != G_BUS_TYPE_NONE), 0);
+  g_return_val_if_fail (callback != NULL, 0);
+  /* TODO: check that passed in data is well-formed */
+
+  G_LOCK (connection_lock);
+
+  rule = args_to_rule (sender, interface_name, member, object_path, arg0);
+
+  if (sender == NULL)
+    sender = "";
+
+  subscriber.callback = callback;
+  subscriber.user_data = user_data;
+  subscriber.user_data_free_func = user_data_free_func;
+  subscriber.id = _global_subscriber_id++; /* TODO: overflow etc. */
+  subscriber.context = g_main_context_get_thread_default ();
+  if (subscriber.context != NULL)
+    g_main_context_ref (subscriber.context);
+
+  /* see if we've already have this rule */
+  signal_data = g_hash_table_lookup (connection->priv->map_rule_to_signal_data, rule);
+  if (signal_data != NULL)
+    {
+      g_array_append_val (signal_data->subscribers, subscriber);
+      g_free (rule);
+      goto out;
+    }
+
+  signal_data = g_new0 (SignalData, 1);
+  signal_data->rule           = rule;
+  signal_data->sender         = g_strdup (sender);
+  signal_data->interface_name = g_strdup (interface_name);
+  signal_data->member         = g_strdup (member);
+  signal_data->object_path    = g_strdup (object_path);
+  signal_data->arg0           = g_strdup (arg0);
+  signal_data->subscribers    = g_array_new (FALSE, FALSE, sizeof (SignalSubscriber));
+  g_array_append_val (signal_data->subscribers, subscriber);
+
+  g_hash_table_insert (connection->priv->map_rule_to_signal_data,
+                       signal_data->rule,
+                       signal_data);
+
+  /* Add the match rule to the bus...
+   *
+   * Avoid adding match rules for NameLost and NameAcquired messages - the bus will
+   * always send such messages to to us.
+   */
+  if (connection->priv->bus_type != G_BUS_TYPE_NONE)
+    {
+      if (!is_signal_data_for_name_lost_or_acquired (signal_data))
+        {
+          add_match_rule (connection, signal_data->rule);
+        }
+    }
+
+ out:
+  g_hash_table_insert (connection->priv->map_id_to_signal_data,
+                       GUINT_TO_POINTER (subscriber.id),
+                       signal_data);
+
+  signal_data_array = g_hash_table_lookup (connection->priv->map_sender_to_signal_data_array,
+                                           signal_data->sender);
+  if (signal_data_array == NULL)
+    {
+      signal_data_array = g_ptr_array_new ();
+      g_hash_table_insert (connection->priv->map_sender_to_signal_data_array,
+                           g_strdup (signal_data->sender),
+                           signal_data_array);
+    }
+  g_ptr_array_add (signal_data_array, signal_data);
+
+  G_UNLOCK (connection_lock);
+
+  return subscriber.id;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* must hold lock when calling this */
+static void
+unsubscribe_id_internal (EDBusConnection    *connection,
+                         guint               subscription_id,
+                         GArray             *out_removed_subscribers)
+{
+  SignalData *signal_data;
+  GPtrArray *signal_data_array;
+  guint n;
+
+  signal_data = g_hash_table_lookup (connection->priv->map_id_to_signal_data,
+                                     GUINT_TO_POINTER (subscription_id));
+  if (signal_data == NULL)
+    {
+      /* Don't warn here, we may have thrown all subscriptions out when the connection was closed */
+      goto out;
+    }
+
+  for (n = 0; n < signal_data->subscribers->len; n++)
+    {
+      SignalSubscriber *subscriber;
+
+      subscriber = &(g_array_index (signal_data->subscribers, SignalSubscriber, n));
+      if (subscriber->id != subscription_id)
+        continue;
+
+      g_assert (g_hash_table_remove (connection->priv->map_id_to_signal_data,
+                                     GUINT_TO_POINTER (subscription_id)));
+      g_array_append_val (out_removed_subscribers, *subscriber);
+      g_array_remove_index (signal_data->subscribers, n);
+
+      if (signal_data->subscribers->len == 0)
+        g_assert (g_hash_table_remove (connection->priv->map_rule_to_signal_data, signal_data->rule));
+
+      signal_data_array = g_hash_table_lookup (connection->priv->map_sender_to_signal_data_array,
+                                               signal_data->sender);
+      g_assert (signal_data_array != NULL);
+      g_assert (g_ptr_array_remove (signal_data_array, signal_data));
+
+      if (signal_data_array->len == 0)
+        {
+          g_assert (g_hash_table_remove (connection->priv->map_sender_to_signal_data_array, signal_data->sender));
+
+          /* remove the match rule from the bus unless NameLost or NameAcquired (see subscribe()) */
+          if (connection->priv->bus_type != G_BUS_TYPE_NONE)
+            {
+              if (!is_signal_data_for_name_lost_or_acquired (signal_data))
+                {
+                  if (connection->priv->dbus_1_connection != NULL)
+                    {
+                      remove_match_rule (connection, signal_data->rule);
+                    }
+                }
+            }
+
+          signal_data_free (signal_data);
+        }
+
+      goto out;
+    }
+
+  g_assert_not_reached ();
+
+ out:
+  ;
+}
+
+/**
+ * e_dbus_connection_signal_unsubscribe:
+ * @connection: A #EDBusConnection.
+ * @subscription_id: A subscription id obtained from e_dbus_connection_signal_subscribe().
+ *
+ * Unsubscribes from signals.
+ **/
+void
+e_dbus_connection_signal_unsubscribe (EDBusConnection    *connection,
+                                      guint               subscription_id)
+{
+  GArray *subscribers;
+  guint n;
+
+  subscribers = g_array_new (FALSE, FALSE, sizeof (SignalSubscriber));
+
+  G_LOCK (connection_lock);
+  unsubscribe_id_internal (connection,
+                           subscription_id,
+                           subscribers);
+  G_UNLOCK (connection_lock);
+
+  /* invariant */
+  g_assert (subscribers->len == 0 || subscribers->len == 1);
+
+  /* call GDestroyNotify without lock held */
+  for (n = 0; n < subscribers->len; n++)
+    {
+      SignalSubscriber *subscriber;
+      subscriber = &(g_array_index (subscribers, SignalSubscriber, n));
+      if (subscriber->user_data_free_func != NULL)
+        subscriber->user_data_free_func (subscriber->user_data);
+      if (subscriber->context != NULL)
+        g_main_context_unref (subscriber->context);
+    }
+
+  g_array_free (subscribers, TRUE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  EDBusSignalCallback  callback;
+  gpointer             user_data;
+  DBusMessage         *message;
+  EDBusConnection     *connection;
+} SignalInstance;
+
+static gboolean
+emit_signal_instance_in_idle_cb (gpointer data)
+{
+  SignalInstance *signal_instance = data;
+
+  EVariant *parameters;
+  GError *error;
+
+  error = NULL;
+  parameters = _e_dbus_dbus_1_to_gvariant (signal_instance->message, &error);
+  if (parameters == NULL)
+    {
+      g_warning ("Error converting signal parameters to a EVariant: %s", error->message);
+      g_error_free (error);
+      goto out;
+    }
+
+  signal_instance->callback (signal_instance->connection,
+                             dbus_message_get_sender (signal_instance->message),
+                             dbus_message_get_path (signal_instance->message),
+                             dbus_message_get_interface (signal_instance->message),
+                             dbus_message_get_member (signal_instance->message),
+                             parameters,
+                             signal_instance->user_data);
+ out:
+  e_variant_unref (parameters);
+
+  return FALSE;
+}
+
+static void
+signal_instance_free (SignalInstance *signal_instance)
+{
+  dbus_message_unref (signal_instance->message);
+  g_object_unref (signal_instance->connection);
+  g_free (signal_instance);
+}
+
+static void
+schedule_callbacks (EDBusConnection *connection,
+                    GPtrArray       *signal_data_array,
+                    DBusMessage     *message)
+{
+  guint n, m;
+
+  /* TODO: if this is slow, then we can change signal_data_array into
+   *       map_object_path_to_signal_data_array or something.
+   */
+  for (n = 0; n < signal_data_array->len; n++)
+    {
+      SignalData *signal_data = signal_data_array->pdata[n];
+      const gchar *arg0;
+
+      if (signal_data->interface_name != NULL &&
+          g_strcmp0 (signal_data->interface_name, dbus_message_get_interface (message)) != 0)
+        continue;
+
+      if (signal_data->member != NULL &&
+          g_strcmp0 (signal_data->member, dbus_message_get_member (message)) != 0)
+        continue;
+
+      if (signal_data->object_path != NULL &&
+          g_strcmp0 (signal_data->object_path, dbus_message_get_path (message)) != 0)
+        continue;
+
+      if (signal_data->arg0 != NULL)
+        {
+          if (!dbus_message_get_args (message,
+                                      NULL,
+                                      DBUS_TYPE_STRING, &arg0,
+                                      DBUS_TYPE_INVALID))
+            continue;
+
+          if (g_strcmp0 (signal_data->arg0, arg0) != 0)
+            continue;
+        }
+
+      for (m = 0; m < signal_data->subscribers->len; m++)
+        {
+          SignalSubscriber *subscriber;
+          GSource *idle_source;
+          SignalInstance *signal_instance;
+
+          subscriber = &(g_array_index (signal_data->subscribers, SignalSubscriber, m));
+
+          signal_instance = g_new0 (SignalInstance, 1);
+          signal_instance->callback = subscriber->callback;
+          signal_instance->user_data = subscriber->user_data;
+          signal_instance->message = dbus_message_ref (message);
+          signal_instance->connection = g_object_ref (connection);
+
+          /* use higher priority that method_reply to ensure signals are handled before method replies */
+          idle_source = g_idle_source_new ();
+          g_source_set_priority (idle_source, G_PRIORITY_HIGH);
+          g_source_set_callback (idle_source,
+                                 emit_signal_instance_in_idle_cb,
+                                 signal_instance,
+                                 (GDestroyNotify) signal_instance_free);
+          g_source_attach (idle_source, subscriber->context);
+          g_source_unref (idle_source);
+        }
+    }
+}
+
+/* do not call with any locks held */
+static void
+distribute_signals (EDBusConnection *connection,
+                    DBusMessage     *message)
+{
+  const gchar *sender;
+  GPtrArray *signal_data_array;
+
+  sender = dbus_message_get_sender (message);
+
+  G_LOCK (connection_lock);
+
+  /* collect subcsribers that match on sender */
+  if (sender != NULL)
+    {
+      signal_data_array = g_hash_table_lookup (connection->priv->map_sender_to_signal_data_array, sender);
+      if (signal_data_array != NULL) {
+        schedule_callbacks (connection, signal_data_array, message);
+      }
+    }
+
+  /* collect subcsribers not matching on sender */
+  signal_data_array = g_hash_table_lookup (connection->priv->map_sender_to_signal_data_array, "");
+  if (signal_data_array != NULL) {
+    schedule_callbacks (connection, signal_data_array, message);
+  }
+
+  G_UNLOCK (connection_lock);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* called from finalize(), removes all subscriptions */
+static void
+purge_all_signal_subscriptions (EDBusConnection *connection)
+{
+  GHashTableIter iter;
+  gpointer key;
+  GArray *ids;
+  GArray *subscribers;
+  guint n;
+
+  G_LOCK (connection_lock);
+  ids = g_array_new (FALSE, FALSE, sizeof (guint));
+  g_hash_table_iter_init (&iter, connection->priv->map_id_to_signal_data);
+  while (g_hash_table_iter_next (&iter, &key, NULL))
+    {
+      guint subscription_id = GPOINTER_TO_UINT (key);
+      g_array_append_val (ids, subscription_id);
+    }
+
+  subscribers = g_array_new (FALSE, FALSE, sizeof (SignalSubscriber));
+  for (n = 0; n < ids->len; n++)
+    {
+      guint subscription_id = g_array_index (ids, guint, n);
+      unsubscribe_id_internal (connection,
+                               subscription_id,
+                               subscribers);
+    }
+  g_array_free (ids, TRUE);
+
+  G_UNLOCK (connection_lock);
+
+  /* call GDestroyNotify without lock held */
+  for (n = 0; n < subscribers->len; n++)
+    {
+      SignalSubscriber *subscriber;
+      subscriber = &(g_array_index (subscribers, SignalSubscriber, n));
+      if (subscriber->user_data_free_func != NULL)
+        subscriber->user_data_free_func (subscriber->user_data);
+      if (subscriber->context != NULL)
+        g_main_context_unref (subscriber->context);
+    }
+
+  g_array_free (subscribers, TRUE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+struct ExportedObject
+{
+  gchar *object_path;
+  EDBusConnection *connection;
+
+  /* maps gchar* -> ExportedInterface* */
+  GHashTable *map_if_name_to_ei;
+};
+
+/* only called with lock held */
+static void
+exported_object_free (ExportedObject *eo)
+{
+  if (eo->connection->priv->dbus_1_connection != NULL)
+    {
+      if (!dbus_connection_unregister_object_path (eo->connection->priv->dbus_1_connection,
+                                                   eo->object_path))
+        _e_dbus_oom ();
+    }
+  g_free (eo->object_path);
+  g_hash_table_unref (eo->map_if_name_to_ei);
+  g_free (eo);
+}
+
+typedef struct
+{
+  ExportedObject *eo;
+
+  guint                       id;
+  gchar                      *interface_name;
+  const EDBusInterfaceVTable *vtable;
+  const EDBusInterfaceInfo   *introspection_data;
+
+  GMainContext               *context;
+  gpointer                    user_data;
+  GDestroyNotify              user_data_free_func;
+} ExportedInterface;
+
+/* called with lock held */
+static void
+exported_interface_free (ExportedInterface *ei)
+{
+  if (ei->user_data_free_func != NULL)
+    {
+      /* TODO: push to thread-default mainloop */
+      ei->user_data_free_func (ei->user_data);
+    }
+  if (ei->context != NULL)
+    {
+      g_main_context_unref (ei->context);
+    }
+  g_free (ei->interface_name);
+  g_free (ei);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  EDBusConnection *connection;
+  DBusMessage *message;
+  gpointer user_data;
+  const char *property_name;
+  const EDBusInterfaceVTable *vtable;
+  const EDBusInterfaceInfo *interface_info;
+  const EDBusPropertyInfo *property_info;
+} PropertyData;
+
+static void
+property_data_free (PropertyData *data)
+{
+  g_object_unref (data->connection);
+  dbus_message_unref (data->message);
+  g_free (data);
+}
+
+/* called in thread where object was registered - no locks held */
+static gboolean
+invoke_get_property_in_idle_cb (gpointer _data)
+{
+  PropertyData *data = _data;
+  EVariant *value;
+  GError *error;
+  DBusMessage *reply;
+
+  error = NULL;
+  value = data->vtable->get_property (data->connection,
+                                      data->user_data,
+                                      dbus_message_get_sender (data->message),
+                                      dbus_message_get_path (data->message),
+                                      data->interface_info->name,
+                                      data->property_name,
+                                      &error);
+
+  if (value != NULL)
+    {
+      EVariant *packed;
+
+      g_assert_no_error (error);
+
+      packed = e_variant_new ("(v)", value);
+
+      reply = dbus_message_new_method_return (data->message);
+      if (!_e_dbus_gvariant_to_dbus_1 (reply,
+                                       packed,
+                                       &error))
+        {
+          g_warning ("Error serializing to DBusMessage: %s", error->message);
+          g_error_free (error);
+          dbus_message_unref (reply);
+          e_variant_unref (value);
+          e_variant_unref (packed);
+          goto out;
+        }
+
+      _e_dbus_connection_send_dbus_1_message (data->connection, reply);
+
+      e_variant_unref (value);
+      e_variant_unref (packed);
+    }
+  else
+    {
+      gchar *dbus_error_name;
+
+      g_assert (error != NULL);
+
+      dbus_error_name = e_dbus_error_encode_gerror (error);
+      reply = dbus_message_new_error (data->message,
+                                      dbus_error_name,
+                                      error->message);
+
+      _e_dbus_connection_send_dbus_1_message (data->connection, reply);
+
+      g_free (dbus_error_name);
+      g_error_free (error);
+      dbus_message_unref (reply);
+    }
+
+ out:
+  return FALSE;
+}
+
+/* called in thread where object was registered - no locks held */
+static gboolean
+invoke_set_property_in_idle_cb (gpointer _data)
+{
+  PropertyData *data = _data;
+  GError *error;
+  DBusMessage *reply;
+  EVariant *parameters;
+  EVariant *value;
+
+  error = NULL;
+  parameters = NULL;
+  value = NULL;
+
+  parameters = _e_dbus_dbus_1_to_gvariant (data->message, &error);
+  if (parameters == NULL)
+    {
+      gchar *dbus_error_name;
+      g_assert (error != NULL);
+      dbus_error_name = e_dbus_error_encode_gerror (error);
+      reply = dbus_message_new_error (data->message,
+                                      dbus_error_name,
+                                      error->message);
+      g_free (dbus_error_name);
+      g_error_free (error);
+      goto out;
+    }
+
+  e_variant_get (parameters,
+                 "(ssv)",
+                 NULL,
+                 NULL,
+                 &value);
+
+  /* Fail with org.freedesktop.DBus.Error.InvalidArgs if the type
+   * of the given value is wrong
+   */
+  if (g_strcmp0 (e_variant_get_type_string (value), data->property_info->signature) != 0)
+    {
+      reply = dbus_message_new_error (data->message,
+                                      "org.freedesktop.DBus.Error.InvalidArgs",
+                                      _("Type of property to set is incorrect"));
+      goto out;
+    }
+
+  if (!data->vtable->set_property (data->connection,
+                                   data->user_data,
+                                   dbus_message_get_sender (data->message),
+                                   dbus_message_get_path (data->message),
+                                   data->interface_info->name,
+                                   data->property_name,
+                                   value,
+                                   &error))
+    {
+      gchar *dbus_error_name;
+      g_assert (error != NULL);
+      dbus_error_name = e_dbus_error_encode_gerror (error);
+      reply = dbus_message_new_error (data->message,
+                                      dbus_error_name,
+                                      error->message);
+      g_free (dbus_error_name);
+      g_error_free (error);
+    }
+  else
+    {
+      reply = dbus_message_new_method_return (data->message);
+    }
+
+ out:
+  g_assert (reply != NULL);
+  _e_dbus_connection_send_dbus_1_message (data->connection, reply);
+  if (value != NULL)
+    e_variant_unref (value);
+  if (parameters != NULL)
+    e_variant_unref (parameters);
+  dbus_message_unref (reply);
+  return FALSE;
+}
+
+/* can be called with or without the lock held */
+static DBusHandlerResult
+validate_and_maybe_schedule_property_getset (EDBusConnection            *connection,
+                                             DBusMessage                *message,
+                                             const EDBusInterfaceInfo   *introspection_data,
+                                             const EDBusInterfaceVTable *vtable,
+                                             GMainContext               *main_context,
+                                             gpointer                    user_data)
+{
+  DBusHandlerResult ret;
+  const char *interface_name;
+  const char *property_name;
+  const EDBusPropertyInfo *property_info;
+  GSource *idle_source;
+  PropertyData *property_data;
+  gboolean is_get;
+  DBusMessage *reply;
+
+  ret = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+  if (!dbus_message_get_args (message,
+                              NULL,
+                              DBUS_TYPE_STRING, &interface_name,
+                              DBUS_TYPE_STRING, &property_name,
+                              DBUS_TYPE_INVALID))
+    goto out;
+
+  is_get = FALSE;
+  if (g_strcmp0 (dbus_message_get_member (message), "Get") == 0)
+    is_get = TRUE;
+
+  if (is_get)
+    {
+      if (vtable == NULL || vtable->get_property == NULL)
+        goto out;
+    }
+  else
+    {
+      if (vtable == NULL || vtable->set_property == NULL)
+        goto out;
+    }
+
+  /* Check that the property exists - if not fail with org.freedesktop.DBus.Error.InvalidArgs
+   */
+  property_info = NULL;
+
+  /* TODO: the cost of this is O(n) - it might be worth caching the result */
+  property_info = e_dbus_interface_info_lookup_property (introspection_data,
+                                                         property_name);
+  if (property_info == NULL)
+    {
+      reply = dbus_message_new_error (message,
+                                      "org.freedesktop.DBus.Error.InvalidArgs",
+                                      _("No such property"));
+      _e_dbus_connection_send_dbus_1_message (connection, reply);
+      dbus_message_unref (reply);
+      ret = DBUS_HANDLER_RESULT_HANDLED;
+      goto out;
+    }
+
+  if (is_get && !(property_info->flags & E_DBUS_PROPERTY_INFO_FLAGS_READABLE))
+    {
+      reply = dbus_message_new_error (message,
+                                      "org.freedesktop.DBus.Error.InvalidArgs",
+                                      _("Property is not readable"));
+      _e_dbus_connection_send_dbus_1_message (connection, reply);
+      dbus_message_unref (reply);
+      ret = DBUS_HANDLER_RESULT_HANDLED;
+      goto out;
+    }
+  else if (!is_get && !(property_info->flags & E_DBUS_PROPERTY_INFO_FLAGS_WRITABLE))
+    {
+      reply = dbus_message_new_error (message,
+                                      "org.freedesktop.DBus.Error.InvalidArgs",
+                                      _("Property is not writable"));
+      _e_dbus_connection_send_dbus_1_message (connection, reply);
+      dbus_message_unref (reply);
+      ret = DBUS_HANDLER_RESULT_HANDLED;
+      goto out;
+    }
+
+  /* ok, got the property info - call user in an idle handler */
+  property_data = g_new0 (PropertyData, 1);
+  property_data->connection = g_object_ref (connection);
+  property_data->message = dbus_message_ref (message);
+  property_data->user_data = user_data;
+  property_data->property_name = property_name;
+  property_data->vtable = vtable;
+  property_data->interface_info = introspection_data;
+  property_data->property_info = property_info;
+
+  idle_source = g_idle_source_new ();
+  g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+  g_source_set_callback (idle_source,
+                         is_get ? invoke_get_property_in_idle_cb : invoke_set_property_in_idle_cb,
+                         property_data,
+                         (GDestroyNotify) property_data_free);
+  g_source_attach (idle_source, main_context);
+  g_source_unref (idle_source);
+
+  ret = DBUS_HANDLER_RESULT_HANDLED;
+
+ out:
+  return ret;
+}
+
+/* called with lock held */
+static DBusHandlerResult
+handle_getset_property (DBusConnection *connection,
+                        ExportedObject *eo,
+                        DBusMessage    *message)
+{
+  ExportedInterface *ei;
+  DBusHandlerResult ret;
+  const char *interface_name;
+  const char *property_name;
+
+  ret = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+  if (!dbus_message_get_args (message,
+                              NULL,
+                              DBUS_TYPE_STRING, &interface_name,
+                              DBUS_TYPE_STRING, &property_name,
+                              DBUS_TYPE_INVALID))
+    goto out;
+
+  /* Fail with org.freedesktop.DBus.Error.InvalidArgs if there is
+   * no such interface registered
+   */
+  ei = g_hash_table_lookup (eo->map_if_name_to_ei, interface_name);
+  if (ei == NULL)
+    {
+      DBusMessage *reply;
+      reply = dbus_message_new_error (message,
+                                      "org.freedesktop.DBus.Error.InvalidArgs",
+                                      _("No such interface"));
+      _e_dbus_connection_send_dbus_1_message (eo->connection, reply);
+      dbus_message_unref (reply);
+      ret = DBUS_HANDLER_RESULT_HANDLED;
+      goto out;
+    }
+
+  ret = validate_and_maybe_schedule_property_getset (eo->connection,
+                                                     message,
+                                                     ei->introspection_data,
+                                                     ei->vtable,
+                                                     ei->context,
+                                                     ei->user_data);
+ out:
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  EDBusConnection *connection;
+  DBusMessage *message;
+  gpointer user_data;
+  const EDBusInterfaceVTable *vtable;
+  const EDBusInterfaceInfo *interface_info;
+} PropertyGetAllData;
+
+static void
+property_get_all_data_free (PropertyData *data)
+{
+  g_object_unref (data->connection);
+  dbus_message_unref (data->message);
+  g_free (data);
+}
+
+/* called in thread where object was registered - no locks held */
+static gboolean
+invoke_get_all_properties_in_idle_cb (gpointer _data)
+{
+  PropertyGetAllData *data = _data;
+  EVariantBuilder *builder;
+  EVariant *packed;
+  EVariant *result;
+  GError *error;
+  DBusMessage *reply;
+  guint n;
+
+  error = NULL;
+
+  /* TODO: Right now we never fail this call - we just omit values if
+   *       a get_property() call is failing.
+   *
+   *       We could fail the whole call if just a single get_property() call
+   *       returns an error. We need clarification in the dbus spec for this.
+   */
+  builder = e_variant_builder_new (E_VARIANT_TYPE_ARRAY);
+  for (n = 0; n < data->interface_info->num_properties; n++)
+    {
+      const EDBusPropertyInfo *property_info = data->interface_info->properties + n;
+      EVariant *value;
+
+      if (!(property_info->flags & E_DBUS_PROPERTY_INFO_FLAGS_READABLE))
+        continue;
+
+      value = data->vtable->get_property (data->connection,
+                                          data->user_data,
+                                          dbus_message_get_sender (data->message),
+                                          dbus_message_get_path (data->message),
+                                          data->interface_info->name,
+                                          property_info->name,
+                                          NULL);
+      if (value == NULL)
+        continue;
+
+      e_variant_builder_add (builder,
+                             "{sv}",
+                             property_info->name,
+                             value);
+      e_variant_unref (value);
+    }
+  result = e_variant_builder_end (builder);
+
+  builder = e_variant_builder_new (E_VARIANT_TYPE_TUPLE);
+  e_variant_builder_add_value (builder, result); /* steals result since result is floating */
+  packed = e_variant_builder_end (builder);
+
+  reply = dbus_message_new_method_return (data->message);
+  if (!_e_dbus_gvariant_to_dbus_1 (reply,
+                                   packed,
+                                   &error))
+    {
+      g_warning ("Error serializing to DBusMessage: %s", error->message);
+      g_error_free (error);
+      dbus_message_unref (reply);
+      e_variant_unref (packed);
+      goto out;
+    }
+  e_variant_unref (packed);
+  _e_dbus_connection_send_dbus_1_message (data->connection, reply);
+  dbus_message_unref (reply);
+
+ out:
+  return FALSE;
+}
+
+/* can be called with or without the lock held */
+static DBusHandlerResult
+validate_and_maybe_schedule_property_get_all (EDBusConnection            *connection,
+                                              DBusMessage                *message,
+                                              const EDBusInterfaceInfo   *introspection_data,
+                                              const EDBusInterfaceVTable *vtable,
+                                              GMainContext               *main_context,
+                                              gpointer                    user_data)
+{
+  DBusHandlerResult ret;
+  const char *interface_name;
+  GSource *idle_source;
+  PropertyGetAllData *property_get_all_data;
+
+  ret = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+  if (!dbus_message_get_args (message,
+                              NULL,
+                              DBUS_TYPE_STRING, &interface_name,
+                              DBUS_TYPE_INVALID))
+    goto out;
+
+  if (vtable == NULL || vtable->get_property == NULL)
+    goto out;
+
+  /* ok, got the property info - call user in an idle handler */
+  property_get_all_data = g_new0 (PropertyGetAllData, 1);
+  property_get_all_data->connection = g_object_ref (connection);
+  property_get_all_data->message = dbus_message_ref (message);
+  property_get_all_data->user_data = user_data;
+  property_get_all_data->vtable = vtable;
+  property_get_all_data->interface_info = introspection_data;
+
+  idle_source = g_idle_source_new ();
+  g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+  g_source_set_callback (idle_source,
+                         invoke_get_all_properties_in_idle_cb,
+                         property_get_all_data,
+                         (GDestroyNotify) property_get_all_data_free);
+  g_source_attach (idle_source, main_context);
+  g_source_unref (idle_source);
+
+  ret = DBUS_HANDLER_RESULT_HANDLED;
+
+ out:
+  return ret;
+}
+
+/* called with lock held */
+static DBusHandlerResult
+handle_get_all_properties (DBusConnection *connection,
+                           ExportedObject *eo,
+                           DBusMessage    *message)
+{
+  ExportedInterface *ei;
+  DBusHandlerResult ret;
+  const char *interface_name;
+
+  ret = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+  if (!dbus_message_get_args (message,
+                              NULL,
+                              DBUS_TYPE_STRING, &interface_name,
+                              DBUS_TYPE_INVALID))
+    goto out;
+
+  /* Fail with org.freedesktop.DBus.Error.InvalidArgs if there is
+   * no such interface registered
+   */
+  ei = g_hash_table_lookup (eo->map_if_name_to_ei, interface_name);
+  if (ei == NULL)
+    {
+      DBusMessage *reply;
+      reply = dbus_message_new_error (message,
+                                      "org.freedesktop.DBus.Error.InvalidArgs",
+                                      _("No such interface"));
+      _e_dbus_connection_send_dbus_1_message (eo->connection, reply);
+      dbus_message_unref (reply);
+      ret = DBUS_HANDLER_RESULT_HANDLED;
+      goto out;
+    }
+
+  ret = validate_and_maybe_schedule_property_get_all (eo->connection,
+                                                      message,
+                                                      ei->introspection_data,
+                                                      ei->vtable,
+                                                      ei->context,
+                                                      ei->user_data);
+ out:
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+introspect_append_header (GString *s)
+{
+  g_string_append (s,
+                   DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+                   "<!-- EDBus 0.1 -->\n"
+                   "<node>\n");
+}
+
+static void
+introspect_append_standard_interfaces (GString *s)
+{
+  g_string_append (s,
+                    "  <interface name=\"org.freedesktop.DBus.Properties\">\n"
+                    "    <method name=\"Get\">\n"
+                    "      <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n"
+                    "      <arg type=\"s\" name=\"property_name\" direction=\"in\"/>\n"
+                    "      <arg type=\"v\" name=\"value\" direction=\"out\"/>\n"
+                    "    </method>\n"
+                    "    <method name=\"GetAll\">\n"
+                    "      <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n"
+                    "      <arg type=\"a{sv}\" name=\"properties\" direction=\"out\"/>\n"
+                    "    </method>\n"
+                    "    <method name=\"Set\">\n"
+                    "      <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n"
+                    "      <arg type=\"s\" name=\"property_name\" direction=\"in\"/>\n"
+                    "      <arg type=\"v\" name=\"value\" direction=\"in\"/>\n"
+                    "    </method>\n"
+                    "    <signal name=\"PropertiesChanged\">\n"
+                    "      <arg type=\"s\" name=\"interface_name\"/>\n"
+                    "      <arg type=\"a{sv}\" name=\"changed_properties\"/>\n"
+                    "    </signal>\n"
+                    "  </interface>\n"
+                    "  <interface name=\"org.freedesktop.DBus.Introspectable\">\n"
+                    "    <method name=\"Introspect\">\n"
+                    "      <arg type=\"s\" name=\"xml_data\" direction=\"out\"/>\n"
+                    "    </method>\n"
+                    "  </interface>\n"
+                    "  <interface name=\"org.freedesktop.DBus.Peer\">\n"
+                    "    <method name=\"Ping\"/>\n"
+                    "    <method name=\"GetMachineId\">\n"
+                    "      <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>\n"
+                    "    </method>\n"
+                    "  </interface>\n");
+}
+
+/* called with lock held */
+static DBusHandlerResult
+handle_introspect (DBusConnection *connection,
+                   ExportedObject *eo,
+                   DBusMessage    *message)
+{
+  guint n;
+  GString *s;
+  char **subnode_paths;
+  DBusMessage *reply;
+  GHashTableIter hash_iter;
+  ExportedInterface *ei;
+
+  //g_debug ("in handle_introspect for %s", eo->object_path);
+
+  /* first the header with the standard interfaces */
+  s = g_string_new (NULL);
+
+  introspect_append_header (s);
+  introspect_append_standard_interfaces (s);
+
+  /* then include the registered interfaces */
+  g_hash_table_iter_init (&hash_iter, eo->map_if_name_to_ei);
+  while (g_hash_table_iter_next (&hash_iter, NULL, (gpointer) &ei))
+    {
+      e_dbus_interface_info_generate_xml (ei->introspection_data,
+                                          2,
+                                          s);
+    }
+
+  /* finally include nodes registered below us */
+  if (!dbus_connection_list_registered (eo->connection->priv->dbus_1_connection,
+                                        eo->object_path,
+                                        &subnode_paths))
+    _e_dbus_oom ();
+  for (n = 0; subnode_paths != NULL && subnode_paths[n] != NULL; n++)
+    {
+      g_string_append_printf (s, "  <node name=\"%s\"/>\n", subnode_paths[n]);
+    }
+  dbus_free_string_array (subnode_paths);
+
+  g_string_append (s, "</node>\n");
+
+  reply = dbus_message_new_method_return (message);
+  if (!dbus_message_append_args (reply,
+                                 DBUS_TYPE_STRING, &s->str,
+                                 DBUS_TYPE_INVALID))
+    _e_dbus_oom ();
+  _e_dbus_connection_send_dbus_1_message (eo->connection, reply);
+  g_string_free (s, TRUE);
+
+  return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+/* called in thread where object was registered - no locks held */
+static gboolean
+invoke_method_in_idle_cb (gpointer user_data)
+{
+  EDBusMethodInvocation *invocation = E_DBUS_METHOD_INVOCATION (user_data);
+  EDBusInterfaceVTable *vtable;
+
+  vtable = g_object_get_data (G_OBJECT (invocation), "g-dbus-interface-vtable");
+  g_assert (vtable != NULL && vtable->method_call != NULL);
+
+  vtable->method_call (e_dbus_method_invocation_get_connection (invocation),
+                       e_dbus_method_invocation_get_user_data (invocation),
+                       e_dbus_method_invocation_get_sender (invocation),
+                       e_dbus_method_invocation_get_object_path (invocation),
+                       e_dbus_method_invocation_get_interface_name (invocation),
+                       e_dbus_method_invocation_get_method_name (invocation),
+                       e_dbus_method_invocation_get_parameters (invocation),
+                       g_object_ref (invocation));
+  return FALSE;
+}
+
+/* can be called with or without the lock held */
+static DBusHandlerResult
+validate_and_maybe_schedule_method_call (EDBusConnection            *connection,
+                                         DBusMessage                *message,
+                                         const EDBusInterfaceInfo   *introspection_data,
+                                         const EDBusInterfaceVTable *vtable,
+                                         GMainContext               *main_context,
+                                         gpointer                    user_data)
+{
+  EDBusMethodInvocation *invocation;
+  DBusHandlerResult result;
+  const EDBusMethodInfo *method_info;
+  DBusMessage *reply;
+  GError *error;
+  EVariant *parameters;
+  GSource *idle_source;
+
+  result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+  /* TODO: the cost of this is O(n) - it might be worth caching the result */
+  method_info = e_dbus_interface_info_lookup_method (introspection_data,
+                                                     dbus_message_get_member (message));
+  /* if the method doesn't exist, return the org.freedesktop.DBus.Error.UnknownMethod
+   * error to the caller */
+  if (method_info == NULL)
+    {
+      reply = dbus_message_new_error (message,
+                                      "org.freedesktop.DBus.Error.UnknownMethod",
+                                      _("No such method"));
+      _e_dbus_connection_send_dbus_1_message (connection, reply);
+      dbus_message_unref (reply);
+      result = DBUS_HANDLER_RESULT_HANDLED;
+      goto out;
+    }
+
+  /* Check that the incoming args are of the right type - if they are not, return
+   * the org.freedesktop.DBus.Error.InvalidArgs error to the caller
+   */
+  if (!dbus_message_has_signature (message, method_info->in_signature))
+    {
+      reply = dbus_message_new_error (message,
+                                      "org.freedesktop.DBus.Error.InvalidArgs",
+                                      _("Signature of message does not match what is expected"));
+      _e_dbus_connection_send_dbus_1_message (connection, reply);
+      dbus_message_unref (reply);
+      result = DBUS_HANDLER_RESULT_HANDLED;
+      goto out;
+    }
+
+  error = NULL;
+  parameters = _e_dbus_dbus_1_to_gvariant (message, &error);
+  if (parameters == NULL)
+    {
+      g_warning ("Error converting signal parameters to a EVariant: %s", error->message);
+      g_error_free (error);
+      goto out;
+    }
+
+  /* schedule the call in idle */
+  invocation = e_dbus_method_invocation_new (dbus_message_get_sender (message),
+                                             dbus_message_get_path (message),
+                                             dbus_message_get_interface (message),
+                                             dbus_message_get_member (message),
+                                             connection,
+                                             parameters,
+                                             user_data);
+  e_variant_unref (parameters);
+  g_object_set_data_full (G_OBJECT (invocation),
+                          "dbus-1-message",
+                          dbus_message_ref (message),
+                          (GDestroyNotify) dbus_message_unref);
+  g_object_set_data (G_OBJECT (invocation),
+                     "g-dbus-interface-vtable",
+                     (gpointer) vtable);
+  g_object_set_data (G_OBJECT (invocation),
+                     "g-dbus-method-info",
+                     (gpointer) method_info);
+
+  idle_source = g_idle_source_new ();
+  g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
+  g_source_set_callback (idle_source,
+                         invoke_method_in_idle_cb,
+                         invocation,
+                         g_object_unref);
+  g_source_attach (idle_source, main_context);
+  g_source_unref (idle_source);
+
+  result = DBUS_HANDLER_RESULT_HANDLED;
+
+ out:
+
+  return result;
+}
+
+static DBusHandlerResult
+dbus_1_obj_vtable_message_func (DBusConnection *connection,
+                                DBusMessage    *message,
+                                void           *user_data)
+{
+  ExportedObject *eo = user_data;
+  const char *interface_name;
+  DBusHandlerResult result;
+
+  G_LOCK (connection_lock);
+
+  result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+  //g_debug ("in dbus_1_obj_vtable_message_func for path %s", eo->object_path);
+  //PRINT_MESSAGE (message);
+
+  /* see if we have an interface for this handling this call */
+  interface_name = dbus_message_get_interface (message);
+  if (interface_name != NULL)
+    {
+      ExportedInterface *ei;
+      ei = g_hash_table_lookup (eo->map_if_name_to_ei, interface_name);
+      if (ei != NULL)
+        {
+          /* we do - invoke the handler in idle in the right thread */
+
+          /* handle no vtable or handler being present */
+          if (ei->vtable == NULL || ei->vtable->method_call == NULL)
+            goto out;
+
+          result = validate_and_maybe_schedule_method_call (eo->connection,
+                                                            message,
+                                                            ei->introspection_data,
+                                                            ei->vtable,
+                                                            ei->context,
+                                                            ei->user_data);
+          goto out;
+        }
+    }
+
+  if (dbus_message_is_method_call (message,
+                                   "org.freedesktop.DBus.Introspectable",
+                                   "Introspect") &&
+      g_strcmp0 (dbus_message_get_signature (message), "") == 0)
+    {
+      result = handle_introspect (connection, eo, message);
+      goto out;
+    }
+  else if (dbus_message_is_method_call (message,
+                                        "org.freedesktop.DBus.Properties",
+                                        "Get") &&
+           g_strcmp0 (dbus_message_get_signature (message), "ss") == 0)
+    {
+      result = handle_getset_property (connection, eo, message);
+      goto out;
+    }
+  else if (dbus_message_is_method_call (message,
+                                        "org.freedesktop.DBus.Properties",
+                                        "Set") &&
+           g_strcmp0 (dbus_message_get_signature (message), "ssv") == 0)
+    {
+      result = handle_getset_property (connection, eo, message);
+      goto out;
+    }
+  else if (dbus_message_is_method_call (message,
+                                        "org.freedesktop.DBus.Properties",
+                                        "GetAll") &&
+           g_strcmp0 (dbus_message_get_signature (message), "s") == 0)
+    {
+      result = handle_get_all_properties (connection, eo, message);
+      goto out;
+    }
+
+ out:
+  G_UNLOCK (connection_lock);
+  return result;
+}
+
+static const DBusObjectPathVTable dbus_1_obj_vtable =
+{
+  NULL,
+  dbus_1_obj_vtable_message_func,
+  NULL,
+  NULL,
+  NULL,
+  NULL
+};
+
+/**
+ * e_dbus_connection_register_object:
+ * @connection: A #EDBusConnection.
+ * @object_path: The object path to register at.
+ * @interface_name: The D-Bus interface to register.
+ * @introspection_data: Introspection data for the interface.
+ * @vtable: A #EDBusInterfaceVTable to call into or %NULL.
+ * @user_data: Data to pass to functions in @vtable.
+ * @user_data_free_func: Function to call when the object path is unregistered.
+ * @error: Return location for error or %NULL.
+ *
+ * Registers callbacks for exported objects at @object_path with the
+ * D-Bus interface @interface_name.
+ *
+ * Calls to functions in @vtable (and @user_data_free_func) will
+ * happen in the <link linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link> of the thread you are calling this method from.
+ *
+ * Note that all #EVariant values passed to functions in @vtable will match
+ * the signature given in @introspection_data - if a remote caller passes
+ * incorrect values, the <literal>org.freedesktop.DBus.Error.InvalidArgs</literal>
+ * is returned to the remote caller.
+ *
+ * Additionally, if the remote caller attempts to invoke methods or
+ * access properties not mentioned in @introspection_data the
+ * <literal>org.freedesktop.DBus.Error.UnknownMethod</literal> resp.
+ * <literal>org.freedesktop.DBus.Error.InvalidArgs</literal> errors
+ * are returned to the caller.
+ *
+ * It is considered a programming error if the get_property() function
+ * in @vtable returns a #EVariant of incorrect type.
+ *
+ * If an existing callback is already registered at @object_path and
+ * @interface_name or another binding is already exporting objects at
+ * @object_path, then @error is set to #E_DBUS_ERROR_OBJECT_PATH_IN_USE.
+ *
+ * Returns: 0 if @error is set, otherwise a registration id (never 0)
+ * that can be used with e_dbus_connection_unregister_object() .
+ */
+guint
+e_dbus_connection_register_object (EDBusConnection            *connection,
+                                   const gchar                *object_path,
+                                   const gchar                *interface_name,
+                                   const EDBusInterfaceInfo   *introspection_data,
+                                   const EDBusInterfaceVTable *vtable,
+                                   gpointer                    user_data,
+                                   GDestroyNotify              user_data_free_func,
+                                   GError                    **error)
+{
+  ExportedObject *eo;
+  ExportedInterface *ei;
+  guint ret;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0);
+  g_return_val_if_fail (!e_dbus_connection_get_is_disconnected (connection), 0);
+  g_return_val_if_fail (interface_name != NULL, 0);
+  g_return_val_if_fail (object_path != NULL, 0);
+  g_return_val_if_fail (introspection_data != NULL, 0);
+
+  ret = 0;
+
+  G_LOCK (connection_lock);
+
+  eo = g_hash_table_lookup (connection->priv->map_object_path_to_eo, object_path);
+  if (eo == NULL)
+    {
+      DBusError dbus_error;
+
+      eo = g_new0 (ExportedObject, 1);
+      eo->object_path = g_strdup (object_path);
+      eo->connection = connection;
+      eo->map_if_name_to_ei = g_hash_table_new_full (g_str_hash,
+                                                     g_str_equal,
+                                                     NULL,
+                                                     (GDestroyNotify) exported_interface_free);
+      g_hash_table_insert (connection->priv->map_object_path_to_eo, eo->object_path, eo);
+
+      dbus_error_init (&dbus_error);
+      if (!dbus_connection_try_register_object_path (connection->priv->dbus_1_connection,
+                                                     object_path,
+                                                     &dbus_1_obj_vtable,
+                                                     eo,
+                                                     &dbus_error))
+        {
+          if (g_strcmp0 (dbus_error.name, DBUS_ERROR_NO_MEMORY) == 0)
+            _e_dbus_oom ();
+
+          e_dbus_error_set_dbus_error (error,
+                                       dbus_error.name,
+                                       dbus_error.message,
+                                       _("Another D-Bus binding is already exporting an object at %s"),
+                                       object_path);
+          if (error != NULL)
+            {
+              /* this is a locally generated error so strip the remote part */
+              e_dbus_error_strip_remote_error (*error);
+            }
+          dbus_error_free (&dbus_error);
+          goto out;
+        }
+    }
+
+  ei = g_hash_table_lookup (eo->map_if_name_to_ei, interface_name);
+  if (ei != NULL)
+    {
+      g_set_error (error,
+                   E_DBUS_ERROR,
+                   E_DBUS_ERROR_OBJECT_PATH_IN_USE,
+                   _("An object is already exported for the interface %s at %s"),
+                   interface_name,
+                   object_path);
+      goto out;
+    }
+
+  ei = g_new0 (ExportedInterface, 1);
+  ei->id = _global_registration_id++; /* TODO: overflow etc. */
+  ei->eo = eo;
+  ei->user_data = user_data;
+  ei->user_data_free_func = user_data_free_func;
+  ei->vtable = vtable;
+  ei->introspection_data = introspection_data;
+  ei->interface_name = g_strdup (interface_name);
+  ei->context = g_main_context_get_thread_default ();
+  if (ei->context != NULL)
+    g_main_context_ref (ei->context);
+
+  g_hash_table_insert (eo->map_if_name_to_ei,
+                       (gpointer) ei->interface_name,
+                       ei);
+  g_hash_table_insert (connection->priv->map_id_to_ei,
+                       GUINT_TO_POINTER (ei->id),
+                       ei);
+
+  ret = ei->id;
+
+ out:
+  G_UNLOCK (connection_lock);
+
+  return ret;
+}
+
+/**
+ * e_dbus_connection_unregister_object:
+ * @connection: A #EDBusConnection.
+ * @registration_id: A registration id obtained from e_dbus_connection_register_object().
+ *
+ * Unregisters an object.
+ *
+ * Returns: %TRUE if the object was unregistered, %FALSE otherwise.
+ */
+gboolean
+e_dbus_connection_unregister_object (EDBusConnection *connection,
+                                     guint            registration_id)
+{
+  ExportedInterface *ei;
+  ExportedObject *eo;
+  gboolean ret;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+
+  ret = FALSE;
+
+  G_LOCK (connection_lock);
+
+  ei = g_hash_table_lookup (connection->priv->map_id_to_ei,
+                            GUINT_TO_POINTER (registration_id));
+  if (ei == NULL)
+    {
+      goto out;
+    }
+
+  eo = ei->eo;
+
+  g_assert (g_hash_table_remove (connection->priv->map_id_to_ei, GUINT_TO_POINTER (ei->id)));
+  g_assert (g_hash_table_remove (eo->map_if_name_to_ei, ei->interface_name));
+  /* unregister object path if we have no more exported interfaces */
+  if (g_hash_table_size (eo->map_if_name_to_ei) == 0)
+    {
+      g_assert (g_hash_table_remove (connection->priv->map_object_path_to_eo,
+                                     eo->object_path));
+    }
+
+  ret = TRUE;
+
+ out:
+  G_UNLOCK (connection_lock);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_connection_emit_signal:
+ * @connection: A #EDBusConnection.
+ * @destination_bus_name: The unique bus name for the destination for the signal or %NULL to emit to all listeners.
+ * @object_path: Path of remote object.
+ * @interface_name: D-Bus interface to emit a signal on.
+ * @signal_name: The name of the signal to emit.
+ * @parameters: A #EVariant tuple with parameters for the signal or %NULL if not passing parameters.
+ * @error: Return location for error or %NULL.
+ *
+ * Emits a signal.
+ *
+ * This can only fail if @parameters is not compatible with the D-Bus protocol.
+ *
+ * Returns: %TRUE unless @error is set.
+ */
+gboolean
+e_dbus_connection_emit_signal (EDBusConnection    *connection,
+                               const gchar        *destination_bus_name,
+                               const gchar        *object_path,
+                               const gchar        *interface_name,
+                               const gchar        *signal_name,
+                               EVariant           *parameters,
+                               GError            **error)
+{
+  DBusMessage *message;
+  gboolean ret;
+
+  message = NULL;
+  ret = FALSE;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+  g_return_val_if_fail (object_path != NULL, FALSE);
+  g_return_val_if_fail (interface_name != NULL, FALSE);
+  g_return_val_if_fail (signal_name != NULL, FALSE);
+  g_return_val_if_fail ((parameters == NULL) || (e_variant_get_type_class (parameters) == E_VARIANT_CLASS_TUPLE), FALSE);
+
+  message = dbus_message_new_signal (object_path,
+                                     interface_name,
+                                     signal_name);
+  if (message == NULL)
+    _e_dbus_oom ();
+
+  if (destination_bus_name != NULL)
+    {
+      if (!dbus_message_set_destination (message, destination_bus_name))
+        _e_dbus_oom ();
+    }
+
+  if (!_e_dbus_gvariant_to_dbus_1 (message,
+                                   parameters,
+                                   error))
+    {
+      goto out;
+    }
+
+  _e_dbus_connection_send_dbus_1_message (connection, message);
+
+  ret = TRUE;
+
+ out:
+  if (message != NULL)
+    dbus_message_unref (message);
+
+  return ret;
+}
+
+/**
+ * e_dbus_connection_invoke_method:
+ * @connection: A #EDBusConnection.
+ * @bus_name: A unique or well-known bus name.
+ * @object_path: Path of remote object.
+ * @interface_name: D-Bus interface to invoke method on.
+ * @method_name: The name of the method to invoke.
+ * @parameters: A #EVariant tuple with parameters for the method or %NULL if not passing parameters.
+ * @timeout_msec: The timeout in milliseconds or -1 to use the default timeout.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL if you don't
+ * care about the result of the method invocation.
+ * @user_data: The data to pass to @callback.
+ *
+ * Asynchronously invokes the @method_name method on the
+ * @interface_name D-Bus interface on the remote object at
+ * @object_path owned by @bus_name.
+ *
+ * If @connection is disconnected then the operation will fail with
+ * %E_DBUS_ERROR_DISCONNECTED. If @cancellable is canceled, the
+ * operation will fail with %E_DBUS_ERROR_CANCELLED. If @parameters
+ * contains a value not compatible with the D-Bus protocol, the operation
+ * fails with %E_DBUS_ERROR_CONVERSION_FAILED.
+ *
+ * This is an asynchronous method. When the operation is finished, @callback will be invoked
+ * in the <link linkend="g-main-context-push-thread-default">thread-default main loop</link>
+ * of the thread you are calling this method from. You can then call
+ * e_dbus_connection_invoke_method_finish() to get the result of the operation.
+ * See e_dbus_connection_invoke_method_sync() for the synchronous version of this
+ * function.
+ */
+void
+e_dbus_connection_invoke_method (EDBusConnection    *connection,
+                                 const gchar        *bus_name,
+                                 const gchar        *object_path,
+                                 const gchar        *interface_name,
+                                 const gchar        *method_name,
+                                 EVariant           *parameters,
+                                 gint                timeout_msec,
+                                 GCancellable       *cancellable,
+                                 GAsyncReadyCallback callback,
+                                 gpointer            user_data)
+{
+  DBusMessage *message;
+  GError *error;
+
+  message = NULL;
+  error = NULL;
+
+  g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+  g_return_if_fail ((connection->priv->bus_type == G_BUS_TYPE_NONE && bus_name == NULL) ||
+                    (connection->priv->bus_type != G_BUS_TYPE_NONE && bus_name != NULL));
+  g_return_if_fail (object_path != NULL);
+  g_return_if_fail (interface_name != NULL);
+  g_return_if_fail (method_name != NULL);
+  g_return_if_fail ((parameters == NULL) || (e_variant_get_type_class (parameters) == E_VARIANT_CLASS_TUPLE));
+
+  message = dbus_message_new_method_call (bus_name,
+                                          object_path,
+                                          interface_name,
+                                          method_name);
+  if (message == NULL)
+    _e_dbus_oom ();
+
+  if (callback == NULL)
+    {
+      if (_e_dbus_gvariant_to_dbus_1 (message,
+                                      parameters,
+                                      &error))
+        {
+          _e_dbus_connection_send_dbus_1_message (connection, message);
+        }
+      else
+        {
+          g_warning ("Tried invoking a method without caring about the reply, "
+                     "and encountered an error serializing the parameters: %s",
+                     error->message);
+          g_error_free (error);
+        }
+    }
+  else
+    {
+      GSimpleAsyncResult *simple;
+
+      if (!_e_dbus_gvariant_to_dbus_1 (message,
+                                       parameters,
+                                       &error))
+        {
+          simple = g_simple_async_result_new (G_OBJECT (connection),
+                                              callback,
+                                              user_data,
+                                              e_dbus_connection_send_dbus_1_message_with_reply);
+          g_simple_async_result_set_from_error (simple, error);
+          g_error_free (error);
+          g_simple_async_result_complete_in_idle (simple);
+          g_object_unref (simple);
+          goto out;
+        }
+
+      e_dbus_connection_send_dbus_1_message_with_reply (connection,
+                                                        message,
+                                                        timeout_msec,
+                                                        cancellable,
+                                                        callback,
+                                                        user_data);
+    }
+
+ out:
+  if (message != NULL)
+    dbus_message_unref (message);
+}
+
+/**
+ * e_dbus_connection_invoke_method_finish:
+ * @connection: A #EDBusConnection.
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to e_dbus_connection_invoke_method().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with e_dbus_connection_invoke_method().
+ *
+ * Returns: %NULL if @error is set. Otherwise a #EVariant tuple with
+ * return values. Free with e_variant_unref().
+ */
+EVariant *
+e_dbus_connection_invoke_method_finish (EDBusConnection    *connection,
+                                        GAsyncResult       *res,
+                                        GError            **error)
+{
+  DBusMessage *reply;
+  EVariant *result;
+
+  result = NULL;
+
+  reply = e_dbus_connection_send_dbus_1_message_with_reply_finish (connection, res, error);
+  if (reply == NULL)
+    goto out;
+
+  result = _e_dbus_dbus_1_to_gvariant (reply, error);
+
+  dbus_message_unref (reply);
+
+ out:
+  return result;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_connection_invoke_method_sync:
+ * @connection: A #EDBusConnection.
+ * @bus_name: A unique or well-known bus name.
+ * @object_path: Path of remote object.
+ * @interface_name: D-Bus interface to invoke method on.
+ * @method_name: The name of the method to invoke.
+ * @parameters: A #EVariant tuple with parameters for the method or %NULL if not passing parameters.
+ * @timeout_msec: The timeout in milliseconds or -1 to use the default timeout.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously invokes the @method_name method on the
+ * @interface_name D-Bus interface on the remote object at
+ * @object_path owned by @bus_name.
+ *
+ * If @connection is disconnected then the operation will fail with
+ * %E_DBUS_ERROR_DISCONNECTED. If @cancellable is canceled, the
+ * operation will fail with %E_DBUS_ERROR_CANCELLED. If @parameters
+ * contains a value not compatible with the D-Bus protocol, the operation
+ * fails with %E_DBUS_ERROR_CONVERSION_FAILED.
+ *
+ * The calling thread is blocked until a reply is received. See
+ * e_dbus_connection_invoke_method() for the asynchronous version of
+ * this method.
+ *
+ * Returns: %NULL if @error is set. Otherwise a #EVariant tuple with
+ * return values. Free with e_variant_unref().
+ */
+EVariant *
+e_dbus_connection_invoke_method_sync (EDBusConnection    *connection,
+                                      const gchar        *bus_name,
+                                      const gchar        *object_path,
+                                      const gchar        *interface_name,
+                                      const gchar        *method_name,
+                                      EVariant           *parameters,
+                                      gint                timeout_msec,
+                                      GCancellable       *cancellable,
+                                      GError            **error)
+{
+  DBusMessage *message;
+  DBusMessage *reply;
+  EVariant *result;
+
+  message = NULL;
+  reply = NULL;
+  result = NULL;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+  g_return_val_if_fail ((connection->priv->bus_type == G_BUS_TYPE_NONE && bus_name == NULL) ||
+                        (connection->priv->bus_type != G_BUS_TYPE_NONE && bus_name != NULL),
+                        NULL);
+  g_return_val_if_fail (object_path != NULL, NULL);
+  g_return_val_if_fail (interface_name != NULL, NULL);
+  g_return_val_if_fail (method_name != NULL, NULL);
+  g_return_val_if_fail ((parameters == NULL) || (e_variant_get_type_class (parameters) == E_VARIANT_CLASS_TUPLE), NULL);
+
+  message = dbus_message_new_method_call (bus_name,
+                                          object_path,
+                                          interface_name,
+                                          method_name);
+  if (message == NULL)
+    _e_dbus_oom ();
+
+  if (parameters != NULL)
+    {
+      e_variant_ref_sink (parameters);
+      if (!_e_dbus_gvariant_to_dbus_1 (message,
+                                       parameters,
+                                       error))
+        goto out;
+      e_variant_unref (parameters);
+    }
+
+  reply = e_dbus_connection_send_dbus_1_message_with_reply_sync (connection,
+                                                                 message,
+                                                                 timeout_msec,
+                                                                 cancellable,
+                                                                 error);
+
+  if (reply == NULL)
+    goto out;
+
+  result = _e_dbus_dbus_1_to_gvariant (reply, error);
+  if (result == NULL)
+    goto out;
+
+ out:
+  if (message != NULL)
+    dbus_message_unref (message);
+  if (reply != NULL)
+    dbus_message_unref (reply);
+
+  return result;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+struct ExportedSubtree
+{
+  guint                     id;
+  gchar                    *object_path;
+  EDBusConnection          *connection;
+  const EDBusSubtreeVTable *vtable;
+  EDBusSubtreeFlags         flags;
+
+  GMainContext             *context;
+  gpointer                  user_data;
+  GDestroyNotify            user_data_free_func;
+};
+
+static void
+exported_subtree_free (ExportedSubtree *es)
+{
+  if (es->user_data_free_func != NULL)
+    {
+      /* TODO: push to thread-default mainloop */
+      es->user_data_free_func (es->user_data);
+    }
+  if (es->context != NULL)
+    {
+      g_main_context_unref (es->context);
+    }
+  g_free (es->object_path);
+  g_free (es);
+}
+
+static gboolean
+_g_strv_has_string (const gchar* const * haystack,
+                    const gchar *needle)
+{
+  guint n;
+
+  for (n = 0; haystack != NULL && haystack[n] != NULL; n++)
+    {
+      if (g_strcmp0 (haystack[n], needle) == 0)
+        return TRUE;
+    }
+  return FALSE;
+}
+
+/* called without lock held */
+static DBusHandlerResult
+handle_subtree_introspect (DBusConnection  *connection,
+                           ExportedSubtree *es,
+                           DBusMessage     *message)
+{
+  GString *s;
+  DBusHandlerResult result;
+  DBusMessage *reply;
+  gchar **children;
+  gboolean is_root;
+  const gchar *sender;
+  const gchar *requested_object_path;
+  const gchar *requested_node;
+  GPtrArray *interfaces;
+  guint n;
+  char **subnode_paths;
+
+  result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+  requested_object_path = dbus_message_get_path (message);
+  sender = dbus_message_get_sender (message);
+  is_root = (g_strcmp0 (requested_object_path, es->object_path) == 0);
+
+  s = g_string_new (NULL);
+  introspect_append_header (s);
+
+  //g_debug ("in handle_subtree_introspect for %s", requested_object_path);
+
+  /* Strictly we don't need the children in dynamic mode, but we avoid the
+   * conditionals to preserve code clarity */
+  children = es->vtable->enumerate (es->connection,
+                                    es->user_data,
+                                    sender,
+                                    es->object_path);
+
+  if (!is_root)
+    {
+      requested_node = strrchr (requested_object_path, '/') + 1;
+
+      /* Assert existence of object if we are not dynamic */
+      if (!(es->flags & E_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES) &&
+          !_g_strv_has_string ((const gchar * const *) children, requested_node))
+        goto out;
+    }
+  else
+    {
+      requested_node = "/";
+    }
+
+  interfaces = es->vtable->introspect (es->connection,
+                                       es->user_data,
+                                       sender,
+                                       es->object_path,
+                                       requested_node);
+  if (interfaces != NULL)
+    {
+      if (interfaces->len > 0)
+        {
+          /* we're in business */
+          introspect_append_standard_interfaces (s);
+
+          for (n = 0; n < interfaces->len; n++)
+            {
+              const EDBusInterfaceInfo *interface_info = interfaces->pdata[n];
+              e_dbus_interface_info_generate_xml (interface_info,
+                                                  2,
+                                                  s);
+            }
+        }
+      g_ptr_array_unref (interfaces);
+    }
+
+  /* then include <node> entries from the Subtree for the root */
+  if (is_root)
+    {
+      for (n = 0; children != NULL && children[n] != NULL; n++)
+        {
+          g_string_append_printf (s, "  <node name=\"%s\"/>\n", children[n]);
+        }
+    }
+
+  /* finally include nodes registered below us */
+  if (!dbus_connection_list_registered (es->connection->priv->dbus_1_connection,
+                                        requested_object_path,
+                                        &subnode_paths))
+    _e_dbus_oom ();
+  for (n = 0; subnode_paths != NULL && subnode_paths[n] != NULL; n++)
+    {
+      g_string_append_printf (s, "  <node name=\"%s\"/>\n", subnode_paths[n]);
+    }
+  dbus_free_string_array (subnode_paths);
+
+  g_string_append (s, "</node>\n");
+
+  reply = dbus_message_new_method_return (message);
+  if (!dbus_message_append_args (reply,
+                                 DBUS_TYPE_STRING, &s->str,
+                                 DBUS_TYPE_INVALID))
+    _e_dbus_oom ();
+  _e_dbus_connection_send_dbus_1_message (es->connection, reply);
+
+  result = DBUS_HANDLER_RESULT_HANDLED;
+
+ out:
+  g_string_free (s, TRUE);
+  g_strfreev (children);
+  return result;
+}
+
+/* called without lock held */
+static DBusHandlerResult
+handle_subtree_method_invocation (DBusConnection *connection,
+                                  ExportedSubtree *es,
+                                  DBusMessage    *message)
+{
+  DBusHandlerResult result;
+  const gchar *sender;
+  const gchar *interface_name;
+  const gchar *requested_object_path;
+  const gchar *requested_node;
+  gboolean is_root;
+  gchar **children;
+  const EDBusInterfaceInfo *introspection_data;
+  const EDBusInterfaceVTable *interface_vtable;
+  gpointer interface_user_data;
+  guint n;
+  GPtrArray *interfaces;
+  gboolean is_property_get;
+  gboolean is_property_set;
+  gboolean is_property_get_all;
+
+  result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+  interfaces = NULL;
+
+  requested_object_path = dbus_message_get_path (message);
+  sender = dbus_message_get_sender (message);
+  interface_name = dbus_message_get_interface (message);
+  is_root = (g_strcmp0 (requested_object_path, es->object_path) == 0);
+
+  is_property_get = FALSE;
+  is_property_set = FALSE;
+  is_property_get_all = FALSE;
+  if (dbus_message_is_method_call (message,
+                                   "org.freedesktop.DBus.Properties",
+                                   "Get") &&
+      g_strcmp0 (dbus_message_get_signature (message), "ss") == 0)
+    {
+      is_property_get = TRUE;
+    }
+  else if (dbus_message_is_method_call (message,
+                                        "org.freedesktop.DBus.Properties",
+                                        "Set") &&
+           g_strcmp0 (dbus_message_get_signature (message), "ssv") == 0)
+    {
+      is_property_set = TRUE;
+    }
+  else if (dbus_message_is_method_call (message,
+                                        "org.freedesktop.DBus.Properties",
+                                        "GetAll") &&
+           g_strcmp0 (dbus_message_get_signature (message), "s") == 0)
+    {
+      is_property_get_all = TRUE;
+    }
+
+  children = es->vtable->enumerate (es->connection,
+                                    es->user_data,
+                                    sender,
+                                    es->object_path);
+
+  if (!is_root)
+    {
+      requested_node = strrchr (requested_object_path, '/') + 1;
+
+      /* If not dynamic, skip if requested node is not part of children */
+      if (!(es->flags & E_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES) &&
+          !_g_strv_has_string ((const gchar * const *) children, requested_node))
+        goto out;
+    }
+  else
+    {
+      requested_node = "/";
+    }
+
+  /* get introspection data for the node */
+  interfaces = es->vtable->introspect (es->connection,
+                                       es->user_data,
+                                       sender,
+                                       requested_object_path,
+                                       requested_node);
+  g_assert (interfaces != NULL);
+  introspection_data = NULL;
+  for (n = 0; n < interfaces->len; n++)
+    {
+      const EDBusInterfaceInfo *id_n = (const EDBusInterfaceInfo *) interfaces->pdata[n];
+      if (g_strcmp0 (id_n->name, interface_name) == 0)
+        {
+          introspection_data = id_n;
+        }
+    }
+
+  /* dispatch the call if the user wants to handle it */
+  if (introspection_data != NULL)
+    {
+      /* figure out where to dispatch the method call */
+      interface_user_data = NULL;
+      interface_vtable = es->vtable->dispatch (es->connection,
+                                               es->user_data,
+                                               sender,
+                                               es->object_path,
+                                               interface_name,
+                                               requested_node,
+                                               &interface_user_data);
+      if (interface_vtable == NULL)
+        goto out;
+
+      result = validate_and_maybe_schedule_method_call (es->connection,
+                                                        message,
+                                                        introspection_data,
+                                                        interface_vtable,
+                                                        es->context,
+                                                        interface_user_data);
+    }
+  /* handle org.freedesktop.DBus.Properties interface if not explicitly handled */
+  else if (is_property_get || is_property_set || is_property_get_all)
+    {
+      if (!dbus_message_get_args (message,
+                                  NULL,
+                                  DBUS_TYPE_STRING, &interface_name,
+                                  DBUS_TYPE_INVALID))
+        goto out;
+
+      /* see if the object supports this interface at all */
+      for (n = 0; n < interfaces->len; n++)
+        {
+          const EDBusInterfaceInfo *id_n = (const EDBusInterfaceInfo *) interfaces->pdata[n];
+          if (g_strcmp0 (id_n->name, interface_name) == 0)
+            {
+              introspection_data = id_n;
+            }
+        }
+
+      /* Fail with org.freedesktop.DBus.Error.InvalidArgs if the user-code
+       * claims it won't support the interface
+       */
+      if (introspection_data == NULL)
+        {
+          DBusMessage *reply;
+          reply = dbus_message_new_error (message,
+                                          "org.freedesktop.DBus.Error.InvalidArgs",
+                                          _("No such interface"));
+          _e_dbus_connection_send_dbus_1_message (es->connection, reply);
+          dbus_message_unref (reply);
+          result = DBUS_HANDLER_RESULT_HANDLED;
+          goto out;
+        }
+
+      /* figure out where to dispatch the property get/set/getall calls */
+      interface_user_data = NULL;
+      interface_vtable = es->vtable->dispatch (es->connection,
+                                               es->user_data,
+                                               sender,
+                                               es->object_path,
+                                               interface_name,
+                                               requested_node,
+                                               &interface_user_data);
+      if (interface_vtable == NULL)
+        goto out;
+
+      if (is_property_get || is_property_set)
+        {
+          result = validate_and_maybe_schedule_property_getset (es->connection,
+                                                                message,
+                                                                introspection_data,
+                                                                interface_vtable,
+                                                                es->context,
+                                                                interface_user_data);
+        }
+      else if (is_property_get_all)
+        {
+          result = validate_and_maybe_schedule_property_get_all (es->connection,
+                                                                 message,
+                                                                 introspection_data,
+                                                                 interface_vtable,
+                                                                 es->context,
+                                                                 interface_user_data);
+        }
+    }
+
+ out:
+  if (interfaces != NULL)
+    g_ptr_array_unref (interfaces);
+  g_strfreev (children);
+  return result;
+}
+
+typedef struct
+{
+  DBusMessage *message;
+  ExportedSubtree *es;
+} SubtreeDeferredData;
+
+static void
+subtree_deferred_data_free (SubtreeDeferredData *data)
+{
+  dbus_message_unref (data->message);
+  g_free (data);
+}
+
+/* called without lock held in the thread where the caller registered the subtree */
+static gboolean
+process_subtree_vtable_message_in_idle_cb (gpointer _data)
+{
+  SubtreeDeferredData *data = _data;
+  DBusHandlerResult result;
+
+  result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+  if (dbus_message_is_method_call (data->message,
+                                   "org.freedesktop.DBus.Introspectable",
+                                   "Introspect") &&
+      g_strcmp0 (dbus_message_get_signature (data->message), "") == 0)
+    {
+      result = handle_subtree_introspect (data->es->connection->priv->dbus_1_connection,
+                                          data->es,
+                                          data->message);
+    }
+  else
+    {
+      result = handle_subtree_method_invocation (data->es->connection->priv->dbus_1_connection,
+                                                 data->es,
+                                                 data->message);
+    }
+
+  /* if we couldn't handle the request, just bail with the UnknownMethod error */
+  if (result == DBUS_HANDLER_RESULT_NOT_YET_HANDLED)
+    {
+      DBusMessage *reply;
+      reply = dbus_message_new_error (data->message,
+                                      "org.freedesktop.DBus.Error.UnknownMethod",
+                                      _("Method doesn't exist"));
+      _e_dbus_connection_send_dbus_1_message (data->es->connection, reply);
+      dbus_message_unref (reply);
+    }
+
+  return FALSE;
+}
+
+/* called without lock held in libdbus-1 dispatching thread */
+static DBusHandlerResult
+dbus_1_subtree_vtable_message_func (DBusConnection *connection,
+                                    DBusMessage    *message,
+                                    void           *user_data)
+{
+  ExportedSubtree *es = user_data;
+  GSource *idle_source;
+  SubtreeDeferredData *data;
+
+  //g_debug ("in dbus_1_subtree_vtable_message_func for path %s", es->object_path);
+  //PRINT_MESSAGE (message);
+
+  data = g_new0 (SubtreeDeferredData, 1);
+  data->message = dbus_message_ref (message);
+  data->es = user_data;
+
+  /* defer this call to an idle handler in the right thread */
+  idle_source = g_idle_source_new ();
+  g_source_set_priority (idle_source, G_PRIORITY_HIGH);
+  g_source_set_callback (idle_source,
+                         process_subtree_vtable_message_in_idle_cb,
+                         data,
+                         (GDestroyNotify) subtree_deferred_data_free);
+  g_source_attach (idle_source, es->context);
+  g_source_unref (idle_source);
+
+  /* since we own the entire subtree, handlers for objects not in the subtree have been
+   * tried already by libdbus-1 - so we just need to ensure that we're always going
+   * to reply to the message
+   */
+  return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static const DBusObjectPathVTable dbus_1_subtree_vtable =
+{
+  NULL,
+  dbus_1_subtree_vtable_message_func,
+  NULL,
+  NULL,
+  NULL,
+  NULL
+};
+
+
+/**
+ * e_dbus_connection_register_subtree:
+ * @connection: A #EDBusConnection.
+ * @object_path: The object path to register the subtree at.
+ * @vtable: A #EDBusSubtreeVTable to enumerate, introspect and dispatch nodes in the subtree.
+ * @flags: Flags used to fine tune the behavior of the subtree.
+ * @user_data: Data to pass to functions in @vtable.
+ * @user_data_free_func: Function to call when the subtree is unregistered.
+ * @error: Return location for error or %NULL.
+ *
+ * Registers a whole subtree of <quote>dynamic</quote> objects.
+ *
+ * The @enumerate and @introspection functions in @vtable are used to
+ * convey, to remote callers, what nodes exist in the subtree rooted
+ * by @object_path.
+ *
+ * When handling remote calls into any node in the subtree, first the
+ * @enumerate function is used to check if the node exists. If the node exists
+ * or the #E_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES flag is set
+ * the @introspection function is used to check if the node supports the
+ * requested method. If so, the @dispatch function is used to determine
+ * where to dispatch the call. The collected #EDBusInterfaceVTable and
+ * #gpointer will be used to call into the interface vtable for processing
+ * the request.
+ *
+ * All calls into user-provided code will be invoked in the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link> of the thread you are calling this method from.
+ *
+ * If an existing subtree is already registered at @object_path or
+ * then @error is set to #E_DBUS_ERROR_OBJECT_PATH_IN_USE.
+ *
+ * Note that it is valid to register regular objects (using
+ * e_dbus_connection_register_object()) in a subtree registered with
+ * e_dbus_connection_register_subtree() - if so, the subtree handler
+ * is tried as the last resort. One way to think about a subtree
+ * handler is to consider it a <quote>fallback handler</quote>
+ * for object paths not registered via e_dbus_connection_register_object()
+ * or other bindings.
+ *
+ * Returns: 0 if @error is set, otherwise a subtree registration id (never 0)
+ * that can be used with e_dbus_connection_unregister_subtree() .
+ */
+guint
+e_dbus_connection_register_subtree (EDBusConnection            *connection,
+                                    const gchar                *object_path,
+                                    const EDBusSubtreeVTable   *vtable,
+                                    EDBusSubtreeFlags           flags,
+                                    gpointer                    user_data,
+                                    GDestroyNotify              user_data_free_func,
+                                    GError                    **error)
+{
+  guint ret;
+  ExportedSubtree *es;
+  DBusError dbus_error;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0);
+  g_return_val_if_fail (!e_dbus_connection_get_is_disconnected (connection), 0);
+  g_return_val_if_fail (object_path != NULL, 0);
+  g_return_val_if_fail (vtable != NULL, 0);
+  g_return_val_if_fail (error == NULL || *error == NULL, 0);
+
+  ret = 0;
+
+  G_LOCK (connection_lock);
+
+  es = g_hash_table_lookup (connection->priv->map_object_path_to_es, object_path);
+  if (es != NULL)
+    {
+      g_set_error (error,
+                   E_DBUS_ERROR,
+                   E_DBUS_ERROR_OBJECT_PATH_IN_USE,
+                   _("A subtree is already exported for %s"),
+                   object_path);
+      goto out;
+    }
+
+  es = g_new0 (ExportedSubtree, 1);
+  es->object_path = g_strdup (object_path);
+  es->connection = connection;
+
+  dbus_error_init (&dbus_error);
+  if (!dbus_connection_try_register_fallback (connection->priv->dbus_1_connection,
+                                              object_path,
+                                              &dbus_1_subtree_vtable,
+                                              es,
+                                              &dbus_error))
+    {
+      if (g_strcmp0 (dbus_error.name, DBUS_ERROR_NO_MEMORY) == 0)
+        _e_dbus_oom ();
+
+      e_dbus_error_set_dbus_error (error,
+                                   dbus_error.name,
+                                   dbus_error.message,
+                                   _("Another D-Bus binding is already exporting a subtree or objects at %s"),
+                                   object_path);
+      if (error != NULL)
+        {
+          /* this is a locally generated error so strip the remote part */
+          e_dbus_error_strip_remote_error (*error);
+        }
+      dbus_error_free (&dbus_error);
+
+      exported_subtree_free (es);
+      goto out;
+    }
+
+  es->vtable = vtable;
+  es->flags = flags;
+  es->id = _global_subtree_registration_id++; /* TODO: overflow etc. */
+  es->user_data = user_data;
+  es->user_data_free_func = user_data_free_func;
+  es->context = g_main_context_get_thread_default ();
+  if (es->context != NULL)
+    g_main_context_ref (es->context);
+
+  g_hash_table_insert (connection->priv->map_object_path_to_es, es->object_path, es);
+  g_hash_table_insert (connection->priv->map_id_to_es,
+                       GUINT_TO_POINTER (es->id),
+                       es);
+
+  ret = es->id;
+
+ out:
+  G_UNLOCK (connection_lock);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_connection_unregister_subtree:
+ * @connection: A #EDBusConnection.
+ * @registration_id: A subtree registration id obtained from e_dbus_connection_register_subtree().
+ *
+ * Unregisters a subtree.
+ *
+ * Returns: %TRUE if the subtree was unregistered, %FALSE otherwise.
+ */
+gboolean
+e_dbus_connection_unregister_subtree (EDBusConnection *connection,
+                                      guint            registration_id)
+{
+  ExportedSubtree *es;
+  gboolean ret;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), FALSE);
+
+  ret = FALSE;
+
+  G_LOCK (connection_lock);
+
+  es = g_hash_table_lookup (connection->priv->map_id_to_es,
+                            GUINT_TO_POINTER (registration_id));
+  if (es == NULL)
+    {
+      goto out;
+    }
+
+  /* unregister with libdbus-1 */
+  g_assert (dbus_connection_unregister_object_path (connection->priv->dbus_1_connection,
+                                                    es->object_path));
+
+  g_assert (g_hash_table_remove (connection->priv->map_id_to_es, GUINT_TO_POINTER (es->id)));
+  g_assert (g_hash_table_remove (connection->priv->map_object_path_to_es, es->object_path));
+
+  ret = TRUE;
+
+ out:
+  G_UNLOCK (connection_lock);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/libedbus/gdbusconnection.h b/libedbus/gdbusconnection.h
new file mode 100644
index 0000000..e617d6a
--- /dev/null
+++ b/libedbus/gdbusconnection.h
@@ -0,0 +1,392 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#if !defined (__E_DBUS_H_INSIDE__) && !defined (E_DBUS_COMPILATION)
+#error "Only <gdbus/gdbus.h> can be included directly."
+#endif
+
+#ifndef __E_DBUS_CONNECTION_H__
+#define __E_DBUS_CONNECTION_H__
+
+#include <gdbus/gdbustypes.h>
+
+G_BEGIN_DECLS
+
+#define E_TYPE_DBUS_CONNECTION         (e_dbus_connection_get_type ())
+#define E_DBUS_CONNECTION(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), E_TYPE_DBUS_CONNECTION, EDBusConnection))
+#define E_DBUS_CONNECTION_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), E_TYPE_DBUS_CONNECTION, EDBusConnectionClass))
+#define E_DBUS_CONNECTION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), E_TYPE_DBUS_CONNECTION, EDBusConnectionClass))
+#define G_IS_DBUS_CONNECTION(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), E_TYPE_DBUS_CONNECTION))
+#define G_IS_DBUS_CONNECTION_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), E_TYPE_DBUS_CONNECTION))
+
+typedef struct _EDBusConnectionClass   EDBusConnectionClass;
+typedef struct _EDBusConnectionPrivate EDBusConnectionPrivate;
+
+/**
+ * EDBusConnection:
+ *
+ * The #EDBusConnection structure contains only private data and
+ * should only be accessed using the provided API.
+ */
+struct _EDBusConnection
+{
+  /*< private >*/
+  GObject parent_instance;
+  EDBusConnectionPrivate *priv;
+};
+
+/**
+ * EDBusConnectionClass:
+ * @disconnected: Signal class handler for the #EDBusConnection::disconnected signal.
+ *
+ * Class structure for #EDBusConnection.
+ */
+struct _EDBusConnectionClass
+{
+  /*< private >*/
+  GObjectClass parent_class;
+
+  /*< public >*/
+
+  /* Signals */
+  void (*disconnected) (EDBusConnection *connection);
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*_g_reserved1) (void);
+  void (*_g_reserved2) (void);
+  void (*_g_reserved3) (void);
+  void (*_g_reserved4) (void);
+  void (*_g_reserved5) (void);
+  void (*_g_reserved6) (void);
+  void (*_g_reserved7) (void);
+  void (*_g_reserved8) (void);
+};
+
+GType            e_dbus_connection_get_type                   (void) G_GNUC_CONST;
+
+void             e_dbus_connection_new                        (const gchar         *address,
+                                                               GCancellable        *cancellable,
+                                                               GAsyncReadyCallback  callback,
+                                                               gpointer             user_data);
+EDBusConnection *e_dbus_connection_new_finish                 (GAsyncResult        *res,
+                                                               GError             **error);
+EDBusConnection *e_dbus_connection_new_sync                   (const gchar         *address,
+                                                               GCancellable       *cancellable,
+                                                               GError            **error);
+
+void             e_dbus_connection_bus_get                    (GBusType             bus_type,
+                                                               GCancellable        *cancellable,
+                                                               GAsyncReadyCallback  callback,
+                                                               gpointer             user_data);
+EDBusConnection *e_dbus_connection_bus_get_finish             (GAsyncResult        *res,
+                                                               GError             **error);
+EDBusConnection *e_dbus_connection_bus_get_sync               (GBusType            bus_type,
+                                                               GCancellable       *cancellable,
+                                                               GError            **error);
+
+void             e_dbus_connection_bus_get_private            (GBusType             bus_type,
+                                                               GCancellable        *cancellable,
+                                                               GAsyncReadyCallback  callback,
+                                                               gpointer             user_data);
+EDBusConnection *e_dbus_connection_bus_get_private_finish     (GAsyncResult        *res,
+                                                               GError             **error);
+EDBusConnection *e_dbus_connection_bus_get_private_sync       (GBusType            bus_type,
+                                                               GCancellable       *cancellable,
+                                                               GError            **error);
+GBusType         e_dbus_connection_get_bus_type               (EDBusConnection    *connection);
+const gchar     *e_dbus_connection_get_address                (EDBusConnection    *connection);
+const gchar     *e_dbus_connection_get_unique_name            (EDBusConnection    *connection);
+gboolean         e_dbus_connection_get_is_private             (EDBusConnection    *connection);
+gboolean         e_dbus_connection_get_is_disconnected        (EDBusConnection    *connection);
+void             e_dbus_connection_set_exit_on_disconnect     (EDBusConnection    *connection,
+                                                               gboolean            exit_on_disconnect);
+void             e_dbus_connection_disconnect                 (EDBusConnection    *connection);
+
+/**
+ * EDBusInterfaceMethodCallFunc:
+ * @connection: A #EDBusConnection.
+ * @user_data: The @user_data #gpointer passed to e_dbus_connection_register_object().
+ * @sender: The unique bus name of the remote caller.
+ * @object_path: The object path that the method was invoked on.
+ * @interface_name: The D-Bus interface name the method was invoked on.
+ * @method_name: The name of the method that was invoked.
+ * @parameters: A #EVariant tuple with parameters.
+ * @invocation: A #EDBusMethodInvocation object that can be used to return a value or error.
+ *
+ * The type of the @method_call function in #EDBusInterfaceVTable.
+ */
+typedef void (*EDBusInterfaceMethodCallFunc) (EDBusConnection       *connection,
+                                              gpointer               user_data,
+                                              const gchar           *sender,
+                                              const gchar           *object_path,
+                                              const gchar           *interface_name,
+                                              const gchar           *method_name,
+                                              EVariant              *parameters,
+                                              EDBusMethodInvocation *invocation);
+
+/**
+ * EDBusInterfaceGetPropertyFunc:
+ * @connection: A #EDBusConnection.
+ * @user_data: The @user_data #gpointer passed to e_dbus_connection_register_object().
+ * @sender: The unique bus name of the remote caller.
+ * @object_path: The object path that the method was invoked on.
+ * @interface_name: The D-Bus interface name for the property.
+ * @property_name: The name of the property to get the value of.
+ * @error: Return location for error.
+ *
+ * The type of the @get_property function in #EDBusInterfaceVTable.
+ *
+ * Returns: A newly-allocated #EVariant with the value for @property_name or %NULL if @error is set.
+ */
+typedef EVariant *(*EDBusInterfaceGetPropertyFunc) (EDBusConnection       *connection,
+                                                    gpointer               user_data,
+                                                    const gchar           *sender,
+                                                    const gchar           *object_path,
+                                                    const gchar           *interface_name,
+                                                    const gchar           *property_name,
+                                                    GError               **error);
+
+/**
+ * EDBusInterfaceSetPropertyFunc:
+ * @connection: A #EDBusConnection.
+ * @user_data: The @user_data #gpointer passed to e_dbus_connection_register_object().
+ * @sender: The unique bus name of the remote caller.
+ * @object_path: The object path that the method was invoked on.
+ * @interface_name: The D-Bus interface name for the property.
+ * @property_name: The name of the property to get the value of.
+ * @value: The value to set the property to.
+ * @error: Return location for error.
+ *
+ * The type of the @set_property function in #EDBusInterfaceVTable.
+ *
+ * Returns: %TRUE if the property was set to @value, %FALSE if @error is set.
+ */
+typedef gboolean  (*EDBusInterfaceSetPropertyFunc) (EDBusConnection       *connection,
+                                                    gpointer               user_data,
+                                                    const gchar           *sender,
+                                                    const gchar           *object_path,
+                                                    const gchar           *interface_name,
+                                                    const gchar           *property_name,
+                                                    EVariant              *value,
+                                                    GError               **error);
+
+/**
+ * EDBusInterfaceVTable:
+ * @method_call: Function for handling incoming method calls.
+ * @get_property: Function for getting a property.
+ * @set_property: Function for setting a property.
+ *
+ * Virtual table for handling properties and method calls for a D-Bus
+ * interface.
+ *
+ * If you want to handle getting/setting D-Bus properties asynchronously, simply
+ * register an object with the <literal>org.freedesktop.DBus.Properties</literal>
+ * D-Bus interface using e_dbus_connection_register_object().
+ */
+struct _EDBusInterfaceVTable
+{
+  EDBusInterfaceMethodCallFunc  method_call;
+  EDBusInterfaceGetPropertyFunc get_property;
+  EDBusInterfaceSetPropertyFunc set_property;
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*_g_reserved1) (void);
+  void (*_g_reserved2) (void);
+  void (*_g_reserved3) (void);
+  void (*_g_reserved4) (void);
+  void (*_g_reserved5) (void);
+  void (*_g_reserved6) (void);
+  void (*_g_reserved7) (void);
+  void (*_g_reserved8) (void);
+};
+
+guint            e_dbus_connection_register_object            (EDBusConnection            *connection,
+                                                               const gchar                *object_path,
+                                                               const gchar                *interface_name,
+                                                               const EDBusInterfaceInfo   *introspection_data,
+                                                               const EDBusInterfaceVTable *vtable,
+                                                               gpointer                    user_data,
+                                                               GDestroyNotify              user_data_free_func,
+                                                               GError                    **error);
+gboolean         e_dbus_connection_unregister_object          (EDBusConnection            *connection,
+                                                               guint                       registration_id);
+
+/**
+ * EDBusSubtreeEnumerateFunc:
+ * @connection: A #EDBusConnection.
+ * @user_data: The @user_data #gpointer passed to e_dbus_connection_register_subtree().
+ * @sender: The unique bus name of the remote caller.
+ * @object_path: The object path that was registered with e_dbus_connection_register_subtree().
+ *
+ * The type of the @enumerate function in #EDBusSubtreeVTable.
+ *
+ * Returns: A newly allocated array of strings for node names that are children of @object_path.
+ */
+typedef gchar** (*EDBusSubtreeEnumerateFunc) (EDBusConnection       *connection,
+                                              gpointer               user_data,
+                                              const gchar           *sender,
+                                              const gchar           *object_path);
+
+/**
+ * EDBusSubtreeIntrospectFunc:
+ * @connection: A #EDBusConnection.
+ * @user_data: The @user_data #gpointer passed to e_dbus_connection_register_subtree().
+ * @sender: The unique bus name of the remote caller.
+ * @object_path: The object path that was registered with e_dbus_connection_register_subtree().
+ * @node: A node that is a child of @object_path (relative to @object_path) or <quote>/</quote> for the root of the subtree.
+ *
+ * The type of the @introspect function in #EDBusSubtreeVTable.
+ *
+ * Returns: A newly-allocated #GPtrArray with pointers to #EDBusInterfaceInfo describing
+ * the interfaces implemented by @node.
+ */
+typedef GPtrArray *(*EDBusSubtreeIntrospectFunc) (EDBusConnection       *connection,
+                                                  gpointer               user_data,
+                                                  const gchar           *sender,
+                                                  const gchar           *object_path,
+                                                  const gchar           *node);
+
+/**
+ * EDBusSubtreeDispatchFunc:
+ * @connection: A #EDBusConnection.
+ * @user_data: The @user_data #gpointer passed to e_dbus_connection_register_subtree().
+ * @sender: The unique bus name of the remote caller.
+ * @object_path: The object path that was registered with e_dbus_connection_register_subtree().
+ * @interface_name: The D-Bus interface name that the method call or property access is for.
+ * @node: A node that is a child of @object_path (relative to @object_path) or <quote>/</quote> for the root of the subtree.
+ * @out_user_data: Return location for user data to pass to functions in the returned #EDBusInterfaceVTable (never %NULL).
+ *
+ * The type of the @dispatch function in #EDBusSubtreeVtable.
+ *
+ * Returns: A #EDBusInterfaceVTable or %NULL if you don't want to handle the methods.
+ */
+typedef const EDBusInterfaceVTable * (*EDBusSubtreeDispatchFunc) (EDBusConnection             *connection,
+                                                                  gpointer                     user_data,
+                                                                  const gchar                 *sender,
+                                                                  const gchar                 *object_path,
+                                                                  const gchar                 *interface_name,
+                                                                  const gchar                 *node,
+                                                                  gpointer                    *out_user_data);
+
+/**
+ * EDBusSubtreeVTable:
+ * @enumerate: Function for enumerating child nodes.
+ * @introspect: Function for introspecting a child node.
+ * @dispatch: Function for dispatching a remote call on a child node.
+ *
+ * Virtual table for handling subtrees registered with e_dbus_connection_register_subtree().
+ */
+struct _EDBusSubtreeVTable
+{
+  EDBusSubtreeEnumerateFunc  enumerate;
+  EDBusSubtreeIntrospectFunc introspect;
+  EDBusSubtreeDispatchFunc   dispatch;
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*_g_reserved1) (void);
+  void (*_g_reserved2) (void);
+  void (*_g_reserved3) (void);
+  void (*_g_reserved4) (void);
+  void (*_g_reserved5) (void);
+  void (*_g_reserved6) (void);
+  void (*_g_reserved7) (void);
+  void (*_g_reserved8) (void);
+};
+
+guint            e_dbus_connection_register_subtree           (EDBusConnection            *connection,
+                                                               const gchar                *object_path,
+                                                               const EDBusSubtreeVTable   *vtable,
+                                                               EDBusSubtreeFlags           flags,
+                                                               gpointer                    user_data,
+                                                               GDestroyNotify              user_data_free_func,
+                                                               GError                    **error);
+gboolean         e_dbus_connection_unregister_subtree         (EDBusConnection            *connection,
+                                                               guint                       registration_id);
+
+gboolean  e_dbus_connection_emit_signal                       (EDBusConnection    *connection,
+                                                               const gchar        *destination_bus_name,
+                                                               const gchar        *object_path,
+                                                               const gchar        *interface_name,
+                                                               const gchar        *signal_name,
+                                                               EVariant           *parameters,
+                                                               GError            **error);
+void      e_dbus_connection_invoke_method                     (EDBusConnection    *connection,
+                                                               const gchar        *bus_name,
+                                                               const gchar        *object_path,
+                                                               const gchar        *interface_name,
+                                                               const gchar        *method_name,
+                                                               EVariant           *parameters,
+                                                               gint                timeout_msec,
+                                                               GCancellable       *cancellable,
+                                                               GAsyncReadyCallback callback,
+                                                               gpointer            user_data);
+EVariant *e_dbus_connection_invoke_method_finish              (EDBusConnection    *connection,
+                                                               GAsyncResult       *res,
+                                                               GError            **error);
+EVariant *e_dbus_connection_invoke_method_sync                (EDBusConnection    *connection,
+                                                               const gchar        *bus_name,
+                                                               const gchar        *object_path,
+                                                               const gchar        *interface_name,
+                                                               const gchar        *method_name,
+                                                               EVariant           *parameters,
+                                                               gint                timeout_msec,
+                                                               GCancellable       *cancellable,
+                                                               GError            **error);
+
+/**
+ * EDBusSignalCallback:
+ * @connection: A #EDBusConnection.
+ * @sender_name: The unique bus name of the sender of the signal.
+ * @object_path: The object path that the signal was emitted on.
+ * @interface_name: The name of the signal.
+ * @signal_name: The name of the signal.
+ * @parameters: A #EVariant tuple with parameters for the signal.
+ * @user_data: User data passed when subscribing to the signal.
+ *
+ * Signature for callback function used in e_dbus_connection_signal_subscribe().
+ */
+typedef void (*EDBusSignalCallback) (EDBusConnection  *connection,
+                                     const gchar      *sender_name,
+                                     const gchar      *object_path,
+                                     const gchar      *interface_name,
+                                     const gchar      *signal_name,
+                                     EVariant         *parameters,
+                                     gpointer          user_data);
+
+guint            e_dbus_connection_signal_subscribe           (EDBusConnection     *connection,
+                                                               const gchar         *sender,
+                                                               const gchar         *interface_name,
+                                                               const gchar         *member,
+                                                               const gchar         *object_path,
+                                                               const gchar         *arg0,
+                                                               EDBusSignalCallback  callback,
+                                                               gpointer             user_data,
+                                                               GDestroyNotify      user_data_free_func);
+void             e_dbus_connection_signal_unsubscribe         (EDBusConnection     *connection,
+                                                               guint                subscription_id);
+
+G_END_DECLS
+
+#endif /* __E_DBUS_CONNECTION_H__ */
diff --git a/libedbus/gdbusconversion.c b/libedbus/gdbusconversion.c
new file mode 100644
index 0000000..08b9e94
--- /dev/null
+++ b/libedbus/gdbusconversion.c
@@ -0,0 +1,509 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright © 2007, 2008  Ryan Lortie
+ * Copyright © 2009 Codethink Limited
+ * Copyright © 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ *         David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbusconversion.h"
+#include "gdbuserror.h"
+#include "gdbusenums.h"
+#include "gdbusprivate.h"
+
+static gboolean
+dconf_dbus_from_gv (DBusMessageIter  *iter,
+                    EVariant         *value,
+                    GError          **error)
+{
+  switch (e_variant_get_type_class (value))
+    {
+     case E_VARIANT_CLASS_BOOLEAN:
+      {
+        dbus_bool_t v = e_variant_get_boolean (value);
+        dbus_message_iter_append_basic (iter, DBUS_TYPE_BOOLEAN, &v);
+        break;
+      }
+
+     case E_VARIANT_CLASS_BYTE:
+      {
+        guint8 v = e_variant_get_byte (value);
+        dbus_message_iter_append_basic (iter, DBUS_TYPE_BYTE, &v);
+        break;
+      }
+
+     case E_VARIANT_CLASS_INT16:
+      {
+        gint16 v = e_variant_get_int16 (value);
+        dbus_message_iter_append_basic (iter, DBUS_TYPE_INT16, &v);
+        break;
+      }
+
+     case E_VARIANT_CLASS_UINT16:
+      {
+        guint16 v = e_variant_get_uint16 (value);
+        dbus_message_iter_append_basic (iter, DBUS_TYPE_UINT16, &v);
+        break;
+      }
+
+     case E_VARIANT_CLASS_INT32:
+      {
+        gint32 v = e_variant_get_int32 (value);
+        dbus_message_iter_append_basic (iter, DBUS_TYPE_INT32, &v);
+        break;
+      }
+
+     case E_VARIANT_CLASS_UINT32:
+      {
+        guint32 v = e_variant_get_uint32 (value);
+        dbus_message_iter_append_basic (iter, DBUS_TYPE_UINT32, &v);
+        break;
+      }
+
+     case E_VARIANT_CLASS_INT64:
+      {
+        gint64 v = e_variant_get_int64 (value);
+        dbus_message_iter_append_basic (iter, DBUS_TYPE_INT64, &v);
+        break;
+      }
+
+     case E_VARIANT_CLASS_UINT64:
+      {
+        guint64 v = e_variant_get_uint64 (value);
+        dbus_message_iter_append_basic (iter, DBUS_TYPE_UINT64, &v);
+        break;
+      }
+
+     case E_VARIANT_CLASS_DOUBLE:
+      {
+        gdouble v = e_variant_get_double (value);
+        dbus_message_iter_append_basic (iter, DBUS_TYPE_DOUBLE, &v);
+        break;
+      }
+
+     case E_VARIANT_CLASS_STRING:
+      {
+        const gchar *v = e_variant_get_string (value, NULL);
+        dbus_message_iter_append_basic (iter, DBUS_TYPE_STRING, &v);
+        break;
+      }
+
+     case E_VARIANT_CLASS_OBJECT_PATH:
+      {
+        const gchar *v = e_variant_get_string (value, NULL);
+        dbus_message_iter_append_basic (iter, DBUS_TYPE_OBJECT_PATH, &v);
+        break;
+      }
+
+     case E_VARIANT_CLASS_SIGNATURE:
+      {
+        const gchar *v = e_variant_get_string (value, NULL);
+        dbus_message_iter_append_basic (iter, DBUS_TYPE_SIGNATURE, &v);
+        break;
+      }
+
+     case E_VARIANT_CLASS_VARIANT:
+      {
+        DBusMessageIter sub;
+        EVariant *child;
+
+        child = e_variant_get_child_value (value, 0);
+        dbus_message_iter_open_container (iter, DBUS_TYPE_VARIANT,
+                                          e_variant_get_type_string (child),
+                                          &sub);
+        if (!dconf_dbus_from_gv (&sub, child, error))
+          {
+            e_variant_unref (child);
+            goto fail;
+          }
+        dbus_message_iter_close_container (iter, &sub);
+        e_variant_unref (child);
+        break;
+      }
+
+     case E_VARIANT_CLASS_ARRAY:
+      {
+        DBusMessageIter dbus_iter;
+        const gchar *type_string;
+        EVariantIter gv_iter;
+        EVariant *item;
+
+        type_string = e_variant_get_type_string (value);
+        type_string++; /* skip the 'a' */
+
+        dbus_message_iter_open_container (iter, DBUS_TYPE_ARRAY,
+                                          type_string, &dbus_iter);
+        e_variant_iter_init (&gv_iter, value);
+
+        while ((item = e_variant_iter_next_value (&gv_iter)))
+          {
+            if (!dconf_dbus_from_gv (&dbus_iter, item, error))
+              {
+                goto fail;
+              }
+          }
+
+        dbus_message_iter_close_container (iter, &dbus_iter);
+        break;
+      }
+
+     case E_VARIANT_CLASS_TUPLE:
+      {
+        DBusMessageIter dbus_iter;
+        EVariantIter gv_iter;
+        EVariant *item;
+
+        dbus_message_iter_open_container (iter, DBUS_TYPE_STRUCT,
+                                          NULL, &dbus_iter);
+        e_variant_iter_init (&gv_iter, value);
+
+        while ((item = e_variant_iter_next_value (&gv_iter)))
+          {
+            if (!dconf_dbus_from_gv (&dbus_iter, item, error))
+              goto fail;
+          }
+
+        dbus_message_iter_close_container (iter, &dbus_iter);
+        break;
+      }
+
+     case E_VARIANT_CLASS_DICT_ENTRY:
+      {
+        DBusMessageIter dbus_iter;
+        EVariant *key, *val;
+
+        dbus_message_iter_open_container (iter, DBUS_TYPE_DICT_ENTRY,
+                                          NULL, &dbus_iter);
+        key = e_variant_get_child_value (value, 0);
+        if (!dconf_dbus_from_gv (&dbus_iter, key, error))
+          {
+            e_variant_unref (key);
+            goto fail;
+          }
+        e_variant_unref (key);
+
+        val = e_variant_get_child_value (value, 1);
+        if (!dconf_dbus_from_gv (&dbus_iter, val, error))
+          {
+            e_variant_unref (val);
+            goto fail;
+          }
+        e_variant_unref (val);
+
+        dbus_message_iter_close_container (iter, &dbus_iter);
+        break;
+      }
+
+     default:
+       g_set_error (error,
+                    E_DBUS_ERROR,
+                    E_DBUS_ERROR_CONVERSION_FAILED,
+                    _("Error serializing EVariant with type-string `%s' to a D-Bus message"),
+                    e_variant_get_type_string (value));
+       goto fail;
+    }
+
+  return TRUE;
+
+ fail:
+  return FALSE;
+}
+
+static EVariant *
+dconf_dbus_to_gv (DBusMessageIter  *iter,
+                  GError          **error)
+{
+  gint arg_type;
+
+  arg_type = dbus_message_iter_get_arg_type (iter);
+
+  switch (dbus_message_iter_get_arg_type (iter))
+    {
+     case DBUS_TYPE_BOOLEAN:
+      {
+        dbus_bool_t value;
+        dbus_message_iter_get_basic (iter, &value);
+        return e_variant_new_boolean (value);
+      }
+
+     case DBUS_TYPE_BYTE:
+      {
+        guchar value;
+        dbus_message_iter_get_basic (iter, &value);
+        return e_variant_new_byte (value);
+      }
+
+     case DBUS_TYPE_INT16:
+      {
+        gint16 value;
+        dbus_message_iter_get_basic (iter, &value);
+        return e_variant_new_int16 (value);
+      }
+
+     case DBUS_TYPE_UINT16:
+      {
+        guint16 value;
+        dbus_message_iter_get_basic (iter, &value);
+        return e_variant_new_uint16 (value);
+      }
+
+     case DBUS_TYPE_INT32:
+      {
+        gint32 value;
+        dbus_message_iter_get_basic (iter, &value);
+        return e_variant_new_int32 (value);
+      }
+
+     case DBUS_TYPE_UINT32:
+      {
+        guint32 value;
+        dbus_message_iter_get_basic (iter, &value);
+        return e_variant_new_uint32 (value);
+      }
+
+     case DBUS_TYPE_INT64:
+      {
+        gint64 value;
+        dbus_message_iter_get_basic (iter, &value);
+        return e_variant_new_int64 (value);
+      }
+
+     case DBUS_TYPE_UINT64:
+      {
+        guint64 value;
+        dbus_message_iter_get_basic (iter, &value);
+        return e_variant_new_uint64 (value);
+      }
+
+     case DBUS_TYPE_DOUBLE:
+      {
+        gdouble value;
+        dbus_message_iter_get_basic (iter, &value);
+        return e_variant_new_double (value);
+      }
+
+     case DBUS_TYPE_STRING:
+      {
+       const gchar *value;
+       dbus_message_iter_get_basic (iter, &value);
+       return e_variant_new_string (value);
+      }
+
+     case DBUS_TYPE_OBJECT_PATH:
+      {
+       const gchar *value;
+       dbus_message_iter_get_basic (iter, &value);
+       return e_variant_new_object_path (value);
+      }
+
+     case DBUS_TYPE_SIGNATURE:
+      {
+       const gchar *value;
+       dbus_message_iter_get_basic (iter, &value);
+       return e_variant_new_signature (value);
+      }
+
+     case DBUS_TYPE_VARIANT:
+       {
+        EVariantBuilder *builder;
+        EVariantClass class;
+        DBusMessageIter sub;
+        char *type;
+        EVariant *val;
+
+        dbus_message_iter_recurse (iter, &sub);
+        class = dbus_message_iter_get_arg_type (iter);
+        type = dbus_message_iter_get_signature (&sub);
+        builder = e_variant_builder_new (E_VARIANT_TYPE_VARIANT);
+        dbus_free (type);
+
+        while (dbus_message_iter_get_arg_type (&sub))
+          {
+            val = dconf_dbus_to_gv (&sub, error);
+            if (val == NULL)
+              {
+                e_variant_builder_cancel (builder);
+                goto fail;
+              }
+            e_variant_builder_add_value (builder, val);
+            dbus_message_iter_next (&sub);
+          }
+
+        return e_variant_builder_end (builder);
+       }
+
+     case DBUS_TYPE_ARRAY:
+     case DBUS_TYPE_STRUCT:
+     case DBUS_TYPE_DICT_ENTRY:
+      {
+        EVariantBuilder *builder;
+        EVariantClass class;
+        DBusMessageIter sub;
+        char *type;
+        EVariant *val;
+
+        dbus_message_iter_recurse (iter, &sub);
+        class = dbus_message_iter_get_arg_type (iter);
+        type = dbus_message_iter_get_signature (iter);
+        builder = e_variant_builder_new (E_VARIANT_TYPE (type));
+        dbus_free (type);
+
+        while (dbus_message_iter_get_arg_type (&sub))
+          {
+            val = dconf_dbus_to_gv (&sub, error);
+            if (val == NULL)
+              {
+                e_variant_builder_cancel (builder);
+                goto fail;
+              }
+            e_variant_builder_add_value (builder, val);
+            dbus_message_iter_next (&sub);
+          }
+
+        return e_variant_builder_end (builder);
+      }
+
+     default:
+       g_set_error (error,
+                    E_DBUS_ERROR,
+                    E_DBUS_ERROR_CONVERSION_FAILED,
+                    _("Error serializing D-Bus message to EVariant. Unsupported arg type `%c' (%d)"),
+                    arg_type,
+                    arg_type);
+      goto fail;
+    }
+
+  g_assert_not_reached ();
+
+ fail:
+  return NULL;
+}
+
+/**
+ * _e_dbus_dbus_1_to_gvariant:
+ * @message: A #DBusMessage
+ * @error: Return location for error or %NULL.
+ *
+ * If @message is an error message (cf. DBUS_MESSAGE_TYPE_ERROR), sets
+ * @error with the contents of the error using
+ * e_dbus_error_set_dbus_error().
+ *
+ * Otherwise build a #EVariant with the message (this never fails).
+ *
+ * Returns: A #EVariant or %NULL if @error is set.
+ **/
+EVariant *
+_e_dbus_dbus_1_to_gvariant (DBusMessage  *message,
+                            GError      **error)
+{
+  DBusMessageIter iter;
+  EVariantBuilder *builder;
+  guint n;
+  EVariant *result;
+  DBusError dbus_error;
+
+  g_assert (message != NULL);
+
+  result = NULL;
+
+  dbus_error_init (&dbus_error);
+  if (dbus_set_error_from_message (&dbus_error, message))
+    {
+      e_dbus_error_set_dbus_error (error,
+                                   dbus_error.name,
+                                   dbus_error.message,
+                                   NULL);
+      dbus_error_free (&dbus_error);
+      goto out;
+    }
+
+  dbus_message_iter_init (message, &iter);
+
+  builder = e_variant_builder_new (E_VARIANT_TYPE_TUPLE);
+  n = 0;
+  while (dbus_message_iter_get_arg_type (&iter) != DBUS_TYPE_INVALID)
+    {
+      EVariant *item;
+
+      item = dconf_dbus_to_gv (&iter, error);
+      if (item == NULL)
+        {
+          e_variant_builder_cancel (builder);
+          g_prefix_error (error,
+                          _("Error decoding out-arg %d: "),
+                          n);
+          goto out;
+        }
+      e_variant_builder_add_value (builder, item);
+      dbus_message_iter_next (&iter);
+    }
+
+  result = e_variant_builder_end (builder);
+
+ out:
+
+  return result;
+}
+
+gboolean
+_e_dbus_gvariant_to_dbus_1 (DBusMessage  *message,
+                            EVariant     *value,
+                            GError      **error)
+{
+  gboolean ret;
+  guint n;
+
+  ret = FALSE;
+
+  if (value != NULL)
+    {
+      DBusMessageIter iter;
+      EVariantIter gv_iter;
+      EVariant *item;
+
+      dbus_message_iter_init_append (message, &iter);
+
+      e_variant_iter_init (&gv_iter, value);
+      n = 0;
+      while ((item = e_variant_iter_next_value (&gv_iter)))
+        {
+          if (!dconf_dbus_from_gv (&iter, item, error))
+            {
+              g_prefix_error (error,
+                              _("Error encoding in-arg %d: "),
+                              n);
+              goto out;
+            }
+          n++;
+        }
+    }
+
+  ret = TRUE;
+
+ out:
+  return ret;
+}
+
diff --git a/libedbus/gdbusconversion.h b/libedbus/gdbusconversion.h
new file mode 100644
index 0000000..ee94251
--- /dev/null
+++ b/libedbus/gdbusconversion.h
@@ -0,0 +1,45 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#if !defined (E_DBUS_COMPILATION)
+#error "gdbusconversion.h is a private header file."
+#endif
+
+#ifndef __E_DBUS_CONVERSION_H__
+#define __E_DBUS_CONVERSION_H__
+
+#include <gdbus/gdbustypes.h>
+#include <dbus/dbus.h>
+
+G_BEGIN_DECLS
+
+EVariant *_e_dbus_dbus_1_to_gvariant (DBusMessage  *message,
+                                      GError      **error);
+
+gboolean _e_dbus_gvariant_to_dbus_1 (DBusMessage  *message,
+                                     EVariant     *value,
+                                     GError      **error);
+
+
+G_END_DECLS
+
+#endif /* __E_DBUS_CONVERSION_H__ */
diff --git a/libedbus/gdbusenums.h b/libedbus/gdbusenums.h
new file mode 100644
index 0000000..6ac7752
--- /dev/null
+++ b/libedbus/gdbusenums.h
@@ -0,0 +1,277 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#if !defined (__E_DBUS_H_INSIDE__) && !defined (E_DBUS_COMPILATION)
+#error "Only <gdbus/gdbus.h> can be included directly."
+#endif
+
+#ifndef __E_DBUS_ENUMS_H__
+#define __E_DBUS_ENUMS_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GBusType:
+ * @G_BUS_TYPE_NONE: Not a message bus connection.
+ * @G_BUS_TYPE_SESSION: The login session message bus.
+ * @G_BUS_TYPE_SYSTEM: The system-wide message bus.
+ * @G_BUS_TYPE_STARTER: Connect to the bus that activated the program.
+ *
+ * An enumeration to specify the type of a #EDBusConnection.
+ */
+typedef enum
+{
+  G_BUS_TYPE_NONE    = -1,
+  G_BUS_TYPE_SESSION = 0,
+  G_BUS_TYPE_SYSTEM  = 1,
+  G_BUS_TYPE_STARTER = 2
+} GBusType;
+
+/**
+ * GBusNameOwnerFlags:
+ * @G_BUS_NAME_OWNER_FLAGS_NONE: No flags set.
+ * @G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT: Allow another message connection to take the name.
+ * @G_BUS_NAME_OWNER_FLAGS_REPLACE: If another message bus connection
+ * owns the name and have specified #G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, then
+ * take the name from the other connection.
+ *
+ * Flags used when constructing a #GBusNameOwner.
+ */
+typedef enum
+{
+  G_BUS_NAME_OWNER_FLAGS_NONE = 0,                    /*< nick=none >*/
+  G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT = (1<<0),  /*< nick=allow-replacement >*/
+  G_BUS_NAME_OWNER_FLAGS_REPLACE = (1<<1),            /*< nick=replace >*/
+} GBusNameOwnerFlags;
+
+/**
+ * EDBusProxyFlags:
+ * @E_DBUS_PROXY_FLAGS_NONE: No flags set.
+ * @E_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES: Don't load properties.
+ * @E_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS: Don't connect to signals on the remote object.
+ *
+ * Flags used when constructing an instance of a #EDBusProxy derived class.
+ */
+typedef enum
+{
+  E_DBUS_PROXY_FLAGS_NONE = 0,                        /*< nick=none >*/
+  E_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES = (1<<0), /*< nick=do-not-load-properties >*/
+  E_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS = (1<<1), /*< nick=do-not-connect-signals >*/
+} EDBusProxyFlags;
+
+/**
+ * EDBusError:
+ * @E_DBUS_ERROR_FAILED: The operation failed.
+ * @E_DBUS_ERROR_CANCELLED: The operation was cancelled.
+ * @E_DBUS_ERROR_CONVERSION_FAILED: An attempt was made to send #EVariant value that isn't
+ * compatible with the D-Bus protocol.
+ * @E_DBUS_ERROR_REMOTE_ERROR: A remote object generated an error
+ * that doesn't correspond to a locally registered #GError error domain.
+ * Use e_dbus_error_get_dbus_error_name() to extract the D-Bus error name
+ * and e_dbus_error_strip() to get fix up the message so it matches what
+ * was received on the wire.
+ * @E_DBUS_ERROR_DBUS_FAILED:
+ * A generic error; "something went wrong" - see the error message for
+ * more.
+ * @E_DBUS_ERROR_NO_MEMORY:
+ * There was not enough memory to complete an operation.
+ * @E_DBUS_ERROR_SERVICE_UNKNOWN:
+ * The bus doesn't know how to launch a service to supply the bus name
+ * you wanted.
+ * @E_DBUS_ERROR_NAME_HAS_NO_OWNER:
+ * The bus name you referenced doesn't exist (i.e. no application owns
+ * it).
+ * @E_DBUS_ERROR_NO_REPLY:
+ * No reply to a message expecting one, usually means a timeout occurred.
+ * @E_DBUS_ERROR_IO_ERROR:
+ * Something went wrong reading or writing to a socket, for example.
+ * @E_DBUS_ERROR_BAD_ADDRESS:
+ * A D-Bus bus address was malformed.
+ * @E_DBUS_ERROR_NOT_SUPPORTED:
+ * Requested operation isn't supported (like ENOSYS on UNIX).
+ * @E_DBUS_ERROR_LIMITS_EXCEEDED:
+ * Some limited resource is exhausted.
+ * @E_DBUS_ERROR_ACCESS_DENIED:
+ * Security restrictions don't allow doing what you're trying to do.
+ * @E_DBUS_ERROR_AUTH_FAILED:
+ * Authentication didn't work.
+ * @E_DBUS_ERROR_NO_SERVER:
+ * Unable to connect to server (probably caused by ECONNREFUSED on a
+ * socket).
+ * @E_DBUS_ERROR_TIMEOUT:
+ * Certain timeout errors, possibly ETIMEDOUT on a socket.  Note that
+ * %E_DBUS_ERROR_NO_REPLY is used for message reply timeouts. Warning:
+ * this is confusingly-named given that %E_DBUS_ERROR_TIMED_OUT also
+ * exists. We can't fix it for compatibility reasons so just be
+ * careful.
+ * @E_DBUS_ERROR_NO_NETWORK:
+ * No network access (probably ENETUNREACH on a socket).
+ * @E_DBUS_ERROR_ADDRESS_IN_USE:
+ * Can't bind a socket since its address is in use (i.e. EADDRINUSE).
+ * @E_DBUS_ERROR_DISCONNECTED:
+ * The connection is disconnected and you're trying to use it.
+ * @E_DBUS_ERROR_INVALID_ARGS:
+ * Invalid arguments passed to a method call.
+ * @E_DBUS_ERROR_FILE_NOT_FOUND:
+ * Missing file.
+ * @E_DBUS_ERROR_FILE_EXISTS:
+ * Existing file and the operation you're using does not silently overwrite.
+ * @E_DBUS_ERROR_UNKNOWN_METHOD:
+ * Method name you invoked isn't known by the object you invoked it on.
+ * @E_DBUS_ERROR_TIMED_OUT:
+ * Certain timeout errors, e.g. while starting a service. Warning: this is
+ * confusingly-named given that %E_DBUS_ERROR_TIMEOUT also exists. We
+ * can't fix it for compatibility reasons so just be careful.
+ * @E_DBUS_ERROR_MATCH_RULE_NOT_FOUND:
+ * Tried to remove or modify a match rule that didn't exist.
+ * @E_DBUS_ERROR_MATCH_RULE_INVALID:
+ * The match rule isn't syntactically valid.
+ * @E_DBUS_ERROR_SPAWN_EXEC_FAILED:
+ * While starting a new process, the exec() call failed.
+ * @E_DBUS_ERROR_SPAWN_FORK_FAILED:
+ * While starting a new process, the fork() call failed.
+ * @E_DBUS_ERROR_SPAWN_CHILD_EXITED:
+ * While starting a new process, the child exited with a status code.
+ * @E_DBUS_ERROR_SPAWN_CHILD_SIGNALED:
+ * While starting a new process, the child exited on a signal.
+ * @E_DBUS_ERROR_SPAWN_FAILED:
+ * While starting a new process, something went wrong.
+ * @E_DBUS_ERROR_SPAWN_SETUP_FAILED:
+ * We failed to setup the environment correctly.
+ * @E_DBUS_ERROR_SPAWN_CONFIG_INVALID:
+ * We failed to setup the config parser correctly.
+ * @E_DBUS_ERROR_SPAWN_SERVICE_INVALID:
+ * Bus name was not valid.
+ * @E_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND:
+ * Service file not found in system-services directory.
+ * @E_DBUS_ERROR_SPAWN_PERMISSIONS_INVALID:
+ * Permissions are incorrect on the setuid helper.
+ * @E_DBUS_ERROR_SPAWN_FILE_INVALID:
+ * Service file invalid (Name, User or Exec missing).
+ * @E_DBUS_ERROR_SPAWN_NO_MEMORY:
+ * Tried to get a UNIX process ID and it wasn't available.
+ * @E_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN:
+ * Tried to get a UNIX process ID and it wasn't available.
+ * @E_DBUS_ERROR_INVALID_SIGNATURE:
+ * A type signature is not valid.
+ * @E_DBUS_ERROR_INVALID_FILE_CONTENT:
+ * A file contains invalid syntax or is otherwise broken.
+ * @E_DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN:
+ * Asked for SELinux security context and it wasn't available.
+ * @E_DBUS_ERROR_ADT_AUDIT_DATA_UNKNOWN:
+ * Asked for ADT audit data and it wasn't available.
+ * @E_DBUS_ERROR_OBJECT_PATH_IN_USE:
+ * There's already an object with the requested object path.
+ * @_E_DBUS_ERROR_MAX_DBUS_ERROR: This is a private member - the value is subject to change.
+ *
+ * Error codes.
+ */
+typedef enum
+{
+  E_DBUS_ERROR_FAILED,
+  E_DBUS_ERROR_CANCELLED,
+  E_DBUS_ERROR_CONVERSION_FAILED,
+  E_DBUS_ERROR_REMOTE_ERROR,
+
+  /* Well-known errors in the org.freedesktop.DBus.Error namespace */
+  E_DBUS_ERROR_DBUS_FAILED            = 1000,    /* org.freedesktop.DBus.Error.Failed */
+  E_DBUS_ERROR_NO_MEMORY,                        /* org.freedesktop.DBus.Error.NoMemory */
+  E_DBUS_ERROR_SERVICE_UNKNOWN,                  /* org.freedesktop.DBus.Error.ServiceUnknown */
+  E_DBUS_ERROR_NAME_HAS_NO_OWNER,                /* org.freedesktop.DBus.Error.NameHasNoOwner */
+  E_DBUS_ERROR_NO_REPLY,                         /* org.freedesktop.DBus.Error.NoReply */
+  E_DBUS_ERROR_IO_ERROR,                         /* org.freedesktop.DBus.Error.IOError */
+  E_DBUS_ERROR_BAD_ADDRESS,                      /* org.freedesktop.DBus.Error.BadAddress */
+  E_DBUS_ERROR_NOT_SUPPORTED,                    /* org.freedesktop.DBus.Error.NotSupported */
+  E_DBUS_ERROR_LIMITS_EXCEEDED,                  /* org.freedesktop.DBus.Error.LimitsExceeded */
+  E_DBUS_ERROR_ACCESS_DENIED,                    /* org.freedesktop.DBus.Error.AccessDenied */
+  E_DBUS_ERROR_AUTH_FAILED,                      /* org.freedesktop.DBus.Error.AuthFailed */
+  E_DBUS_ERROR_NO_SERVER,                        /* org.freedesktop.DBus.Error.NoServer */
+  E_DBUS_ERROR_TIMEOUT,                          /* org.freedesktop.DBus.Error.Timeout */
+  E_DBUS_ERROR_NO_NETWORK,                       /* org.freedesktop.DBus.Error.NoNetwork */
+  E_DBUS_ERROR_ADDRESS_IN_USE,                   /* org.freedesktop.DBus.Error.AddressInUse */
+  E_DBUS_ERROR_DISCONNECTED,                     /* org.freedesktop.DBus.Error.Disconnected */
+  E_DBUS_ERROR_INVALID_ARGS,                     /* org.freedesktop.DBus.Error.InvalidArgs */
+  E_DBUS_ERROR_FILE_NOT_FOUND,                   /* org.freedesktop.DBus.Error.FileNotFound */
+  E_DBUS_ERROR_FILE_EXISTS,                      /* org.freedesktop.DBus.Error.FileExists */
+  E_DBUS_ERROR_UNKNOWN_METHOD,                   /* org.freedesktop.DBus.Error.UnknownMethod */
+  E_DBUS_ERROR_TIMED_OUT,                        /* org.freedesktop.DBus.Error.TimedOut */
+  E_DBUS_ERROR_MATCH_RULE_NOT_FOUND,             /* org.freedesktop.DBus.Error.MatchRuleNotFound */
+  E_DBUS_ERROR_MATCH_RULE_INVALID,               /* org.freedesktop.DBus.Error.MatchRuleInvalid */
+  E_DBUS_ERROR_SPAWN_EXEC_FAILED,                /* org.freedesktop.DBus.Error.Spawn.ExecFailed */
+  E_DBUS_ERROR_SPAWN_FORK_FAILED,                /* org.freedesktop.DBus.Error.Spawn.ForkFailed */
+  E_DBUS_ERROR_SPAWN_CHILD_EXITED,               /* org.freedesktop.DBus.Error.Spawn.ChildExited */
+  E_DBUS_ERROR_SPAWN_CHILD_SIGNALED,             /* org.freedesktop.DBus.Error.Spawn.ChildSignaled */
+  E_DBUS_ERROR_SPAWN_FAILED,                     /* org.freedesktop.DBus.Error.Spawn.Failed */
+  E_DBUS_ERROR_SPAWN_SETUP_FAILED,               /* org.freedesktop.DBus.Error.Spawn.FailedToSetup */
+  E_DBUS_ERROR_SPAWN_CONFIG_INVALID,             /* org.freedesktop.DBus.Error.Spawn.ConfigInvalid */
+  E_DBUS_ERROR_SPAWN_SERVICE_INVALID,            /* org.freedesktop.DBus.Error.Spawn.ServiceNotValid */
+  E_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND,          /* org.freedesktop.DBus.Error.Spawn.ServiceNotFound */
+  E_DBUS_ERROR_SPAWN_PERMISSIONS_INVALID,        /* org.freedesktop.DBus.Error.Spawn.PermissionsInvalid */
+  E_DBUS_ERROR_SPAWN_FILE_INVALID,               /* org.freedesktop.DBus.Error.Spawn.FileInvalid */
+  E_DBUS_ERROR_SPAWN_NO_MEMORY,                  /* org.freedesktop.DBus.Error.Spawn.NoMemory */
+  E_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN,          /* org.freedesktop.DBus.Error.UnixProcessIdUnknown */
+  E_DBUS_ERROR_INVALID_SIGNATURE,                /* org.freedesktop.DBus.Error.InvalidSignature */
+  E_DBUS_ERROR_INVALID_FILE_CONTENT,             /* org.freedesktop.DBus.Error.InvalidFileContent */
+  E_DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN, /* org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown */
+  E_DBUS_ERROR_ADT_AUDIT_DATA_UNKNOWN,           /* org.freedesktop.DBus.Error.AdtAuditDataUnknown */
+  E_DBUS_ERROR_OBJECT_PATH_IN_USE,               /* org.freedesktop.DBus.Error.ObjectPathInUse */
+
+  /* This is a private member - the value is subject to change */
+  _E_DBUS_ERROR_MAX_DBUS_ERROR                   /*< skip >*/
+} EDBusError;
+
+/**
+ * EDBusPropertyInfoFlags:
+ * @E_DBUS_PROPERTY_INFO_FLAGS_NONE: No flags set.
+ * @E_DBUS_PROPERTY_INFO_FLAGS_READABLE: Property is readable.
+ * @E_DBUS_PROPERTY_INFO_FLAGS_WRITABLE: Property is writable.
+ *
+ * Flags describing the access control of a D-Bus property.
+ */
+typedef enum
+{
+  E_DBUS_PROPERTY_INFO_FLAGS_NONE = 0,
+  E_DBUS_PROPERTY_INFO_FLAGS_READABLE = (1<<0),
+  E_DBUS_PROPERTY_INFO_FLAGS_WRITABLE = (1<<1),
+} EDBusPropertyInfoFlags;
+
+/**
+ * EDBusSubtreeFlags:
+ * @E_DBUS_SUBTREE_FLAGS_NONE: No flags set.
+ * @E_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES: Method calls to objects not in the enumerated range
+ *                                                       will still be dispatched. This is useful if you want
+ *                                                       to dynamically spawn objects in the subtree.
+ *
+ * Flags passed to e_dbus_connection_register_subtree().
+ */
+typedef enum
+{
+  E_DBUS_SUBTREE_FLAGS_NONE = 0,
+  E_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES = (1<<0),
+} EDBusSubtreeFlags;
+
+G_END_DECLS
+
+#endif /* __E_DBUS_ENUMS_H__ */
diff --git a/libedbus/gdbusenumtypes.c b/libedbus/gdbusenumtypes.c
new file mode 100644
index 0000000..e20109a
--- /dev/null
+++ b/libedbus/gdbusenumtypes.c
@@ -0,0 +1,258 @@
+
+/* Generated data (by glib-mkenums) */
+
+#include <gdbus.h>
+
+/* enumerations from "gvariant.h" */
+GType
+struct_struct_get_type (void)
+{
+  static volatile gsize g_define_type_id__volatile = 0;
+
+  if (g_once_init_enter (&g_define_type_id__volatile))
+    {
+      static const GEnumValue values[] = {
+        { E_VARIANT_CLASS_BOOLEAN, "E_VARIANT_CLASS_BOOLEAN", "boolean" },
+        { E_VARIANT_CLASS_BYTE, "E_VARIANT_CLASS_BYTE", "byte" },
+        { E_VARIANT_CLASS_INT16, "E_VARIANT_CLASS_INT16", "int16" },
+        { E_VARIANT_CLASS_UINT16, "E_VARIANT_CLASS_UINT16", "uint16" },
+        { E_VARIANT_CLASS_INT32, "E_VARIANT_CLASS_INT32", "int32" },
+        { E_VARIANT_CLASS_UINT32, "E_VARIANT_CLASS_UINT32", "uint32" },
+        { E_VARIANT_CLASS_INT64, "E_VARIANT_CLASS_INT64", "int64" },
+        { E_VARIANT_CLASS_UINT64, "E_VARIANT_CLASS_UINT64", "uint64" },
+        { E_VARIANT_CLASS_HANDLE, "E_VARIANT_CLASS_HANDLE", "handle" },
+        { E_VARIANT_CLASS_DOUBLE, "E_VARIANT_CLASS_DOUBLE", "double" },
+        { E_VARIANT_CLASS_STRING, "E_VARIANT_CLASS_STRING", "string" },
+        { E_VARIANT_CLASS_OBJECT_PATH, "E_VARIANT_CLASS_OBJECT_PATH", "object-path" },
+        { E_VARIANT_CLASS_SIGNATURE, "E_VARIANT_CLASS_SIGNATURE", "signature" },
+        { E_VARIANT_CLASS_VARIANT, "E_VARIANT_CLASS_VARIANT", "variant" },
+        { E_VARIANT_CLASS_MAYBE, "E_VARIANT_CLASS_MAYBE", "maybe" },
+        { E_VARIANT_CLASS_ARRAY, "E_VARIANT_CLASS_ARRAY", "array" },
+        { E_VARIANT_CLASS_TUPLE, "E_VARIANT_CLASS_TUPLE", "tuple" },
+        { E_VARIANT_CLASS_DICT_ENTRY, "E_VARIANT_CLASS_DICT_ENTRY", "dict-entry" },
+        { 0, NULL, NULL }
+      };
+      GType g_define_type_id =
+        g_enum_register_static (g_intern_static_string ("struct"), values);
+      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+    }
+
+  return g_define_type_id__volatile;
+}
+
+GType
+e_variant_builder_error_get_type (void)
+{
+  static volatile gsize g_define_type_id__volatile = 0;
+
+  if (g_once_init_enter (&g_define_type_id__volatile))
+    {
+      static const GEnumValue values[] = {
+        { E_VARIANT_BUILDER_ERROR_TOO_MANY, "E_VARIANT_BUILDER_ERROR_TOO_MANY", "too-many" },
+        { E_VARIANT_BUILDER_ERROR_TOO_FEW, "E_VARIANT_BUILDER_ERROR_TOO_FEW", "too-few" },
+        { E_VARIANT_BUILDER_ERROR_INFER, "E_VARIANT_BUILDER_ERROR_INFER", "infer" },
+        { E_VARIANT_BUILDER_ERROR_TYPE, "E_VARIANT_BUILDER_ERROR_TYPE", "type" },
+        { 0, NULL, NULL }
+      };
+      GType g_define_type_id =
+        g_enum_register_static (g_intern_static_string ("EVariantBuilderError"), values);
+      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+    }
+
+  return g_define_type_id__volatile;
+}
+
+GType
+e_variant_flags_get_type (void)
+{
+  static volatile gsize g_define_type_id__volatile = 0;
+
+  if (g_once_init_enter (&g_define_type_id__volatile))
+    {
+      static const GEnumValue values[] = {
+        { E_VARIANT_LITTLE_ENDIAN, "E_VARIANT_LITTLE_ENDIAN", "little-endian" },
+        { E_VARIANT_BIG_ENDIAN, "E_VARIANT_BIG_ENDIAN", "big-endian" },
+        { E_VARIANT_TRUSTED, "E_VARIANT_TRUSTED", "trusted" },
+        { E_VARIANT_LAZY_BYTESWAP, "E_VARIANT_LAZY_BYTESWAP", "lazy-byteswap" },
+        { 0, NULL, NULL }
+      };
+      GType g_define_type_id =
+        g_enum_register_static (g_intern_static_string ("EVariantFlags"), values);
+      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+    }
+
+  return g_define_type_id__volatile;
+}
+
+/* enumerations from "gdbusenums.h" */
+GType
+e_bus_type_get_type (void)
+{
+  static volatile gsize g_define_type_id__volatile = 0;
+
+  if (g_once_init_enter (&g_define_type_id__volatile))
+    {
+      static const GEnumValue values[] = {
+        { G_BUS_TYPE_NONE, "G_BUS_TYPE_NONE", "none" },
+        { G_BUS_TYPE_SESSION, "G_BUS_TYPE_SESSION", "session" },
+        { G_BUS_TYPE_SYSTEM, "G_BUS_TYPE_SYSTEM", "system" },
+        { G_BUS_TYPE_STARTER, "G_BUS_TYPE_STARTER", "starter" },
+        { 0, NULL, NULL }
+      };
+      GType g_define_type_id =
+        g_enum_register_static (g_intern_static_string ("GBusType"), values);
+      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+    }
+
+  return g_define_type_id__volatile;
+}
+
+GType
+e_bus_name_owner_flags_get_type (void)
+{
+  static volatile gsize g_define_type_id__volatile = 0;
+
+  if (g_once_init_enter (&g_define_type_id__volatile))
+    {
+      static const GFlagsValue values[] = {
+        { G_BUS_NAME_OWNER_FLAGS_NONE, "G_BUS_NAME_OWNER_FLAGS_NONE", "none" },
+        { G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, "G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT", "allow-replacement" },
+        { G_BUS_NAME_OWNER_FLAGS_REPLACE, "G_BUS_NAME_OWNER_FLAGS_REPLACE", "replace" },
+        { 0, NULL, NULL }
+      };
+      GType g_define_type_id =
+        g_flags_register_static (g_intern_static_string ("GBusNameOwnerFlags"), values);
+      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+    }
+
+  return g_define_type_id__volatile;
+}
+
+GType
+e_dbus_proxy_flags_get_type (void)
+{
+  static volatile gsize g_define_type_id__volatile = 0;
+
+  if (g_once_init_enter (&g_define_type_id__volatile))
+    {
+      static const GFlagsValue values[] = {
+        { E_DBUS_PROXY_FLAGS_NONE, "E_DBUS_PROXY_FLAGS_NONE", "none" },
+        { E_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, "E_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES", "do-not-load-properties" },
+        { E_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, "E_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS", "do-not-connect-signals" },
+        { 0, NULL, NULL }
+      };
+      GType g_define_type_id =
+        g_flags_register_static (g_intern_static_string ("EDBusProxyFlags"), values);
+      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+    }
+
+  return g_define_type_id__volatile;
+}
+
+GType
+e_dbus_error_get_type (void)
+{
+  static volatile gsize g_define_type_id__volatile = 0;
+
+  if (g_once_init_enter (&g_define_type_id__volatile))
+    {
+      static const GEnumValue values[] = {
+        { E_DBUS_ERROR_FAILED, "E_DBUS_ERROR_FAILED", "failed" },
+        { E_DBUS_ERROR_CANCELLED, "E_DBUS_ERROR_CANCELLED", "cancelled" },
+        { E_DBUS_ERROR_CONVERSION_FAILED, "E_DBUS_ERROR_CONVERSION_FAILED", "conversion-failed" },
+        { E_DBUS_ERROR_REMOTE_ERROR, "E_DBUS_ERROR_REMOTE_ERROR", "remote-error" },
+        { E_DBUS_ERROR_DBUS_FAILED, "E_DBUS_ERROR_DBUS_FAILED", "dbus-failed" },
+        { E_DBUS_ERROR_NO_MEMORY, "E_DBUS_ERROR_NO_MEMORY", "no-memory" },
+        { E_DBUS_ERROR_SERVICE_UNKNOWN, "E_DBUS_ERROR_SERVICE_UNKNOWN", "service-unknown" },
+        { E_DBUS_ERROR_NAME_HAS_NO_OWNER, "E_DBUS_ERROR_NAME_HAS_NO_OWNER", "name-has-no-owner" },
+        { E_DBUS_ERROR_NO_REPLY, "E_DBUS_ERROR_NO_REPLY", "no-reply" },
+        { E_DBUS_ERROR_IO_ERROR, "E_DBUS_ERROR_IO_ERROR", "io-error" },
+        { E_DBUS_ERROR_BAD_ADDRESS, "E_DBUS_ERROR_BAD_ADDRESS", "bad-address" },
+        { E_DBUS_ERROR_NOT_SUPPORTED, "E_DBUS_ERROR_NOT_SUPPORTED", "not-supported" },
+        { E_DBUS_ERROR_LIMITS_EXCEEDED, "E_DBUS_ERROR_LIMITS_EXCEEDED", "limits-exceeded" },
+        { E_DBUS_ERROR_ACCESS_DENIED, "E_DBUS_ERROR_ACCESS_DENIED", "access-denied" },
+        { E_DBUS_ERROR_AUTH_FAILED, "E_DBUS_ERROR_AUTH_FAILED", "auth-failed" },
+        { E_DBUS_ERROR_NO_SERVER, "E_DBUS_ERROR_NO_SERVER", "no-server" },
+        { E_DBUS_ERROR_TIMEOUT, "E_DBUS_ERROR_TIMEOUT", "timeout" },
+        { E_DBUS_ERROR_NO_NETWORK, "E_DBUS_ERROR_NO_NETWORK", "no-network" },
+        { E_DBUS_ERROR_ADDRESS_IN_USE, "E_DBUS_ERROR_ADDRESS_IN_USE", "address-in-use" },
+        { E_DBUS_ERROR_DISCONNECTED, "E_DBUS_ERROR_DISCONNECTED", "disconnected" },
+        { E_DBUS_ERROR_INVALID_ARGS, "E_DBUS_ERROR_INVALID_ARGS", "invalid-args" },
+        { E_DBUS_ERROR_FILE_NOT_FOUND, "E_DBUS_ERROR_FILE_NOT_FOUND", "file-not-found" },
+        { E_DBUS_ERROR_FILE_EXISTS, "E_DBUS_ERROR_FILE_EXISTS", "file-exists" },
+        { E_DBUS_ERROR_UNKNOWN_METHOD, "E_DBUS_ERROR_UNKNOWN_METHOD", "unknown-method" },
+        { E_DBUS_ERROR_TIMED_OUT, "E_DBUS_ERROR_TIMED_OUT", "timed-out" },
+        { E_DBUS_ERROR_MATCH_RULE_NOT_FOUND, "E_DBUS_ERROR_MATCH_RULE_NOT_FOUND", "match-rule-not-found" },
+        { E_DBUS_ERROR_MATCH_RULE_INVALID, "E_DBUS_ERROR_MATCH_RULE_INVALID", "match-rule-invalid" },
+        { E_DBUS_ERROR_SPAWN_EXEC_FAILED, "E_DBUS_ERROR_SPAWN_EXEC_FAILED", "spawn-exec-failed" },
+        { E_DBUS_ERROR_SPAWN_FORK_FAILED, "E_DBUS_ERROR_SPAWN_FORK_FAILED", "spawn-fork-failed" },
+        { E_DBUS_ERROR_SPAWN_CHILD_EXITED, "E_DBUS_ERROR_SPAWN_CHILD_EXITED", "spawn-child-exited" },
+        { E_DBUS_ERROR_SPAWN_CHILD_SIGNALED, "E_DBUS_ERROR_SPAWN_CHILD_SIGNALED", "spawn-child-signaled" },
+        { E_DBUS_ERROR_SPAWN_FAILED, "E_DBUS_ERROR_SPAWN_FAILED", "spawn-failed" },
+        { E_DBUS_ERROR_SPAWN_SETUP_FAILED, "E_DBUS_ERROR_SPAWN_SETUP_FAILED", "spawn-setup-failed" },
+        { E_DBUS_ERROR_SPAWN_CONFIG_INVALID, "E_DBUS_ERROR_SPAWN_CONFIG_INVALID", "spawn-config-invalid" },
+        { E_DBUS_ERROR_SPAWN_SERVICE_INVALID, "E_DBUS_ERROR_SPAWN_SERVICE_INVALID", "spawn-service-invalid" },
+        { E_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND, "E_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND", "spawn-service-not-found" },
+        { E_DBUS_ERROR_SPAWN_PERMISSIONS_INVALID, "E_DBUS_ERROR_SPAWN_PERMISSIONS_INVALID", "spawn-permissions-invalid" },
+        { E_DBUS_ERROR_SPAWN_FILE_INVALID, "E_DBUS_ERROR_SPAWN_FILE_INVALID", "spawn-file-invalid" },
+        { E_DBUS_ERROR_SPAWN_NO_MEMORY, "E_DBUS_ERROR_SPAWN_NO_MEMORY", "spawn-no-memory" },
+        { E_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN, "E_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN", "unix-process-id-unknown" },
+        { E_DBUS_ERROR_INVALID_SIGNATURE, "E_DBUS_ERROR_INVALID_SIGNATURE", "invalid-signature" },
+        { E_DBUS_ERROR_INVALID_FILE_CONTENT, "E_DBUS_ERROR_INVALID_FILE_CONTENT", "invalid-file-content" },
+        { E_DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN, "E_DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN", "selinux-security-context-unknown" },
+        { E_DBUS_ERROR_ADT_AUDIT_DATA_UNKNOWN, "E_DBUS_ERROR_ADT_AUDIT_DATA_UNKNOWN", "adt-audit-data-unknown" },
+        { E_DBUS_ERROR_OBJECT_PATH_IN_USE, "E_DBUS_ERROR_OBJECT_PATH_IN_USE", "object-path-in-use" },
+        { 0, NULL, NULL }
+      };
+      GType g_define_type_id =
+        g_enum_register_static (g_intern_static_string ("EDBusError"), values);
+      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+    }
+
+  return g_define_type_id__volatile;
+}
+
+GType
+e_dbus_property_info_flags_get_type (void)
+{
+  static volatile gsize g_define_type_id__volatile = 0;
+
+  if (g_once_init_enter (&g_define_type_id__volatile))
+    {
+      static const GFlagsValue values[] = {
+        { E_DBUS_PROPERTY_INFO_FLAGS_NONE, "E_DBUS_PROPERTY_INFO_FLAGS_NONE", "none" },
+        { E_DBUS_PROPERTY_INFO_FLAGS_READABLE, "E_DBUS_PROPERTY_INFO_FLAGS_READABLE", "readable" },
+        { E_DBUS_PROPERTY_INFO_FLAGS_WRITABLE, "E_DBUS_PROPERTY_INFO_FLAGS_WRITABLE", "writable" },
+        { 0, NULL, NULL }
+      };
+      GType g_define_type_id =
+        g_flags_register_static (g_intern_static_string ("EDBusPropertyInfoFlags"), values);
+      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+    }
+
+  return g_define_type_id__volatile;
+}
+
+GType
+e_dbus_subtree_flags_get_type (void)
+{
+  static volatile gsize g_define_type_id__volatile = 0;
+
+  if (g_once_init_enter (&g_define_type_id__volatile))
+    {
+      static const GFlagsValue values[] = {
+        { E_DBUS_SUBTREE_FLAGS_NONE, "E_DBUS_SUBTREE_FLAGS_NONE", "none" },
+        { E_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES, "E_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES", "dispatch-to-unenumerated-nodes" },
+        { 0, NULL, NULL }
+      };
+      GType g_define_type_id =
+        g_flags_register_static (g_intern_static_string ("EDBusSubtreeFlags"), values);
+      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
+    }
+
+  return g_define_type_id__volatile;
+}
+
+
+/* Generated data ends here */
+
diff --git a/libedbus/gdbusenumtypes.h b/libedbus/gdbusenumtypes.h
new file mode 100644
index 0000000..ef037a0
--- /dev/null
+++ b/libedbus/gdbusenumtypes.h
@@ -0,0 +1,37 @@
+
+/* Generated data (by glib-mkenums) */
+
+#ifndef __G_DBUS_ENUM_TYPES_H__
+#define __G_DBUS_ENUM_TYPES_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/* enumerations from "gvariant.h" */
+GType struct_struct_get_type (void) G_GNUC_CONST;
+#define STRUCT_TYPE_STRUCT (struct_struct_get_type ())
+GType e_variant_builder_error_get_type (void) G_GNUC_CONST;
+#define E_TYPE_VARIANT_BUILDER_ERROR (e_variant_builder_error_get_type ())
+GType e_variant_flags_get_type (void) G_GNUC_CONST;
+#define E_TYPE_VARIANT_FLAGS (e_variant_flags_get_type ())
+
+/* enumerations from "gdbusenums.h" */
+GType e_bus_type_get_type (void) G_GNUC_CONST;
+#define G_TYPE_BUS_TYPE (e_bus_type_get_type ())
+GType e_bus_name_owner_flags_get_type (void) G_GNUC_CONST;
+#define G_TYPE_BUS_NAME_OWNER_FLAGS (e_bus_name_owner_flags_get_type ())
+GType e_dbus_proxy_flags_get_type (void) G_GNUC_CONST;
+#define E_TYPE_DBUS_PROXY_FLAGS (e_dbus_proxy_flags_get_type ())
+GType e_dbus_error_get_type (void) G_GNUC_CONST;
+#define E_TYPE_DBUS_ERROR (e_dbus_error_get_type ())
+GType e_dbus_property_info_flags_get_type (void) G_GNUC_CONST;
+#define E_TYPE_DBUS_PROPERTY_INFO_FLAGS (e_dbus_property_info_flags_get_type ())
+GType e_dbus_subtree_flags_get_type (void) G_GNUC_CONST;
+#define E_TYPE_DBUS_SUBTREE_FLAGS (e_dbus_subtree_flags_get_type ())
+G_END_DECLS
+
+#endif /* __G_DBUS_ENUM_TYPES_H__ */
+
+/* Generated data ends here */
+
diff --git a/libedbus/gdbuserror.c b/libedbus/gdbuserror.c
new file mode 100644
index 0000000..8555cf7
--- /dev/null
+++ b/libedbus/gdbuserror.c
@@ -0,0 +1,822 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbuserror.h"
+#include "gdbusenums.h"
+#include "gdbusenumtypes.h"
+#include "gdbusprivate.h"
+
+/**
+ * SECTION:gdbuserror
+ * @title: EDBusError
+ * @short_description: Error helper functions
+ * @include: gdbus/gdbus.h
+ *
+ * Error helper functions for EDBus.
+ *
+ * All facilities in EDBus (such as e_dbus_connection_invoke_method_sync())
+ * that return errors as a result of remote method invocations returning
+ * errors use #GError. To check if a returned #GError is the result of
+ * a remote error, use e_dbus_error_is_remote_error(). To get
+ * the actual D-Bus error name, use e_dbus_error_get_remote_error().
+ *
+ * In addition, facilities that are used to return errors to a remote
+ * caller also use #GError. See e_dbus_method_invocation_return_error()
+ * for discussion about how the D-Bus error name is set.
+ *
+ * Applications that receive and return errors typically wants to
+ * associate a #GError domain with a set of D-Bus errors in order to
+ * automatically map from wire-format errors to #GError and back. This
+ * is typically done in the function returning the #GQuark for the
+ * error domain:
+ * <programlisting>
+ * #define FOO_BAR_ERROR (foo_bar_error_quark ())
+ *
+ * [...]
+ *
+ * typedef enum
+ * {
+ *   FOO_BAR_ERROR_FAILED,
+ *   FOO_BAR_ERROR_ANOTHER_ERROR,
+ *   FOO_BAR_ERROR_SOME_THIRD_ERROR,
+ *   FOO_BAR_ERROR_MAX_ERROR_NUM      /<!-- -->*< skip >*<!-- -->/
+ * } FooBarError;
+ *
+ * [...]
+ *
+ * GQuark
+ * foo_bar_error_quark (void)
+ * {
+ *   static volatile gsize quark_volatile = 0;
+ *
+ *   if (g_once_init_enter (&quark_volatile))
+ *     {
+ *       guint n;
+ *       GQuark quark;
+ *       static const struct
+ *       {
+ *         gint error_code;
+ *         const gchar *dbus_error_name;
+ *       } error_mapping[] =
+ *           {
+ *             {FOO_BAR_ERROR_FAILED,           "org.project.Foo.Bar.Error.Failed"},
+ *             {FOO_BAR_ERROR_ANOTHER_ERROR,    "org.project.Foo.Bar.Error.AnotherError"},
+ *             {FOO_BAR_ERROR_SOME_THIRD_ERROR, "org.project.Foo.Bar.Error.SomeThirdError"},
+ *             {-1, NULL}
+ *           };
+ *
+ *       quark = g_quark_from_static_string ("foo-bar-error-quark");
+ *
+ *       for (n = 0; error_mapping[n].dbus_error_name != NULL; n++)
+ *         {
+ *           g_assert (e_dbus_error_register_error (quark, /<!-- -->* Can't use FOO_BAR_ERROR here because of reentrancy *<!-- -->/
+ *                                                  error_mapping[n].error_code,
+ *                                                  error_mapping[n].dbus_error_name));
+ *         }
+ *       g_assert (n == FOO_BAR_ERROR_MAX_ERROR_NUM);
+ *       g_once_init_leave (&quark_volatile, quark);
+ *     }
+ *
+ *   return (GQuark) quark_volatile;
+ * }
+ * </programlisting>
+ * With this setup, a server can transparently pass e.g. %FOO_BAR_ERROR_ANOTHER_ERROR and
+ * clients will see the D-Bus error name <literal>org.project.Foo.Bar.Error.AnotherError</literal>.
+ * If the client is using EDBus, the client will see also %FOO_BAR_ERROR_ANOTHER_ERROR instead
+ * of %E_DBUS_ERROR_REMOTE_ERROR. Note that EDBus clients can still recover
+ * <literal>org.project.Foo.Bar.Error.AnotherError</literal> using e_dbus_error_get_remote_error().
+ */
+
+/**
+ * e_dbus_error_quark:
+ *
+ * Gets the EDBus Error Quark.
+ *
+ * Return value: a #GQuark.
+ **/
+GQuark
+e_dbus_error_quark (void)
+{
+  static volatile gsize quark_volatile = 0;
+
+  if (g_once_init_enter (&quark_volatile))
+    {
+      guint n;
+      GQuark quark;
+      static const struct
+      {
+        gint error_code;
+        const gchar *dbus_error_name;
+      } error_mapping[] =
+          {
+            {E_DBUS_ERROR_DBUS_FAILED,                      "org.freedesktop.DBus.Error.Failed"},
+            {E_DBUS_ERROR_NO_MEMORY,                        "org.freedesktop.DBus.Error.NoMemory"},
+            {E_DBUS_ERROR_SERVICE_UNKNOWN,                  "org.freedesktop.DBus.Error.ServiceUnknown"},
+            {E_DBUS_ERROR_NAME_HAS_NO_OWNER,                "org.freedesktop.DBus.Error.NameHasNoOwner"},
+            {E_DBUS_ERROR_NO_REPLY,                         "org.freedesktop.DBus.Error.NoReply"},
+            {E_DBUS_ERROR_IO_ERROR,                         "org.freedesktop.DBus.Error.IOError"},
+            {E_DBUS_ERROR_BAD_ADDRESS,                      "org.freedesktop.DBus.Error.BadAddress"},
+            {E_DBUS_ERROR_NOT_SUPPORTED,                    "org.freedesktop.DBus.Error.NotSupported"},
+            {E_DBUS_ERROR_LIMITS_EXCEEDED,                  "org.freedesktop.DBus.Error.LimitsExceeded"},
+            {E_DBUS_ERROR_ACCESS_DENIED,                    "org.freedesktop.DBus.Error.AccessDenied"},
+            {E_DBUS_ERROR_AUTH_FAILED,                      "org.freedesktop.DBus.Error.AuthFailed"},
+            {E_DBUS_ERROR_NO_SERVER,                        "org.freedesktop.DBus.Error.NoServer"},
+            {E_DBUS_ERROR_TIMEOUT,                          "org.freedesktop.DBus.Error.Timeout"},
+            {E_DBUS_ERROR_NO_NETWORK,                       "org.freedesktop.DBus.Error.NoNetwork"},
+            {E_DBUS_ERROR_ADDRESS_IN_USE,                   "org.freedesktop.DBus.Error.AddressInUse"},
+            {E_DBUS_ERROR_DISCONNECTED,                     "org.freedesktop.DBus.Error.Disconnected"},
+            {E_DBUS_ERROR_INVALID_ARGS,                     "org.freedesktop.DBus.Error.InvalidArgs"},
+            {E_DBUS_ERROR_FILE_NOT_FOUND,                   "org.freedesktop.DBus.Error.FileNotFound"},
+            {E_DBUS_ERROR_FILE_EXISTS,                      "org.freedesktop.DBus.Error.FileExists"},
+            {E_DBUS_ERROR_UNKNOWN_METHOD,                   "org.freedesktop.DBus.Error.UnknownMethod"},
+            {E_DBUS_ERROR_TIMED_OUT,                        "org.freedesktop.DBus.Error.TimedOut"},
+            {E_DBUS_ERROR_MATCH_RULE_NOT_FOUND,             "org.freedesktop.DBus.Error.MatchRuleNotFound"},
+            {E_DBUS_ERROR_MATCH_RULE_INVALID,               "org.freedesktop.DBus.Error.MatchRuleInvalid"},
+            {E_DBUS_ERROR_SPAWN_EXEC_FAILED,                "org.freedesktop.DBus.Error.Spawn.ExecFailed"},
+            {E_DBUS_ERROR_SPAWN_FORK_FAILED,                "org.freedesktop.DBus.Error.Spawn.ForkFailed"},
+            {E_DBUS_ERROR_SPAWN_CHILD_EXITED,               "org.freedesktop.DBus.Error.Spawn.ChildExited"},
+            {E_DBUS_ERROR_SPAWN_CHILD_SIGNALED,             "org.freedesktop.DBus.Error.Spawn.ChildSignaled"},
+            {E_DBUS_ERROR_SPAWN_FAILED,                     "org.freedesktop.DBus.Error.Spawn.Failed"},
+            {E_DBUS_ERROR_SPAWN_SETUP_FAILED,               "org.freedesktop.DBus.Error.Spawn.FailedToSetup"},
+            {E_DBUS_ERROR_SPAWN_CONFIG_INVALID,             "org.freedesktop.DBus.Error.Spawn.ConfigInvalid"},
+            {E_DBUS_ERROR_SPAWN_SERVICE_INVALID,            "org.freedesktop.DBus.Error.Spawn.ServiceNotValid"},
+            {E_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND,          "org.freedesktop.DBus.Error.Spawn.ServiceNotFound"},
+            {E_DBUS_ERROR_SPAWN_PERMISSIONS_INVALID,        "org.freedesktop.DBus.Error.Spawn.PermissionsInvalid"},
+            {E_DBUS_ERROR_SPAWN_FILE_INVALID,               "org.freedesktop.DBus.Error.Spawn.FileInvalid"},
+            {E_DBUS_ERROR_SPAWN_NO_MEMORY,                  "org.freedesktop.DBus.Error.Spawn.NoMemory"},
+            {E_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN,          "org.freedesktop.DBus.Error.UnixProcessIdUnknown"},
+            {E_DBUS_ERROR_INVALID_SIGNATURE,                "org.freedesktop.DBus.Error.InvalidSignature"},
+            {E_DBUS_ERROR_INVALID_FILE_CONTENT,             "org.freedesktop.DBus.Error.InvalidFileContent"},
+            {E_DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN, "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"},
+            {E_DBUS_ERROR_ADT_AUDIT_DATA_UNKNOWN,           "org.freedesktop.DBus.Error.AdtAuditDataUnknown"},
+            {E_DBUS_ERROR_OBJECT_PATH_IN_USE,               "org.freedesktop.DBus.Error.ObjectPathInUse"},
+            {-1, NULL}
+          };
+
+      quark = g_quark_from_static_string ("g-dbus-error-quark");
+
+      for (n = 0; error_mapping[n].dbus_error_name != NULL; n++)
+        {
+          g_assert (e_dbus_error_register_error (quark, /* Can't use E_DBUS_ERROR here because of reentrancy ;-) */
+                                                 error_mapping[n].error_code,
+                                                 error_mapping[n].dbus_error_name));
+        }
+      g_assert (n == _E_DBUS_ERROR_MAX_DBUS_ERROR - 1000);
+
+      g_once_init_leave (&quark_volatile, quark);
+    }
+
+  return (GQuark) quark_volatile;
+}
+
+static gboolean
+_e_dbus_error_decode_gerror (const gchar *dbus_name,
+                             GQuark      *out_error_domain,
+                             gint        *out_error_code)
+{
+  gboolean ret;
+  guint n;
+  GString *s;
+  gchar *domain_quark_string;
+
+  ret = FALSE;
+  s = NULL;
+
+  if (g_str_has_prefix (dbus_name, "org.gtk.EDBus.UnmappedGError.Quark0x"))
+    {
+      s = g_string_new (NULL);
+
+      for (n = sizeof "org.gtk.EDBus.UnmappedGError.Quark0x" - 1;
+           dbus_name[n] != '.' && dbus_name[n] != '\0';
+           n++)
+        {
+          guint nibble_top;
+          guint nibble_bottom;
+
+          nibble_top = dbus_name[n];
+          if (nibble_top >= '0' && nibble_top <= '9')
+            nibble_top -= '0';
+          else if (nibble_top >= 'a' && nibble_top <= 'f')
+            nibble_top -= ('a' - 10);
+          else
+            goto not_mapped;
+
+          n++;
+
+          nibble_bottom = dbus_name[n];
+          if (nibble_bottom >= '0' && nibble_bottom <= '9')
+            nibble_bottom -= '0';
+          else if (nibble_bottom >= 'a' && nibble_bottom <= 'f')
+            nibble_bottom -= ('a' - 10);
+          else
+            goto not_mapped;
+
+          g_string_append_c (s, (nibble_top<<4) | nibble_bottom);
+
+        }
+
+      if (!g_str_has_prefix (dbus_name + n, ".Code"))
+        goto not_mapped;
+
+      domain_quark_string = g_string_free (s, FALSE);
+      s = NULL;
+
+      if (out_error_domain != NULL)
+        *out_error_domain = g_quark_from_string (domain_quark_string);
+      g_free (domain_quark_string);
+
+      if (out_error_code != NULL)
+        *out_error_code = atoi (dbus_name + n + sizeof ".Code" - 1);
+
+      ret = TRUE;
+    }
+
+ not_mapped:
+
+  if (s != NULL)
+    g_string_free (s, TRUE);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GQuark error_domain;
+  gint   error_code;
+} QuarkCodePair;
+
+static guint
+quark_code_pair_hash_func (const QuarkCodePair *pair)
+{
+  gint val;
+  val = pair->error_domain + pair->error_code;
+  return g_int_hash (&val);
+}
+
+static gboolean
+quark_code_pair_equal_func (const QuarkCodePair *a,
+                            const QuarkCodePair *b)
+{
+  return (a->error_domain == b->error_domain) && (a->error_code == b->error_code);
+}
+
+typedef struct
+{
+  QuarkCodePair pair;
+  gchar *dbus_error_name;
+} RegisteredError;
+
+static void
+registered_error_free (RegisteredError *re)
+{
+  g_free (re->dbus_error_name);
+  g_free (re);
+}
+
+G_LOCK_DEFINE_STATIC (error_lock);
+
+/* maps from QuarkCodePair* -> RegisteredError* */
+static GHashTable *quark_code_pair_to_re = NULL;
+
+/* maps from gchar* -> RegisteredError* */
+static GHashTable *dbus_error_name_to_re = NULL;
+
+/**
+ * e_dbus_error_register_error:
+ * @error_domain: A #GQuark for a error domain.
+ * @error_code: An error code.
+ * @dbus_error_name: A D-Bus error name.
+ *
+ * Creates an association to map between @dbus_error_name and
+ * #GError<!-- -->s specified by @error_domain and @error_code.
+ *
+ * This is typically done in the routine that returns the #GQuark for
+ * an error domain.
+ *
+ * Returns: %TRUE unless an association already existed.
+ */
+gboolean
+e_dbus_error_register_error (GQuark       error_domain,
+                             gint         error_code,
+                             const gchar *dbus_error_name)
+{
+  gboolean ret;
+  QuarkCodePair pair;
+  RegisteredError *re;
+
+  g_return_val_if_fail (dbus_error_name != NULL, FALSE);
+
+  ret = FALSE;
+
+  G_LOCK (error_lock);
+
+  if (quark_code_pair_to_re == NULL)
+    {
+      g_assert (dbus_error_name_to_re == NULL); /* check invariant */
+      quark_code_pair_to_re = g_hash_table_new ((GHashFunc) quark_code_pair_hash_func,
+                                                (GEqualFunc) quark_code_pair_equal_func);
+      dbus_error_name_to_re = g_hash_table_new_full (g_str_hash,
+                                                     g_str_equal,
+                                                     NULL,
+                                                     (GDestroyNotify) registered_error_free);
+    }
+
+  if (g_hash_table_lookup (dbus_error_name_to_re, dbus_error_name) != NULL)
+    goto out;
+
+  pair.error_domain = error_domain;
+  pair.error_code = error_code;
+
+  if (g_hash_table_lookup (quark_code_pair_to_re, &pair) != NULL)
+    goto out;
+
+  re = g_new0 (RegisteredError, 1);
+  re->pair = pair;
+  re->dbus_error_name = g_strdup (dbus_error_name);
+
+  g_hash_table_insert (quark_code_pair_to_re, &(re->pair), re);
+  g_hash_table_insert (dbus_error_name_to_re, re->dbus_error_name, re);
+
+  ret = TRUE;
+
+ out:
+  G_UNLOCK (error_lock);
+  return ret;
+}
+
+/**
+ * e_dbus_error_unregister_error:
+ * @error_domain: A #GQuark for a error domain.
+ * @error_code: An error code.
+ * @dbus_error_name: A D-Bus error name.
+ *
+ * Destroys an association previously set up with e_dbus_error_register_error().
+ *
+ * Returns: %TRUE if the association was found and destroyed, %FALSE otherwise.
+ */
+gboolean
+e_dbus_error_unregister_error (GQuark       error_domain,
+                               gint         error_code,
+                               const gchar *dbus_error_name)
+{
+  gboolean ret;
+  RegisteredError *re;
+  guint hash_size;
+
+  ret = FALSE;
+
+  G_LOCK (error_lock);
+
+  if (dbus_error_name_to_re == NULL)
+    {
+      g_assert (quark_code_pair_to_re == NULL); /* check invariant */
+      goto out;
+    }
+
+  re = g_hash_table_lookup (dbus_error_name_to_re, dbus_error_name);
+  if (re == NULL)
+    {
+      QuarkCodePair pair;
+      pair.error_domain = error_domain;
+      pair.error_code = error_code;
+      g_assert (g_hash_table_lookup (quark_code_pair_to_re, &pair) == NULL); /* check invariant */
+      goto out;
+    }
+  g_assert (g_hash_table_lookup (quark_code_pair_to_re, &(re->pair)) == re); /* check invariant */
+
+  g_assert (g_hash_table_remove (quark_code_pair_to_re, &(re->pair)));
+  g_assert (g_hash_table_remove (dbus_error_name_to_re, re));
+
+  /* destroy hashes if empty */
+  hash_size = g_hash_table_size (dbus_error_name_to_re);
+  if (hash_size == 0)
+    {
+      g_assert (g_hash_table_size (quark_code_pair_to_re) == 0); /* check invariant */
+
+      g_hash_table_unref (dbus_error_name_to_re);
+      dbus_error_name_to_re = NULL;
+      g_hash_table_unref (quark_code_pair_to_re);
+      quark_code_pair_to_re = NULL;
+    }
+  else
+    {
+      g_assert (g_hash_table_size (quark_code_pair_to_re) == hash_size); /* check invariant */
+    }
+
+ out:
+  G_UNLOCK (error_lock);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_error_is_remote_error:
+ * @error: A #GError.
+ *
+ * Checks if @error represents an error from a remote process. If so,
+ * use e_dbus_error_get_remote_error() to get the name of the error.
+ *
+ * Returns: %TRUE if @error represents an error from a remote process,
+ * %FALSE otherwise.
+ */
+gboolean
+e_dbus_error_is_remote_error (const GError *error)
+{
+  g_return_val_if_fail (error != NULL, FALSE);
+  return g_str_has_prefix (error->message, "EDBus.Error:");
+}
+
+
+/**
+ * e_dbus_error_get_remote_error:
+ * @error: A #GError.
+ *
+ * Gets the D-Bus error name used for @error, if any.
+ *
+ * This function is guaranteed to return a D-Bus error name for all #GError<!-- -->s returned from
+ * functions handling remote method calls (e.g. e_dbus_connection_invoke_method_finish())
+ * unless e_dbus_error_strip_remote_error() has been used on @error.
+ *
+ * Returns: An allocated string or %NULL if the D-Bus error name could not be found. Free with g_free().
+ */
+gchar *
+e_dbus_error_get_remote_error (const GError *error)
+{
+  RegisteredError *re;
+  gchar *ret;
+  volatile GQuark error_domain;
+
+  g_return_val_if_fail (error != NULL, NULL);
+
+  /* Ensure that the E_DBUS_ERROR is registered using e_dbus_error_register_error() */
+  error_domain = E_DBUS_ERROR;
+
+  ret = NULL;
+
+  G_LOCK (error_lock);
+
+  re = NULL;
+  if (quark_code_pair_to_re != NULL)
+    {
+      QuarkCodePair pair;
+      pair.error_domain = error->domain;
+      pair.error_code = error->code;
+      g_assert (dbus_error_name_to_re != NULL); /* check invariant */
+      re = g_hash_table_lookup (quark_code_pair_to_re, &pair);
+    }
+
+  if (re != NULL)
+    {
+      ret = g_strdup (re->dbus_error_name);
+    }
+  else
+    {
+      if (g_str_has_prefix (error->message, "EDBus.Error:"))
+        {
+          const gchar *begin;
+          const gchar *end;
+          begin = error->message + sizeof ("EDBus.Error:") -1;
+          end = strstr (begin, ":");
+          if (end != NULL && end[1] == ' ')
+            {
+              ret = g_strndup (begin, end - begin);
+            }
+        }
+    }
+
+  G_UNLOCK (error_lock);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_error_new_for_dbus_error:
+ * @dbus_error_name: D-Bus error name.
+ * @dbus_error_message: D-Bus error message.
+ *
+ * Creates a #GError based on the contents of @dbus_error_name and
+ * @dbus_error_message.
+ *
+ * Errors registered with e_dbus_error_register_error() will be looked
+ * up using @dbus_error_name and if a match is found, the error domain
+ * and code is used. Applications can use e_dbus_error_get_remote_error()
+ * to recover @dbus_error_name.
+ *
+ * If a match against a registered error is not found and the D-Bus
+ * error name is in a form as returned by e_dbus_error_encode_gerror()
+ * the error domain and code encoded in the name is used to
+ * create the #GError. Also, @dbus_error_name is added to the error message
+ * such that it can be recovered with e_dbus_error_get_remote_error().
+ *
+ * Otherwise, a #GError with the error code %E_DBUS_ERROR_REMOTE_ERROR
+ * in the #E_DBUS_ERROR error domain is returned. Also, @dbus_error_name is
+ * added to the error message such that it can be recovered with
+ * e_dbus_error_get_remote_error().
+ *
+ * In all three cases, @dbus_error_name can always be recovered from the
+ * returned #GError using the e_dbus_error_get_remote_error() function
+ * (unless e_dbus_error_strip_remote_error() hasn't been used on the returned error).
+ *
+ * This function is typically only used in object mappings to prepare
+ * #GError instances for applications. Regular applications should not use
+ * it.
+ *
+ * Returns: An allocated #GError. Free with g_error_free().
+ */
+GError *
+e_dbus_error_new_for_dbus_error (const gchar *dbus_error_name,
+                                 const gchar *dbus_error_message)
+{
+  GError *error;
+  RegisteredError *re;
+  volatile GQuark error_domain;
+
+  g_return_val_if_fail (dbus_error_name != NULL, NULL);
+  g_return_val_if_fail (dbus_error_message != NULL, NULL);
+
+  /* Ensure that the E_DBUS_ERROR is registered using e_dbus_error_register_error() */
+  error_domain = E_DBUS_ERROR;
+
+  G_LOCK (error_lock);
+
+  re = NULL;
+  if (dbus_error_name_to_re != NULL)
+    {
+      g_assert (quark_code_pair_to_re != NULL); /* check invariant */
+      re = g_hash_table_lookup (dbus_error_name_to_re, dbus_error_name);
+    }
+
+  if (re != NULL)
+    {
+      error = g_error_new (re->pair.error_domain,
+                           re->pair.error_code,
+                           "EDBus.Error:%s: %s",
+                           dbus_error_name,
+                           dbus_error_message);
+    }
+  else
+    {
+      GQuark error_domain;
+      gint error_code;
+
+      if (_e_dbus_error_decode_gerror (dbus_error_name,
+                                       &error_domain,
+                                       &error_code))
+        {
+          error = g_error_new (error_domain,
+                               error_code,
+                               "EDBus.Error:%s: %s",
+                               dbus_error_name,
+                               dbus_error_message);
+        }
+      else
+        {
+          error = g_error_new (E_DBUS_ERROR,
+                               E_DBUS_ERROR_REMOTE_ERROR,
+                               "EDBus.Error:%s: %s",
+                               dbus_error_name,
+                               dbus_error_message);
+        }
+    }
+
+  G_UNLOCK (error_lock);
+  return error;
+}
+
+/**
+ * e_dbus_error_set_dbus_error:
+ * @error: A pointer to a #GError or %NULL.
+ * @dbus_error_name: D-Bus error name.
+ * @dbus_error_message: D-Bus error message.
+ * @format: printf()-style format to prepend to @dbus_error_message or %NULL.
+ * @...: Arguments for @format.
+ *
+ * Does nothing if @error is %NULL. Otherwise sets * error to
+ * a new #GError created with e_dbus_error_new_for_dbus_error()
+ * with @dbus_error_message prepend with @format (unless %NULL).
+ */
+void
+e_dbus_error_set_dbus_error (GError      **error,
+                             const gchar  *dbus_error_name,
+                             const gchar  *dbus_error_message,
+                             const gchar  *format,
+                             ...)
+{
+  g_return_if_fail (dbus_error_name != NULL);
+  g_return_if_fail (dbus_error_message != NULL);
+
+  if (error == NULL)
+    return;
+
+  if (format == NULL)
+    {
+      *error = e_dbus_error_new_for_dbus_error (dbus_error_name, dbus_error_message);
+    }
+  else
+    {
+      va_list var_args;
+      va_start (var_args, format);
+      e_dbus_error_set_dbus_error_valist (error,
+                                          dbus_error_name,
+                                          dbus_error_message,
+                                          format,
+                                          var_args);
+      va_end (var_args);
+    }
+}
+
+/**
+ * e_dbus_error_set_dbus_error_valist:
+ * @error: A pointer to a #GError or %NULL.
+ * @dbus_error_name: D-Bus error name.
+ * @dbus_error_message: D-Bus error message.
+ * @format: printf()-style format to prepend to @dbus_error_message or %NULL.
+ * @var_args: Arguments for @format.
+ *
+ * Like e_dbus_error_set_dbus_error() but intended for language bindings.
+ */
+void
+e_dbus_error_set_dbus_error_valist (GError      **error,
+                                    const gchar  *dbus_error_name,
+                                    const gchar  *dbus_error_message,
+                                    const gchar  *format,
+                                    va_list       var_args)
+{
+  g_return_if_fail (dbus_error_name != NULL);
+  g_return_if_fail (dbus_error_message != NULL);
+
+  if (error == NULL)
+    return;
+
+  if (format != NULL)
+    {
+      gchar *message;
+      gchar *s;
+      message = g_strdup_vprintf (format, var_args);
+      s = g_strdup_printf ("%s: %s", message, dbus_error_message);
+      *error = e_dbus_error_new_for_dbus_error (dbus_error_name, s);
+      g_free (s);
+      g_free (message);
+    }
+  else
+    {
+      *error = e_dbus_error_new_for_dbus_error (dbus_error_name, dbus_error_message);
+    }
+}
+
+/**
+ * e_dbus_error_strip_remote_error:
+ * @error: A #GError.
+ *
+ * Looks for extra information in the error message used to recover
+ * the D-Bus error name and strips it if found. If stripped, the
+ * message field in @error will correspond exactly to what was
+ * received on the wire.
+ *
+ * This is typically used when presenting errors to the end user.
+ *
+ * Returns: %TRUE if information was stripped, %FALSE otherwise.
+ */
+gboolean
+e_dbus_error_strip_remote_error (GError *error)
+{
+  gboolean ret;
+
+  g_return_val_if_fail (error != NULL, FALSE);
+
+  ret = FALSE;
+
+  if (g_str_has_prefix (error->message, "EDBus.Error:"))
+    {
+      const gchar *begin;
+      const gchar *end;
+      gchar *new_message;
+
+      begin = error->message + sizeof ("EDBus.Error:") -1;
+      end = strstr (begin, ":");
+      if (end != NULL && end[1] == ' ')
+        {
+          new_message = g_strdup (end + 2);
+          g_free (error->message);
+          error->message = new_message;
+          ret = TRUE;
+        }
+    }
+
+  return ret;
+}
+
+/**
+ * e_dbus_error_encode_gerror:
+ * @error: A #GError.
+ *
+ * Creates a D-Bus error name to use for @error. If @error matches
+ * a registered error (cf. e_dbus_error_register_error()), the corresponding
+ * D-Bus error name will be returned.
+ *
+ * Otherwise the a name of the form
+ * <literal>org.gtk.EDBus.UnmappedGError.Quark0xHEXENCODED_QUARK_NAME_.Code_ERROR_CODE</literal>
+ * will be used. This allows other EDBus applications to map the error
+ * on the wire back to a #GError using e_dbus_error_new_for_dbus_error().
+ *
+ * This function is typically only used in object mappings to put a
+ * #GError on the wire. Regular applications should not use it.
+ *
+ * Returns: A D-Bus error name (never %NULL). Free with g_free().
+ */
+gchar *
+e_dbus_error_encode_gerror (const GError *error)
+{
+  RegisteredError *re;
+  gchar *error_name;
+  volatile GQuark error_domain;
+
+  g_return_val_if_fail (error != NULL, NULL);
+
+  /* Ensure that the E_DBUS_ERROR is registered using e_dbus_error_register_error() */
+  error_domain = E_DBUS_ERROR;
+
+  error_name = NULL;
+
+  G_LOCK (error_lock);
+  re = NULL;
+  if (quark_code_pair_to_re != NULL)
+    {
+      QuarkCodePair pair;
+      pair.error_domain = error->domain;
+      pair.error_code = error->code;
+      g_assert (dbus_error_name_to_re != NULL); /* check invariant */
+      re = g_hash_table_lookup (quark_code_pair_to_re, &pair);
+    }
+  if (re != NULL)
+    {
+      error_name = g_strdup (re->dbus_error_name);
+      G_UNLOCK (error_lock);
+    }
+  else
+    {
+      const gchar *domain_as_string;
+      GString *s;
+      guint n;
+
+      G_UNLOCK (error_lock);
+
+      /* We can't make a lot of assumptions about what domain_as_string
+       * looks like and D-Bus is extremely picky about error names so
+       * hex-encode it for transport across the wire.
+       */
+      domain_as_string = g_quark_to_string (error->domain);
+      s = g_string_new ("org.gtk.EDBus.UnmappedGError.Quark0x");
+      for (n = 0; domain_as_string[n] != 0; n++)
+        {
+          guint nibble_top;
+          guint nibble_bottom;
+          nibble_top = ((int) domain_as_string[n]) >> 4;
+          nibble_bottom = ((int) domain_as_string[n]) & 0x0f;
+          if (nibble_top < 10)
+            nibble_top += '0';
+          else
+            nibble_top += 'a' - 10;
+          if (nibble_bottom < 10)
+            nibble_bottom += '0';
+          else
+            nibble_bottom += 'a' - 10;
+          g_string_append_c (s, nibble_top);
+          g_string_append_c (s, nibble_bottom);
+        }
+      g_string_append_printf (s, ".Code%d", error->code);
+      error_name = g_string_free (s, FALSE);
+    }
+
+  return error_name;
+}
diff --git a/libedbus/gdbuserror.h b/libedbus/gdbuserror.h
new file mode 100644
index 0000000..f8b4993
--- /dev/null
+++ b/libedbus/gdbuserror.h
@@ -0,0 +1,74 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#if !defined (__E_DBUS_H_INSIDE__) && !defined (E_DBUS_COMPILATION)
+#error "Only <gdbus/gdbus.h> can be included directly."
+#endif
+
+#ifndef __E_DBUS_ERROR_H__
+#define __E_DBUS_ERROR_H__
+
+#include <gdbus/gdbustypes.h>
+
+G_BEGIN_DECLS
+
+/**
+ * E_DBUS_ERROR:
+ *
+ * Error domain for EDBus. Errors in this domain will be from the #EDBusError enumeration.
+ * See #GError for more information on error domains.
+ **/
+#define E_DBUS_ERROR e_dbus_error_quark()
+
+GQuark e_dbus_error_quark (void);
+
+/* Used by applications to check, get and strip the D-Bus error name */
+gboolean e_dbus_error_is_remote_error       (const GError *error);
+gchar   *e_dbus_error_get_remote_error      (const GError *error);
+gboolean e_dbus_error_strip_remote_error    (GError       *error);
+
+/* Used by applications to associate GError domains with D-Bus error names */
+gboolean e_dbus_error_register_error        (GQuark        error_domain,
+                                             gint          error_code,
+                                             const gchar  *dbus_error_name);
+gboolean e_dbus_error_unregister_error      (GQuark        error_domain,
+                                             gint          error_code,
+                                             const gchar  *dbus_error_name);
+
+/* Only used by object mappings to map back and forth to GError */
+GError  *e_dbus_error_new_for_dbus_error    (const gchar  *dbus_error_name,
+                                             const gchar  *dbus_error_message);
+void     e_dbus_error_set_dbus_error        (GError      **error,
+                                             const gchar  *dbus_error_name,
+                                             const gchar  *dbus_error_message,
+                                             const gchar  *format,
+                                             ...);
+void     e_dbus_error_set_dbus_error_valist (GError      **error,
+                                             const gchar  *dbus_error_name,
+                                             const gchar  *dbus_error_message,
+                                             const gchar  *format,
+                                             va_list       var_args);
+gchar   *e_dbus_error_encode_gerror         (const GError *error);
+
+G_END_DECLS
+
+#endif /* __E_DBUS_ERROR_H__ */
diff --git a/libedbus/gdbusintrospection.c b/libedbus/gdbusintrospection.c
new file mode 100644
index 0000000..f480265
--- /dev/null
+++ b/libedbus/gdbusintrospection.c
@@ -0,0 +1,1826 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbusintrospection.h"
+
+/**
+ * SECTION:gdbusintrospection
+ * @title: Introspection XML
+ * @short_description: Parse and Generate Introspection XML
+ * @include: gdbus/gdbus.h
+ *
+ * Various data structures and convenience routines to parse and
+ * generate D-Bus introspection XML.
+ */
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  /* stuff we are currently collecting */
+  GArray *args;
+  GArray *out_args;
+  GArray *methods;
+  GArray *signals;
+  GArray *properties;
+  GArray *interfaces;
+  GArray *nodes;
+  GArray *annotations;
+
+  /* A list of GArray's containing annotations */
+  GSList *annotations_stack;
+
+  /* A list of GArray's containing interfaces */
+  GSList *interfaces_stack;
+
+  /* A list of GArray's containing nodes */
+  GSList *nodes_stack;
+
+  /* Whether the direction was "in" for last parsed arg */
+  gboolean last_arg_was_in;
+
+  /* Number of args currently being collected; used for assigning
+   * names to args without a "name" attribute
+   */
+  guint num_args;
+
+} ParseData;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void e_dbus_annotation_info_free (EDBusAnnotationInfo *info);
+
+static void
+e_dbus_introspector_free_annotation_array (EDBusAnnotationInfo *annotations)
+{
+  guint n;
+  for (n = 0; annotations != NULL && annotations[n].key != NULL; n++)
+    e_dbus_annotation_info_free (&(annotations[n]));
+  g_free (annotations);
+}
+
+static void
+e_dbus_annotation_info_free (EDBusAnnotationInfo *info)
+{
+  g_free ((gpointer) info->key);
+  g_free ((gpointer) info->value);
+  e_dbus_introspector_free_annotation_array ((EDBusAnnotationInfo *) info->annotations);
+}
+
+static void
+e_dbus_arg_info_free (EDBusArgInfo *info)
+{
+  g_free ((gpointer) info->name);
+  g_free ((gpointer) info->signature);
+  e_dbus_introspector_free_annotation_array ((EDBusAnnotationInfo *) info->annotations);
+}
+
+static void
+e_dbus_method_info_free (EDBusMethodInfo *info)
+{
+  guint n;
+
+  g_free ((gpointer) info->name);
+
+  g_free ((gpointer) info->in_signature);
+  for (n = 0; n < info->in_num_args; n++)
+    e_dbus_arg_info_free ((EDBusArgInfo *) &(info->in_args[n]));
+  g_free ((gpointer) info->in_args);
+
+  g_free ((gpointer) info->out_signature);
+  for (n = 0; n < info->out_num_args; n++)
+    e_dbus_arg_info_free ((EDBusArgInfo *) &(info->out_args[n]));
+  g_free ((gpointer) info->out_args);
+
+  e_dbus_introspector_free_annotation_array ((EDBusAnnotationInfo *) info->annotations);
+}
+
+static void
+e_dbus_signal_info_free (EDBusSignalInfo *info)
+{
+  guint n;
+
+  g_free ((gpointer) info->name);
+
+  g_free ((gpointer) info->signature);
+  for (n = 0; n < info->num_args; n++)
+    e_dbus_arg_info_free ((EDBusArgInfo *) &(info->args[n]));
+  g_free ((gpointer) info->args);
+
+  e_dbus_introspector_free_annotation_array ((EDBusAnnotationInfo *) info->annotations);
+}
+
+static void
+e_dbus_property_info_free (EDBusPropertyInfo *info)
+{
+  g_free ((gpointer) info->name);
+  g_free ((gpointer) info->signature);
+  e_dbus_introspector_free_annotation_array ((EDBusAnnotationInfo *) info->annotations);
+}
+
+static void
+e_dbus_interface_info_free (EDBusInterfaceInfo *info)
+{
+  guint n;
+
+  g_free ((gpointer) info->name);
+
+  for (n = 0; n < info->num_methods; n++)
+    e_dbus_method_info_free ((EDBusMethodInfo *) &(info->methods[n]));
+  g_free ((gpointer) info->methods);
+
+  for (n = 0; n < info->num_signals; n++)
+    e_dbus_signal_info_free ((EDBusSignalInfo *) &(info->signals[n]));
+  g_free ((gpointer) info->signals);
+
+  for (n = 0; n < info->num_properties; n++)
+    e_dbus_property_info_free ((EDBusPropertyInfo *) &(info->properties[n]));
+  g_free ((gpointer) info->properties);
+
+  e_dbus_introspector_free_annotation_array ((EDBusAnnotationInfo *) info->annotations);
+}
+
+/**
+ * e_dbus_node_info_free:
+ * @node_info: A #EDBusNodeInfo.
+ *
+ * Frees @node_info. This is a deep free, all nodes of @node_info and
+ * its children will be freed as well.
+ */
+void
+e_dbus_node_info_free (EDBusNodeInfo *node_info)
+{
+  guint n;
+
+  g_free ((gpointer) node_info->path);
+
+  for (n = 0; n < node_info->num_interfaces; n++)
+    e_dbus_interface_info_free ((EDBusInterfaceInfo *) &(node_info->interfaces[n]));
+  g_free ((gpointer) node_info->interfaces);
+
+  for (n = 0; n < node_info->num_nodes; n++)
+    e_dbus_node_info_free ((EDBusNodeInfo *) &(node_info->nodes[n]));
+  g_free ((gpointer) node_info->nodes);
+
+  e_dbus_introspector_free_annotation_array ((EDBusAnnotationInfo *) node_info->annotations);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+e_dbus_annotation_info_set (ParseData                      *data,
+                                        EDBusAnnotationInfo *info,
+                                        const gchar                    *key,
+                                        const gchar                    *value,
+                                        EDBusAnnotationInfo *embedded_annotations)
+{
+  if (key != NULL)
+    info->key = g_strdup (key);
+
+  if (value != NULL)
+    info->value = g_strdup (value);
+
+  if (embedded_annotations != NULL)
+    info->annotations = embedded_annotations;
+}
+
+static void
+e_dbus_arg_info_set (ParseData           *data,
+                     EDBusArgInfo        *info,
+                     const gchar         *name,
+                     const gchar         *signature,
+                     EDBusAnnotationInfo *annotations)
+{
+  /* name may be NULL - TODO: compute name? */
+  if (name != NULL)
+    info->name = g_strdup (name);
+
+  if (signature != NULL)
+    info->signature = g_strdup (signature);
+
+  if (annotations != NULL)
+    info->annotations = annotations;
+}
+
+static gchar *
+compute_signature (EDBusArgInfo  *args,
+                   guint          num_args)
+{
+  GString *s;
+  guint n;
+
+  s = g_string_new ("");
+  for (n = 0; n < num_args; n++)
+    g_string_append (s, args[n].signature);
+
+  return g_string_free (s, FALSE);
+}
+
+static void
+e_dbus_method_info_set (ParseData           *data,
+                        EDBusMethodInfo     *info,
+                        const gchar         *name,
+                        guint                in_num_args,
+                        EDBusArgInfo        *in_args,
+                        guint                out_num_args,
+                        EDBusArgInfo        *out_args,
+                        EDBusAnnotationInfo *annotations)
+{
+  if (name != NULL)
+    info->name = g_strdup (name);
+
+  if (in_num_args != 0)
+    {
+      info->in_num_args = in_num_args;
+      info->in_args = in_args;
+    }
+  g_free ((gpointer) info->in_signature);
+  info->in_signature = compute_signature (in_args, in_num_args);
+
+  if (out_num_args != 0)
+    {
+      info->out_num_args = out_num_args;
+      info->out_args = out_args;
+    }
+  g_free ((gpointer) info->out_signature);
+  info->out_signature = compute_signature (out_args, out_num_args);
+
+  if (annotations != NULL)
+    info->annotations = annotations;
+}
+
+static void
+e_dbus_signal_info_set (ParseData           *data,
+                        EDBusSignalInfo     *info,
+                        const gchar         *name,
+                        guint                num_args,
+                        EDBusArgInfo        *args,
+                        EDBusAnnotationInfo *annotations)
+{
+  if (name != NULL)
+    info->name = g_strdup (name);
+
+  if (num_args != 0)
+    {
+      info->num_args = num_args;
+      info->args = args;
+    }
+  g_free ((gpointer) info->signature);
+  info->signature = compute_signature (args, num_args);
+
+  if (annotations != NULL)
+    {
+      info->annotations = annotations;
+    }
+}
+
+static void
+e_dbus_property_info_set (ParseData              *data,
+                          EDBusPropertyInfo      *info,
+                          const gchar            *name,
+                          const gchar            *signature,
+                          EDBusPropertyInfoFlags  flags,
+                          EDBusAnnotationInfo    *annotations)
+{
+  if (name != NULL)
+    info->name = g_strdup (name);
+
+  if (flags != E_DBUS_PROPERTY_INFO_FLAGS_NONE)
+    info->flags = flags;
+
+  if (signature != NULL)
+    {
+      info->signature = g_strdup (signature);
+    }
+
+  if (annotations != NULL)
+    {
+      info->annotations = annotations;
+    }
+}
+
+static void
+e_dbus_interface_info_set (ParseData           *data,
+                           EDBusInterfaceInfo  *info,
+                           const gchar         *name,
+                           guint                num_methods,
+                           EDBusMethodInfo     *methods,
+                           guint                num_signals,
+                           EDBusSignalInfo     *signals,
+                           guint                num_properties,
+                           EDBusPropertyInfo   *properties,
+                           EDBusAnnotationInfo *annotations)
+{
+  if (name != NULL)
+    {
+      info->name = g_strdup (name);
+    }
+
+  if (num_methods != 0)
+    {
+      info->num_methods    = num_methods;
+      info->methods        = methods;
+    }
+
+  if (num_signals != 0)
+    {
+      info->num_signals    = num_signals;
+      info->signals        = signals;
+    }
+
+  if (num_properties != 0)
+    {
+      info->num_properties = num_properties;
+      info->properties     = properties;
+    }
+
+  if (annotations != NULL)
+    {
+      info->annotations = annotations;
+    }
+}
+
+static void
+e_dbus_node_info_set (ParseData           *data,
+                      EDBusNodeInfo       *info,
+                      const gchar         *path,
+                      guint                num_interfaces,
+                      EDBusInterfaceInfo  *interfaces,
+                      guint                num_nodes,
+                      EDBusNodeInfo       *nodes,
+                      EDBusAnnotationInfo *annotations)
+{
+  if (path != NULL)
+    {
+      info->path = g_strdup (path);
+      /* TODO: relative / absolute path snafu */
+    }
+
+  if (num_interfaces != 0)
+    {
+      info->num_interfaces = num_interfaces;
+      info->interfaces     = interfaces;
+    }
+
+  if (num_nodes != 0)
+    {
+      info->num_nodes      = num_nodes;
+      info->nodes          = nodes;
+    }
+
+  if (annotations != NULL)
+    {
+      info->annotations = annotations;
+    }
+
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+e_dbus_annotation_info_generate_xml (const EDBusAnnotationInfo  *info,
+                                     guint                       indent,
+                                     GString                    *string_builder)
+{
+  guint n;
+
+  g_string_append_printf (string_builder, "%*s<annotation name=\"%s\" value=\"%s\"",
+                          indent, "",
+                          info->key,
+                          info->value);
+
+  if (info->annotations == NULL)
+    {
+      g_string_append (string_builder, "/>\n");
+    }
+  else
+    {
+      g_string_append (string_builder, ">\n");
+
+      for (n = 0; info->annotations != NULL && info->annotations[n].key != NULL; n++)
+        e_dbus_annotation_info_generate_xml (&(info->annotations[n]),
+                                             indent + 2,
+                                             string_builder);
+
+      g_string_append_printf (string_builder, "%*s</annotation>\n",
+                              indent, "");
+    }
+
+}
+
+static void
+e_dbus_arg_info_generate_xml (const EDBusArgInfo  *info,
+                              guint                indent,
+                              const gchar         *extra_attributes,
+                              GString             *string_builder)
+{
+  guint n;
+
+  g_string_append_printf (string_builder, "%*s<arg type=\"%s\"",
+                          indent, "",
+                          info->signature);
+
+  if (info->name != NULL)
+    g_string_append_printf (string_builder, " name=\"%s\"", info->name);
+
+  if (extra_attributes != NULL)
+    g_string_append_printf (string_builder, " %s", extra_attributes);
+
+  if (info->annotations == NULL)
+    {
+      g_string_append (string_builder, "/>\n");
+    }
+  else
+    {
+      g_string_append (string_builder, ">\n");
+
+      for (n = 0; info->annotations != NULL && info->annotations[n].key != NULL; n++)
+        e_dbus_annotation_info_generate_xml (&(info->annotations[n]),
+                                             indent + 2,
+                                             string_builder);
+
+      g_string_append_printf (string_builder, "%*s</arg>\n", indent, "");
+    }
+
+}
+
+static void
+e_dbus_method_info_generate_xml (const EDBusMethodInfo  *info,
+                                 guint                   indent,
+                                 GString                *string_builder)
+{
+  guint n;
+
+  g_string_append_printf (string_builder, "%*s<method name=\"%s\"",
+                          indent, "",
+                          info->name);
+
+  if (info->annotations == NULL && info->in_num_args == 0 && info->out_num_args == 0)
+    {
+      g_string_append (string_builder, "/>\n");
+    }
+  else
+    {
+      g_string_append (string_builder, ">\n");
+
+      for (n = 0; info->annotations != NULL && info->annotations[n].key != NULL; n++)
+        e_dbus_annotation_info_generate_xml (&(info->annotations[n]),
+                                             indent + 2,
+                                             string_builder);
+
+      for (n = 0; n < info->in_num_args; n++)
+        e_dbus_arg_info_generate_xml (&(info->in_args[n]),
+                                      indent + 2,
+                                      "direction=\"in\"",
+                                      string_builder);
+
+      for (n = 0; n < info->out_num_args; n++)
+        e_dbus_arg_info_generate_xml (&(info->out_args[n]),
+                                      indent + 2,
+                                      "direction=\"out\"",
+                                      string_builder);
+
+      g_string_append_printf (string_builder, "%*s</method>\n", indent, "");
+    }
+}
+
+static void
+e_dbus_signal_info_generate_xml (const EDBusSignalInfo  *info,
+                                 guint                   indent,
+                                 GString                *string_builder)
+{
+  guint n;
+
+  g_string_append_printf (string_builder, "%*s<signal name=\"%s\"",
+                          indent, "",
+                          info->name);
+
+  if (info->annotations == NULL && info->num_args == 0)
+    {
+      g_string_append (string_builder, "/>\n");
+    }
+  else
+    {
+      g_string_append (string_builder, ">\n");
+
+      for (n = 0; info->annotations != NULL && info->annotations[n].key != NULL; n++)
+        e_dbus_annotation_info_generate_xml (&(info->annotations[n]),
+                                             indent + 2,
+                                             string_builder);
+
+      for (n = 0; n < info->num_args; n++)
+        e_dbus_arg_info_generate_xml (&(info->args[n]),
+                                      indent + 2,
+                                      NULL,
+                                      string_builder);
+
+      g_string_append_printf (string_builder, "%*s</signal>\n", indent, "");
+    }
+}
+
+static void
+e_dbus_property_info_generate_xml (const EDBusPropertyInfo  *info,
+                                   guint                     indent,
+                                   GString                  *string_builder)
+{
+  guint n;
+  const gchar *access_string;
+
+  if ((info->flags & E_DBUS_PROPERTY_INFO_FLAGS_READABLE) &&
+      (info->flags & E_DBUS_PROPERTY_INFO_FLAGS_WRITABLE))
+    {
+      access_string = "readwrite";
+    }
+  else if (info->flags & E_DBUS_PROPERTY_INFO_FLAGS_READABLE)
+    {
+      access_string = "read";
+    }
+  else if (info->flags & E_DBUS_PROPERTY_INFO_FLAGS_WRITABLE)
+    {
+      access_string = "write";
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
+
+  g_string_append_printf (string_builder, "%*s<property type=\"%s\" name=\"%s\" access=\"%s\"",
+                          indent, "",
+                          info->signature,
+                          info->name,
+                          access_string);
+
+  if (info->annotations == NULL)
+    {
+      g_string_append (string_builder, "/>\n");
+    }
+  else
+    {
+      g_string_append (string_builder, ">\n");
+
+      for (n = 0; info->annotations != NULL && info->annotations[n].key != NULL; n++)
+        e_dbus_annotation_info_generate_xml (&(info->annotations[n]),
+                                               indent + 2,
+                                               string_builder);
+
+      g_string_append_printf (string_builder, "%*s</property>\n", indent, "");
+    }
+
+}
+
+/**
+ * e_dbus_interface_info_generate_xml:
+ * @interface_info: A #EDBusNodeInfo
+ * @indent: Indentation level.
+ * @string_builder: A #GString to to append XML data to.
+ *
+ * Appends an XML representation of @interface_info (and its children) to @string_builder.
+ *
+ * This function is typically used for generating introspection XML
+ * documents at run-time for handling the
+ * <literal>org.freedesktop.DBus.Introspectable.Introspect</literal>
+ * method.
+ */
+void
+e_dbus_interface_info_generate_xml (const EDBusInterfaceInfo  *interface_info,
+                                    guint                      indent,
+                                    GString                   *string_builder)
+{
+  guint n;
+
+  g_string_append_printf (string_builder, "%*s<interface name=\"%s\">\n",
+                          indent, "",
+                          interface_info->name);
+
+  for (n = 0; interface_info->annotations != NULL && interface_info->annotations[n].key != NULL; n++)
+    e_dbus_annotation_info_generate_xml (&(interface_info->annotations[n]),
+                                         indent + 2,
+                                         string_builder);
+
+  for (n = 0; n < interface_info->num_methods; n++)
+    e_dbus_method_info_generate_xml (&(interface_info->methods[n]),
+                                     indent + 2,
+                                     string_builder);
+
+  for (n = 0; n < interface_info->num_signals; n++)
+    e_dbus_signal_info_generate_xml (&(interface_info->signals[n]),
+                                     indent + 2,
+                                     string_builder);
+
+  for (n = 0; n < interface_info->num_properties; n++)
+    e_dbus_property_info_generate_xml (&(interface_info->properties[n]),
+                                       indent + 2,
+                                       string_builder);
+
+  g_string_append_printf (string_builder, "%*s</interface>\n", indent, "");
+}
+
+/**
+ * e_dbus_node_info_generate_xml:
+ * @node_info: A #EDBusNodeInfo.
+ * @indent: Indentation level.
+ * @string_builder: A #GString to to append XML data to.
+ *
+ * Appends an XML representation of @node_info (and its children) to @string_builder.
+ *
+ * This function is typically used for generating introspection XML documents at run-time for
+ * handling the <literal>org.freedesktop.DBus.Introspectable.Introspect</literal> method.
+ */
+void
+e_dbus_node_info_generate_xml (const EDBusNodeInfo  *node_info,
+                               guint                 indent,
+                               GString              *string_builder)
+{
+  guint n;
+
+  g_string_append_printf (string_builder, "%*s<node", indent, "");
+  if (node_info->path != NULL)
+    g_string_append_printf (string_builder, " name=\"%s\"", node_info->path);
+
+  if (node_info->num_interfaces == 0 && node_info->num_nodes == 0 && node_info->annotations == NULL)
+    {
+      g_string_append (string_builder, "/>\n");
+    }
+  else
+    {
+      g_string_append (string_builder, ">\n");
+
+      for (n = 0; node_info->annotations != NULL && node_info->annotations[n].key != NULL; n++)
+        e_dbus_annotation_info_generate_xml (&(node_info->annotations[n]),
+                                             indent + 2,
+                                             string_builder);
+
+      for (n = 0; n < node_info->num_interfaces; n++)
+        e_dbus_interface_info_generate_xml (&(node_info->interfaces[n]),
+                                            indent + 2,
+                                            string_builder);
+
+      for (n = 0; n < node_info->num_nodes; n++)
+        e_dbus_node_info_generate_xml (&(node_info->nodes[n]),
+                                       indent + 2,
+                                       string_builder);
+
+      g_string_append_printf (string_builder, "%*s</node>\n", indent, "");
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static EDBusAnnotationInfo *
+parse_data_steal_annotations (ParseData *data, guint *out_num_elements)
+{
+  EDBusAnnotationInfo *ret;
+  if (out_num_elements != NULL)
+    *out_num_elements = data->annotations->len;
+  if (data->annotations == NULL)
+    ret = NULL;
+  else
+    ret = (EDBusAnnotationInfo *) g_array_free (data->annotations, FALSE);
+  data->annotations = g_array_new (FALSE, TRUE, sizeof (EDBusAnnotationInfo));
+  return ret;
+}
+
+static EDBusArgInfo *
+parse_data_steal_args (ParseData *data, guint *out_num_elements)
+{
+  EDBusArgInfo *ret;
+  if (out_num_elements != NULL)
+    *out_num_elements = data->args->len;
+  if (data->args == NULL)
+    ret = NULL;
+  else
+    ret = (EDBusArgInfo *) g_array_free (data->args, FALSE);
+  data->args = g_array_new (FALSE, TRUE, sizeof (EDBusArgInfo));
+  return ret;
+}
+
+static EDBusArgInfo *
+parse_data_steal_out_args (ParseData *data, guint *out_num_elements)
+{
+  EDBusArgInfo *ret;
+  if (out_num_elements != NULL)
+    *out_num_elements = data->out_args->len;
+  if (data->out_args == NULL)
+    ret = NULL;
+  else
+    ret = (EDBusArgInfo *) g_array_free (data->out_args, FALSE);
+  data->out_args = g_array_new (FALSE, TRUE, sizeof (EDBusArgInfo));
+  return ret;
+}
+
+static EDBusMethodInfo *
+parse_data_steal_methods (ParseData *data, guint *out_num_elements)
+{
+  EDBusMethodInfo *ret;
+  if (out_num_elements != NULL)
+    *out_num_elements = data->methods->len;
+  if (data->methods == NULL)
+    ret = NULL;
+  else
+    ret = (EDBusMethodInfo *) g_array_free (data->methods, FALSE);
+  data->methods = g_array_new (FALSE, TRUE, sizeof (EDBusMethodInfo));
+  return ret;
+}
+
+static EDBusSignalInfo *
+parse_data_steal_signals (ParseData *data, guint *out_num_elements)
+{
+  EDBusSignalInfo *ret;
+  if (out_num_elements != NULL)
+    *out_num_elements = data->signals->len;
+  if (data->signals == NULL)
+    ret = NULL;
+  else
+    ret = (EDBusSignalInfo *) g_array_free (data->signals, FALSE);
+  data->signals = g_array_new (FALSE, TRUE, sizeof (EDBusSignalInfo));
+  return ret;
+}
+
+static EDBusPropertyInfo *
+parse_data_steal_properties (ParseData *data, guint *out_num_elements)
+{
+  EDBusPropertyInfo *ret;
+  if (out_num_elements != NULL)
+    *out_num_elements = data->properties->len;
+  if (data->properties == NULL)
+    ret = NULL;
+  else
+    ret = (EDBusPropertyInfo *) g_array_free (data->properties, FALSE);
+  data->properties = g_array_new (FALSE, TRUE, sizeof (EDBusPropertyInfo));
+  return ret;
+}
+
+static EDBusInterfaceInfo *
+parse_data_steal_interfaces (ParseData *data, guint *out_num_elements)
+{
+  EDBusInterfaceInfo *ret;
+  if (out_num_elements != NULL)
+    *out_num_elements = data->interfaces->len;
+  if (data->interfaces == NULL)
+    ret = NULL;
+  else
+    ret = (EDBusInterfaceInfo *) g_array_free (data->interfaces, FALSE);
+  data->interfaces = g_array_new (FALSE, TRUE, sizeof (EDBusInterfaceInfo));
+  return ret;
+}
+
+static EDBusNodeInfo *
+parse_data_steal_nodes (ParseData *data, guint *out_num_elements)
+{
+  EDBusNodeInfo *ret;
+  if (out_num_elements != NULL)
+    *out_num_elements = data->nodes->len;
+  if (data->nodes == NULL)
+    ret = NULL;
+  else
+    ret = (EDBusNodeInfo *) g_array_free (data->nodes, FALSE);
+  data->nodes = g_array_new (FALSE, TRUE, sizeof (EDBusNodeInfo));
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+parse_data_free_annotations (ParseData *data)
+{
+  guint n;
+  if (data->annotations == NULL)
+    return;
+  for (n = 0; n < data->annotations->len; n++)
+    e_dbus_annotation_info_free (&g_array_index (data->annotations, EDBusAnnotationInfo, n));
+  g_array_free (data->annotations, TRUE);
+  data->annotations = NULL;
+}
+
+static void
+parse_data_free_args (ParseData *data)
+{
+  guint n;
+  if (data->args == NULL)
+    return;
+  for (n = 0; n < data->args->len; n++)
+    e_dbus_arg_info_free (&g_array_index (data->args, EDBusArgInfo, n));
+  g_array_free (data->args, TRUE);
+  data->args = NULL;
+}
+
+static void
+parse_data_free_out_args (ParseData *data)
+{
+  guint n;
+  if (data->out_args == NULL)
+    return;
+  for (n = 0; n < data->out_args->len; n++)
+    e_dbus_arg_info_free (&g_array_index (data->out_args, EDBusArgInfo, n));
+  g_array_free (data->out_args, TRUE);
+  data->out_args = NULL;
+}
+
+static void
+parse_data_free_methods (ParseData *data)
+{
+  guint n;
+  if (data->methods == NULL)
+    return;
+  for (n = 0; n < data->methods->len; n++)
+    e_dbus_method_info_free (&g_array_index (data->methods, EDBusMethodInfo, n));
+  g_array_free (data->methods, TRUE);
+  data->methods = NULL;
+}
+
+static void
+parse_data_free_signals (ParseData *data)
+{
+  guint n;
+  if (data->signals == NULL)
+    return;
+  for (n = 0; n < data->signals->len; n++)
+    e_dbus_signal_info_free (&g_array_index (data->signals, EDBusSignalInfo, n));
+  g_array_free (data->signals, TRUE);
+  data->signals = NULL;
+}
+
+static void
+parse_data_free_properties (ParseData *data)
+{
+  guint n;
+  if (data->properties == NULL)
+    return;
+  for (n = 0; n < data->properties->len; n++)
+    e_dbus_property_info_free (&g_array_index (data->properties, EDBusPropertyInfo, n));
+  g_array_free (data->properties, TRUE);
+  data->properties = NULL;
+}
+
+static void
+parse_data_free_interfaces (ParseData *data)
+{
+  guint n;
+  if (data->interfaces == NULL)
+    return;
+  for (n = 0; n < data->interfaces->len; n++)
+    e_dbus_interface_info_free (&g_array_index (data->interfaces, EDBusInterfaceInfo, n));
+  g_array_free (data->interfaces, TRUE);
+  data->interfaces = NULL;
+}
+
+static void
+parse_data_free_nodes (ParseData *data)
+{
+  guint n;
+  if (data->nodes == NULL)
+    return;
+  for (n = 0; n < data->nodes->len; n++)
+    e_dbus_node_info_free (&g_array_index (data->nodes, EDBusNodeInfo, n));
+  g_array_free (data->nodes, TRUE);
+  data->nodes = NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static EDBusAnnotationInfo *
+parse_data_get_annotation (ParseData *data, gboolean create_new)
+{
+  if (create_new)
+    g_array_set_size (data->annotations, data->annotations->len + 1);
+  return &g_array_index (data->annotations, EDBusAnnotationInfo, data->annotations->len - 1);
+}
+
+static EDBusArgInfo *
+parse_data_get_arg (ParseData *data, gboolean create_new)
+{
+  if (create_new)
+    g_array_set_size (data->args, data->args->len + 1);
+  return &g_array_index (data->args, EDBusArgInfo, data->args->len - 1);
+}
+
+static EDBusArgInfo *
+parse_data_get_out_arg (ParseData *data, gboolean create_new)
+{
+  if (create_new)
+    g_array_set_size (data->out_args, data->out_args->len + 1);
+  return &g_array_index (data->out_args, EDBusArgInfo, data->out_args->len - 1);
+}
+
+static EDBusMethodInfo *
+parse_data_get_method (ParseData *data, gboolean create_new)
+{
+  if (create_new)
+    g_array_set_size (data->methods, data->methods->len + 1);
+  return &g_array_index (data->methods, EDBusMethodInfo, data->methods->len - 1);
+}
+
+static EDBusSignalInfo *
+parse_data_get_signal (ParseData *data, gboolean create_new)
+{
+  if (create_new)
+    g_array_set_size (data->signals, data->signals->len + 1);
+  return &g_array_index (data->signals, EDBusSignalInfo, data->signals->len - 1);
+}
+
+static EDBusPropertyInfo *
+parse_data_get_property (ParseData *data, gboolean create_new)
+{
+  if (create_new)
+    g_array_set_size (data->properties, data->properties->len + 1);
+  return &g_array_index (data->properties, EDBusPropertyInfo, data->properties->len - 1);
+}
+
+static EDBusInterfaceInfo *
+parse_data_get_interface (ParseData *data, gboolean create_new)
+{
+  if (create_new)
+    g_array_set_size (data->interfaces, data->interfaces->len + 1);
+  return &g_array_index (data->interfaces, EDBusInterfaceInfo, data->interfaces->len - 1);
+}
+
+static EDBusNodeInfo *
+parse_data_get_node (ParseData *data, gboolean create_new)
+{
+  if (create_new)
+    g_array_set_size (data->nodes, data->nodes->len + 1);
+  return &g_array_index (data->nodes, EDBusNodeInfo, data->nodes->len - 1);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static ParseData *
+parse_data_new (void)
+{
+  ParseData *data;
+
+  data = g_new0 (ParseData, 1);
+
+  /* initialize arrays */
+  parse_data_steal_annotations (data, NULL);
+  parse_data_steal_args (data, NULL);
+  parse_data_steal_out_args (data, NULL);
+  parse_data_steal_methods (data, NULL);
+  parse_data_steal_signals (data, NULL);
+  parse_data_steal_properties (data, NULL);
+  parse_data_steal_interfaces (data, NULL);
+  parse_data_steal_nodes (data, NULL);
+
+  return data;
+}
+
+static void
+parse_data_free (ParseData *data)
+{
+  GSList *l;
+
+  /* free stack of annotation arrays */
+  for (l = data->annotations_stack; l != NULL; l = l->next)
+    {
+      GArray *annotations = l->data;
+      guint n;
+
+      for (n = 0; n < annotations->len; n++)
+        e_dbus_annotation_info_free (&g_array_index (annotations, EDBusAnnotationInfo, n));
+      g_array_free (annotations, TRUE);
+    }
+  g_slist_free (data->annotations_stack);
+
+  /* free stack of interface arrays */
+  for (l = data->interfaces_stack; l != NULL; l = l->next)
+    {
+      GArray *interfaces = l->data;
+      guint n;
+
+      for (n = 0; n < interfaces->len; n++)
+        e_dbus_interface_info_free (&g_array_index (interfaces, EDBusInterfaceInfo, n));
+      g_array_free (interfaces, TRUE);
+    }
+  g_slist_free (data->interfaces_stack);
+
+  /* free stack of node arrays */
+  for (l = data->nodes_stack; l != NULL; l = l->next)
+    {
+      GArray *nodes = l->data;
+      guint n;
+
+      for (n = 0; n < nodes->len; n++)
+        e_dbus_node_info_free (&g_array_index (nodes, EDBusNodeInfo, n));
+      g_array_free (nodes, TRUE);
+    }
+  g_slist_free (data->nodes_stack);
+
+  /* free arrays (data->annotations, data->interfaces and data->nodes have been freed above) */
+  parse_data_free_args (data);
+  parse_data_free_out_args (data);
+  parse_data_free_methods (data);
+  parse_data_free_signals (data);
+  parse_data_free_properties (data);
+
+  g_free (data);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+parser_start_element (GMarkupParseContext *context,
+                      const gchar         *element_name,
+                      const gchar        **attribute_names,
+                      const gchar        **attribute_values,
+                      gpointer             user_data,
+                      GError             **error)
+{
+  ParseData *data = user_data;
+  GSList *stack;
+  const gchar *name;
+  const gchar *type;
+  const gchar *access;
+  const gchar *direction;
+  const gchar *value;
+
+  name = NULL;
+  type = NULL;
+  access = NULL;
+  direction = NULL;
+  value = NULL;
+
+  stack = (GSList *) g_markup_parse_context_get_element_stack (context);
+
+  /* ---------------------------------------------------------------------------------------------------- */
+  if (strcmp (element_name, "node") == 0)
+    {
+      if (!(g_slist_length (stack) >= 1 || strcmp (stack->next->data, "node") != 0))
+        {
+          g_set_error_literal (error,
+                               G_MARKUP_ERROR,
+                               G_MARKUP_ERROR_INVALID_CONTENT,
+                               "<node> elements can only be top-level or embedded in other <node> elements");
+          goto out;
+        }
+
+      if (!g_markup_collect_attributes (element_name,
+                                        attribute_names,
+                                        attribute_values,
+                                        error,
+                                        G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "name", &name,
+                                        /* some hand-written introspection XML documents use this */
+                                        G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "xmlns:doc", NULL,
+                                        G_MARKUP_COLLECT_INVALID))
+        goto out;
+
+      e_dbus_node_info_set (data,
+                            parse_data_get_node (data, TRUE),
+                            name,
+                            0, NULL,
+                            0, NULL,
+                            NULL);
+
+      /* push the currently retrieved interfaces and nodes on the stack and prepare new arrays */
+      data->interfaces_stack = g_slist_prepend (data->interfaces_stack, data->interfaces);
+      data->interfaces = NULL;
+      parse_data_steal_interfaces (data, NULL);
+
+      data->nodes_stack = g_slist_prepend (data->nodes_stack, data->nodes);
+      data->nodes = NULL;
+      parse_data_steal_nodes (data, NULL);
+
+    }
+  /* ---------------------------------------------------------------------------------------------------- */
+  else if (strcmp (element_name, "interface") == 0)
+    {
+      if (g_slist_length (stack) < 2 || strcmp (stack->next->data, "node") != 0)
+        {
+          g_set_error_literal (error,
+                               G_MARKUP_ERROR,
+                               G_MARKUP_ERROR_INVALID_CONTENT,
+                               "<interface> elements can only be embedded in <node> elements");
+          goto out;
+        }
+
+      if (!g_markup_collect_attributes (element_name,
+                                        attribute_names,
+                                        attribute_values,
+                                        error,
+                                        G_MARKUP_COLLECT_STRING, "name", &name,
+                                        G_MARKUP_COLLECT_INVALID))
+        goto out;
+
+      e_dbus_interface_info_set (data,
+                                 parse_data_get_interface (data, TRUE),
+                                 name,
+                                 0, NULL,
+                                 0, NULL,
+                                 0, NULL,
+                                 NULL);
+
+    }
+  /* ---------------------------------------------------------------------------------------------------- */
+  else if (strcmp (element_name, "method") == 0)
+    {
+      if (g_slist_length (stack) < 2 || strcmp (stack->next->data, "interface") != 0)
+        {
+          g_set_error_literal (error,
+                               G_MARKUP_ERROR,
+                               G_MARKUP_ERROR_INVALID_CONTENT,
+                               "<method> elements can only be embedded in <interface> elements");
+          goto out;
+        }
+
+      if (!g_markup_collect_attributes (element_name,
+                                        attribute_names,
+                                        attribute_values,
+                                        error,
+                                        G_MARKUP_COLLECT_STRING, "name", &name,
+                                        G_MARKUP_COLLECT_INVALID))
+        goto out;
+
+      e_dbus_method_info_set (data,
+                              parse_data_get_method (data, TRUE),
+                              name,
+                              0, NULL,
+                              0, NULL,
+                              NULL);
+
+      data->num_args = 0;
+
+    }
+  /* ---------------------------------------------------------------------------------------------------- */
+  else if (strcmp (element_name, "signal") == 0)
+    {
+      if (g_slist_length (stack) < 2 || strcmp (stack->next->data, "interface") != 0)
+        {
+          g_set_error_literal (error,
+                               G_MARKUP_ERROR,
+                               G_MARKUP_ERROR_INVALID_CONTENT,
+                               "<signal> elements can only be embedded in <interface> elements");
+          goto out;
+        }
+
+      if (!g_markup_collect_attributes (element_name,
+                                        attribute_names,
+                                        attribute_values,
+                                        error,
+                                        G_MARKUP_COLLECT_STRING, "name", &name,
+                                        G_MARKUP_COLLECT_INVALID))
+        goto out;
+
+      e_dbus_signal_info_set (data,
+                              parse_data_get_signal (data, TRUE),
+                              name,
+                              0, NULL,
+                              NULL);
+
+      data->num_args = 0;
+
+    }
+  /* ---------------------------------------------------------------------------------------------------- */
+  else if (strcmp (element_name, "property") == 0)
+    {
+      EDBusPropertyInfoFlags flags;
+
+      if (g_slist_length (stack) < 2 || strcmp (stack->next->data, "interface") != 0)
+        {
+          g_set_error_literal (error,
+                               G_MARKUP_ERROR,
+                               G_MARKUP_ERROR_INVALID_CONTENT,
+                               "<property> elements can only be embedded in <interface> elements");
+          goto out;
+        }
+
+      if (!g_markup_collect_attributes (element_name,
+                                        attribute_names,
+                                        attribute_values,
+                                        error,
+                                        G_MARKUP_COLLECT_STRING, "name", &name,
+                                        G_MARKUP_COLLECT_STRING, "type", &type,
+                                        G_MARKUP_COLLECT_STRING, "access", &access,
+                                        G_MARKUP_COLLECT_INVALID))
+        goto out;
+
+      if (strcmp (access, "read") == 0)
+        flags = E_DBUS_PROPERTY_INFO_FLAGS_READABLE;
+      else if (strcmp (access, "write") == 0)
+        flags = E_DBUS_PROPERTY_INFO_FLAGS_WRITABLE;
+      else if (strcmp (access, "readwrite") == 0)
+        flags = E_DBUS_PROPERTY_INFO_FLAGS_READABLE | E_DBUS_PROPERTY_INFO_FLAGS_WRITABLE;
+      else
+        {
+          g_set_error (error,
+                       G_MARKUP_ERROR,
+                       G_MARKUP_ERROR_INVALID_CONTENT,
+                       "Unknown value '%s' of access attribute for element <property>",
+                       access);
+          goto out;
+        }
+
+      e_dbus_property_info_set (data,
+                                parse_data_get_property (data, TRUE),
+                                name,
+                                type,
+                                flags,
+                                NULL);
+
+    }
+  /* ---------------------------------------------------------------------------------------------------- */
+  else if (strcmp (element_name, "arg") == 0)
+    {
+      gboolean is_in;
+      gchar *name_to_use;
+
+      if (g_slist_length (stack) < 2 ||
+          (strcmp (stack->next->data, "method") != 0 &&
+           strcmp (stack->next->data, "signal") != 0))
+        {
+          g_set_error_literal (error,
+                               G_MARKUP_ERROR,
+                               G_MARKUP_ERROR_INVALID_CONTENT,
+                               "<arg> elements can only be embedded in <method> or <signal> elements");
+          goto out;
+        }
+
+      if (!g_markup_collect_attributes (element_name,
+                                        attribute_names,
+                                        attribute_values,
+                                        error,
+                                        G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "name", &name,
+                                        G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "direction", &direction,
+                                        G_MARKUP_COLLECT_STRING, "type", &type,
+                                        G_MARKUP_COLLECT_INVALID))
+        goto out;
+
+      is_in = FALSE;
+      if (direction != NULL)
+        {
+          if (strcmp (direction, "in") == 0)
+            is_in = TRUE;
+          else if (strcmp (direction, "out") == 0)
+            is_in = FALSE;
+          else
+            {
+              g_set_error (error,
+                           G_MARKUP_ERROR,
+                           G_MARKUP_ERROR_INVALID_CONTENT,
+                           "Unknown value '%s' of direction attribute",
+                           direction);
+              goto out;
+            }
+        }
+
+      if (is_in && strcmp (stack->next->data, "signal") == 0)
+        {
+          g_set_error_literal (error,
+                               G_MARKUP_ERROR,
+                               G_MARKUP_ERROR_INVALID_CONTENT,
+                               "Only direction 'out' is allowed for <arg> elements embedded in <signal>");
+          goto out;
+        }
+
+      if (name == NULL)
+        name_to_use = g_strdup_printf ("arg_%d", data->num_args);
+      else
+        name_to_use = g_strdup (name);
+      data->num_args++;
+
+      if (is_in)
+        {
+          e_dbus_arg_info_set (data,
+                               parse_data_get_arg (data, TRUE),
+                               name_to_use,
+                               type,
+                               NULL);
+          data->last_arg_was_in = TRUE;
+        }
+      else
+        {
+          e_dbus_arg_info_set (data,
+                               parse_data_get_out_arg (data, TRUE),
+                               name_to_use,
+                               type,
+                               NULL);
+          data->last_arg_was_in = FALSE;
+
+        }
+
+      g_free (name_to_use);
+    }
+  /* ---------------------------------------------------------------------------------------------------- */
+  else if (strcmp (element_name, "annotation") == 0)
+    {
+      if (g_slist_length (stack) < 2 ||
+          (strcmp (stack->next->data, "node") != 0 &&
+           strcmp (stack->next->data, "interface") != 0 &&
+           strcmp (stack->next->data, "signal") != 0 &&
+           strcmp (stack->next->data, "method") != 0 &&
+           strcmp (stack->next->data, "property") != 0 &&
+           strcmp (stack->next->data, "arg") != 0 &&
+           strcmp (stack->next->data, "annotation") != 0))
+        {
+          g_set_error_literal (error,
+                               G_MARKUP_ERROR,
+                               G_MARKUP_ERROR_INVALID_CONTENT,
+                               "<annotation> elements can only be embedded in <node>, <interface>, <signal>, <method>, <property>, <arg> or <annotation> elements");
+          goto out;
+        }
+
+      if (!g_markup_collect_attributes (element_name,
+                                        attribute_names,
+                                        attribute_values,
+                                        error,
+                                        G_MARKUP_COLLECT_STRING, "name", &name,
+                                        G_MARKUP_COLLECT_STRING, "value", &value,
+                                        G_MARKUP_COLLECT_INVALID))
+        goto out;
+
+      e_dbus_annotation_info_set (data,
+                                  parse_data_get_annotation (data, TRUE),
+                                  name,
+                                  value,
+                                  NULL);
+    }
+  /* ---------------------------------------------------------------------------------------------------- */
+  else
+    {
+      /* don't bail on unknown elements; just ignore them */
+    }
+  /* ---------------------------------------------------------------------------------------------------- */
+
+  /* push the currently retrieved annotations on the stack and prepare a new one */
+  data->annotations_stack = g_slist_prepend (data->annotations_stack, data->annotations);
+  data->annotations = NULL;
+  parse_data_steal_annotations (data, NULL);
+
+ out:
+  ;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static EDBusAnnotationInfo *
+steal_annotations (ParseData *data)
+{
+  EDBusAnnotationInfo *annotations;
+  guint num_annotations;
+
+  if (data->annotations->len == 0)
+    {
+      annotations = parse_data_steal_annotations (data, &num_annotations);
+      g_free (annotations);
+      annotations = NULL;
+    }
+  else
+    {
+      /* NULL terminate */
+      e_dbus_annotation_info_set (data,
+                                  parse_data_get_annotation (data, TRUE),
+                                  NULL,
+                                  NULL,
+                                  NULL);
+      annotations = parse_data_steal_annotations (data, &num_annotations);
+    }
+  return annotations;
+}
+
+
+static void
+parser_end_element (GMarkupParseContext *context,
+                    const gchar         *element_name,
+                    gpointer             user_data,
+                    GError             **error)
+{
+  ParseData *data = user_data;
+  gboolean have_popped_annotations;
+
+  have_popped_annotations = FALSE;
+
+  if (strcmp (element_name, "node") == 0)
+    {
+      guint num_nodes;
+      guint num_interfaces;
+      EDBusNodeInfo *nodes;
+      EDBusInterfaceInfo *interfaces;
+
+      nodes = parse_data_steal_nodes (data, &num_nodes);
+      interfaces = parse_data_steal_interfaces (data, &num_interfaces);
+
+      /* destroy the nodes, interfaces for scope we're exiting and and pop the nodes, interfaces from the
+       * scope we're reentering
+       */
+      parse_data_free_interfaces (data);
+      data->interfaces = (GArray *) data->interfaces_stack->data;
+      data->interfaces_stack = g_slist_remove (data->interfaces_stack, data->interfaces_stack->data);
+
+      parse_data_free_nodes (data);
+      data->nodes = (GArray *) data->nodes_stack->data;
+      data->nodes_stack = g_slist_remove (data->nodes_stack, data->nodes_stack->data);
+
+      e_dbus_node_info_set (data,
+                            parse_data_get_node (data, FALSE),
+                            NULL,
+                            num_interfaces,
+                            interfaces,
+                            num_nodes,
+                            nodes,
+                            steal_annotations (data));
+
+    }
+  else if (strcmp (element_name, "interface") == 0)
+    {
+      guint num_methods;
+      guint num_signals;
+      guint num_properties;
+      EDBusMethodInfo *methods;
+      EDBusSignalInfo *signals;
+      EDBusPropertyInfo *properties;
+
+      methods    = parse_data_steal_methods    (data, &num_methods);
+      signals    = parse_data_steal_signals    (data, &num_signals);
+      properties = parse_data_steal_properties (data, &num_properties);
+
+      e_dbus_interface_info_set (data,
+                                 parse_data_get_interface (data, FALSE),
+                                 NULL,
+                                 num_methods,
+                                 methods,
+                                 num_signals,
+                                 signals,
+                                 num_properties,
+                                 properties,
+                                 steal_annotations (data));
+
+    }
+  else if (strcmp (element_name, "method") == 0)
+    {
+      guint in_num_args;
+      guint out_num_args;
+      EDBusArgInfo *in_args;
+      EDBusArgInfo *out_args;
+
+      in_args  = parse_data_steal_args     (data, &in_num_args);
+      out_args = parse_data_steal_out_args (data, &out_num_args);
+
+      e_dbus_method_info_set (data,
+                              parse_data_get_method (data, FALSE),
+                              NULL,
+                              in_num_args,
+                              in_args,
+                              out_num_args,
+                              out_args,
+                              steal_annotations (data));
+    }
+  else if (strcmp (element_name, "signal") == 0)
+    {
+      guint num_args;
+      EDBusArgInfo *args;
+
+      args = parse_data_steal_out_args (data, &num_args);
+
+      e_dbus_signal_info_set (data,
+                              parse_data_get_signal (data, FALSE),
+                              NULL,
+                              num_args,
+                              args,
+                              steal_annotations (data));
+    }
+  else if (strcmp (element_name, "property") == 0)
+    {
+      e_dbus_property_info_set (data,
+                                parse_data_get_property (data, FALSE),
+                                NULL,
+                                NULL,
+                                E_DBUS_PROPERTY_INFO_FLAGS_NONE,
+                                steal_annotations (data));
+    }
+  else if (strcmp (element_name, "arg") == 0)
+    {
+      e_dbus_arg_info_set (data,
+                           data->last_arg_was_in ? parse_data_get_arg (data, FALSE) : parse_data_get_out_arg (data, FALSE),
+                           NULL,
+                           NULL,
+                           steal_annotations (data));
+    }
+  else if (strcmp (element_name, "annotation") == 0)
+    {
+      EDBusAnnotationInfo *embedded_annotations;
+
+      embedded_annotations = steal_annotations (data);
+
+      /* destroy the annotations for scope we're exiting and and pop the annotations from the scope we're reentering */
+      parse_data_free_annotations (data);
+      data->annotations = (GArray *) data->annotations_stack->data;
+      data->annotations_stack = g_slist_remove (data->annotations_stack, data->annotations_stack->data);
+
+      have_popped_annotations = TRUE;
+
+      e_dbus_annotation_info_set (data,
+                                  parse_data_get_annotation (data, FALSE),
+                                  NULL,
+                                  NULL,
+                                  embedded_annotations);
+    }
+  else
+    {
+      /* don't bail on unknown elements; just ignore them */
+    }
+
+  if (!have_popped_annotations)
+    {
+      /* destroy the annotations for scope we're exiting and and pop the annotations from the scope we're reentering */
+      parse_data_free_annotations (data);
+      data->annotations = (GArray *) data->annotations_stack->data;
+      data->annotations_stack = g_slist_remove (data->annotations_stack, data->annotations_stack->data);
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+parser_error (GMarkupParseContext *context,
+              GError              *error,
+              gpointer             user_data)
+{
+  gint line_number;
+  gint char_number;
+
+  g_markup_parse_context_get_position (context, &line_number, &char_number);
+
+  g_prefix_error (&error, "%d:%d: ",
+                  line_number,
+                  char_number);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_node_info_new_for_xml:
+ * @xml_data: Valid D-Bus introspection XML.
+ * @error: Return location for error.
+ *
+ * Parses @xml_data and returns a #EDBusNodeInfo representing the data.
+ *
+ * Returns: A #EDBusNodeInfo structure or %NULL if @error is set. Free
+ * with e_dbus_node_info_free().
+ */
+EDBusNodeInfo *
+e_dbus_node_info_new_for_xml (const gchar  *xml_data,
+                              GError      **error)
+{
+  EDBusNodeInfo *ret;
+  GMarkupParseContext *context;
+  GMarkupParser *parser;
+  guint num_nodes;
+  ParseData *data;
+
+  ret = NULL;
+  parser = NULL;
+  context = NULL;
+
+  parser = g_new0 (GMarkupParser, 1);
+  parser->start_element = parser_start_element;
+  parser->end_element   = parser_end_element;
+  parser->error         = parser_error;
+
+  data = parse_data_new ();
+  context = g_markup_parse_context_new (parser,
+                                        0,
+                                        data,
+                                        (GDestroyNotify) parse_data_free);
+
+  if (!g_markup_parse_context_parse (context,
+                                     xml_data,
+                                     strlen (xml_data),
+                                     error))
+    goto out;
+
+  ret = parse_data_steal_nodes (data, &num_nodes);
+
+  if (num_nodes != 1)
+    {
+      guint n;
+
+      g_set_error (error,
+                   G_MARKUP_ERROR,
+                   G_MARKUP_ERROR_INVALID_CONTENT,
+                   "Expected a single node in introspection XML, found %d",
+                   num_nodes);
+
+      /* clean up */
+      for (n = 0; n < num_nodes; n++)
+        {
+          for (n = 0; n < num_nodes; n++)
+            e_dbus_node_info_free (&(ret[n]));
+        }
+      g_free (ret);
+      ret = NULL;
+    }
+
+ out:
+  if (parser != NULL)
+    g_free (parser);
+  if (context != NULL)
+    g_markup_parse_context_free (context);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_annotation_info_lookup:
+ * @annotations: An array of annotations.
+ * @name: The name of the annotation to look up.
+ *
+ * Looks up the value of an annotation.
+ *
+ * This cost of this function is O(n) in number of annotations.
+ *
+ * Returns: The value or %NULL if not found. Do not free, it is owned by @annotations.
+ **/
+const gchar *
+e_dbus_annotation_info_lookup (const EDBusAnnotationInfo *annotations,
+                               const gchar               *name)
+{
+  guint n;
+  const gchar *ret;
+
+  ret = NULL;
+
+  for (n = 0; annotations != NULL && annotations[n].key != NULL; n++)
+    {
+      if (g_strcmp0 (annotations[n].key, name) == 0)
+        {
+          ret = annotations[n].value;
+          goto out;
+        }
+    }
+
+ out:
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_interface_info_lookup_method:
+ * @interface_info: A #EDBusInterfaceInfo.
+ * @name: A D-Bus method name (typically in CamelCase)
+ *
+ * Looks up information about a method.
+ *
+ * This cost of this function is O(n) in number of methods.
+ *
+ * Returns: A #EDBusMethodInfo or %NULL if not found. Do not free, it is owned by @interface_info.
+ **/
+const EDBusMethodInfo *
+e_dbus_interface_info_lookup_method (const EDBusInterfaceInfo *interface_info,
+                                     const gchar              *name)
+{
+  guint n;
+  const EDBusMethodInfo *result;
+
+  for (n = 0; n < interface_info->num_methods; n++)
+    {
+      const EDBusMethodInfo *i = interface_info->methods + n;
+
+      if (g_strcmp0 (i->name, name) == 0)
+        {
+          result = i;
+          goto out;
+        }
+    }
+
+  result = NULL;
+
+ out:
+  return result;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_interface_info_lookup_signal:
+ * @interface_info: A #EDBusInterfaceInfo.
+ * @name: A D-Bus signal name (typically in CamelCase)
+ *
+ * Looks up information about a signal.
+ *
+ * This cost of this function is O(n) in number of signals.
+ *
+ * Returns: A #EDBusSignalInfo or %NULL if not found. Do not free, it is owned by @interface_info.
+ **/
+const EDBusSignalInfo *
+e_dbus_interface_info_lookup_signal (const EDBusInterfaceInfo *interface_info,
+                                     const gchar              *name)
+{
+  guint n;
+  const EDBusSignalInfo *result;
+
+  for (n = 0; n < interface_info->num_signals; n++)
+    {
+      const EDBusSignalInfo *i = interface_info->signals + n;
+
+      if (g_strcmp0 (i->name, name) == 0)
+        {
+          result = i;
+          goto out;
+        }
+    }
+
+  result = NULL;
+
+ out:
+  return result;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_interface_info_lookup_property:
+ * @interface_info: A #EDBusInterfaceInfo.
+ * @name: A D-Bus property name (typically in CamelCase).
+ *
+ * Looks up information about a property.
+ *
+ * This cost of this function is O(n) in number of properties.
+ *
+ * Returns: A #EDBusPropertyInfo or %NULL if not found. Do not free, it is owned by @interface_info.
+ **/
+const EDBusPropertyInfo *
+e_dbus_interface_info_lookup_property (const EDBusInterfaceInfo *interface_info,
+                                       const gchar              *name)
+{
+  guint n;
+  const EDBusPropertyInfo *result;
+
+  for (n = 0; n < interface_info->num_properties; n++)
+    {
+      const EDBusPropertyInfo *i = interface_info->properties + n;
+
+      if (g_strcmp0 (i->name, name) == 0)
+        {
+          result = i;
+          goto out;
+        }
+    }
+
+  result = NULL;
+
+ out:
+  return result;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_node_info_lookup_interface:
+ * @node_info: A #EDBusNodeInfo.
+ * @name: A D-Bus interface name.
+ *
+ * Looks up information about an interface.
+ *
+ * This cost of this function is O(n) in number of interfaces.
+ *
+ * Returns: A #EDBusInterfaceInfo or %NULL if not found. Do not free, it is owned by @node_info.
+ **/
+const EDBusInterfaceInfo *
+e_dbus_node_info_lookup_interface (const EDBusNodeInfo *node_info,
+                                   const gchar         *name)
+{
+  guint n;
+  const EDBusInterfaceInfo *result;
+
+  for (n = 0; n < node_info->num_interfaces; n++)
+    {
+      const EDBusInterfaceInfo *i = node_info->interfaces + n;
+
+      if (g_strcmp0 (i->name, name) == 0)
+        {
+          result = i;
+          goto out;
+        }
+    }
+
+  result = NULL;
+
+ out:
+  return result;
+}
diff --git a/libedbus/gdbusintrospection.h b/libedbus/gdbusintrospection.h
new file mode 100644
index 0000000..9d4e715
--- /dev/null
+++ b/libedbus/gdbusintrospection.h
@@ -0,0 +1,209 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#if !defined (__E_DBUS_H_INSIDE__) && !defined (E_DBUS_COMPILATION)
+#error "Only <gdbus/gdbus.h> can be included directly."
+#endif
+
+#ifndef __E_DBUS_INTROSPECTION_H__
+#define __E_DBUS_INTROSPECTION_H__
+
+#include <gdbus/gdbustypes.h>
+
+G_BEGIN_DECLS
+
+/**
+ * EDBusAnnotationInfo:
+ * @key: The name of the annotation, e.g. <literal>org.freedesktop.DBus.Deprecated</literal>
+ * @value: The value of the annotation.
+ * @annotations: A pointer to an array of annotations for the annotation or %NULL if there are no annotations.
+ *
+ * Information about an annotation.
+ *
+ * By convention, an array of annotations is always terminated by an element
+ * where @key is %NULL.
+ */
+struct _EDBusAnnotationInfo
+{
+  const gchar                *key;
+  const gchar                *value;
+  const EDBusAnnotationInfo  *annotations;
+};
+
+/**
+ * EDBusArgInfo:
+ * @name: Name of the argument, e.g. @unix_user_id.
+ * @signature: D-Bus signature of the argument (a single complete type).
+ * @annotations: A pointer to an array of annotations for the argument or %NULL if there are no annotations.
+ *
+ * Information about an argument for a method or a signal.
+ */
+struct _EDBusArgInfo
+{
+  const gchar                *name;
+  const gchar                *signature;
+  const EDBusAnnotationInfo  *annotations;
+};
+
+/**
+ * EDBusMethodInfo:
+ * @name: The name of the D-Bus method, e.g. @RequestName.
+ * @in_signature: The combined D-Bus signature of all arguments passed to the method (@in_num_args complete types).
+ * @in_num_args: Number of arguments passed to the method.
+ * @in_args: A pointer to an array of @in_num_args #EDBusArgInfo structures or %NULL if @in_num_args is 0.
+ * @out_signature: The combined D-Bus signature of all arguments the method returns (@out_num_args complete types).
+ * @out_num_args: Number of arguments the method returns.
+ * @out_args: A pointer to an array of @out_num_args #EDBusArgInfo structures or %NULL if @out_num_args is 0.
+ * @annotations: A pointer to an array of annotations for the method or %NULL if there are no annotations.
+ *
+ * Information about a method on an D-Bus interface.
+ */
+struct _EDBusMethodInfo
+{
+  const gchar                *name;
+
+  const gchar                *in_signature;
+  guint                       in_num_args;
+  const EDBusArgInfo         *in_args;
+
+  const gchar                *out_signature;
+  guint                       out_num_args;
+  const EDBusArgInfo         *out_args;
+
+  const EDBusAnnotationInfo  *annotations;
+};
+
+/**
+ * EDBusSignalInfo:
+ * @name: The name of the D-Bus signal, e.g. @NameOwnerChanged.
+ * @signature: The combined D-Bus signature of all arguments of the signal (@num_args complete types).
+ * @num_args: Number of arguments of the signal.
+ * @args: A pointer to an array of @num_args #EDBusArgInfo structures or %NULL if @num_args is 0.
+ * @annotations: A pointer to an array of annotations for the signal or %NULL if there are no annotations.
+ *
+ * Information about a signal on a D-Bus interface.
+ */
+struct _EDBusSignalInfo
+{
+  const gchar                *name;
+
+  const gchar                *signature;
+  guint                       num_args;
+  const EDBusArgInfo         *args;
+
+  const EDBusAnnotationInfo  *annotations;
+};
+
+/**
+ * EDBusPropertyInfo:
+ * @name: The name of the D-Bus property, e.g. @SupportedFilesystems.
+ * @signature: The D-Bus signature of the property (a single complete type).
+ * @flags: Access control flags for the property.
+ * @annotations: A pointer to an array of annotations for the property or %NULL if there are no annotations.
+ *
+ * Information about a D-Bus property on a D-Bus interface.
+ */
+struct _EDBusPropertyInfo
+{
+  const gchar                *name;
+  const gchar                *signature;
+  EDBusPropertyInfoFlags      flags;
+  const EDBusAnnotationInfo  *annotations;
+};
+
+/**
+ * EDBusInterfaceInfo:
+ * @name: The name of the D-Bus interface, e.g. <literal>org.freedesktop.DBus.Properties</literal>.
+ * @num_methods: Number of methods on the interface.
+ * @methods: A pointer to an array of @num_methods #EDBusMethodInfo structures or %NULL if @num_methods is 0.
+ * @num_signals: Number of signals on the interface.
+ * @signals: A pointer to an array of @num_signals #EDBusSignalInfo structures or %NULL if @num_signals is 0.
+ * @num_properties: Number of properties on the interface.
+ * @properties: A pointer to an array of @num_properties #EDBusPropertyInfo structures or %NULL if @num_properties is 0.
+ * @annotations: A pointer to an array of annotations for the interface or %NULL if there are no annotations.
+ *
+ * Information about a D-Bus interface.
+ */
+struct _EDBusInterfaceInfo
+{
+  const gchar                *name;
+
+  guint                       num_methods;
+  const EDBusMethodInfo      *methods;
+
+  guint                       num_signals;
+  const EDBusSignalInfo      *signals;
+
+  guint                       num_properties;
+  const EDBusPropertyInfo    *properties;
+
+  const EDBusAnnotationInfo  *annotations;
+};
+
+/**
+ * EDBusNodeInfo:
+ * @path: The path of the node or %NULL if omitted. Note that this may be a relative path. See the D-Bus specification for more details.
+ * @num_interfaces: Number of interfaces of the node.
+ * @interfaces: A pointer to an array of @num_interfaces #EDBusInterfaceInfo structures or %NULL if @num_interfaces is 0.
+ * @num_nodes: Number of child nodes.
+ * @nodes: A pointer to an array of @num_nodes #EDBusNodeInfo structures or %NULL if @num_nodes is 0.
+ * @annotations: A pointer to an array of annotations for the node or %NULL if there are no annotations.
+ *
+ * Information about nodes in a remote object hierarchy.
+ */
+struct _EDBusNodeInfo
+{
+  const gchar                *path;
+
+  guint                       num_interfaces;
+  const EDBusInterfaceInfo   *interfaces;
+
+  guint                       num_nodes;
+  const EDBusNodeInfo        *nodes;
+
+  const EDBusAnnotationInfo  *annotations;
+};
+
+const gchar              *e_dbus_annotation_info_lookup          (const EDBusAnnotationInfo *annotations,
+                                                                  const gchar               *name);
+const EDBusMethodInfo    *e_dbus_interface_info_lookup_method    (const EDBusInterfaceInfo  *interface_info,
+                                                                  const gchar               *name);
+const EDBusSignalInfo    *e_dbus_interface_info_lookup_signal    (const EDBusInterfaceInfo  *interface_info,
+                                                                  const gchar               *name);
+const EDBusPropertyInfo  *e_dbus_interface_info_lookup_property  (const EDBusInterfaceInfo  *interface_info,
+                                                                  const gchar               *name);
+void                      e_dbus_interface_info_generate_xml     (const EDBusInterfaceInfo  *interface_info,
+                                                                  guint                      indent,
+                                                                  GString                   *string_builder);
+
+EDBusNodeInfo            *e_dbus_node_info_new_for_xml           (const gchar               *xml_data,
+                                                                  GError                   **error);
+const EDBusInterfaceInfo *e_dbus_node_info_lookup_interface      (const EDBusNodeInfo       *node_info,
+                                                                  const gchar               *name);
+void                      e_dbus_node_info_free                  (EDBusNodeInfo             *node_info);
+void                      e_dbus_node_info_generate_xml          (const EDBusNodeInfo       *node_info,
+                                                                  guint                      indent,
+                                                                  GString                   *string_builder);
+
+G_END_DECLS
+
+#endif /* __E_DBUS_INTROSPECTION_H__ */
diff --git a/libedbus/gdbusmethodinvocation.c b/libedbus/gdbusmethodinvocation.c
new file mode 100644
index 0000000..3051434
--- /dev/null
+++ b/libedbus/gdbusmethodinvocation.c
@@ -0,0 +1,721 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <glib/gi18n.h>
+
+#include "gdbusconnection.h"
+#include "gdbusmethodinvocation.h"
+#include "gdbusintrospection.h"
+#include "gdbusconversion.h"
+#include "gdbuserror.h"
+#include "gdbusprivate.h"
+
+/**
+ * SECTION:gdbusmethodinvocation
+ * @short_description: Object for handling remote calls
+ * @include: gdbus/gdbus.h
+ *
+ * Instances of the #EDBusMethodInvocation class are used when
+ * handling D-Bus method calls. It provides a way to asynchronously
+ * return results and errors.
+ */
+
+struct _EDBusMethodInvocationPrivate
+{
+  /* construct-only properties */
+  gchar           *sender;
+  gchar           *object_path;
+  gchar           *interface_name;
+  gchar           *method_name;
+  EDBusConnection *connection;
+  EVariant        *parameters;
+  gpointer         user_data;
+};
+
+enum
+{
+  PROP_0,
+  PROP_SENDER,
+  PROP_OBJECT_PATH,
+  PROP_INTERFACE_NAME,
+  PROP_METHOD_NAME,
+  PROP_CONNECTION,
+  PROP_PARAMETERS,
+  PROP_USER_DATA
+};
+
+G_DEFINE_TYPE (EDBusMethodInvocation, e_dbus_method_invocation, G_TYPE_OBJECT);
+
+static void
+e_dbus_method_invocation_finalize (GObject *object)
+{
+  EDBusMethodInvocation *invocation = E_DBUS_METHOD_INVOCATION (object);
+
+  g_free (invocation->priv->sender);
+  g_free (invocation->priv->object_path);
+  g_free (invocation->priv->interface_name);
+  g_free (invocation->priv->method_name);
+  g_object_unref (invocation->priv->connection);
+  e_variant_unref (invocation->priv->parameters);
+
+  if (G_OBJECT_CLASS (e_dbus_method_invocation_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (e_dbus_method_invocation_parent_class)->finalize (object);
+}
+
+static void
+e_dbus_method_invocation_get_property (GObject    *object,
+                                       guint       prop_id,
+                                       GValue     *value,
+                                       GParamSpec *pspec)
+{
+  EDBusMethodInvocation *invocation = E_DBUS_METHOD_INVOCATION (object);
+
+  switch (prop_id)
+    {
+    case PROP_SENDER:
+      g_value_set_string (value, e_dbus_method_invocation_get_sender (invocation));
+      break;
+
+    case PROP_OBJECT_PATH:
+      g_value_set_string (value, e_dbus_method_invocation_get_object_path (invocation));
+      break;
+
+    case PROP_INTERFACE_NAME:
+      g_value_set_string (value, e_dbus_method_invocation_get_interface_name (invocation));
+      break;
+
+    case PROP_METHOD_NAME:
+      g_value_set_string (value, e_dbus_method_invocation_get_method_name (invocation));
+      break;
+
+    case PROP_CONNECTION:
+      g_value_set_object (value, e_dbus_method_invocation_get_connection (invocation));
+      break;
+
+    case PROP_PARAMETERS:
+      g_value_set_boxed (value, e_dbus_method_invocation_get_parameters (invocation));
+      break;
+
+    case PROP_USER_DATA:
+      g_value_set_pointer (value, e_dbus_method_invocation_get_user_data (invocation));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+e_dbus_method_invocation_set_property (GObject      *object,
+                                       guint         prop_id,
+                                       const GValue *value,
+                                       GParamSpec   *pspec)
+{
+  EDBusMethodInvocation *invocation = E_DBUS_METHOD_INVOCATION (object);
+
+  switch (prop_id)
+    {
+    case PROP_SENDER:
+      invocation->priv->sender = g_value_dup_string (value);
+      break;
+
+    case PROP_OBJECT_PATH:
+      invocation->priv->object_path = g_value_dup_string (value);
+      break;
+
+    case PROP_INTERFACE_NAME:
+      invocation->priv->interface_name = g_value_dup_string (value);
+      break;
+
+    case PROP_METHOD_NAME:
+      invocation->priv->method_name = g_value_dup_string (value);
+      break;
+
+    case PROP_CONNECTION:
+      invocation->priv->connection = g_value_dup_object (value);
+      break;
+
+    case PROP_PARAMETERS:
+      invocation->priv->parameters = g_value_dup_boxed (value);
+      break;
+
+    case PROP_USER_DATA:
+      invocation->priv->user_data = g_value_get_pointer (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+static void
+e_dbus_method_invocation_class_init (EDBusMethodInvocationClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize     = e_dbus_method_invocation_finalize;
+  gobject_class->set_property = e_dbus_method_invocation_set_property;
+  gobject_class->get_property = e_dbus_method_invocation_get_property;
+
+  /**
+   * EDBusMethodInvocation:sender:
+   *
+   * The bus name that invoked the method or %NULL if the connection is not a bus connection.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_SENDER,
+                                   g_param_spec_string ("sender",
+                                                        _("Sender"),
+                                                        _("The bus name that invoked the method."),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * EDBusMethodInvocation:object-path:
+   *
+   * The object path the method was invoked on.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_OBJECT_PATH,
+                                   g_param_spec_string ("object-path",
+                                                        _("Object Path"),
+                                                        _("The object path the method was invoked on."),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * EDBusMethodInvocation:interface-name:
+   *
+   * The name of the D-Bus interface the method was invoked on.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_INTERFACE_NAME,
+                                   g_param_spec_string ("interface-name",
+                                                        _("Interface Name"),
+                                                        _("The name of the D-Bus interface the method was invoked on."),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * EDBusMethodInvocation:method-name:
+   *
+   * The name of the method that was invoked.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_METHOD_NAME,
+                                   g_param_spec_string ("method-name",
+                                                        _("Method Name"),
+                                                        _("The name of the method that was invoked."),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * EDBusMethodInvocation:connection:
+   *
+   * The #EDBusConnection the method was invoked on.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_CONNECTION,
+                                   g_param_spec_object ("connection",
+                                                        _("Connection"),
+                                                        _("The #EDBusConnection the method was invoked on."),
+                                                        E_TYPE_DBUS_CONNECTION,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * EDBusMethodInvocation:parameters:
+   *
+   * The parameters as a #EVariant tuple.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_PARAMETERS,
+                                   g_param_spec_boxed ("parameters",
+                                                       _("Parameters"),
+                                                       _("The parameters as a #EVariant tuple."),
+                                                       E_TYPE_VARIANT,
+                                                       G_PARAM_READABLE |
+                                                       G_PARAM_WRITABLE |
+                                                       G_PARAM_CONSTRUCT_ONLY |
+                                                       G_PARAM_STATIC_NAME |
+                                                       G_PARAM_STATIC_BLURB |
+                                                       G_PARAM_STATIC_NICK));
+
+  /**
+   * EDBusMethodInvocation:user-data:
+   *
+   * The @user_data #gpointer passed to e_dbus_connection_register_object().
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_USER_DATA,
+                                   g_param_spec_pointer ("user-data",
+                                                        _("User Data"),
+                                                        _("The gpointer passed to e_dbus_connection_register_object()."),
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  g_type_class_add_private (klass, sizeof (EDBusMethodInvocationPrivate));
+}
+
+static void
+e_dbus_method_invocation_init (EDBusMethodInvocation *invocation)
+{
+  invocation->priv = G_TYPE_INSTANCE_GET_PRIVATE (invocation,
+                                                  E_TYPE_DBUS_METHOD_INVOCATION,
+                                                  EDBusMethodInvocationPrivate);
+}
+
+/**
+ * e_dbus_method_invocation_get_sender:
+ * @invocation: A #EDBusMethodInvocation.
+ *
+ * Gets the bus name that invoked the method.
+ *
+ * Returns: A string. Do not free, it is owned by @invocation.
+ */
+const gchar *
+e_dbus_method_invocation_get_sender (EDBusMethodInvocation *invocation)
+{
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+  return invocation->priv->sender;
+}
+
+/**
+ * e_dbus_method_invocation_get_object_path:
+ * @invocation: A #EDBusMethodInvocation.
+ *
+ * Gets the object path the method was invoked on.
+ *
+ * Returns: A string. Do not free, it is owned by @invocation.
+ */
+const gchar *
+e_dbus_method_invocation_get_object_path (EDBusMethodInvocation *invocation)
+{
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+  return invocation->priv->object_path;
+}
+
+/**
+ * e_dbus_method_invocation_get_interface_name:
+ * @invocation: A #EDBusMethodInvocation.
+ *
+ * Gets the name of the D-Bus interface the method was invoked on.
+ *
+ * Returns: A string. Do not free, it is owned by @invocation.
+ */
+const gchar *
+e_dbus_method_invocation_get_interface_name (EDBusMethodInvocation *invocation)
+{
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+  return invocation->priv->interface_name;
+}
+
+/**
+ * e_dbus_method_invocation_get_method_name:
+ * @invocation: A #EDBusMethodInvocation.
+ *
+ * Gets the name of the method that was invoked.
+ *
+ * Returns: A string. Do not free, it is owned by @invocation.
+ */
+const gchar *
+e_dbus_method_invocation_get_method_name (EDBusMethodInvocation *invocation)
+{
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+  return invocation->priv->method_name;
+}
+
+/**
+ * e_dbus_method_invocation_get_connection:
+ * @invocation: A #EDBusMethodInvocation.
+ *
+ * Gets the #EDBusConnection the method was invoked on.
+ *
+ * Returns: A #EDBusConnection. Do not free, it is owned by @invocation.
+ */
+EDBusConnection *
+e_dbus_method_invocation_get_connection (EDBusMethodInvocation *invocation)
+{
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+  return invocation->priv->connection;
+}
+
+/**
+ * e_dbus_method_invocation_get_parameters:
+ * @invocation: A #EDBusMethodInvocation.
+ *
+ * Gets the parameters of the method invocation.
+ *
+ * Returns: A #EVariant. Do not free, it is owned by @invocation.
+ */
+EVariant *
+e_dbus_method_invocation_get_parameters (EDBusMethodInvocation *invocation)
+{
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+  return invocation->priv->parameters;
+}
+
+/**
+ * e_dbus_method_invocation_get_user_data:
+ * @invocation: A #EDBusMethodInvocation.
+ *
+ * Gets the @user_data #gpointer passed to e_dbus_connection_register_object().
+ *
+ * Returns: A #gpointer.
+ */
+gpointer
+e_dbus_method_invocation_get_user_data (EDBusMethodInvocation *invocation)
+{
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
+  return invocation->priv->user_data;
+}
+
+/**
+ * e_dbus_method_invocation_new:
+ * @sender: The bus name that invoked the method or %NULL if @connection is not a bus connection.
+ * @object_path: The object path the method was invoked on.
+ * @interface_name: The name of the D-Bus interface the method was invoked on.
+ * @method_name: The name of the method that was invoked.
+ * @connection: The #EDBusConnection the method was invoked on.
+ * @parameters: The parameters as a #EVariant tuple.
+ * @user_data: The @user_data #gpointer passed to e_dbus_connection_register_object().
+ *
+ * Creates a new #EDBusMethodInvocation object.
+ *
+ * Returns: A #EDBusMethodInvocation. Free with g_object_unref().
+ */
+EDBusMethodInvocation *
+e_dbus_method_invocation_new (const gchar      *sender,
+                              const gchar      *object_path,
+                              const gchar      *interface_name,
+                              const gchar      *method_name,
+                              EDBusConnection  *connection,
+                              EVariant         *parameters,
+                              gpointer          user_data)
+{
+  g_return_val_if_fail (object_path != NULL, NULL);
+  g_return_val_if_fail (interface_name != NULL, NULL);
+  g_return_val_if_fail (method_name != NULL, NULL);
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+  g_return_val_if_fail (parameters != NULL, NULL);
+  g_return_val_if_fail ((e_dbus_connection_get_bus_type (connection) == G_BUS_TYPE_NONE && sender == NULL) ||
+                        (e_dbus_connection_get_bus_type (connection) != G_BUS_TYPE_NONE && sender != NULL), NULL);
+
+  return E_DBUS_METHOD_INVOCATION (g_object_new (E_TYPE_DBUS_METHOD_INVOCATION,
+                                                 "sender", sender,
+                                                 "object-path", object_path,
+                                                 "interface-name", interface_name,
+                                                 "method-name", method_name,
+                                                 "connection", connection,
+                                                 "parameters", parameters,
+                                                 "user-data", user_data,
+                                                 NULL));
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_method_invocation_return_value:
+ * @invocation: A #EDBusMethodInvocation.
+ * @parameters: A #EVariant tuple with out parameters for the method or %NULL if not passing any parameters.
+ *
+ * Finishes handling a D-Bus method call by returning @parameters.
+ *
+ * It is an error if @parameters is not of the right format.
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+e_dbus_method_invocation_return_value (EDBusMethodInvocation *invocation,
+                                       EVariant              *parameters)
+{
+  DBusMessage *message;
+  EDBusMethodInfo *method_info;
+  DBusMessage *reply;
+  GError *error;
+
+  g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_return_if_fail ((parameters == NULL) || (e_variant_get_type_class (parameters) == E_VARIANT_CLASS_TUPLE));
+
+  message = g_object_get_data (G_OBJECT (invocation), "dbus-1-message");
+  g_assert (message != NULL);
+
+  if (parameters != NULL)
+    e_variant_ref_sink (parameters);
+
+  /* if we have introspection data, check that the signature of @parameters is correct */
+  method_info = g_object_get_data (G_OBJECT (invocation), "g-dbus-method-info");
+  if (method_info != NULL)
+    {
+      gboolean pass;
+
+      pass = FALSE;
+      if (parameters == NULL)
+        {
+          if (g_strcmp0 (method_info->out_signature, "") == 0)
+            pass = TRUE;
+        }
+      else
+        {
+          const gchar *type_string;
+          gchar *sig_with_paren;
+
+          /* this can probably be done a lot more efficiently */
+          sig_with_paren = g_strdup_printf ("(%s)", method_info->out_signature);
+          type_string = e_variant_get_type_string (parameters);
+          if (g_strcmp0 (sig_with_paren, type_string) == 0)
+            pass = TRUE;
+          g_free (sig_with_paren);
+        }
+
+      if (!pass)
+        {
+          g_warning (_("Type of return value is incorrect, expected type `(%s)'"),
+                     method_info->out_signature);
+          goto out;
+        }
+    }
+
+  reply = dbus_message_new_method_return (message);
+  error = NULL;
+  if (parameters != NULL)
+    {
+      if (!_e_dbus_gvariant_to_dbus_1 (reply,
+                                       parameters,
+                                       &error))
+        {
+          g_warning ("Error serializing return value to D-Bus format: %s", error->message);
+          g_error_free (error);
+          goto out;
+        }
+    }
+
+  _e_dbus_connection_send_dbus_1_message (e_dbus_method_invocation_get_connection (invocation), reply);
+  dbus_message_unref (reply);
+
+ out:
+  g_object_unref (invocation);
+  if (parameters != NULL)
+    e_variant_unref (parameters);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_method_invocation_return_error:
+ * @invocation: A #EDBusMethodInvocation.
+ * @domain: A #GQuark for the #GError error domain.
+ * @code: The error code.
+ * @format: printf()-style format.
+ * @...: Parameters for @format.
+ *
+ * Finishes handling a D-Bus method call by returning an error.
+ *
+ * See e_dbus_error_encode_gerror() for details about what error name
+ * will be returned on the wire. In a nutshell, if the given error is
+ * registered using e_dbus_error_register_error() the name given
+ * during registration is used. Otherwise, a name of the form
+ * <literal>org.gtk.EDBus.UnmappedGError.Quark0x...</literal> is
+ * used. This provides transparent mapping of #GError between
+ * applications using EDBus.
+ *
+ * If you are writing an application intended to be portable,
+ * <emphasis>always</emphasis> register errors with e_dbus_error_register_error()
+ * or use e_dbus_method_invocation_return_dbus_error().
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+e_dbus_method_invocation_return_error (EDBusMethodInvocation *invocation,
+                                       GQuark                 domain,
+                                       gint                   code,
+                                       const gchar           *format,
+                                       ...)
+{
+  va_list var_args;
+
+  g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_return_if_fail (format != NULL);
+
+  va_start (var_args, format);
+  e_dbus_method_invocation_return_error_valist (invocation,
+                                                domain,
+                                                code,
+                                                format,
+                                                var_args);
+  va_end (var_args);
+}
+
+/**
+ * e_dbus_method_invocation_return_error_valist:
+ * @invocation: A #EDBusMethodInvocation.
+ * @domain: A #GQuark for the #GError error domain.
+ * @code: The error code.
+ * @format: printf()-style format.
+ * @var_args: #va_list of parameters for @format.
+ *
+ * Like e_dbus_method_invocation_return_error() but intended for
+ * language bindings.
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+e_dbus_method_invocation_return_error_valist (EDBusMethodInvocation *invocation,
+                                              GQuark                 domain,
+                                              gint                   code,
+                                              const gchar           *format,
+                                              va_list                var_args)
+{
+  gchar *literal_message;
+
+  g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_return_if_fail (format != NULL);
+
+  literal_message = g_strdup_vprintf (format, var_args);
+  e_dbus_method_invocation_return_error_literal (invocation,
+                                                 domain,
+                                                 code,
+                                                 literal_message);
+  g_free (literal_message);
+}
+
+/**
+ * e_dbus_method_invocation_return_error_literal:
+ * @invocation: A #EDBusMethodInvocation.
+ * @domain: A #GQuark for the #GError error domain.
+ * @code: The error code.
+ * @message: The error message.
+ *
+ * Like e_dbus_method_invocation_return_error() but without printf()-style formatting.
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+e_dbus_method_invocation_return_error_literal (EDBusMethodInvocation *invocation,
+                                               GQuark                 domain,
+                                               gint                   code,
+                                               const gchar           *message)
+{
+  GError *error;
+
+  g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_return_if_fail (message != NULL);
+
+  error = g_error_new_literal (domain, code, message);
+  e_dbus_method_invocation_return_gerror (invocation, error);
+  g_error_free (error);
+}
+
+/**
+ * e_dbus_method_invocation_return_gerror:
+ * @invocation: A #EDBusMethodInvocation.
+ * @error: A #GError.
+ *
+ * Like e_dbus_method_invocation_return_error() but takes a #GError
+ * instead of the error domain, error code and message.
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+e_dbus_method_invocation_return_gerror (EDBusMethodInvocation *invocation,
+                                        const GError          *error)
+{
+  gchar *dbus_error_name;
+
+  g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_return_if_fail (error != NULL);
+
+  dbus_error_name = e_dbus_error_encode_gerror (error);
+
+  e_dbus_method_invocation_return_dbus_error (invocation,
+                                              dbus_error_name,
+                                              error->message);
+  g_free (dbus_error_name);
+}
+
+/**
+ * e_dbus_method_invocation_return_dbus_error:
+ * @invocation: A #EDBusMethodInvocation.
+ * @error_name: A valid D-Bus error name.
+ * @error_message: A valid D-Bus error message.
+ *
+ * Finishes handling a D-Bus method call by returning an error.
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void
+e_dbus_method_invocation_return_dbus_error (EDBusMethodInvocation *invocation,
+                                            const gchar           *error_name,
+                                            const gchar           *error_message)
+{
+  DBusMessage *message;
+  DBusMessage *reply;
+
+  g_return_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation));
+  g_return_if_fail (error_name != NULL);
+  g_return_if_fail (error_message != NULL);
+
+  message = g_object_get_data (G_OBJECT (invocation), "dbus-1-message");
+  g_assert (message != NULL);
+
+  reply = dbus_message_new_error (message,
+                                  error_name,
+                                  error_message);
+  _e_dbus_connection_send_dbus_1_message (e_dbus_method_invocation_get_connection (invocation), reply);
+  dbus_message_unref (reply);
+
+  g_object_unref (invocation);
+}
diff --git a/libedbus/gdbusmethodinvocation.h b/libedbus/gdbusmethodinvocation.h
new file mode 100644
index 0000000..7eaf194
--- /dev/null
+++ b/libedbus/gdbusmethodinvocation.h
@@ -0,0 +1,119 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#if !defined (__E_DBUS_H_INSIDE__) && !defined (E_DBUS_COMPILATION)
+#error "Only <gdbus/gdbus.h> can be included directly."
+#endif
+
+#ifndef __E_DBUS_METHOD_INVOCATION_H__
+#define __E_DBUS_METHOD_INVOCATION_H__
+
+#include <gdbus/gdbustypes.h>
+
+G_BEGIN_DECLS
+
+#define E_TYPE_DBUS_METHOD_INVOCATION         (e_dbus_method_invocation_get_type ())
+#define E_DBUS_METHOD_INVOCATION(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), E_TYPE_DBUS_METHOD_INVOCATION, EDBusMethodInvocation))
+#define E_DBUS_METHOD_INVOCATION_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), E_TYPE_DBUS_METHOD_INVOCATION, EDBusMethodInvocationClass))
+#define E_DBUS_METHOD_INVOCATION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), E_TYPE_DBUS_METHOD_INVOCATION, EDBusMethodInvocationClass))
+#define G_IS_DBUS_METHOD_INVOCATION(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), E_TYPE_DBUS_METHOD_INVOCATION))
+#define G_IS_DBUS_METHOD_INVOCATION_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), E_TYPE_DBUS_METHOD_INVOCATION))
+
+typedef struct _EDBusMethodInvocationClass   EDBusMethodInvocationClass;
+typedef struct _EDBusMethodInvocationPrivate EDBusMethodInvocationPrivate;
+
+/**
+ * EDBusMethodInvocation:
+ *
+ * The #EDBusMethodInvocation structure contains only private data and
+ * should only be accessed using the provided API.
+ */
+struct _EDBusMethodInvocation
+{
+  /*< private >*/
+  GObject parent_instance;
+  EDBusMethodInvocationPrivate *priv;
+};
+
+/**
+ * EDBusMethodInvocationClass:
+ *
+ * Class structure for #EDBusMethodInvocation.
+ */
+struct _EDBusMethodInvocationClass
+{
+  /*< private >*/
+  GObjectClass parent_class;
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*_g_reserved1) (void);
+  void (*_g_reserved2) (void);
+  void (*_g_reserved3) (void);
+  void (*_g_reserved4) (void);
+  void (*_g_reserved5) (void);
+  void (*_g_reserved6) (void);
+  void (*_g_reserved7) (void);
+  void (*_g_reserved8) (void);
+};
+
+GType                  e_dbus_method_invocation_get_type             (void) G_GNUC_CONST;
+EDBusMethodInvocation *e_dbus_method_invocation_new                  (const gchar           *sender,
+                                                                      const gchar           *object_path,
+                                                                      const gchar           *interface_name,
+                                                                      const gchar           *method_name,
+                                                                      EDBusConnection       *connection,
+                                                                      EVariant              *parameters,
+                                                                      gpointer               user_data);
+const gchar           *e_dbus_method_invocation_get_sender           (EDBusMethodInvocation *invocation);
+const gchar           *e_dbus_method_invocation_get_object_path      (EDBusMethodInvocation *invocation);
+const gchar           *e_dbus_method_invocation_get_interface_name   (EDBusMethodInvocation *invocation);
+const gchar           *e_dbus_method_invocation_get_method_name      (EDBusMethodInvocation *invocation);
+EDBusConnection       *e_dbus_method_invocation_get_connection       (EDBusMethodInvocation *invocation);
+EVariant              *e_dbus_method_invocation_get_parameters       (EDBusMethodInvocation *invocation);
+gpointer               e_dbus_method_invocation_get_user_data        (EDBusMethodInvocation *invocation);
+
+void                   e_dbus_method_invocation_return_value         (EDBusMethodInvocation *invocation,
+                                                                      EVariant              *parameters);
+void                   e_dbus_method_invocation_return_error         (EDBusMethodInvocation *invocation,
+                                                                      GQuark                 domain,
+                                                                      gint                   code,
+                                                                      const gchar           *format,
+                                                                      ...);
+void                   e_dbus_method_invocation_return_error_valist  (EDBusMethodInvocation *invocation,
+                                                                      GQuark                 domain,
+                                                                      gint                   code,
+                                                                      const gchar           *format,
+                                                                      va_list                var_args);
+void                   e_dbus_method_invocation_return_error_literal (EDBusMethodInvocation *invocation,
+                                                                      GQuark                 domain,
+                                                                      gint                   code,
+                                                                      const gchar           *message);
+void                   e_dbus_method_invocation_return_gerror        (EDBusMethodInvocation *invocation,
+                                                                      const GError          *error);
+void                   e_dbus_method_invocation_return_dbus_error    (EDBusMethodInvocation *invocation,
+                                                                      const gchar           *error_name,
+                                                                      const gchar           *error_message);
+
+G_END_DECLS
+
+#endif /* __E_DBUS_METHOD_INVOCATION_H__ */
diff --git a/libedbus/gdbusnameowning.c b/libedbus/gdbusnameowning.c
new file mode 100644
index 0000000..ee26dbe
--- /dev/null
+++ b/libedbus/gdbusnameowning.c
@@ -0,0 +1,679 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbusnameowning.h"
+#include "gdbuserror.h"
+#include "gdbusprivate.h"
+#include "gdbusconnection.h"
+
+/**
+ * SECTION:gdbusnameowning
+ * @title: Owning Bus Names
+ * @short_description: Simple API for owning bus names
+ * @include: gdbus/gdbus.h
+ *
+ * Convenience API for owning bus names.
+ *
+ * <example id="gdbus-owning-names"><title>Simple application owning a name</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude"; parse="text" href="../../../../gdbus/example-own-name.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
+ */
+
+G_LOCK_DEFINE_STATIC (lock);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef enum
+{
+  PREVIOUS_CALL_NONE = 0,
+  PREVIOUS_CALL_ACQUIRED,
+  PREVIOUS_CALL_LOST,
+} PreviousCall;
+
+typedef struct
+{
+  volatile gint             ref_count;
+  guint                     id;
+  GBusNameOwnerFlags        flags;
+  gchar                    *name;
+  GBusNameAcquiredCallback  name_acquired_handler;
+  GBusNameLostCallback      name_lost_handler;
+  gpointer                  user_data;
+  GDestroyNotify            user_data_free_func;
+  GMainContext             *main_context;
+
+  PreviousCall              previous_call;
+
+  EDBusConnection          *connection;
+  gulong                    disconnected_signal_handler_id;
+  guint                     name_acquired_subscription_id;
+  guint                     name_lost_subscription_id;
+
+  gboolean                  cancelled;
+
+  gboolean                  needs_release;
+} Client;
+
+static guint next_global_id = 1;
+static GHashTable *map_id_to_client = NULL;
+
+
+static Client *
+client_ref (Client *client)
+{
+  g_atomic_int_inc (&client->ref_count);
+  return client;
+}
+
+static void
+client_unref (Client *client)
+{
+  if (g_atomic_int_dec_and_test (&client->ref_count))
+    {
+      if (client->connection != NULL)
+        {
+          if (client->disconnected_signal_handler_id > 0)
+            g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
+          if (client->name_acquired_subscription_id > 0)
+            e_dbus_connection_signal_unsubscribe (client->connection, client->name_acquired_subscription_id);
+          if (client->name_lost_subscription_id > 0)
+            e_dbus_connection_signal_unsubscribe (client->connection, client->name_lost_subscription_id);
+          g_object_unref (client->connection);
+        }
+      if (client->main_context != NULL)
+        g_main_context_unref (client->main_context);
+      g_free (client->name);
+      if (client->user_data_free_func != NULL)
+        client->user_data_free_func (client->user_data);
+      g_free (client);
+    }
+}
+
+static gboolean
+schedule_unref_in_idle_cb (gpointer data)
+{
+  Client *client = data;
+  client_unref (client);
+  return FALSE;
+}
+
+static void
+schedule_unref_in_idle (Client *client)
+{
+  GSource *idle_source;
+
+  idle_source = g_idle_source_new ();
+  g_source_set_priority (idle_source, G_PRIORITY_HIGH);
+  g_source_set_callback (idle_source,
+                         schedule_unref_in_idle_cb,
+                         client_ref (client),
+                         (GDestroyNotify) client_unref);
+  g_source_attach (idle_source, client->main_context);
+  g_source_unref (idle_source);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  Client *client;
+
+  /* keep this separate because client->connection may
+   * be set to NULL after scheduling the call
+   */
+  EDBusConnection *connection;
+
+  /* set to TRUE to call acquired */
+  gboolean call_acquired;
+} CallHandlerData;
+
+static void
+call_handler_data_free (CallHandlerData *data)
+{
+  if (data->connection != NULL)
+    g_object_unref (data->connection);
+  client_unref (data->client);
+  g_free (data);
+}
+
+static gboolean
+call_in_idle_cb (gpointer _data)
+{
+  CallHandlerData *data = _data;
+
+  if (data->call_acquired)
+    {
+      if (data->client->name_acquired_handler != NULL)
+        {
+          data->client->name_acquired_handler (data->connection,
+                                               data->client->name,
+                                               data->client->user_data);
+        }
+    }
+  else
+    {
+      if (data->client->name_lost_handler != NULL)
+        {
+          data->client->name_lost_handler (data->connection,
+                                           data->client->name,
+                                           data->client->user_data);
+        }
+    }
+
+  return FALSE;
+}
+
+static void
+schedule_call_in_idle (Client *client,
+                       gboolean call_acquired)
+{
+  CallHandlerData *data;
+  GSource *idle_source;
+
+  data = g_new0 (CallHandlerData, 1);
+  data->client = client_ref (client);
+  data->connection = client->connection != NULL ? g_object_ref (client->connection) : NULL;
+  data->call_acquired = call_acquired;
+
+  idle_source = g_idle_source_new ();
+  g_source_set_priority (idle_source, G_PRIORITY_HIGH);
+  g_source_set_callback (idle_source,
+                         call_in_idle_cb,
+                         data,
+                         (GDestroyNotify) call_handler_data_free);
+  g_source_attach (idle_source, client->main_context);
+  g_source_unref (idle_source);
+}
+
+static void
+call_acquired_handler (Client *client)
+{
+  if (client->previous_call != PREVIOUS_CALL_ACQUIRED)
+    {
+      client->previous_call = PREVIOUS_CALL_ACQUIRED;
+      if (!client->cancelled)
+        {
+          schedule_call_in_idle (client, TRUE);
+        }
+    }
+}
+
+static void
+call_lost_handler (Client  *client,
+                   gboolean ignore_cancelled)
+{
+  if (client->previous_call != PREVIOUS_CALL_LOST)
+    {
+      client->previous_call = PREVIOUS_CALL_LOST;
+      if ((!client->cancelled) || ignore_cancelled)
+        {
+          schedule_call_in_idle (client, FALSE);
+        }
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_name_lost_or_acquired (EDBusConnection  *connection,
+                          const gchar      *sender_name,
+                          const gchar      *object_path,
+                          const gchar      *interface_name,
+                          const gchar      *signal_name,
+                          EVariant         *parameters,
+                          gpointer          user_data)
+{
+  Client *client = user_data;
+  const gchar *name;
+
+  if (g_strcmp0 (object_path, "/org/freedesktop/DBus") != 0 ||
+      g_strcmp0 (interface_name, "org.freedesktop.DBus") != 0 ||
+      g_strcmp0 (sender_name, "org.freedesktop.DBus") != 0)
+    goto out;
+
+  if (g_strcmp0 (signal_name, "NameLost") == 0)
+    {
+      e_variant_get (parameters, "(s)", &name);
+      if (g_strcmp0 (name, client->name) == 0)
+        {
+          call_lost_handler (client, FALSE);
+        }
+    }
+  else if (g_strcmp0 (signal_name, "NameAcquired") == 0)
+    {
+      e_variant_get (parameters, "(s)", &name);
+      if (g_strcmp0 (name, client->name) == 0)
+        {
+          call_acquired_handler (client);
+        }
+    }
+ out:
+  ;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+request_name_cb (GObject      *source_object,
+                 GAsyncResult *res,
+                 gpointer      user_data)
+{
+  Client *client = user_data;
+  EVariant *result;
+  guint32 request_name_reply;
+  gboolean subscribe;
+
+  request_name_reply = 0;
+  result = NULL;
+
+  result = e_dbus_connection_invoke_method_finish (client->connection,
+                                                   res,
+                                                   NULL);
+  if (result != NULL)
+    {
+      e_variant_get (result, "(u)", &request_name_reply);
+      e_variant_unref (result);
+    }
+
+  subscribe = FALSE;
+
+  switch (request_name_reply)
+    {
+    case 1: /* DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER */
+      /* We got the name - now listen for NameLost and NameAcquired */
+      call_acquired_handler (client);
+      subscribe = TRUE;
+      client->needs_release = TRUE;
+      break;
+
+    case 2: /* DBUS_REQUEST_NAME_REPLY_IN_QUEUE */
+      /* Waiting in line - listen for NameLost and NameAcquired */
+      call_lost_handler (client, FALSE);
+      subscribe = TRUE;
+      client->needs_release = TRUE;
+      break;
+
+    default:
+      /* assume we couldn't get the name - explicit fallthrough */
+    case 3: /* DBUS_REQUEST_NAME_REPLY_EXISTS */
+    case 4: /* DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER */
+      /* Some other part of the process is already owning the name */
+      call_lost_handler (client, FALSE);
+      break;
+    }
+
+  if (subscribe)
+    {
+      /* start listening to NameLost and NameAcquired messages */
+      client->name_lost_subscription_id =
+        e_dbus_connection_signal_subscribe (client->connection,
+                                            "org.freedesktop.DBus",
+                                            "org.freedesktop.DBus",
+                                            "NameLost",
+                                            "/org/freedesktop/DBus",
+                                            client->name,
+                                            on_name_lost_or_acquired,
+                                            client,
+                                            NULL);
+      client->name_acquired_subscription_id =
+        e_dbus_connection_signal_subscribe (client->connection,
+                                            "org.freedesktop.DBus",
+                                            "org.freedesktop.DBus",
+                                            "NameAcquired",
+                                            "/org/freedesktop/DBus",
+                                            client->name,
+                                            on_name_lost_or_acquired,
+                                            client,
+                                            NULL);
+    }
+
+  client_unref (client);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_connection_disconnected (EDBusConnection *connection,
+                            gpointer         user_data)
+{
+  Client *client = user_data;
+
+  if (client->disconnected_signal_handler_id > 0)
+    g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
+  if (client->name_acquired_subscription_id > 0)
+    e_dbus_connection_signal_unsubscribe (client->connection, client->name_acquired_subscription_id);
+  if (client->name_lost_subscription_id > 0)
+    e_dbus_connection_signal_unsubscribe (client->connection, client->name_lost_subscription_id);
+  g_object_unref (client->connection);
+  client->disconnected_signal_handler_id = 0;
+  client->name_acquired_subscription_id = 0;
+  client->name_lost_subscription_id = 0;
+  client->connection = NULL;
+
+  call_lost_handler (client, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+has_connection (Client *client)
+{
+  /* listen for disconnection */
+  client->disconnected_signal_handler_id = g_signal_connect (client->connection,
+                                                             "disconnected",
+                                                             G_CALLBACK (on_connection_disconnected),
+                                                             client);
+
+  /* attempt to acquire the name */
+  e_dbus_connection_invoke_method (client->connection,
+                                   "org.freedesktop.DBus",  /* bus name */
+                                   "/org/freedesktop/DBus", /* object path */
+                                   "org.freedesktop.DBus",  /* interface name */
+                                   "RequestName",           /* method name */
+                                   e_variant_new ("(su)",
+                                                  client->name,
+                                                  client->flags),
+                                   -1,
+                                   NULL,
+                                   (GAsyncReadyCallback) request_name_cb,
+                                   client_ref (client));
+}
+
+
+static void
+connection_get_cb (GObject      *source_object,
+                   GAsyncResult *res,
+                   gpointer      user_data)
+{
+  Client *client = user_data;
+
+  client->connection = e_dbus_connection_bus_get_finish (res, NULL);
+  if (client->connection == NULL)
+    {
+      call_lost_handler (client, FALSE);
+      goto out;
+    }
+
+  has_connection (client);
+
+ out:
+  client_unref (client);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_bus_own_name_on_connection:
+ * @connection: A #EDBusConnection that has not been disconnected.
+ * @name: The well-known name to own.
+ * @flags: A set of flags from the #GBusNameOwnerFlags enumeration.
+ * @name_acquired_handler: Handler to invoke when @name is acquired.
+ * @name_lost_handler: Handler to invoke when @name is lost.
+ * @user_data: User data to pass to handlers.
+ * @user_data_free_func: Function for freeing @user_data or %NULL.
+ *
+ * Like e_bus_own_name() but takes a #EDBusConnection instead of a
+ * #GBusType.
+ *
+ * Returns: An identifier (never 0) that an be used with
+ * e_bus_unown_name() to stop owning the name.
+ **/
+guint
+e_bus_own_name_on_connection (EDBusConnection          *connection,
+                              const gchar              *name,
+                              GBusNameOwnerFlags        flags,
+                              GBusNameAcquiredCallback  name_acquired_handler,
+                              GBusNameLostCallback      name_lost_handler,
+                              gpointer                  user_data,
+                              GDestroyNotify            user_data_free_func)
+{
+  Client *client;
+
+  g_return_val_if_fail (connection != NULL, 0);
+  g_return_val_if_fail (!e_dbus_connection_get_is_disconnected (connection), 0);
+  g_return_val_if_fail (name != NULL, 0);
+  //g_return_val_if_fail (TODO_is_well_known_name (), 0);
+  g_return_val_if_fail (name_acquired_handler != NULL, 0);
+  g_return_val_if_fail (name_lost_handler != NULL, 0);
+
+  G_LOCK (lock);
+
+  client = g_new0 (Client, 1);
+  client->ref_count = 1;
+  client->id = next_global_id++; /* TODO: uh oh, handle overflow */
+  client->name = g_strdup (name);
+  client->flags = flags;
+  client->name_acquired_handler = name_acquired_handler;
+  client->name_lost_handler = name_lost_handler;
+  client->user_data = user_data;
+  client->user_data_free_func = user_data_free_func;
+  client->main_context = g_main_context_get_thread_default ();
+  if (client->main_context != NULL)
+    g_main_context_ref (client->main_context);
+
+  client->connection = g_object_ref (connection);
+
+  if (map_id_to_client == NULL)
+    {
+      map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal);
+    }
+  g_hash_table_insert (map_id_to_client,
+                       GUINT_TO_POINTER (client->id),
+                       client);
+
+  G_UNLOCK (lock);
+
+  has_connection (client);
+
+  return client->id;
+}
+
+/**
+ * e_bus_own_name:
+ * @bus_type: The type of bus to own a name on (can't be #G_BUS_TYPE_NONE).
+ * @name: The well-known name to own.
+ * @flags: A set of flags from the #GBusNameOwnerFlags enumeration.
+ * @name_acquired_handler: Handler to invoke when @name is acquired.
+ * @name_lost_handler: Handler to invoke when @name is lost.
+ * @user_data: User data to pass to handlers.
+ * @user_data_free_func: Function for freeing @user_data or %NULL.
+ *
+ * Starts acquiring @name on the bus specified by @bus_type and calls
+ * @name_acquired_handler and @name_lost_handler when the name is
+ * acquired respectively lost. Callbacks will be invoked in the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link> of the thread you are calling this function from.
+ *
+ * You are guaranteed that one of the handlers will be invoked after
+ * calling this function. When you are done owning the name, just call
+ * e_bus_unown_name() with the owner id this function returns.
+ *
+ * If the name is acquired or lost (for example another application
+ * could acquire the name if you allow replacement), the handlers are
+ * also invoked. If the #EDBusConnection that is used for attempting
+ * to own the name disconnects, then @name_lost_handler is invoked since
+ * it is no longer possible for other processes to access the
+ * process.
+ *
+ * You cannot use e_bus_own_name() several times (unless interleaved
+ * with calls to e_bus_unown_name()) - only the first call will work.
+ *
+ * Another guarantee is that invocations of @name_acquired_handler
+ * and @name_lost_handler are guaranteed to alternate; that
+ * is, if @name_acquired_handler is invoked then you are
+ * guaranteed that the next time one of the handlers is invoked, it
+ * will be @name_lost_handler. The reverse is also true.
+ *
+ * This behavior makes it very simple to write applications that wants
+ * to own names, see <xref linkend="gdbus-owning-names"/>. Simply
+ * register objects to be exported in @name_acquired_handler and
+ * unregister the objects (if any) in @name_lost_handler.
+ *
+ * Returns: An identifier (never 0) that an be used with
+ * e_bus_unown_name() to stop owning the name.
+ **/
+guint
+e_bus_own_name (GBusType                  bus_type,
+                const gchar              *name,
+                GBusNameOwnerFlags        flags,
+                GBusNameAcquiredCallback  name_acquired_handler,
+                GBusNameLostCallback      name_lost_handler,
+                gpointer                  user_data,
+                GDestroyNotify            user_data_free_func)
+{
+  Client *client;
+
+  g_return_val_if_fail (bus_type != G_BUS_TYPE_NONE, 0);
+  g_return_val_if_fail (name != NULL, 0);
+  //g_return_val_if_fail (TODO_is_well_known_name (), 0);
+  g_return_val_if_fail (name_acquired_handler != NULL, 0);
+  g_return_val_if_fail (name_lost_handler != NULL, 0);
+
+  G_LOCK (lock);
+
+  client = g_new0 (Client, 1);
+  client->ref_count = 1;
+  client->id = next_global_id++; /* TODO: uh oh, handle overflow */
+  client->name = g_strdup (name);
+  client->flags = flags;
+  client->name_acquired_handler = name_acquired_handler;
+  client->name_lost_handler = name_lost_handler;
+  client->user_data = user_data;
+  client->user_data_free_func = user_data_free_func;
+  client->main_context = g_main_context_get_thread_default ();
+  if (client->main_context != NULL)
+    g_main_context_ref (client->main_context);
+
+  if (map_id_to_client == NULL)
+    {
+      map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal);
+    }
+  g_hash_table_insert (map_id_to_client,
+                       GUINT_TO_POINTER (client->id),
+                       client);
+
+  e_dbus_connection_bus_get (bus_type,
+                             NULL,
+                             connection_get_cb,
+                             client_ref (client));
+  G_UNLOCK (lock);
+
+  return client->id;
+}
+
+/**
+ * e_bus_unown_name:
+ * @owner_id: An identifier obtained from e_bus_own_name()
+ *
+ * Stops owning a name.
+ *
+ * If currently owning the name (e.g. @name_acquired_handler was the
+ * last handler to be invoked), then @name_lost_handler will be invoked.
+ **/
+void
+e_bus_unown_name (guint owner_id)
+{
+  Client *client;
+
+  client = NULL;
+
+  G_LOCK (lock);
+  if (owner_id == 0 || map_id_to_client == NULL ||
+      (client = g_hash_table_lookup (map_id_to_client, GUINT_TO_POINTER (owner_id))) == NULL)
+    {
+      g_warning ("Invalid id %d passed to e_bus_unown_name()", owner_id);
+      goto out;
+    }
+
+  client->cancelled = TRUE;
+  g_warn_if_fail (g_hash_table_remove (map_id_to_client, GUINT_TO_POINTER (owner_id)));
+
+ out:
+  G_UNLOCK (lock);
+
+  /* do callback without holding lock */
+  if (client != NULL)
+    {
+      /* Release the name if needed */
+      if (client->needs_release && client->connection != NULL)
+        {
+          EVariant *result;
+          GError *error;
+          guint32 release_name_reply;
+
+          /* TODO: it kinda sucks having to do a sync call to release the name - but if
+           * we don't, then a subsequent grab of the name will make the bus daemon return
+           * IN_QUEUE which will trigger name_lost().
+           *
+           * I believe this is a bug in the bus daemon.
+           */
+          error = NULL;
+          result = e_dbus_connection_invoke_method_sync (client->connection,
+                                                         "org.freedesktop.DBus",  /* bus name */
+                                                         "/org/freedesktop/DBus", /* object path */
+                                                         "org.freedesktop.DBus",  /* interface name */
+                                                         "ReleaseName",           /* method name */
+                                                         e_variant_new ("(s)", client->name),
+                                                         -1,
+                                                         NULL,
+                                                         &error);
+          if (result == NULL)
+            {
+              g_warning ("Error releasing name %s: %s", client->name, error->message);
+              g_error_free (error);
+            }
+          else
+            {
+              e_variant_get (result, "(u)", &release_name_reply);
+              if (release_name_reply != 1 /* DBUS_RELEASE_NAME_REPLY_RELEASED */)
+                {
+                  g_warning ("Unexpected reply %d when releasing name %s", release_name_reply, client->name);
+                }
+              e_variant_unref (result);
+            }
+
+          call_lost_handler (client, TRUE);
+
+          if (client->disconnected_signal_handler_id > 0)
+            g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
+          if (client->name_acquired_subscription_id > 0)
+            e_dbus_connection_signal_unsubscribe (client->connection, client->name_acquired_subscription_id);
+          if (client->name_lost_subscription_id > 0)
+            e_dbus_connection_signal_unsubscribe (client->connection, client->name_lost_subscription_id);
+          g_object_unref (client->connection);
+          client->disconnected_signal_handler_id = 0;
+          client->name_acquired_subscription_id = 0;
+          client->name_lost_subscription_id = 0;
+          client->connection = NULL;
+        }
+      else
+        {
+          call_lost_handler (client, TRUE);
+        }
+
+      schedule_unref_in_idle (client);
+    }
+}
diff --git a/libedbus/gdbusnameowning.h b/libedbus/gdbusnameowning.h
new file mode 100644
index 0000000..e44664f
--- /dev/null
+++ b/libedbus/gdbusnameowning.h
@@ -0,0 +1,77 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#if !defined (__E_DBUS_H_INSIDE__) && !defined (E_DBUS_COMPILATION)
+#error "Only <gdbus/gdbus.h> can be included directly."
+#endif
+
+#ifndef __E_DBUS_NAME_OWNING_H__
+#define __E_DBUS_NAME_OWNING_H__
+
+#include <gdbus/gdbustypes.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GBusNameAcquiredCallback:
+ * @connection: The #EDBusConnection on which to acquired the name.
+ * @name: The name being owned.
+ * @user_data: User data passed to e_bus_own_name().
+ *
+ * Invoked when the name is acquired.
+ */
+typedef void (*GBusNameAcquiredCallback) (EDBusConnection *connection,
+                                          const gchar     *name,
+                                          gpointer         user_data);
+
+/**
+ * GBusNameLostCallback:
+ * @connection: The #EDBusConnection on which to acquire the name or %NULL if
+ * the connection was disconnected.
+ * @name: The name being owned.
+ * @user_data: User data passed to e_bus_own_name().
+ *
+ * Invoked when the name is lost or @connection has been disconnected.
+ */
+typedef void (*GBusNameLostCallback) (EDBusConnection *connection,
+                                      const gchar     *name,
+                                      gpointer         user_data);
+
+guint e_bus_own_name                 (GBusType                  bus_type,
+                                      const gchar              *name,
+                                      GBusNameOwnerFlags        flags,
+                                      GBusNameAcquiredCallback  name_acquired_handler,
+                                      GBusNameLostCallback      name_lost_handler,
+                                      gpointer                  user_data,
+                                      GDestroyNotify            user_data_free_func);
+guint e_bus_own_name_on_connection   (EDBusConnection          *connection,
+                                      const gchar              *name,
+                                      GBusNameOwnerFlags        flags,
+                                      GBusNameAcquiredCallback  name_acquired_handler,
+                                      GBusNameLostCallback      name_lost_handler,
+                                      gpointer                  user_data,
+                                      GDestroyNotify            user_data_free_func);
+void  e_bus_unown_name               (guint                     owner_id);
+
+G_END_DECLS
+
+#endif /* __E_DBUS_NAME_OWNING_H__ */
diff --git a/libedbus/gdbusnamewatching.c b/libedbus/gdbusnamewatching.c
new file mode 100644
index 0000000..7b5f7c0
--- /dev/null
+++ b/libedbus/gdbusnamewatching.c
@@ -0,0 +1,537 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbusnamewatching.h"
+#include "gdbuserror.h"
+#include "gdbusprivate.h"
+#include "gdbusconnection.h"
+
+/**
+ * SECTION:gdbusnamewatching
+ * @title: Watching Bus Names
+ * @short_description: Simple API for watching bus names
+ * @include: gdbus/gdbus.h
+ *
+ * Convenience API for watching bus names.
+ *
+ * <example id="gdbus-watching-names"><title>Simple application watching a name</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude"; parse="text" href="../../../../gdbus/example-watch-name.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
+ */
+
+G_LOCK_DEFINE_STATIC (lock);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef enum
+{
+  PREVIOUS_CALL_NONE = 0,
+  PREVIOUS_CALL_APPEARED,
+  PREVIOUS_CALL_VANISHED,
+} PreviousCall;
+
+typedef struct
+{
+  volatile gint             ref_count;
+  guint                     id;
+  gchar                    *name;
+  gchar                    *name_owner;
+  GBusNameAppearedCallback  name_appeared_handler;
+  GBusNameVanishedCallback  name_vanished_handler;
+  gpointer                  user_data;
+  GDestroyNotify            user_data_free_func;
+  GMainContext             *main_context;
+
+  EDBusConnection          *connection;
+  gulong                    disconnected_signal_handler_id;
+  guint                     name_owner_changed_subscription_id;
+
+  PreviousCall              previous_call;
+
+  gboolean                  cancelled;
+  gboolean                  initialized;
+} Client;
+
+static guint next_global_id = 1;
+static GHashTable *map_id_to_client = NULL;
+
+static Client *
+client_ref (Client *client)
+{
+  g_atomic_int_inc (&client->ref_count);
+  return client;
+}
+
+static void
+client_unref (Client *client)
+{
+  if (g_atomic_int_dec_and_test (&client->ref_count))
+    {
+      if (client->connection != NULL)
+        {
+          if (client->name_owner_changed_subscription_id > 0)
+            e_dbus_connection_signal_unsubscribe (client->connection, client->name_owner_changed_subscription_id);
+          if (client->disconnected_signal_handler_id > 0)
+            g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
+          g_object_unref (client->connection);
+        }
+      g_free (client->name);
+      g_free (client->name_owner);
+      if (client->main_context != NULL)
+        g_main_context_unref (client->main_context);
+      if (client->user_data_free_func != NULL)
+        client->user_data_free_func (client->user_data);
+      g_free (client);
+    }
+}
+
+static gboolean
+schedule_unref_in_idle_cb (gpointer data)
+{
+  Client *client = data;
+  client_unref (client);
+  return FALSE;
+}
+
+static void
+schedule_unref_in_idle (Client *client)
+{
+  GSource *idle_source;
+
+  idle_source = g_idle_source_new ();
+  g_source_set_priority (idle_source, G_PRIORITY_HIGH);
+  g_source_set_callback (idle_source,
+                         schedule_unref_in_idle_cb,
+                         client_ref (client),
+                         (GDestroyNotify) client_unref);
+  g_source_attach (idle_source, client->main_context);
+  g_source_unref (idle_source);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  Client *client;
+
+  /* keep this separate because client->connection may
+   * be set to NULL after scheduling the call
+   */
+  EDBusConnection *connection;
+
+  /* ditto */
+  gchar *name_owner;
+
+  /* set to TRUE to call appeared */
+  gboolean call_appeared;
+} CallHandlerData;
+
+static void
+call_handler_data_free (CallHandlerData *data)
+{
+  if (data->connection != NULL)
+    g_object_unref (data->connection);
+  g_free (data->name_owner);
+  client_unref (data->client);
+  g_free (data);
+}
+
+static gboolean
+call_in_idle_cb (gpointer _data)
+{
+  CallHandlerData *data = _data;
+
+  if (data->call_appeared)
+    {
+      if (data->client->name_appeared_handler != NULL)
+        {
+          data->client->name_appeared_handler (data->connection,
+                                               data->client->name,
+                                               data->name_owner,
+                                               data->client->user_data);
+        }
+    }
+  else
+    {
+      if (data->client->name_vanished_handler != NULL)
+        {
+          data->client->name_vanished_handler (data->connection,
+                                               data->client->name,
+                                               data->client->user_data);
+        }
+    }
+
+  return FALSE;
+}
+
+static void
+schedule_call_in_idle (Client *client,
+                       gboolean call_appeared)
+{
+  CallHandlerData *data;
+  GSource *idle_source;
+
+  data = g_new0 (CallHandlerData, 1);
+  data->client = client_ref (client);
+  data->connection = client->connection != NULL ? g_object_ref (client->connection) : NULL;
+  data->name_owner = g_strdup (client->name_owner);
+  data->call_appeared = call_appeared;
+
+  idle_source = g_idle_source_new ();
+  g_source_set_priority (idle_source, G_PRIORITY_HIGH);
+  g_source_set_callback (idle_source,
+                         call_in_idle_cb,
+                         data,
+                         (GDestroyNotify) call_handler_data_free);
+  g_source_attach (idle_source, client->main_context);
+  g_source_unref (idle_source);
+}
+
+static void
+call_appeared_handler (Client *client)
+{
+  if (client->previous_call != PREVIOUS_CALL_APPEARED)
+    {
+      client->previous_call = PREVIOUS_CALL_APPEARED;
+      if (!client->cancelled && client->name_appeared_handler != NULL)
+        {
+          schedule_call_in_idle (client, TRUE);
+        }
+    }
+}
+
+static void
+call_vanished_handler (Client  *client,
+                       gboolean ignore_cancelled)
+{
+  if (client->previous_call != PREVIOUS_CALL_VANISHED)
+    {
+      client->previous_call = PREVIOUS_CALL_VANISHED;
+      if (((!client->cancelled) || ignore_cancelled) && client->name_vanished_handler != NULL)
+        {
+          schedule_call_in_idle (client, FALSE);
+        }
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_connection_disconnected (EDBusConnection *connection,
+                            gpointer         user_data)
+{
+  Client *client = user_data;
+
+  if (client->name_owner_changed_subscription_id > 0)
+    e_dbus_connection_signal_unsubscribe (client->connection, client->name_owner_changed_subscription_id);
+  if (client->disconnected_signal_handler_id > 0)
+    g_signal_handler_disconnect (client->connection, client->disconnected_signal_handler_id);
+  g_object_unref (client->connection);
+  client->disconnected_signal_handler_id = 0;
+  client->name_owner_changed_subscription_id = 0;
+  client->connection = NULL;
+
+  call_vanished_handler (client, FALSE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_name_owner_changed (EDBusConnection *connection,
+                       const gchar      *sender_name,
+                       const gchar      *object_path,
+                       const gchar      *interface_name,
+                       const gchar      *signal_name,
+                       EVariant         *parameters,
+                       gpointer          user_data)
+{
+  Client *client = user_data;
+  const gchar *name;
+  const gchar *old_owner;
+  const gchar *new_owner;
+
+  if (!client->initialized)
+    goto out;
+
+  if (g_strcmp0 (object_path, "/org/freedesktop/DBus") != 0 ||
+      g_strcmp0 (interface_name, "org.freedesktop.DBus") != 0 ||
+      g_strcmp0 (sender_name, "org.freedesktop.DBus") != 0)
+    goto out;
+
+  e_variant_get (parameters,
+                 "(sss)",
+                 &name,
+                 &old_owner,
+                 &new_owner);
+
+  /* we only care about a specific name */
+  if (g_strcmp0 (name, client->name) != 0)
+    goto out;
+
+  if ((old_owner != NULL && strlen (old_owner) > 0) && client->name_owner != NULL)
+    {
+      g_free (client->name_owner);
+      client->name_owner = NULL;
+      call_vanished_handler (client, FALSE);
+    }
+
+  if (new_owner != NULL && strlen (new_owner) > 0)
+    {
+      g_warn_if_fail (client->name_owner == NULL);
+      g_free (client->name_owner);
+      client->name_owner = g_strdup (new_owner);
+      call_appeared_handler (client);
+    }
+
+ out:
+  ;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+get_name_owner_cb (GObject      *source_object,
+                   GAsyncResult *res,
+                   gpointer      user_data)
+{
+  Client *client = user_data;
+  EVariant *result;
+  const char *name_owner;
+
+  name_owner = NULL;
+  result = NULL;
+
+  result = e_dbus_connection_invoke_method_finish (client->connection,
+                                                   res,
+                                                   NULL);
+  if (result != NULL)
+    {
+      e_variant_get (result, "(s)", &name_owner);
+    }
+
+  if (name_owner != NULL)
+    {
+      g_warn_if_fail (client->name_owner == NULL);
+      client->name_owner = g_strdup (name_owner);
+      call_appeared_handler (client);
+    }
+  else
+    {
+      call_vanished_handler (client, FALSE);
+    }
+
+  client->initialized = TRUE;
+
+  if (result != NULL)
+    e_variant_unref (result);
+  client_unref (client);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+has_connection (Client *client)
+{
+  /* listen for disconnection */
+  client->disconnected_signal_handler_id = g_signal_connect (client->connection,
+                                                             "disconnected",
+                                                             G_CALLBACK (on_connection_disconnected),
+                                                             client);
+
+  /* start listening to NameOwnerChanged messages immediately */
+  client->name_owner_changed_subscription_id = e_dbus_connection_signal_subscribe (client->connection,
+                                                                                   "org.freedesktop.DBus",  /* name */
+                                                                                   "org.freedesktop.DBus",  /* if */
+                                                                                   "NameOwnerChanged",      /* signal */
+                                                                                   "/org/freedesktop/DBus", /* path */
+                                                                                   client->name,
+                                                                                   on_name_owner_changed,
+                                                                                   client,
+                                                                                   NULL);
+
+  /* check owner */
+  e_dbus_connection_invoke_method (client->connection,
+                                   "org.freedesktop.DBus",  /* bus name */
+                                   "/org/freedesktop/DBus", /* object path */
+                                   "org.freedesktop.DBus",  /* interface name */
+                                   "GetNameOwner",          /* method name */
+                                   e_variant_new ("(s)", client->name),
+                                   -1,
+                                   NULL,
+                                   (GAsyncReadyCallback) get_name_owner_cb,
+                                   client_ref (client));
+}
+
+
+static void
+connection_get_cb (GObject      *source_object,
+                   GAsyncResult *res,
+                   gpointer      user_data)
+{
+  Client *client = user_data;
+
+  client->connection = e_dbus_connection_bus_get_finish (res, NULL);
+  if (client->connection == NULL)
+    {
+      call_vanished_handler (client, FALSE);
+      goto out;
+    }
+
+  has_connection (client);
+
+ out:
+  client_unref (client);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_bus_watch_name:
+ * @bus_type: The type of bus to watch a name on (can't be #G_BUS_TYPE_NONE).
+ * @name: The name (well-known or unique) to watch.
+ * @name_appeared_handler: Handler to invoke when @name is known to exist.
+ * @name_vanished_handler: Handler to invoke when @name is known to not exist.
+ * @user_data: User data to pass to handlers.
+ * @user_data_free_func: Function for freeing @user_data or %NULL.
+ *
+ * Starts watching @name on the bus specified by @bus_type and calls
+ * @name_appeared_handler and @name_vanished_handler when the name is
+ * known to have a owner respectively known to lose its
+ * owner. Callbacks will be invoked in the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link> of the thread you are calling this function from.
+ *
+ * You are guaranteed that one of the handlers will be invoked after
+ * calling this function. When you are done watching the name, just
+ * call e_bus_unwatch_name() with the watcher id this function
+ * returns.
+ *
+ * If the name vanishes or appears (for example the application owning
+ * the name could restart), the handlers are also invoked. If the
+ * #EDBusConnection that is used for watching the name disconnects, then
+ * @name_vanished_handler is invoked since it is no longer
+ * possible to access the name.
+ *
+ * Another guarantee is that invocations of @name_appeared_handler
+ * and @name_vanished_handler are guaranteed to alternate; that
+ * is, if @name_appeared_handler is invoked then you are
+ * guaranteed that the next time one of the handlers is invoked, it
+ * will be @name_vanished_handler. The reverse is also true.
+ *
+ * This behavior makes it very simple to write applications that wants
+ * to take action when a certain name exists, see <xref
+ * linkend="gdbus-watching-names"/>. Basically, the application
+ * should create object proxies in @name_appeared_handler and destroy
+ * them again (if any) in @name_vanished_handler.
+ *
+ * Returns: An identifier (never 0) that an be used with
+ * e_bus_unwatch_name() to stop watching the name.
+ **/
+guint
+e_bus_watch_name (GBusType                  bus_type,
+                  const gchar              *name,
+                  GBusNameAppearedCallback  name_appeared_handler,
+                  GBusNameVanishedCallback  name_vanished_handler,
+                  gpointer                  user_data,
+                  GDestroyNotify            user_data_free_func)
+{
+  Client *client;
+
+  g_return_val_if_fail (bus_type != G_BUS_TYPE_NONE, 0);
+  g_return_val_if_fail (name != NULL, 0);
+  g_return_val_if_fail (name_appeared_handler != NULL, 0);
+  g_return_val_if_fail (name_vanished_handler != NULL, 0);
+
+  G_LOCK (lock);
+
+  client = g_new0 (Client, 1);
+  client->ref_count = 1;
+  client->id = next_global_id++; /* TODO: uh oh, handle overflow */
+  client->name = g_strdup (name);
+  client->name_appeared_handler = name_appeared_handler;
+  client->name_vanished_handler = name_vanished_handler;
+  client->user_data = user_data;
+  client->user_data_free_func = user_data_free_func;
+  client->main_context = g_main_context_get_thread_default ();
+  if (client->main_context != NULL)
+    g_main_context_ref (client->main_context);
+
+  if (map_id_to_client == NULL)
+    {
+      map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal);
+    }
+  g_hash_table_insert (map_id_to_client,
+                       GUINT_TO_POINTER (client->id),
+                       client);
+
+  e_dbus_connection_bus_get (bus_type,
+                             NULL,
+                             connection_get_cb,
+                             client_ref (client));
+
+  G_UNLOCK (lock);
+
+  return client->id;
+}
+
+/**
+ * e_bus_unwatch_name:
+ * @watcher_id: An identifier obtained from e_bus_watch_name()
+ *
+ * Stops watching a name.
+ *
+ * If the name being watched currently has an owner the name (e.g. @name_appeared_handler
+ * was the last handler to be invoked), then @name_vanished_handler will be invoked.
+ **/
+void
+e_bus_unwatch_name (guint watcher_id)
+{
+  Client *client;
+
+  client = NULL;
+
+  G_LOCK (lock);
+  if (watcher_id == 0 ||
+      map_id_to_client == NULL ||
+      (client = g_hash_table_lookup (map_id_to_client, GUINT_TO_POINTER (watcher_id))) == NULL)
+    {
+      g_warning ("Invalid id %d passed to e_bus_unwatch_name()", watcher_id);
+      goto out;
+    }
+
+  client->cancelled = TRUE;
+  g_warn_if_fail (g_hash_table_remove (map_id_to_client, GUINT_TO_POINTER (watcher_id)));
+
+ out:
+  G_UNLOCK (lock);
+
+  /* do callback without holding lock */
+  if (client != NULL)
+    {
+      call_vanished_handler (client, TRUE);
+      schedule_unref_in_idle (client);
+    }
+}
diff --git a/libedbus/gdbusnamewatching.h b/libedbus/gdbusnamewatching.h
new file mode 100644
index 0000000..4d4411b
--- /dev/null
+++ b/libedbus/gdbusnamewatching.h
@@ -0,0 +1,71 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#if !defined (__E_DBUS_H_INSIDE__) && !defined (E_DBUS_COMPILATION)
+#error "Only <gdbus/gdbus.h> can be included directly."
+#endif
+
+#ifndef __E_DBUS_NAME_WATCHING_H__
+#define __E_DBUS_NAME_WATCHING_H__
+
+#include <gdbus/gdbustypes.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GBusNameAppearedCallback:
+ * @connection: The #EDBusConnection the name is being watched on.
+ * @name: The name being watched.
+ * @name_owner: Unique name of the owner of the name being watched.
+ * @user_data: User data passed to e_bus_watch_name().
+ *
+ * Invoked when the name being watched is known to have to have a owner.
+ */
+typedef void (*GBusNameAppearedCallback) (EDBusConnection *connection,
+                                          const gchar     *name,
+                                          const gchar     *name_owner,
+                                          gpointer         user_data);
+
+/**
+ * GBusNameVanishedCallback:
+ * @connection: The #EDBusConnection the name is being watched on.
+ * @name: The name being watched.
+ * @user_data: User data passed to e_bus_watch_name().
+ *
+ * Invoked when the name being watched is known not to have to have a owner.
+ */
+typedef void (*GBusNameVanishedCallback) (EDBusConnection *connection,
+                                          const gchar     *name,
+                                          gpointer         user_data);
+
+
+guint e_bus_watch_name   (GBusType                  bus_type,
+                          const gchar              *name,
+                          GBusNameAppearedCallback  name_appeared_handler,
+                          GBusNameVanishedCallback  name_vanished_handler,
+                          gpointer                  user_data,
+                          GDestroyNotify            user_data_free_func);
+void  e_bus_unwatch_name (guint                     watcher_id);
+
+G_END_DECLS
+
+#endif /* __E_DBUS_NAME_WATCHING_H__ */
diff --git a/libedbus/gdbusprivate.c b/libedbus/gdbusprivate.c
new file mode 100644
index 0000000..8227018
--- /dev/null
+++ b/libedbus/gdbusprivate.c
@@ -0,0 +1,779 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include <dbus/dbus.h>
+
+#include "gdbustypes.h"
+#include "gdbusprivate.h"
+
+void
+_e_dbus_oom (void)
+{
+  /* TODO: print stack trace etc. */
+  g_error ("OOM from libdbus");
+  abort ();
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* The following code is adopted from dbus/dbus-gmain.c in dbus-glib. Copright notices
+ * and license:
+ *
+ * Copyright (C) 2002, 2003 CodeFactory AB
+ * Copyright (C) 2005 Red Hat, Inc.
+ *
+ * Licensed under the Academic Free License version 2.1
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+typedef struct
+{
+  GSource source; /**< the parent GSource */
+  DBusConnection *connection; /**< the connection to dispatch */
+} DBusGMessageQueue;
+
+static gboolean message_queue_prepare  (GSource     *source,
+                                        gint        *timeout);
+static gboolean message_queue_check    (GSource     *source);
+static gboolean message_queue_dispatch (GSource     *source,
+                                        GSourceFunc  callback,
+                                        gpointer     user_data);
+
+static const GSourceFuncs message_queue_funcs = {
+  message_queue_prepare,
+  message_queue_check,
+  message_queue_dispatch,
+  NULL
+};
+
+static gboolean
+message_queue_prepare (GSource *source,
+                       gint    *timeout)
+{
+  DBusConnection *connection = ((DBusGMessageQueue *)source)->connection;
+
+  *timeout = -1;
+
+  return (dbus_connection_get_dispatch_status (connection) == DBUS_DISPATCH_DATA_REMAINS);
+}
+
+static gboolean
+message_queue_check (GSource *source)
+{
+  return FALSE;
+}
+
+static gboolean
+message_queue_dispatch (GSource     *source,
+                        GSourceFunc  callback,
+                        gpointer     user_data)
+{
+  DBusConnection *connection = ((DBusGMessageQueue *)source)->connection;
+
+  dbus_connection_ref (connection);
+
+  /* Only dispatch once - we don't want to starve other GSource */
+  dbus_connection_dispatch (connection);
+
+  dbus_connection_unref (connection);
+
+  return TRUE;
+}
+
+typedef struct
+{
+  GMainContext *context;      /**< the main context */
+  GSList *ios;                /**< all IOHandler */
+  GSList *timeouts;           /**< all TimeoutHandler */
+  DBusConnection *connection; /**< NULL if this is really for a server not a connection */
+  GSource *message_queue_source; /**< DBusGMessageQueue */
+} ConnectionSetup;
+
+
+typedef struct
+{
+  ConnectionSetup *cs;
+  GSource *source;
+  DBusWatch *watch;
+} IOHandler;
+
+typedef struct
+{
+  ConnectionSetup *cs;
+  GSource *source;
+  DBusTimeout *timeout;
+} TimeoutHandler;
+
+G_LOCK_DEFINE_STATIC (main_loop_lock);
+
+dbus_int32_t _dbus_gmain_connection_slot = -1;
+static dbus_int32_t server_slot = -1;
+
+static ConnectionSetup*
+connection_setup_new (GMainContext   *context,
+                      DBusConnection *connection)
+{
+  ConnectionSetup *cs;
+
+  cs = g_new0 (ConnectionSetup, 1);
+
+  g_assert (context != NULL);
+
+  cs->context = context;
+  g_main_context_ref (cs->context);
+
+  if (connection)
+    {
+      cs->connection = connection;
+
+      cs->message_queue_source = g_source_new ((GSourceFuncs *) &message_queue_funcs,
+                                               sizeof (DBusGMessageQueue));
+      ((DBusGMessageQueue*)cs->message_queue_source)->connection = connection;
+      g_source_attach (cs->message_queue_source, cs->context);
+    }
+
+  return cs;
+}
+
+static void
+io_handler_source_finalized (gpointer data)
+{
+  IOHandler *handler;
+
+  handler = data;
+
+  if (handler->watch)
+    dbus_watch_set_data (handler->watch, NULL, NULL);
+
+  g_free (handler);
+}
+
+static void
+io_handler_destroy_source (void *data)
+{
+  IOHandler *handler;
+
+  handler = data;
+
+  if (handler->source)
+    {
+      GSource *source = handler->source;
+      handler->source = NULL;
+      handler->cs->ios = g_slist_remove (handler->cs->ios, handler);
+      g_source_destroy (source);
+      g_source_unref (source);
+    }
+}
+
+static void
+io_handler_watch_freed (void *data)
+{
+  IOHandler *handler;
+
+  handler = data;
+
+  handler->watch = NULL;
+
+  io_handler_destroy_source (handler);
+}
+
+static gboolean
+io_handler_dispatch (GIOChannel   *source,
+                     GIOCondition  condition,
+                     gpointer      data)
+{
+  IOHandler *handler;
+  guint dbus_condition = 0;
+  DBusConnection *connection;
+
+  handler = data;
+
+  connection = handler->cs->connection;
+
+  if (connection)
+    dbus_connection_ref (connection);
+
+  if (condition & G_IO_IN)
+    dbus_condition |= DBUS_WATCH_READABLE;
+  if (condition & G_IO_OUT)
+    dbus_condition |= DBUS_WATCH_WRITABLE;
+  if (condition & G_IO_ERR)
+    dbus_condition |= DBUS_WATCH_ERROR;
+  if (condition & G_IO_HUP)
+    dbus_condition |= DBUS_WATCH_HANGUP;
+
+  /* Note that we don't touch the handler after this, because
+   * dbus may have disabled the watch and thus killed the
+   * handler.
+   */
+  dbus_watch_handle (handler->watch, dbus_condition);
+  handler = NULL;
+
+  if (connection)
+    dbus_connection_unref (connection);
+
+  return TRUE;
+}
+
+static void
+connection_setup_add_watch (ConnectionSetup *cs,
+                            DBusWatch       *watch)
+{
+  guint flags;
+  GIOCondition condition;
+  GIOChannel *channel;
+  IOHandler *handler;
+
+  if (!dbus_watch_get_enabled (watch))
+    return;
+
+  g_assert (dbus_watch_get_data (watch) == NULL);
+
+  flags = dbus_watch_get_flags (watch);
+
+  condition = G_IO_ERR | G_IO_HUP;
+  if (flags & DBUS_WATCH_READABLE)
+    condition |= G_IO_IN;
+  if (flags & DBUS_WATCH_WRITABLE)
+    condition |= G_IO_OUT;
+
+  handler = g_new0 (IOHandler, 1);
+  handler->cs = cs;
+  handler->watch = watch;
+
+  channel = g_io_channel_unix_new (dbus_watch_get_unix_fd (watch));
+
+  handler->source = g_io_create_watch (channel, condition);
+  g_source_set_callback (handler->source, (GSourceFunc) io_handler_dispatch, handler,
+                         io_handler_source_finalized);
+  g_source_attach (handler->source, cs->context);
+
+  cs->ios = g_slist_prepend (cs->ios, handler);
+
+  dbus_watch_set_data (watch, handler, io_handler_watch_freed);
+  g_io_channel_unref (channel);
+}
+
+static void
+connection_setup_remove_watch (ConnectionSetup *cs,
+                               DBusWatch       *watch)
+{
+  IOHandler *handler;
+
+  handler = dbus_watch_get_data (watch);
+
+  if (handler == NULL)
+    return;
+
+  io_handler_destroy_source (handler);
+}
+
+static void
+timeout_handler_source_finalized (gpointer data)
+{
+  TimeoutHandler *handler;
+
+  handler = data;
+
+  if (handler->timeout)
+    dbus_timeout_set_data (handler->timeout, NULL, NULL);
+
+  g_free (handler);
+}
+
+static void
+timeout_handler_destroy_source (void *data)
+{
+  TimeoutHandler *handler;
+
+  handler = data;
+
+  if (handler->source)
+    {
+      GSource *source = handler->source;
+      handler->source = NULL;
+      handler->cs->timeouts = g_slist_remove (handler->cs->timeouts, handler);
+      g_source_destroy (source);
+      g_source_unref (source);
+    }
+}
+
+static void
+timeout_handler_timeout_freed (void *data)
+{
+  TimeoutHandler *handler;
+
+  handler = data;
+
+  handler->timeout = NULL;
+
+  timeout_handler_destroy_source (handler);
+}
+
+static gboolean
+timeout_handler_dispatch (gpointer      data)
+{
+  TimeoutHandler *handler;
+
+  handler = data;
+
+  dbus_timeout_handle (handler->timeout);
+
+  return TRUE;
+}
+
+static void
+connection_setup_add_timeout (ConnectionSetup *cs,
+                              DBusTimeout     *timeout)
+{
+  TimeoutHandler *handler;
+
+  if (!dbus_timeout_get_enabled (timeout))
+    return;
+
+  g_assert (dbus_timeout_get_data (timeout) == NULL);
+
+  handler = g_new0 (TimeoutHandler, 1);
+  handler->cs = cs;
+  handler->timeout = timeout;
+
+  handler->source = g_timeout_source_new (dbus_timeout_get_interval (timeout));
+  g_source_set_callback (handler->source, timeout_handler_dispatch, handler,
+                         timeout_handler_source_finalized);
+  g_source_attach (handler->source, handler->cs->context);
+
+  cs->timeouts = g_slist_prepend (cs->timeouts, handler);
+
+  dbus_timeout_set_data (timeout, handler, timeout_handler_timeout_freed);
+}
+
+static void
+connection_setup_remove_timeout (ConnectionSetup *cs,
+                                 DBusTimeout       *timeout)
+{
+  TimeoutHandler *handler;
+
+  handler = dbus_timeout_get_data (timeout);
+
+  if (handler == NULL)
+    return;
+
+  timeout_handler_destroy_source (handler);
+}
+
+static void
+connection_setup_free (ConnectionSetup *cs)
+{
+  while (cs->ios)
+    io_handler_destroy_source (cs->ios->data);
+
+  while (cs->timeouts)
+    timeout_handler_destroy_source (cs->timeouts->data);
+
+  if (cs->message_queue_source)
+    {
+      GSource *source;
+
+      source = cs->message_queue_source;
+      cs->message_queue_source = NULL;
+
+      g_source_destroy (source);
+      g_source_unref (source);
+    }
+
+  g_main_context_unref (cs->context);
+  g_free (cs);
+}
+
+static dbus_bool_t
+add_watch (DBusWatch *watch,
+           gpointer   data)
+{
+  ConnectionSetup *cs;
+
+  cs = data;
+
+  connection_setup_add_watch (cs, watch);
+
+  return TRUE;
+}
+
+static void
+remove_watch (DBusWatch *watch,
+              gpointer   data)
+{
+  ConnectionSetup *cs;
+
+  cs = data;
+
+  connection_setup_remove_watch (cs, watch);
+}
+
+static void
+watch_toggled (DBusWatch *watch,
+               void      *data)
+{
+  /* Because we just exit on OOM, enable/disable is
+   * no different from add/remove
+   */
+  if (dbus_watch_get_enabled (watch))
+    add_watch (watch, data);
+  else
+    remove_watch (watch, data);
+}
+
+static dbus_bool_t
+add_timeout (DBusTimeout *timeout,
+             void        *data)
+{
+  ConnectionSetup *cs;
+
+  cs = data;
+
+  if (!dbus_timeout_get_enabled (timeout))
+    return TRUE;
+
+  connection_setup_add_timeout (cs, timeout);
+
+  return TRUE;
+}
+
+static void
+remove_timeout (DBusTimeout *timeout,
+                void        *data)
+{
+  ConnectionSetup *cs;
+
+  cs = data;
+
+  connection_setup_remove_timeout (cs, timeout);
+}
+
+static void
+timeout_toggled (DBusTimeout *timeout,
+                 void        *data)
+{
+  /* Because we just exit on OOM, enable/disable is
+   * no different from add/remove
+   */
+  if (dbus_timeout_get_enabled (timeout))
+    add_timeout (timeout, data);
+  else
+    remove_timeout (timeout, data);
+}
+
+static void
+wakeup_main (void *data)
+{
+  ConnectionSetup *cs = data;
+
+  g_main_context_wakeup (cs->context);
+}
+
+
+/* Move to a new context */
+static ConnectionSetup*
+connection_setup_new_from_old (GMainContext    *context,
+                               ConnectionSetup *old)
+{
+  GSList *tmp;
+  ConnectionSetup *cs;
+
+  g_assert (old->context != context);
+
+  cs = connection_setup_new (context, old->connection);
+
+  tmp = old->ios;
+  while (tmp != NULL)
+    {
+      IOHandler *handler = tmp->data;
+
+      connection_setup_add_watch (cs, handler->watch);
+
+      tmp = tmp->next;
+    }
+
+  tmp = old->timeouts;
+  while (tmp != NULL)
+    {
+      TimeoutHandler *handler = tmp->data;
+
+      connection_setup_add_timeout (cs, handler->timeout);
+
+      tmp = tmp->next;
+    }
+
+  return cs;
+}
+
+static void
+ensure_slots (void)
+{
+  G_LOCK (main_loop_lock);
+  if (_dbus_gmain_connection_slot < 0)
+    {
+      dbus_connection_allocate_data_slot (&_dbus_gmain_connection_slot);
+      if (_dbus_gmain_connection_slot < 0)
+        {
+          G_UNLOCK (main_loop_lock);
+          _e_dbus_oom ();
+        }
+    }
+  if (server_slot < 0)
+    {
+      dbus_server_allocate_data_slot (&server_slot);
+      if (server_slot < 0)
+        {
+          G_UNLOCK (main_loop_lock);
+          _e_dbus_oom ();
+        }
+    }
+  G_UNLOCK (main_loop_lock);
+}
+
+/**
+ * _e_dbus_integrate_dbus_1_connection:
+ * @connection: A #DBusConnection.
+ * @context: the #GMainContext or #NULL for default context
+ *
+ * Sets the watch and timeout functions of @connection to integrate
+ * the connection with the GLib main loop.  Pass in #NULL for the
+ * #GMainContext unless you're doing something specialized.
+ *
+ * If called twice for the same context, does nothing the second
+ * time. If called once with context A and once with context B,
+ * context B replaces context A as the context monitoring the
+ * connection.
+ */
+void
+_e_dbus_integrate_dbus_1_connection (DBusConnection *connection,
+                                     GMainContext   *context)
+{
+  ConnectionSetup *old_setup;
+  ConnectionSetup *cs;
+
+  ensure_slots ();
+
+  if (context == NULL)
+    context = g_main_context_default ();
+
+  cs = NULL;
+
+  old_setup = dbus_connection_get_data (connection, _dbus_gmain_connection_slot);
+  if (old_setup != NULL)
+    {
+      if (old_setup->context == context)
+        return; /* nothing to do */
+
+      cs = connection_setup_new_from_old (context, old_setup);
+
+      /* Nuke the old setup */
+      dbus_connection_set_data (connection, _dbus_gmain_connection_slot, NULL, NULL);
+      old_setup = NULL;
+    }
+
+  if (cs == NULL)
+    cs = connection_setup_new (context, connection);
+
+  if (!dbus_connection_set_data (connection, _dbus_gmain_connection_slot, cs,
+                                 (DBusFreeFunction)connection_setup_free))
+    goto nomem;
+
+  if (!dbus_connection_set_watch_functions (connection,
+                                            add_watch,
+                                            remove_watch,
+                                            watch_toggled,
+                                            cs, NULL))
+    goto nomem;
+
+  if (!dbus_connection_set_timeout_functions (connection,
+                                              add_timeout,
+                                              remove_timeout,
+                                              timeout_toggled,
+                                              cs, NULL))
+    goto nomem;
+
+  dbus_connection_set_wakeup_main_function (connection,
+                                            wakeup_main,
+                                            cs, NULL);
+
+  return;
+
+ nomem:
+  _e_dbus_oom ();
+}
+
+/**
+ * _e_dbus_unintegrate_dbus_1_connection:
+ * @connection: A #DBusConnection.
+ *
+ * Removes mainloop integration for @connection.
+ **/
+void
+_e_dbus_unintegrate_dbus_1_connection (DBusConnection *connection)
+{
+  ensure_slots ();
+
+  if (!dbus_connection_set_data (connection, _dbus_gmain_connection_slot, NULL,
+                                 NULL))
+    goto nomem;
+
+  if (!dbus_connection_set_watch_functions (connection,
+                                            NULL,
+                                            NULL,
+                                            NULL,
+                                            NULL, NULL))
+    goto nomem;
+
+  if (!dbus_connection_set_timeout_functions (connection,
+                                              NULL,
+                                              NULL,
+                                              NULL,
+                                              NULL, NULL))
+    goto nomem;
+
+  dbus_connection_set_wakeup_main_function (connection,
+                                            NULL,
+                                            NULL, NULL);
+
+  return;
+
+ nomem:
+  _e_dbus_oom ();
+}
+
+/**
+ * _e_dbus_integrate_dbus_1_server:
+ * @server: A #DBusServer.
+ * @context: the #GMainContext or #NULL for default
+ *
+ * Sets the watch and timeout functions of a #DBusServer
+ * to integrate the server with the GLib main loop.
+ * In most cases the context argument should be #NULL.
+ *
+ * If called twice for the same context, does nothing the second
+ * time. If called once with context A and once with context B,
+ * context B replaces context A as the context monitoring the
+ * connection.
+ */
+void
+_e_dbus_integrate_dbus_1_server (DBusServer   *server,
+                                 GMainContext *context)
+{
+  ConnectionSetup *old_setup;
+  ConnectionSetup *cs;
+
+  ensure_slots ();
+
+  dbus_server_allocate_data_slot (&server_slot);
+  if (server_slot < 0)
+    goto nomem;
+
+  if (context == NULL)
+    context = g_main_context_default ();
+
+  cs = NULL;
+
+  old_setup = dbus_server_get_data (server, server_slot);
+  if (old_setup != NULL)
+    {
+      if (old_setup->context == context)
+        return; /* nothing to do */
+
+      cs = connection_setup_new_from_old (context, old_setup);
+
+      /* Nuke the old setup */
+      dbus_server_set_data (server, server_slot, NULL, NULL);
+      old_setup = NULL;
+    }
+
+  if (cs == NULL)
+    cs = connection_setup_new (context, NULL);
+
+  if (!dbus_server_set_data (server, server_slot, cs,
+                             (DBusFreeFunction)connection_setup_free))
+    goto nomem;
+
+  if (!dbus_server_set_watch_functions (server,
+                                        add_watch,
+                                        remove_watch,
+                                        watch_toggled,
+                                        cs, NULL))
+    goto nomem;
+
+  if (!dbus_server_set_timeout_functions (server,
+                                          add_timeout,
+                                          remove_timeout,
+                                          timeout_toggled,
+                                          cs, NULL))
+    goto nomem;
+
+  return;
+
+ nomem:
+  _e_dbus_oom ();
+}
+
+/**
+ * _e_dbus_unintegrate_dbus_1_server:
+ * @server: A #DBusServer.
+ *
+ * Removes mainloop integration for @server.
+ **/
+void
+_e_dbus_unintegrate_dbus_1_server (DBusServer *server)
+{
+  ensure_slots ();
+
+  if (!dbus_server_set_data (server, server_slot, NULL,
+                             NULL))
+    goto nomem;
+
+  if (!dbus_server_set_watch_functions (server,
+                                        NULL,
+                                        NULL,
+                                        NULL,
+                                        NULL, NULL))
+    goto nomem;
+
+  if (!dbus_server_set_timeout_functions (server,
+                                          NULL,
+                                          NULL,
+                                          NULL,
+                                          NULL, NULL))
+    goto nomem;
+
+  return;
+
+ nomem:
+  _e_dbus_oom ();
+}
diff --git a/libedbus/gdbusprivate.h b/libedbus/gdbusprivate.h
new file mode 100644
index 0000000..2d5a8f8
--- /dev/null
+++ b/libedbus/gdbusprivate.h
@@ -0,0 +1,52 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#if !defined (E_DBUS_COMPILATION)
+#error "gdbusprivate.h is a private header file."
+#endif
+
+#ifndef __E_DBUS_PRIVATE_H__
+#define __E_DBUS_PRIVATE_H__
+
+#include <gdbus/gdbustypes.h>
+#include <dbus/dbus.h>
+
+G_BEGIN_DECLS
+
+void _e_dbus_oom (void);
+
+void _e_dbus_integrate_dbus_1_connection   (DBusConnection *connection,
+                                            GMainContext   *context);
+void _e_dbus_unintegrate_dbus_1_connection (DBusConnection *connection);
+
+void _e_dbus_integrate_dbus_1_server       (DBusServer   *server,
+                                            GMainContext *context);
+void _e_dbus_unintegrate_dbus_1_server     (DBusServer   *server);
+
+void _e_dbus_connection_send_dbus_1_message (EDBusConnection    *connection,
+                                             DBusMessage        *message);
+
+EDBusConnection *_e_dbus_connection_new_for_dbus_1_connection (DBusConnection *dbus_1_connection);
+
+G_END_DECLS
+
+#endif /* __E_DBUS_PRIVATE_H__ */
diff --git a/libedbus/gdbusproxy.c b/libedbus/gdbusproxy.c
new file mode 100644
index 0000000..f840e0a
--- /dev/null
+++ b/libedbus/gdbusproxy.c
@@ -0,0 +1,1311 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <glib/gi18n.h>
+#include <gobject/gvaluecollector.h>
+
+#include "gdbusproxy.h"
+#include "gdbusenumtypes.h"
+#include "gdbusconnection.h"
+#include "gdbuserror.h"
+#include "gdbus-marshal.h"
+#include "gdbusprivate.h"
+
+/**
+ * SECTION:gdbusproxy
+ * @short_description: Base class for proxies
+ * @include: gdbus/gdbus.h
+ *
+ * #EDBusProxy is a base class used for proxies to access a D-Bus
+ * interface on a remote object. A #EDBusProxy can only be constructed
+ * for unique name bus and does not track whether the name
+ * vanishes. Use e_bus_watch_proxy() to construct #EDBusProxy proxies
+ * for owners of a well-known names.
+ */
+
+struct _EDBusProxyPrivate
+{
+  EDBusConnection *connection;
+  EDBusProxyFlags flags;
+  gchar *unique_bus_name;
+  gchar *object_path;
+  gchar *interface_name;
+  gint timeout_msec;
+
+  /* gchar* -> EVariant* */
+  GHashTable *properties;
+
+  guint properties_changed_subscriber_id;
+  guint signals_subscriber_id;
+};
+
+enum
+{
+  PROP_0,
+  PROP_G_CONNECTION,
+  PROP_G_UNIQUE_BUS_NAME,
+  PROP_G_FLAGS,
+  PROP_G_OBJECT_PATH,
+  PROP_G_INTERFACE_NAME,
+  PROP_G_DEFAULT_TIMEOUT,
+};
+
+enum
+{
+  PROPERTIES_CHANGED_SIGNAL,
+  SIGNAL_SIGNAL,
+  LAST_SIGNAL,
+};
+
+static void e_dbus_proxy_constructed (GObject *object);
+
+guint signals[LAST_SIGNAL] = {0};
+
+static void initable_iface_init       (GInitableIface *initable_iface);
+static void async_initable_iface_init (GAsyncInitableIface *async_initable_iface);
+
+G_DEFINE_TYPE_WITH_CODE (EDBusProxy, e_dbus_proxy, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)
+                         G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
+                         );
+
+static void
+e_dbus_proxy_finalize (GObject *object)
+{
+  EDBusProxy *proxy = E_DBUS_PROXY (object);
+
+  if (proxy->priv->properties_changed_subscriber_id > 0)
+    {
+      e_dbus_connection_signal_unsubscribe (proxy->priv->connection,
+                                            proxy->priv->properties_changed_subscriber_id);
+    }
+
+  if (proxy->priv->signals_subscriber_id > 0)
+    {
+      e_dbus_connection_signal_unsubscribe (proxy->priv->connection,
+                                            proxy->priv->signals_subscriber_id);
+    }
+
+  g_object_unref (proxy->priv->connection);
+  g_free (proxy->priv->unique_bus_name);
+  g_free (proxy->priv->object_path);
+  g_free (proxy->priv->interface_name);
+  if (proxy->priv->properties != NULL)
+    g_hash_table_unref (proxy->priv->properties);
+
+  if (G_OBJECT_CLASS (e_dbus_proxy_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (e_dbus_proxy_parent_class)->finalize (object);
+}
+
+static void
+e_dbus_proxy_get_property (GObject    *object,
+                           guint       prop_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+  EDBusProxy *proxy = E_DBUS_PROXY (object);
+
+  switch (prop_id)
+    {
+    case PROP_G_CONNECTION:
+      g_value_set_object (value, proxy->priv->connection);
+      break;
+
+    case PROP_G_FLAGS:
+      g_value_set_flags (value, proxy->priv->flags);
+      break;
+
+    case PROP_G_UNIQUE_BUS_NAME:
+      g_value_set_string (value, proxy->priv->unique_bus_name);
+      break;
+
+    case PROP_G_OBJECT_PATH:
+      g_value_set_string (value, proxy->priv->object_path);
+      break;
+
+    case PROP_G_INTERFACE_NAME:
+      g_value_set_string (value, proxy->priv->interface_name);
+      break;
+
+    case PROP_G_DEFAULT_TIMEOUT:
+      g_value_set_int (value, proxy->priv->timeout_msec);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+e_dbus_proxy_set_property (GObject      *object,
+                           guint         prop_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
+{
+  EDBusProxy *proxy = E_DBUS_PROXY (object);
+
+  switch (prop_id)
+    {
+    case PROP_G_CONNECTION:
+      proxy->priv->connection = g_value_dup_object (value);
+      break;
+
+    case PROP_G_FLAGS:
+      proxy->priv->flags = g_value_get_flags (value);
+      break;
+
+    case PROP_G_UNIQUE_BUS_NAME:
+      proxy->priv->unique_bus_name = g_value_dup_string (value);
+      break;
+
+    case PROP_G_OBJECT_PATH:
+      proxy->priv->object_path = g_value_dup_string (value);
+      break;
+
+    case PROP_G_INTERFACE_NAME:
+      proxy->priv->interface_name = g_value_dup_string (value);
+      break;
+
+    case PROP_G_DEFAULT_TIMEOUT:
+      e_dbus_proxy_set_default_timeout (proxy, g_value_get_int (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+e_dbus_proxy_class_init (EDBusProxyClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize     = e_dbus_proxy_finalize;
+  gobject_class->set_property = e_dbus_proxy_set_property;
+  gobject_class->get_property = e_dbus_proxy_get_property;
+  gobject_class->constructed  = e_dbus_proxy_constructed;
+
+  /* Note that all property names are prefixed to avoid collisions with D-Bus property names
+   * in derived classes */
+
+  /**
+   * EDBusProxy:g-connection:
+   *
+   * The @EDBusConnection the proxy is for.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_G_CONNECTION,
+                                   g_param_spec_object ("g-connection",
+                                                        _("g-connection"),
+                                                        _("The connection the proxy is for"),
+                                                        E_TYPE_DBUS_CONNECTION,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * EDBusProxy:g-flags:
+   *
+   * Flags from the #EDBusProxyFlags enumeration.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_G_FLAGS,
+                                   g_param_spec_flags ("g-flags",
+                                                       _("g-flags"),
+                                                       _("Flags for the proxy"),
+                                                       E_TYPE_DBUS_PROXY_FLAGS,
+                                                       E_DBUS_PROXY_FLAGS_NONE,
+                                                       G_PARAM_READABLE |
+                                                       G_PARAM_WRITABLE |
+                                                       G_PARAM_CONSTRUCT_ONLY |
+                                                       G_PARAM_STATIC_NAME |
+                                                       G_PARAM_STATIC_BLURB |
+                                                       G_PARAM_STATIC_NICK));
+
+  /**
+   * EDBusProxy:g-unique-bus-name:
+   *
+   * The unique bus name the proxy is for.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_G_UNIQUE_BUS_NAME,
+                                   g_param_spec_string ("g-unique-bus-name",
+                                                        _("g-unique-bus-name"),
+                                                        _("The unique bus name the proxy is for"),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * EDBusProxy:g-object-path:
+   *
+   * The object path the proxy is for.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_G_OBJECT_PATH,
+                                   g_param_spec_string ("g-object-path",
+                                                        _("g-object-path"),
+                                                        _("The object path the proxy is for"),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * EDBusProxy:g-interface-name:
+   *
+   * The D-Bus interface name the proxy is for.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_G_INTERFACE_NAME,
+                                   g_param_spec_string ("g-interface-name",
+                                                        _("g-interface-name"),
+                                                        _("The D-Bus interface name the proxy is for"),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+  /**
+   * EDBusProxy:g-default-timeout:
+   *
+   * The timeout to use if -1 (specifying default timeout) is passed
+   * as @timeout_msec in the e_dbus_proxy_invoke_method() and
+   * e_dbus_proxy_invoke_method_sync() functions.
+   *
+   * This allows applications to set a proxy-wide timeout for all
+   * remote method invocations on the proxy. If this property is -1,
+   * the default timeout (typically 25 seconds) is used. If set to
+   * %G_MAXINT, then no timeout is used.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_G_DEFAULT_TIMEOUT,
+                                   g_param_spec_int ("g-default-timeout",
+                                                     _("Default Timeout"),
+                                                     _("Timeout for remote method invocation"),
+                                                     -1,
+                                                     G_MAXINT,
+                                                     -1,
+                                                     G_PARAM_READABLE |
+                                                     G_PARAM_WRITABLE |
+                                                     G_PARAM_CONSTRUCT |
+                                                     G_PARAM_STATIC_NAME |
+                                                     G_PARAM_STATIC_BLURB |
+                                                     G_PARAM_STATIC_NICK));
+
+  /**
+   * EDBusProxy::g-properties-changed:
+   * @proxy: The #EDBusProxy emitting the signal.
+   * @changed_properties: A #GHashTable containing the properties that changed.
+   *
+   * Emitted when one or more D-Bus properties on @proxy changes. The cached properties
+   * are already replaced when this signal fires.
+   */
+  signals[PROPERTIES_CHANGED_SIGNAL] = g_signal_new ("g-properties-changed",
+                                                     E_TYPE_DBUS_PROXY,
+                                                     G_SIGNAL_RUN_LAST,
+                                                     G_STRUCT_OFFSET (EDBusProxyClass, properties_changed),
+                                                     NULL,
+                                                     NULL,
+                                                     g_cclosure_marshal_VOID__BOXED,
+                                                     G_TYPE_NONE,
+                                                     1,
+                                                     G_TYPE_HASH_TABLE);
+
+  /**
+   * EDBusProxy::g-signal:
+   * @proxy: The #EDBusProxy emitting the signal.
+   * @sender_name: The sender of the signal or %NULL if the connection is not a bus connection.
+   * @signal_name: The name of the signal.
+   * @parameters: A #EVariant tuple with parameters for the signal.
+   *
+   * Emitted when a signal from the remote object and interface that @proxy is for, has been received.
+   **/
+  signals[SIGNAL_SIGNAL] = g_signal_new ("g-signal",
+                                         E_TYPE_DBUS_PROXY,
+                                         G_SIGNAL_RUN_LAST,
+                                         G_STRUCT_OFFSET (EDBusProxyClass, signal),
+                                         NULL,
+                                         NULL,
+                                         _gdbus_marshal_VOID__STRING_STRING_BOXED,
+                                         G_TYPE_NONE,
+                                         3,
+                                         G_TYPE_STRING,
+                                         G_TYPE_STRING,
+                                         E_TYPE_VARIANT);
+
+
+  g_type_class_add_private (klass, sizeof (EDBusProxyPrivate));
+}
+
+static void
+e_dbus_proxy_init (EDBusProxy *proxy)
+{
+  proxy->priv = G_TYPE_INSTANCE_GET_PRIVATE (proxy, E_TYPE_DBUS_PROXY, EDBusProxyPrivate);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_proxy_get_cached_property_names:
+ * @proxy: A #EDBusProxy.
+ * @error: Return location for error or %NULL.
+ *
+ * Gets the names of all cached properties on @proxy.
+ *
+ * Returns: A %NULL-terminated array of strings or %NULL if @error is set. Free with
+ * g_strfreev().
+ */
+gchar **
+e_dbus_proxy_get_cached_property_names (EDBusProxy          *proxy,
+                                        GError             **error)
+{
+  gchar **names;
+  GPtrArray *p;
+  GHashTableIter iter;
+  const gchar *key;
+
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+
+  names = NULL;
+
+  if (proxy->priv->flags & E_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES)
+    {
+      g_set_error (error,
+                   E_DBUS_ERROR,
+                   E_DBUS_ERROR_FAILED,
+                   _("Properties are not available (proxy created with E_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES)"));
+      goto out;
+    }
+
+  p = g_ptr_array_new ();
+
+  g_hash_table_iter_init (&iter, proxy->priv->properties);
+  while (g_hash_table_iter_next (&iter, (gpointer) &key, NULL))
+    {
+      g_ptr_array_add (p, g_strdup (key));
+    }
+  g_ptr_array_sort (p, (GCompareFunc) g_strcmp0);
+  g_ptr_array_add (p, NULL);
+
+  names = (gchar **) g_ptr_array_free (p, FALSE);
+
+ out:
+  return names;
+}
+
+/**
+ * e_dbus_proxy_get_cached_property:
+ * @proxy: A #EDBusProxy.
+ * @property_name: Property name.
+ * @error: Return location for error or %NULL.
+ *
+ * Looks up the value for a property from the cache. This call does no blocking IO.
+ *
+ * Normally you will not need to modify the returned variant since it is updated automatically
+ * in response to <literal>org.freedesktop.DBus.Properties.PropertiesChanged</literal>
+ * D-Bus signals (which also causes #EDBusProxy::g-properties-changed to be emitted).
+ *
+ * However, for properties for which said D-Bus signal is not emitted, you
+ * can catch other signals and modify the returned variant accordingly (remember to emit
+ * #EDBusProxy::g-properties-changed yourself).
+ *
+ * Returns: A reference to the #EVariant instance that holds the value for @property_name or
+ * %NULL if @error is set. Free the reference with e_variant_unref().
+ */
+EVariant *
+e_dbus_proxy_get_cached_property (EDBusProxy          *proxy,
+                                  const gchar         *property_name,
+                                  GError             **error)
+{
+  EVariant *value;
+
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+  g_return_val_if_fail (property_name != NULL, NULL);
+
+  value = NULL;
+
+  if (proxy->priv->flags & E_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES)
+    {
+      g_set_error (error,
+                   E_DBUS_ERROR,
+                   E_DBUS_ERROR_FAILED,
+                   _("Properties are not available (proxy created with E_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES)"));
+      goto out;
+    }
+
+  value = g_hash_table_lookup (proxy->priv->properties, property_name);
+  if (value == NULL)
+    {
+      g_set_error (error,
+                   E_DBUS_ERROR,
+                   E_DBUS_ERROR_FAILED,
+                   _("No property with name %s"),
+                   property_name);
+      goto out;
+    }
+
+  e_variant_ref (value);
+
+ out:
+
+  return value;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_signal_received (EDBusConnection  *connection,
+                    const gchar      *sender_name,
+                    const gchar      *object_path,
+                    const gchar      *interface_name,
+                    const gchar      *signal_name,
+                    EVariant         *parameters,
+                    gpointer          user_data)
+{
+  EDBusProxy *proxy = E_DBUS_PROXY (user_data);
+
+  g_signal_emit (proxy,
+                 signals[SIGNAL_SIGNAL],
+                 0,
+                 sender_name,
+                 signal_name,
+                 parameters);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_properties_changed (EDBusConnection  *connection,
+                       const gchar      *sender_name,
+                       const gchar      *object_path,
+                       const gchar      *interface_name,
+                       const gchar      *signal_name,
+                       EVariant         *parameters,
+                       gpointer          user_data)
+{
+  EDBusProxy *proxy = E_DBUS_PROXY (user_data);
+  GError *error;
+  const gchar *interface_name_for_signal;
+  EVariantIter iter;
+  EVariant *item;
+  GHashTable *changed_properties;
+
+  error = NULL;
+
+#if 0 // TODO!
+  /* Ignore this signal if properties are not yet available
+   *
+   * (can happen in the window between subscribing to PropertiesChanged() and until
+   *  org.freedesktop.DBus.Properties.GetAll() returns)
+   */
+  if (!proxy->priv->properties_available)
+    goto out;
+#endif
+
+  e_variant_get (parameters,
+                 "(sa{sv})",
+                 &interface_name_for_signal,
+                 &iter);
+
+  if (g_strcmp0 (interface_name_for_signal, proxy->priv->interface_name) != 0)
+    goto out;
+
+  changed_properties = g_hash_table_new_full (g_str_hash,
+                                              g_str_equal,
+                                              g_free,
+                                              (GDestroyNotify) e_variant_unref);
+
+  while ((item = e_variant_iter_next_value (&iter)))
+    {
+      const gchar *key;
+      EVariant *value;
+
+      e_variant_get (item,
+                     "{sv}",
+                     &key,
+                     &value);
+
+      g_hash_table_insert (proxy->priv->properties,
+                           g_strdup (key),
+                           value); /* steals value */
+
+      g_hash_table_insert (changed_properties,
+                           g_strdup (key),
+                           e_variant_ref (value));
+    }
+
+
+  /* emit signal */
+  g_signal_emit (proxy, signals[PROPERTIES_CHANGED_SIGNAL], 0, changed_properties);
+
+  g_hash_table_unref (changed_properties);
+
+ out:
+  ;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+e_dbus_proxy_constructed (GObject *object)
+{
+  if (G_OBJECT_CLASS (e_dbus_proxy_parent_class)->constructed != NULL)
+    G_OBJECT_CLASS (e_dbus_proxy_parent_class)->constructed (object);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+subscribe_to_signals (EDBusProxy *proxy)
+{
+  if (!(proxy->priv->flags & E_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES))
+    {
+      /* subscribe to PropertiesChanged() */
+      proxy->priv->properties_changed_subscriber_id =
+        e_dbus_connection_signal_subscribe (proxy->priv->connection,
+                                            proxy->priv->unique_bus_name,
+                                            "org.freedesktop.DBus.Properties",
+                                            "PropertiesChanged",
+                                            proxy->priv->object_path,
+                                            proxy->priv->interface_name,
+                                            on_properties_changed,
+                                            proxy,
+                                            NULL);
+    }
+
+  if (!(proxy->priv->flags & E_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS))
+    {
+      /* subscribe to all signals for the object */
+      proxy->priv->signals_subscriber_id =
+        e_dbus_connection_signal_subscribe (proxy->priv->connection,
+                                            proxy->priv->unique_bus_name,
+                                            proxy->priv->interface_name,
+                                            NULL,                        /* member */
+                                            proxy->priv->object_path,
+                                            NULL,                        /* arg0 */
+                                            on_signal_received,
+                                            proxy,
+                                            NULL);
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+process_get_all_reply (EDBusProxy *proxy,
+                       EVariant   *result)
+{
+  EVariantIter iter;
+  EVariant *item;
+
+  proxy->priv->properties = g_hash_table_new_full (g_str_hash,
+                                                   g_str_equal,
+                                                   g_free,
+                                                   (GDestroyNotify) e_variant_unref);
+
+  e_variant_iter_init (&iter, e_variant_get_child_value (result, 0));
+  while ((item = e_variant_iter_next_value (&iter)) != NULL)
+    {
+      const gchar *key;
+      EVariant *value;
+
+      e_variant_get (item,
+                     "{sv}",
+                     &key,
+                     &value);
+      //g_print ("got %s -> %s\n", key, e_variant_markup_print (value, FALSE, 0, 0));
+
+      g_hash_table_insert (proxy->priv->properties,
+                           g_strdup (key),
+                           value); /* steals value */
+    }
+}
+
+static gboolean
+initable_init (GInitable       *initable,
+               GCancellable    *cancellable,
+               GError         **error)
+{
+  EDBusProxy *proxy = E_DBUS_PROXY (initable);
+  EVariant *result;
+  gboolean ret;
+
+  ret = FALSE;
+
+  if (!(proxy->priv->flags & E_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES))
+    {
+      /* load all properties synchronously */
+      result = e_dbus_connection_invoke_method_sync (proxy->priv->connection,
+                                                     proxy->priv->unique_bus_name,
+                                                     proxy->priv->object_path,
+                                                     "org.freedesktop.DBus.Properties",
+                                                     "GetAll",
+                                                     e_variant_new ("(s)", proxy->priv->interface_name),
+                                                     -1,           /* timeout */
+                                                     cancellable,
+                                                     error);
+      if (result == NULL)
+        goto out;
+
+      process_get_all_reply (proxy, result);
+
+      e_variant_unref (result);
+    }
+
+  subscribe_to_signals (proxy);
+
+  ret = TRUE;
+
+ out:
+  return ret;
+}
+
+static void
+initable_iface_init (GInitableIface *initable_iface)
+{
+  initable_iface->init = initable_init;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+get_all_cb (EDBusConnection *connection,
+            GAsyncResult    *res,
+            gpointer         user_data)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
+  EVariant *result;
+  GError *error;
+
+  result = e_dbus_connection_invoke_method_finish (connection,
+                                                   res,
+                                                   &error);
+  if (result == NULL)
+    {
+      g_simple_async_result_set_from_error (simple, error);
+      g_error_free (error);
+    }
+  else
+    {
+      g_simple_async_result_set_op_res_gpointer (simple,
+                                                 result,
+                                                 (GDestroyNotify) e_variant_unref);
+    }
+
+  g_simple_async_result_complete_in_idle (simple);
+  g_object_unref (simple);
+}
+
+static void
+async_initable_init_async (GAsyncInitable     *initable,
+                           gint                io_priority,
+                           GCancellable       *cancellable,
+                           GAsyncReadyCallback callback,
+                           gpointer            user_data)
+{
+  EDBusProxy *proxy = E_DBUS_PROXY (initable);
+  GSimpleAsyncResult *simple;
+
+  simple = g_simple_async_result_new (G_OBJECT (proxy),
+                                      callback,
+                                      user_data,
+                                      NULL);
+
+  if (!(proxy->priv->flags & E_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES))
+    {
+      /* load all properties asynchronously */
+      e_dbus_connection_invoke_method (proxy->priv->connection,
+                                       proxy->priv->unique_bus_name,
+                                       proxy->priv->object_path,
+                                       "org.freedesktop.DBus.Properties",
+                                       "GetAll",
+                                       e_variant_new ("(s)", proxy->priv->interface_name),
+                                       -1,           /* timeout */
+                                       cancellable,
+                                       (GAsyncReadyCallback) get_all_cb,
+                                       simple);
+    }
+  else
+    {
+      g_simple_async_result_complete_in_idle (simple);
+      g_object_unref (simple);
+    }
+}
+
+static gboolean
+async_initable_init_finish (GAsyncInitable  *initable,
+                            GAsyncResult    *res,
+                            GError         **error)
+{
+  EDBusProxy *proxy = E_DBUS_PROXY (initable);
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+  EVariant *result;
+  gboolean ret;
+
+  ret = FALSE;
+
+  result = g_simple_async_result_get_op_res_gpointer (simple);
+  if (result == NULL)
+    goto out;
+
+  process_get_all_reply (proxy, result);
+
+  subscribe_to_signals (proxy);
+
+  ret = TRUE;
+
+ out:
+  return ret;
+}
+
+static void
+async_initable_iface_init (GAsyncInitableIface *async_initable_iface)
+{
+  async_initable_iface->init_async = async_initable_init_async;
+  async_initable_iface->init_finish = async_initable_init_finish;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_proxy_new:
+ * @connection: A #EDBusConnection.
+ * @object_type: Either #E_TYPE_DBUS_PROXY or the #GType for the #EDBusProxy<!-- -->-derived type of proxy to create.
+ * @flags: Flags used when constructing the proxy.
+ * @unique_bus_name: A unique bus name or %NULL if @connection is not a message bus connection.
+ * @object_path: An object path.
+ * @interface_name: A D-Bus interface name.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: Callback function to invoke when the proxy is ready.
+ * @user_data: User data to pass to @callback.
+ *
+ * Creates a proxy for accessing @interface_name on the remote object at @object_path
+ * owned by @unique_bus_name at @connection and asynchronously loads D-Bus properties unless the
+ * #E_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES flag is used. Connect to the
+ * #EDBusProxy::g-properties-changed signal to get notified about property changes.
+ *
+ * If the #E_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS flag is not set, also sets up
+ * match rules for signals. Connect to the #EDBusProxy::g-signal signal
+ * to handle signals from the remote object.
+ *
+ * This is a failable asynchronous constructor - when the proxy is
+ * ready, @callback will be invoked and you can use
+ * e_dbus_proxy_new_finish() to get the result.
+ *
+ * See e_dbus_proxy_new_sync() and for a synchronous version of this constructor.
+ **/
+void
+e_dbus_proxy_new (EDBusConnection     *connection,
+                  GType                object_type,
+                  EDBusProxyFlags      flags,
+                  const gchar         *unique_bus_name,
+                  const gchar         *object_path,
+                  const gchar         *interface_name,
+                  GCancellable        *cancellable,
+                  GAsyncReadyCallback  callback,
+                  gpointer             user_data)
+{
+  g_return_if_fail (G_IS_DBUS_CONNECTION (connection));
+  g_return_if_fail (g_type_is_a (object_type, E_TYPE_DBUS_PROXY));
+  /* TODO: check that unique_bus_name is unique */
+  g_return_if_fail ((e_dbus_connection_get_bus_type (connection) == G_BUS_TYPE_NONE && unique_bus_name == NULL) ||
+                    (e_dbus_connection_get_bus_type (connection) != G_BUS_TYPE_NONE || unique_bus_name != NULL));
+  g_return_if_fail (object_path != NULL);
+  g_return_if_fail (interface_name);
+
+  g_async_initable_new_async (object_type,
+                              G_PRIORITY_DEFAULT,
+                              cancellable,
+                              callback,
+                              user_data,
+                              "g-flags", flags,
+                              "g-unique-bus-name", unique_bus_name,
+                              "g-connection", connection,
+                              "g-object-path", object_path,
+                              "g-interface-name", interface_name,
+                              NULL);
+}
+
+/**
+ * e_dbus_proxy_new_finish:
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback function passed to e_dbus_proxy_new().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes creating a #EDBusProxy.
+ *
+ * Returns: A #EDBusProxy or %NULL if @error is set. Free with g_object_unref().
+ **/
+EDBusProxy *
+e_dbus_proxy_new_finish (GAsyncResult  *res,
+                         GError       **error)
+{
+  GObject *object;
+  GObject *source_object;
+
+  source_object = g_async_result_get_source_object (res);
+  g_assert (source_object != NULL);
+
+  object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
+                                        res,
+                                        error);
+  g_object_unref (source_object);
+
+  if (object != NULL)
+    return E_DBUS_PROXY (object);
+  else
+    return NULL;
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_proxy_new_sync:
+ * @connection: A #EDBusConnection.
+ * @object_type: Either #E_TYPE_DBUS_PROXY or the #GType for the #EDBusProxy<!-- -->-derived type of proxy to create.
+ * @flags: Flags used when constructing the proxy.
+ * @unique_bus_name: A unique bus name or %NULL if @connection is not a message bus connection.
+ * @object_path: An object path.
+ * @interface_name: A D-Bus interface name.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Creates a proxy for accessing @interface_name on the remote object at @object_path
+ * owned by @unique_bus_name at @connection and synchronously loads D-Bus properties unless the
+ * #E_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES flag is used.
+ *
+ * If the #E_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS flag is not set, also sets up
+ * match rules for signals. Connect to the #EDBusProxy::g-signal signal
+ * to handle signals from the remote object.
+ *
+ * This is a synchronous failable constructor. See e_dbus_proxy_new()
+ * and e_dbus_proxy_new_finish() for the asynchronous version.
+ *
+ * Returns: A #EDBusProxy or %NULL if error is set. Free with g_object_unref().
+ **/
+EDBusProxy *
+e_dbus_proxy_new_sync (EDBusConnection     *connection,
+                       GType                object_type,
+                       EDBusProxyFlags      flags,
+                       const gchar         *unique_bus_name,
+                       const gchar         *object_path,
+                       const gchar         *interface_name,
+                       GCancellable        *cancellable,
+                       GError             **error)
+{
+  GInitable *initable;
+
+  g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
+  g_return_val_if_fail (g_type_is_a (object_type, E_TYPE_DBUS_PROXY), NULL);
+  g_return_val_if_fail ((e_dbus_connection_get_bus_type (connection) == G_BUS_TYPE_NONE && unique_bus_name == NULL) ||
+                        (e_dbus_connection_get_bus_type (connection) != G_BUS_TYPE_NONE || unique_bus_name != NULL),
+                        NULL);
+  g_return_val_if_fail (object_path != NULL, NULL);
+  g_return_val_if_fail (interface_name, NULL);
+
+  initable = g_initable_new (object_type,
+                             cancellable,
+                             error,
+                             "g-flags", flags,
+                             "g-unique-bus-name", unique_bus_name,
+                             "g-connection", connection,
+                             "g-object-path", object_path,
+                             "g-interface-name", interface_name,
+                             NULL);
+  if (initable != NULL)
+    return E_DBUS_PROXY (initable);
+  else
+    return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_proxy_get_connection:
+ * @proxy: A #EDBusProxy.
+ *
+ * Gets the connection @proxy is for.
+ *
+ * Returns: A #EDBusConnection owned by @proxy. Do not free.
+ **/
+EDBusConnection *
+e_dbus_proxy_get_connection (EDBusProxy *proxy)
+{
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+  return proxy->priv->connection;
+}
+
+/**
+ * e_dbus_proxy_get_flags:
+ * @proxy: A #EDBusProxy.
+ *
+ * Gets the flags that @proxy was constructed with.
+ *
+ * Returns: Flags from the #EDBusProxyFlags enumeration.
+ **/
+EDBusProxyFlags
+e_dbus_proxy_get_flags (EDBusProxy *proxy)
+{
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), 0);
+  return proxy->priv->flags;
+}
+
+/**
+ * e_dbus_proxy_get_unique_bus_name:
+ * @proxy: A #EDBusProxy.
+ *
+ * Gets the unique bus name @proxy is for.
+ *
+ * Returns: A string owned by @proxy. Do not free.
+ **/
+const gchar *
+e_dbus_proxy_get_unique_bus_name (EDBusProxy *proxy)
+{
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+  return proxy->priv->unique_bus_name;
+}
+
+/**
+ * e_dbus_proxy_get_object_path:
+ * @proxy: A #EDBusProxy.
+ *
+ * Gets the object path @proxy is for.
+ *
+ * Returns: A string owned by @proxy. Do not free.
+ **/
+const gchar *
+e_dbus_proxy_get_object_path (EDBusProxy *proxy)
+{
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+  return proxy->priv->object_path;
+}
+
+/**
+ * e_dbus_proxy_get_interface_name:
+ * @proxy: A #EDBusProxy.
+ *
+ * Gets the D-Bus interface name @proxy is for.
+ *
+ * Returns: A string owned by @proxy. Do not free.
+ **/
+const gchar *
+e_dbus_proxy_get_interface_name (EDBusProxy *proxy)
+{
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+  return proxy->priv->interface_name;
+}
+
+/**
+ * e_dbus_proxy_get_default_timeout:
+ * @proxy: A #EDBusProxy.
+ *
+ * Gets the timeout to use if -1 (specifying default timeout) is
+ * passed as @timeout_msec in the e_dbus_proxy_invoke_method() and
+ * e_dbus_proxy_invoke_method_sync() functions.
+ *
+ * See the #EDBusProxy:g-default-timeout property for more details.
+ *
+ * Returns: Timeout to use for @proxy.
+ */
+gint
+e_dbus_proxy_get_default_timeout (EDBusProxy *proxy)
+{
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), -1);
+  return proxy->priv->timeout_msec;
+}
+
+/**
+ * e_dbus_proxy_set_default_timeout:
+ * @proxy: A #EDBusProxy.
+ * @timeout_msec: Timeout in milliseconds.
+ *
+ * Sets the timeout to use if -1 (specifying default timeout) is
+ * passed as @timeout_msec in the e_dbus_proxy_invoke_method() and
+ * e_dbus_proxy_invoke_method_sync() functions.
+ *
+ * See the #EDBusProxy:g-default-timeout property for more details.
+ */
+void
+e_dbus_proxy_set_default_timeout (EDBusProxy *proxy,
+                                  gint        timeout_msec)
+{
+  g_return_if_fail (G_IS_DBUS_PROXY (proxy));
+  g_return_if_fail (timeout_msec >= -1);
+
+  /* TODO: locking? */
+  if (proxy->priv->timeout_msec != timeout_msec)
+    {
+      proxy->priv->timeout_msec = timeout_msec;
+      g_object_notify (G_OBJECT (proxy), "g-default-timeout");
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+maybe_split_method_name (const gchar   *method_name,
+                         gchar        **out_interface_name,
+                         const gchar  **out_method_name)
+{
+  gboolean was_split;
+
+  was_split = FALSE;
+  g_assert (out_interface_name != NULL);
+  g_assert (out_method_name != NULL);
+  *out_interface_name = NULL;
+  *out_method_name = NULL;
+
+  if (strchr (method_name, '.') != NULL)
+    {
+      gchar *p;
+      gchar *last_dot;
+
+      p = g_strdup (method_name);
+      last_dot = strrchr (p, '.');
+      *last_dot = '\0';
+
+      *out_interface_name = p;
+      *out_method_name = last_dot + 1;
+
+      was_split = TRUE;
+    }
+
+  return was_split;
+}
+
+
+static void
+reply_cb (EDBusConnection *connection,
+          GAsyncResult    *res,
+          gpointer         user_data)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (user_data);
+  EVariant *value;
+  GError *error;
+
+  error = NULL;
+  value = e_dbus_connection_invoke_method_finish (connection,
+                                                  res,
+                                                  &error);
+  if (error != NULL)
+    {
+      g_simple_async_result_set_from_error (simple,
+                                            error);
+      g_error_free (error);
+    }
+  else
+    {
+      g_simple_async_result_set_op_res_gpointer (simple,
+                                                 value,
+                                                 (GDestroyNotify) e_variant_unref);
+    }
+
+  /* no need to complete in idle since the method EDBusConnection already does */
+  g_simple_async_result_complete (simple);
+}
+
+/**
+ * e_dbus_proxy_invoke_method:
+ * @proxy: A #EDBusProxy.
+ * @method_name: Name of method to invoke.
+ * @parameters: A #EVariant tuple with parameters for the signal or %NULL if not passing parameters.
+ * @timeout_msec: The timeout in milliseconds or -1 to use the proxy default timeout.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL if you don't
+ * care about the result of the method invocation.
+ * @user_data: The data to pass to @callback.
+ *
+ * Asynchronously invokes the @method_name method on @proxy.
+ *
+ * If @method_name contains any dots, then @name is split into interface and
+ * method name parts. This allows using @proxy for invoking methods on
+ * other interfaces.
+ *
+ * If the #EDBusConnection associated with @proxy is disconnected then
+ * the operation will fail with %E_DBUS_ERROR_DISCONNECTED. If
+ * @cancellable is canceled, the operation will fail with
+ * %E_DBUS_ERROR_CANCELLED. If @parameters contains a value not
+ * compatible with the D-Bus protocol, the operation fails with
+ * %E_DBUS_ERROR_CONVERSION_FAILED.
+ *
+ * This is an asynchronous method. When the operation is finished, @callback will be invoked
+ * in the <link linkend="g-main-context-push-thread-default">thread-default main loop</link>
+ * of the thread you are calling this method from. You can then call
+ * e_dbus_proxy_invoke_method_finish() to get the result of the operation.
+ * See e_dbus_proxy_invoke_method_sync() for the
+ * synchronous version of this method.
+ */
+void
+e_dbus_proxy_invoke_method (EDBusProxy          *proxy,
+                            const gchar         *method_name,
+                            EVariant            *parameters,
+                            gint                 timeout_msec,
+                            GCancellable        *cancellable,
+                            GAsyncReadyCallback  callback,
+                            gpointer             user_data)
+{
+  GSimpleAsyncResult *simple;
+  gboolean was_split;
+  gchar *split_interface_name;
+  const gchar *split_method_name;
+
+  g_return_if_fail (G_IS_DBUS_PROXY (proxy));
+  g_return_if_fail (method_name != NULL);
+
+  simple = g_simple_async_result_new (G_OBJECT (proxy),
+                                      callback,
+                                      user_data,
+                                      e_dbus_proxy_invoke_method);
+
+  was_split = maybe_split_method_name (method_name, &split_interface_name, &split_method_name);
+
+  e_dbus_connection_invoke_method (proxy->priv->connection,
+                                   proxy->priv->unique_bus_name,
+                                   proxy->priv->object_path,
+                                   was_split ? split_interface_name : proxy->priv->interface_name,
+                                   was_split ? split_method_name : method_name,
+                                   parameters,
+                                   timeout_msec == -1 ? proxy->priv->timeout_msec : timeout_msec,
+                                   cancellable,
+                                   (GAsyncReadyCallback) reply_cb,
+                                   simple);
+
+  g_free (split_interface_name);
+}
+
+/**
+ * e_dbus_proxy_invoke_method_finish:
+ * @proxy: A #EDBusProxy.
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to e_dbus_proxy_invoke_method().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with e_dbus_proxy_invoke_method().
+ *
+ * Returns: %NULL if @error is set. Otherwise a #EVariant tuple with
+ * return values. Free with e_variant_unref().
+ */
+EVariant *
+e_dbus_proxy_invoke_method_finish (EDBusProxy    *proxy,
+                                   GAsyncResult  *res,
+                                   GError       **error)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+  EVariant *value;
+
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+  g_return_val_if_fail (res != NULL, NULL);
+
+  g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == e_dbus_proxy_invoke_method);
+
+  value = NULL;
+
+  if (g_simple_async_result_propagate_error (simple, error))
+    goto out;
+
+  value = g_simple_async_result_get_op_res_gpointer (simple);
+
+ out:
+  return value;
+}
+
+/**
+ * e_dbus_proxy_invoke_method_sync:
+ * @proxy: A #EDBusProxy.
+ * @method_name: Name of method to invoke.
+ * @parameters: A #EVariant tuple with parameters for the signal or %NULL if not passing parameters.
+ * @timeout_msec: The timeout in milliseconds or -1 to use the proxy default timeout.
+ * @cancellable: A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously invokes the @method_name method on @proxy.
+ *
+ * If @method_name contains any dots, then @name is split into interface and
+ * method name parts. This allows using @proxy for invoking methods on
+ * other interfaces.
+ *
+ * If the #EDBusConnection associated with @proxy is disconnected then
+ * the operation will fail with %E_DBUS_ERROR_DISCONNECTED. If
+ * @cancellable is canceled, the operation will fail with
+ * %E_DBUS_ERROR_CANCELLED. If @parameters contains a value not
+ * compatible with the D-Bus protocol, the operation fails with
+ * %E_DBUS_ERROR_CONVERSION_FAILED.
+ *
+ * The calling thread is blocked until a reply is received. See
+ * e_dbus_proxy_invoke_method() for the asynchronous version of this
+ * method.
+ *
+ * Returns: %NULL if @error is set. Otherwise a #EVariant tuple with
+ * return values. Free with e_variant_unref().
+ */
+EVariant *
+e_dbus_proxy_invoke_method_sync (EDBusProxy     *proxy,
+                                 const gchar    *method_name,
+                                 EVariant       *parameters,
+                                 gint            timeout_msec,
+                                 GCancellable   *cancellable,
+                                 GError        **error)
+{
+  EVariant *ret;
+  gboolean was_split;
+  gchar *split_interface_name;
+  const gchar *split_method_name;
+
+  g_return_val_if_fail (G_IS_DBUS_PROXY (proxy), NULL);
+  g_return_val_if_fail (method_name != NULL, NULL);
+
+  was_split = maybe_split_method_name (method_name, &split_interface_name, &split_method_name);
+
+  ret = e_dbus_connection_invoke_method_sync (proxy->priv->connection,
+                                              proxy->priv->unique_bus_name,
+                                              proxy->priv->object_path,
+                                              was_split ? split_interface_name : proxy->priv->interface_name,
+                                              was_split ? split_method_name : method_name,
+                                              parameters,
+                                              timeout_msec == -1 ? proxy->priv->timeout_msec : timeout_msec,
+                                              cancellable,
+                                              error);
+
+  g_free (split_interface_name);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/libedbus/gdbusproxy.h b/libedbus/gdbusproxy.h
new file mode 100644
index 0000000..56afca4
--- /dev/null
+++ b/libedbus/gdbusproxy.h
@@ -0,0 +1,141 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#if !defined (__E_DBUS_H_INSIDE__) && !defined (E_DBUS_COMPILATION)
+#error "Only <gdbus/gdbus.h> can be included directly."
+#endif
+
+#ifndef __E_DBUS_PROXY_H__
+#define __E_DBUS_PROXY_H__
+
+#include <gdbus/gdbustypes.h>
+
+G_BEGIN_DECLS
+
+#define E_TYPE_DBUS_PROXY         (e_dbus_proxy_get_type ())
+#define E_DBUS_PROXY(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), E_TYPE_DBUS_PROXY, EDBusProxy))
+#define E_DBUS_PROXY_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), E_TYPE_DBUS_PROXY, EDBusProxyClass))
+#define E_DBUS_PROXY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), E_TYPE_DBUS_PROXY, EDBusProxyClass))
+#define G_IS_DBUS_PROXY(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), E_TYPE_DBUS_PROXY))
+#define G_IS_DBUS_PROXY_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), E_TYPE_DBUS_PROXY))
+
+typedef struct _EDBusProxyClass   EDBusProxyClass;
+typedef struct _EDBusProxyPrivate EDBusProxyPrivate;
+
+/**
+ * EDBusProxy:
+ *
+ * The #EDBusProxy structure contains only private data and
+ * should only be accessed using the provided API.
+ */
+struct _EDBusProxy
+{
+  /*< private >*/
+  GObject parent_instance;
+  EDBusProxyPrivate *priv;
+};
+
+/**
+ * EDBusProxyClass:
+ * @properties_changed: Signal class handler for the #EDBusProxy::g-dbus-proxy-properties-changed signal.
+ * @signal: Signal class handler for the #EDBusProxy::g-dbus-proxy-signal signal.
+ *
+ * Class structure for #EDBusProxy.
+ */
+struct _EDBusProxyClass
+{
+  /*< private >*/
+  GObjectClass parent_class;
+
+  /*< public >*/
+  void (*properties_changed)           (EDBusProxy   *proxy,
+                                        GHashTable   *changed_properties);
+  void (*signal)                       (EDBusProxy   *proxy,
+                                        const gchar  *sender_name,
+                                        const gchar  *signal_name,
+                                        EVariant     *parameters);
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*_g_reserved1) (void);
+  void (*_g_reserved2) (void);
+  void (*_g_reserved3) (void);
+  void (*_g_reserved4) (void);
+  void (*_g_reserved5) (void);
+  void (*_g_reserved6) (void);
+  void (*_g_reserved7) (void);
+  void (*_g_reserved8) (void);
+};
+
+GType            e_dbus_proxy_get_type                  (void) G_GNUC_CONST;
+void             e_dbus_proxy_new                       (EDBusConnection     *connection,
+                                                         GType                object_type,
+                                                         EDBusProxyFlags      flags,
+                                                         const gchar         *unique_bus_name,
+                                                         const gchar         *object_path,
+                                                         const gchar         *interface_name,
+                                                         GCancellable        *cancellable,
+                                                         GAsyncReadyCallback  callback,
+                                                         gpointer             user_data);
+EDBusProxy      *e_dbus_proxy_new_finish                (GAsyncResult        *res,
+                                                         GError             **error);
+EDBusProxy      *e_dbus_proxy_new_sync                  (EDBusConnection     *connection,
+                                                         GType                object_type,
+                                                         EDBusProxyFlags      flags,
+                                                         const gchar         *unique_bus_name,
+                                                         const gchar         *object_path,
+                                                         const gchar         *interface_name,
+                                                         GCancellable        *cancellable,
+                                                         GError             **error);
+EDBusConnection *e_dbus_proxy_get_connection            (EDBusProxy          *proxy);
+EDBusProxyFlags  e_dbus_proxy_get_flags                 (EDBusProxy          *proxy);
+const gchar     *e_dbus_proxy_get_unique_bus_name       (EDBusProxy          *proxy);
+const gchar     *e_dbus_proxy_get_object_path           (EDBusProxy          *proxy);
+const gchar     *e_dbus_proxy_get_interface_name        (EDBusProxy          *proxy);
+gint             e_dbus_proxy_get_default_timeout       (EDBusProxy          *proxy);
+void             e_dbus_proxy_set_default_timeout       (EDBusProxy          *proxy,
+                                                         gint                 timeout_msec);
+EVariant        *e_dbus_proxy_get_cached_property       (EDBusProxy          *proxy,
+                                                         const gchar         *property_name,
+                                                         GError             **error);
+gchar          **e_dbus_proxy_get_cached_property_names (EDBusProxy          *proxy,
+                                                         GError             **error);
+void             e_dbus_proxy_invoke_method             (EDBusProxy          *proxy,
+                                                         const gchar         *method_name,
+                                                         EVariant            *parameters,
+                                                         gint                 timeout_msec,
+                                                         GCancellable        *cancellable,
+                                                         GAsyncReadyCallback  callback,
+                                                         gpointer             user_data);
+EVariant        *e_dbus_proxy_invoke_method_finish      (EDBusProxy          *proxy,
+                                                         GAsyncResult        *res,
+                                                         GError             **error);
+EVariant        *e_dbus_proxy_invoke_method_sync        (EDBusProxy          *proxy,
+                                                         const gchar         *method_name,
+                                                         EVariant            *parameters,
+                                                         gint                 timeout_msec,
+                                                         GCancellable        *cancellable,
+                                                         GError             **error);
+
+G_END_DECLS
+
+#endif /* __E_DBUS_PROXY_H__ */
diff --git a/libedbus/gdbusproxywatching.c b/libedbus/gdbusproxywatching.c
new file mode 100644
index 0000000..ed05b6f
--- /dev/null
+++ b/libedbus/gdbusproxywatching.c
@@ -0,0 +1,431 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbusnamewatching.h"
+#include "gdbusproxywatching.h"
+#include "gdbuserror.h"
+#include "gdbusprivate.h"
+#include "gdbusproxy.h"
+#include "gdbusnamewatching.h"
+
+/**
+ * SECTION:gdbusproxywatching
+ * @title: Watching Proxies
+ * @short_description: Simple API for watching proxies
+ * @include: gdbus/gdbus.h
+ *
+ * Convenience API for watching bus proxies.
+ *
+ * <example id="gdbus-watching-proxy"><title>Simple application watching a proxy</title><programlisting><xi:include xmlns:xi="http://www.w3.org/2001/XInclude"; parse="text" href="../../../../gdbus/example-watch-proxy.c"><xi:fallback>FIXME: MISSING XINCLUDE CONTENT</xi:fallback></xi:include></programlisting></example>
+ */
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+G_LOCK_DEFINE_STATIC (lock);
+
+static guint next_global_id = 1;
+static GHashTable *map_id_to_client = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  volatile gint              ref_count;
+  guint                      id;
+  GBusProxyAppearedCallback  proxy_appeared_handler;
+  GBusProxyVanishedCallback  proxy_vanished_handler;
+  gpointer                   user_data;
+  GDestroyNotify             user_data_free_func;
+  GMainContext              *main_context;
+
+  gchar                     *name;
+  gchar                     *name_owner;
+  EDBusConnection           *connection;
+  guint                      name_watcher_id;
+
+  GCancellable              *cancellable;
+
+  gchar                     *object_path;
+  gchar                     *interface_name;
+  GType                      interface_type;
+  EDBusProxyFlags            proxy_flags;
+  EDBusProxy                *proxy;
+
+  gboolean initial_construction;
+} Client;
+
+static Client *
+client_ref (Client *client)
+{
+  g_atomic_int_inc (&client->ref_count);
+  return client;
+}
+
+static void
+client_unref (Client *client)
+{
+  if (g_atomic_int_dec_and_test (&client->ref_count))
+    {
+      /* ensure we're only called from e_bus_unwatch_proxy */
+      g_assert (client->name_watcher_id == 0);
+
+      /* we can do this because on_name_vanished() will have cleared these */
+      g_assert (client->name_owner == NULL);
+      g_assert (client->connection == NULL);
+      g_assert (client->proxy == NULL);
+
+      g_free (client->name);
+      g_free (client->object_path);
+      g_free (client->interface_name);
+
+      if (client->main_context != NULL)
+        g_main_context_unref (client->main_context);
+
+      if (client->user_data_free_func != NULL)
+        client->user_data_free_func (client->user_data);
+      g_free (client);
+    }
+}
+
+static gboolean
+schedule_unref_in_idle_cb (gpointer data)
+{
+  Client *client = data;
+  client_unref (client);
+  return FALSE;
+}
+
+static void
+schedule_unref_in_idle (Client *client)
+{
+  GSource *idle_source;
+
+  idle_source = g_idle_source_new ();
+  g_source_set_priority (idle_source, G_PRIORITY_HIGH);
+  g_source_set_callback (idle_source,
+                         schedule_unref_in_idle_cb,
+                         client_ref (client),
+                         (GDestroyNotify) client_unref);
+  g_source_attach (idle_source, client->main_context);
+  g_source_unref (idle_source);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+proxy_constructed_cb (GObject       *source_object,
+                      GAsyncResult  *res,
+                      gpointer       user_data)
+{
+  Client *client = user_data;
+  EDBusProxy *proxy;
+  GError *error;
+
+  error = NULL;
+  proxy = e_dbus_proxy_new_finish (res, &error);
+  if (proxy == NULL)
+    {
+      /* g_warning ("error while constructing proxy: %s", error->message); */
+      g_error_free (error);
+
+      /* handle initial construction, send out vanished if the name
+       * is there but we constructing a proxy fails
+       */
+      if (client->initial_construction)
+        {
+          if (client->proxy_vanished_handler != NULL)
+            {
+              client->proxy_vanished_handler (client->connection,
+                                              client->name,
+                                              client->user_data);
+            }
+          client->initial_construction = FALSE;
+        }
+    }
+  else
+    {
+      g_assert (client->proxy == NULL);
+      g_assert (client->cancellable != NULL);
+      client->proxy = E_DBUS_PROXY (proxy);
+
+      g_object_unref (client->cancellable);
+      client->cancellable = NULL;
+
+      /* perform callback */
+      if (client->proxy_appeared_handler != NULL)
+        {
+          client->proxy_appeared_handler (client->connection,
+                                          client->name,
+                                          client->name_owner,
+                                          client->proxy,
+                                          client->user_data);
+        }
+      client->initial_construction = FALSE;
+    }
+}
+
+static void
+on_name_appeared (EDBusConnection *connection,
+                  const gchar *name,
+                  const gchar *name_owner,
+                  gpointer user_data)
+{
+  Client *client = user_data;
+
+  /*g_debug ("\n\nname appeared (owner `%s')", name_owner);*/
+
+  /* invariants */
+  g_assert (client->name_owner == NULL);
+  g_assert (client->connection == NULL);
+  g_assert (client->cancellable == NULL);
+
+  client->name_owner = g_strdup (name_owner);
+  client->connection = g_object_ref (connection);
+  client->cancellable = g_cancellable_new ();
+
+  e_dbus_proxy_new (client->connection,
+                    client->interface_type,
+                    client->proxy_flags,
+                    client->name_owner,
+                    client->object_path,
+                    client->interface_name,
+                    client->cancellable,
+                    proxy_constructed_cb,
+                    client);
+}
+
+static void
+on_name_vanished (EDBusConnection *connection,
+                  const gchar *name,
+                  gpointer user_data)
+{
+  Client *client = user_data;
+
+  /*g_debug ("\n\nname vanished");*/
+
+  g_free (client->name_owner);
+  if (client->connection != NULL)
+    g_object_unref (client->connection);
+  client->name_owner = NULL;
+  client->connection = NULL;
+
+  /* free the proxy if we have it */
+  if (client->proxy != NULL)
+    {
+      g_assert (client->cancellable == NULL);
+
+      g_object_unref (client->proxy);
+      client->proxy = NULL;
+
+      /* if we have the proxy, it means we last sent out a 'appeared'
+       * callback - so send out a 'vanished' callback
+       */
+      if (client->proxy_vanished_handler != NULL)
+        {
+          client->proxy_vanished_handler (client->connection,
+                                          client->name,
+                                          client->user_data);
+        }
+      client->initial_construction = FALSE;
+    }
+  else
+    {
+      /* otherwise cancel construction of the proxy if applicable */
+      if (client->cancellable != NULL)
+        {
+          g_cancellable_cancel (client->cancellable);
+          g_object_unref (client->cancellable);
+          client->cancellable = NULL;
+        }
+      else
+        {
+          /* handle initial construction, send out vanished if
+           * the name isn't there
+           */
+          if (client->initial_construction)
+            {
+              if (client->proxy_vanished_handler != NULL)
+                {
+                  client->proxy_vanished_handler (client->connection,
+                                                  client->name,
+                                                  client->user_data);
+                }
+              client->initial_construction = FALSE;
+            }
+        }
+    }
+}
+
+/**
+ * e_bus_watch_proxy:
+ * @bus_type: The type of bus to watch a name on (can't be #G_BUS_TYPE_NONE).
+ * @name: The name (well-known or unique) to watch.
+ * @object_path: The object path of the remote object to watch.
+ * @interface_name: The D-Bus interface name for the proxy.
+ * @interface_type: The #GType for the kind of proxy to create. This must be a #EDBusProxy derived type.
+ * @proxy_flags: Flags from #EDBusProxyFlags to use when constructing the proxy.
+ * @proxy_appeared_handler: Handler to invoke when @name is known to exist and the
+ * requested proxy is available.
+ * @proxy_vanished_handler: Handler to invoke when @name is known to not exist
+ * and the previously created proxy is no longer available.
+ * @user_data: User data to pass to handlers.
+ * @user_data_free_func: Function for freeing @user_data or %NULL.
+ *
+ * Starts watching a remote object at @object_path owned by @name on
+ * the bus specified by @bus_type. When the object is available, a
+ * #EDBusProxy (or derived class cf. @interface_type) instance is
+ * constructed for the @interface_name D-Bus interface and then
+ * @proxy_appeared_handler will be called when the proxy is ready and
+ * all properties have been loaded. When @name vanishes,
+ * @proxy_vanished_handler is called.
+ *
+ * This function makes it very simple to write applications that wants
+ * to watch a well-known remote object on a well-known name, see <xref
+ * linkend="gdbus-watching-proxy"/>. Basically, the application simply
+ * starts using the proxy when @proxy_appeared_handler is called and
+ * stops using it when @proxy_vanished_handler is called. Callbacks
+ * will be invoked in the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * loop</link> of the thread you are calling this function from.
+ *
+ * Applications typically use this function to watch the
+ * <quote>manager</quote> object of a well-known name. Upon acquiring
+ * a proxy for the manager object, applications typically construct
+ * additional proxies in response to the result of enumeration methods
+ * on the manager object.
+ *
+ * Many of the comment that applies to e_bus_watch_name() also applies
+ * here. For example, you are guaranteed that one of the handlers will
+ * be invoked (on the main thread) after calling this function and
+ * also that the two handlers alternate. When you are done watching the
+ * proxy, just call e_bus_unwatch_proxy().
+ *
+ * Returns: An identifier (never 0) that can be used with
+ * e_bus_unwatch_proxy() to stop watching the remote object.
+ **/
+guint
+e_bus_watch_proxy (GBusType                   bus_type,
+                   const gchar               *name,
+                   const gchar               *object_path,
+                   const gchar               *interface_name,
+                   GType                      interface_type,
+                   EDBusProxyFlags            proxy_flags,
+                   GBusProxyAppearedCallback  proxy_appeared_handler,
+                   GBusProxyVanishedCallback  proxy_vanished_handler,
+                   gpointer                   user_data,
+                   GDestroyNotify             user_data_free_func)
+{
+  Client *client;
+
+  g_return_val_if_fail (bus_type != G_BUS_TYPE_NONE, 0);
+  g_return_val_if_fail (name != NULL, 0);
+  g_return_val_if_fail (proxy_appeared_handler != NULL, 0);
+  g_return_val_if_fail (proxy_vanished_handler != NULL, 0);
+  g_return_val_if_fail (object_path != NULL, 0);
+  g_return_val_if_fail (interface_name != NULL, 0);
+  g_return_val_if_fail (g_type_is_a (interface_type, E_TYPE_DBUS_PROXY), 0);
+
+  G_LOCK (lock);
+
+  client = g_new0 (Client, 1);
+  client->id = next_global_id++; /* TODO: uh oh, handle overflow */
+  client->name = g_strdup (name);
+  client->proxy_appeared_handler = proxy_appeared_handler;
+  client->proxy_vanished_handler = proxy_vanished_handler;
+  client->user_data = user_data;
+  client->user_data_free_func = user_data_free_func;
+  client->main_context = g_main_context_get_thread_default ();
+  if (client->main_context != NULL)
+    g_main_context_ref (client->main_context);
+  client->name_watcher_id = e_bus_watch_name (bus_type,
+                                              name,
+                                              on_name_appeared,
+                                              on_name_vanished,
+                                              client,
+                                              NULL);
+
+  client->object_path = g_strdup (object_path);
+  client->interface_name = g_strdup (interface_name);
+  client->interface_type = interface_type;
+  client->proxy_flags = proxy_flags;
+  client->initial_construction = TRUE;
+
+  if (map_id_to_client == NULL)
+    {
+      map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal);
+    }
+  g_hash_table_insert (map_id_to_client,
+                       GUINT_TO_POINTER (client->id),
+                       client);
+
+  G_UNLOCK (lock);
+
+  return client->id;
+}
+
+/**
+ * e_bus_unwatch_proxy:
+ * @watcher_id: An identifier obtained from e_bus_watch_proxy()
+ *
+ * Stops watching proxy.
+ *
+ * If the proxy being watched currently exists
+ * (e.g. @proxy_appeared_handler was the last handler to be invoked),
+ * then @proxy_vanished_handler will be invoked before this function returns.
+ **/
+void
+e_bus_unwatch_proxy (guint watcher_id)
+{
+  Client *client;
+
+  client = NULL;
+
+  G_LOCK (lock);
+  if (watcher_id == 0 ||
+      map_id_to_client == NULL ||
+      (client = g_hash_table_lookup (map_id_to_client, GUINT_TO_POINTER (watcher_id))) == NULL)
+    {
+      g_warning ("Invalid id %d passed to e_bus_unwatch_proxy()", watcher_id);
+      goto out;
+    }
+
+  g_warn_if_fail (g_hash_table_remove (map_id_to_client, GUINT_TO_POINTER (watcher_id)));
+
+ out:
+  G_UNLOCK (lock);
+
+  /* TODO: think about this: this will trigger callbacks from the name_watcher object being used */
+  if (client != NULL)
+    {
+      /* this will trigger on_name_vanished() */
+      e_bus_unwatch_name (client->name_watcher_id);
+      client->name_watcher_id = 0;
+      /* this will happen *after* on_name_vanished() */
+      schedule_unref_in_idle (client);
+    }
+}
diff --git a/libedbus/gdbusproxywatching.h b/libedbus/gdbusproxywatching.h
new file mode 100644
index 0000000..a461aed
--- /dev/null
+++ b/libedbus/gdbusproxywatching.h
@@ -0,0 +1,80 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#if !defined (__E_DBUS_H_INSIDE__) && !defined (E_DBUS_COMPILATION)
+#error "Only <gdbus/gdbus.h> can be included directly."
+#endif
+
+#ifndef __E_DBUS_PROXY_WATCHING_H__
+#define __E_DBUS_PROXY_WATCHING_H__
+
+#include <gdbus/gdbustypes.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GBusProxyAppearedCallback:
+ * @connection: The #EDBusConnection the proxy is being watched on.
+ * @name: The name being watched.
+ * @name_owner: Unique name of the owner of the name being watched.
+ * @proxy: A #EDBusProxy (or derived) instance with all properties loaded.
+ * @user_data: User data passed to e_bus_watch_proxy().
+ *
+ * Invoked when the proxy being watched is ready for use - the passed
+ * @proxy object is valid until the #GBusProxyVanishedCallback
+ * callback is invoked.
+ */
+typedef void (*GBusProxyAppearedCallback) (EDBusConnection *connection,
+                                           const gchar     *name,
+                                           const gchar     *name_owner,
+                                           EDBusProxy      *proxy,
+                                           gpointer         user_data);
+
+/**
+ * GBusProxyVanishedCallback:
+ * @connection: The #EDBusConnection the proxy is being watched on.
+ * @name: The name being watched.
+ * @user_data: User data passed to e_bus_watch_proxy().
+ *
+ * Invoked when the proxy being watched has vanished. The #EDBusProxy
+ * object passed in the #GBusProxyAppearedCallback callback is no
+ * longer valid.
+ */
+typedef void (*GBusProxyVanishedCallback) (EDBusConnection *connection,
+                                           const gchar     *name,
+                                           gpointer         user_data);
+
+guint e_bus_watch_proxy   (GBusType                   bus_type,
+                           const gchar               *name,
+                           const gchar               *object_path,
+                           const gchar               *interface_name,
+                           GType                      interface_type,
+                           EDBusProxyFlags            proxy_flags,
+                           GBusProxyAppearedCallback  proxy_appeared_handler,
+                           GBusProxyVanishedCallback  proxy_vanished_handler,
+                           gpointer                   user_data,
+                           GDestroyNotify             user_data_free_func);
+void  e_bus_unwatch_proxy (guint                      watcher_id);
+
+G_END_DECLS
+
+#endif /* __E_DBUS_PROXY_WATCHING_H__ */
diff --git a/libedbus/gdbusserver.c b/libedbus/gdbusserver.c
new file mode 100644
index 0000000..34f477d
--- /dev/null
+++ b/libedbus/gdbusserver.c
@@ -0,0 +1,336 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "gdbusconnection.h"
+#include "gdbuserror.h"
+#include "gdbusenumtypes.h"
+#include "gdbusserver.h"
+#include "gdbusprivate.h"
+
+/**
+ * SECTION:gdbusserver
+ * @short_description: Helper for accepting peer-to-peer connections
+ * @include: gdbus/gdbus.h
+ *
+ * TODO
+ */
+
+struct _EDBusServerPrivate
+{
+  /* construct properties */
+  gchar *address;
+
+  DBusServer *dbus_1_server;
+};
+
+enum
+{
+  NEW_CONNECTION_SIGNAL,
+  LAST_SIGNAL
+};
+
+enum
+{
+  PROP_0,
+  PROP_ADDRESS
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void on_new_dbus_1_connection (DBusServer     *server,
+                                      DBusConnection *new_connection,
+                                      void           *user_data);
+
+static void initable_iface_init       (GInitableIface *initable_iface);
+
+G_DEFINE_TYPE_WITH_CODE (EDBusServer, e_dbus_server, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)
+                         );
+
+static void
+e_dbus_server_finalize (GObject *object)
+{
+  EDBusServer *server = E_DBUS_SERVER (object);
+
+  if (server->priv->dbus_1_server != NULL)
+    {
+      _e_dbus_unintegrate_dbus_1_server (server->priv->dbus_1_server);
+      dbus_server_disconnect (server->priv->dbus_1_server);
+      dbus_server_unref (server->priv->dbus_1_server);
+    }
+
+  if (G_OBJECT_CLASS (e_dbus_server_parent_class)->finalize != NULL)
+    G_OBJECT_CLASS (e_dbus_server_parent_class)->finalize (object);
+}
+
+static void
+e_dbus_server_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  EDBusServer *server = E_DBUS_SERVER (object);
+
+  switch (prop_id)
+    {
+    case PROP_ADDRESS:
+      g_value_set_string (value, e_dbus_server_get_address (server));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+e_dbus_server_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  EDBusServer *server = E_DBUS_SERVER (object);
+
+  switch (prop_id)
+    {
+    case PROP_ADDRESS:
+      server->priv->address = g_value_dup_string (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+static void
+e_dbus_server_init (EDBusServer *server)
+{
+  server->priv = G_TYPE_INSTANCE_GET_PRIVATE (server, E_TYPE_DBUS_SERVER, EDBusServerPrivate);
+}
+
+static void
+e_dbus_server_class_init (EDBusServerClass *klass)
+{
+  GObjectClass *gobject_class;
+
+  g_type_class_add_private (klass, sizeof (EDBusServerPrivate));
+
+  gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize     = e_dbus_server_finalize;
+  gobject_class->set_property = e_dbus_server_set_property;
+  gobject_class->get_property = e_dbus_server_get_property;
+
+  /**
+   * EDBusServer:address:
+   *
+   * The address to listen on. See the D-Bus specification for details
+   * on address formats.
+   *
+   * If there are multiple semicolon-separated address entries in
+   * @address, tries each one and listens on the first one that works.
+   */
+  g_object_class_install_property (gobject_class,
+                                   PROP_ADDRESS,
+                                   g_param_spec_string ("address",
+                                                        _("Address"),
+                                                        _("The address to listen on"),
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_NAME |
+                                                        G_PARAM_STATIC_BLURB |
+                                                        G_PARAM_STATIC_NICK));
+
+
+  /**
+   * EDBusServer::new-connection:
+   * @server: The #EDBusServer emitting the signal.
+   * @connection: The new connection.
+   *
+   * Emitted when there is a new connection. If you wish to keep the
+   * connection open, take a reference to @connection. Connect to
+   * #EDBusConnection::disconnected to find out when the client
+   * disconnects. Use e_dbus_connection_disconnect() to disconnect the
+   * client.
+   *
+   * Note that the returned @connection object will have
+   * #EDBusConnection:exit-on-disconnect set to %FALSE.
+   */
+  signals[NEW_CONNECTION_SIGNAL] = g_signal_new ("new-connection",
+                                                 E_TYPE_DBUS_SERVER,
+                                                 G_SIGNAL_RUN_LAST,
+                                                 G_STRUCT_OFFSET (EDBusServerClass, new_connection),
+                                                 NULL,
+                                                 NULL,
+                                                 g_cclosure_marshal_VOID__OBJECT,
+                                                 G_TYPE_NONE,
+                                                 1,
+                                                 E_TYPE_DBUS_CONNECTION);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+initable_init (GInitable       *initable,
+               GCancellable    *cancellable,
+               GError         **error)
+{
+  EDBusServer *server = E_DBUS_SERVER (initable);
+  DBusError dbus_error;
+  gboolean ret;
+
+  ret = FALSE;
+
+  dbus_connection_set_change_sigpipe (TRUE);
+
+  dbus_error_init (&dbus_error);
+  server->priv->dbus_1_server = dbus_server_listen (server->priv->address,
+                                                    &dbus_error);
+  if (server->priv->dbus_1_server != NULL)
+    {
+      _e_dbus_integrate_dbus_1_server (server->priv->dbus_1_server, NULL);
+
+      dbus_server_set_new_connection_function (server->priv->dbus_1_server,
+                                               on_new_dbus_1_connection,
+                                               server,
+                                               NULL);
+
+      ret = TRUE;
+    }
+  else
+    {
+      if (error != NULL)
+        {
+          e_dbus_error_set_dbus_error (error,
+                                       dbus_error.name,
+                                       dbus_error.message,
+                                       NULL);
+          /* this is a locally generated error so strip the remote part */
+          e_dbus_error_strip_remote_error (*error);
+        }
+      dbus_error_free (&dbus_error);
+    }
+
+  return ret;
+}
+
+static void
+initable_iface_init (GInitableIface *initable_iface)
+{
+  initable_iface->init = initable_init;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_server_new:
+ * @address: The address to listen on. See the D-Bus specification for details on address formats.
+ * @error: Return location for error or %NULL.
+ *
+ * Listens for new connections on @address. Whenever there's a new
+ * connection the #EDBusServer::new-connection signal is emitted in
+ * the thread where the returned #EDBusServer was constructed.
+ *
+ * If there are multiple semicolon-separated address entries in
+ * @address, tries each one and listens on the first one that works.
+ *
+ * Returns: A #EDBusServer or %NULL if @error is set. Free with
+ * g_object_unref().
+ */
+EDBusServer *
+e_dbus_server_new (const gchar   *address,
+                   GError       **error)
+{
+  GInitable *initable;
+
+  initable = g_initable_new (E_TYPE_DBUS_SERVER,
+                             NULL, /* GCancellable */
+                             error,
+                             "address", address,
+                             NULL);
+
+  if (initable != NULL)
+    return E_DBUS_SERVER (initable);
+  else
+    return NULL;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+/**
+ * e_dbus_server_get_address:
+ * @server: A #EDBusServer.
+ *
+ * Gets the address that was passed when @server was constructed.
+ *
+ * Returns: The address that @server listens on. See the D-Bus
+ * specification for details on address formats. Do not free, this
+ * string is owned by @server.
+ */
+const gchar *
+e_dbus_server_get_address (EDBusServer *server)
+{
+  g_return_val_if_fail (G_IS_DBUS_SERVER (server), NULL);
+  return server->priv->address;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_new_dbus_1_connection (DBusServer     *_server,
+                          DBusConnection *new_connection,
+                          void           *user_data)
+{
+  EDBusServer *server = E_DBUS_SERVER (user_data);
+  EDBusConnection *connection;
+
+  /* create a new connection */
+  connection = _e_dbus_connection_new_for_dbus_1_connection (new_connection);
+
+  /* give up ownership of connection */
+  dbus_connection_unref (new_connection);
+
+  g_signal_emit (server, signals[NEW_CONNECTION_SIGNAL], 0, connection);
+
+  /* if the signal receivers didn't ref, we'll close the connection here */
+  g_object_unref (connection);
+}
+
+extern void _g_futex_thread_init(void);
+
+/* a hack to initialize threads */
+void e_dbus_threads_init (void)
+{
+  /* ugly - but we need a working 'bitlock' */
+  _g_futex_thread_init ();
+  dbus_threads_init_default ();
+}
diff --git a/libedbus/gdbusserver.h b/libedbus/gdbusserver.h
new file mode 100644
index 0000000..a478395
--- /dev/null
+++ b/libedbus/gdbusserver.h
@@ -0,0 +1,93 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#if !defined (__E_DBUS_H_INSIDE__) && !defined (E_DBUS_COMPILATION)
+#error "Only <gdbus/gdbus.h> can be included directly."
+#endif
+
+#ifndef __E_DBUS_SERVER_H__
+#define __E_DBUS_SERVER_H__
+
+#include <gdbus/gdbustypes.h>
+
+G_BEGIN_DECLS
+
+#define E_TYPE_DBUS_SERVER         (e_dbus_server_get_type ())
+#define E_DBUS_SERVER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), E_TYPE_DBUS_SERVER, EDBusServer))
+#define E_DBUS_SERVER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), E_TYPE_DBUS_SERVER, EDBusServerClass))
+#define E_DBUS_SERVER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), E_TYPE_DBUS_SERVER, EDBusServerClass))
+#define G_IS_DBUS_SERVER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), E_TYPE_DBUS_SERVER))
+#define G_IS_DBUS_SERVER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), E_TYPE_DBUS_SERVER))
+
+typedef struct _EDBusServerClass   EDBusServerClass;
+typedef struct _EDBusServerPrivate EDBusServerPrivate;
+
+/**
+ * EDBusServer:
+ *
+ * The #EDBusServer structure contains only private data and
+ * should only be accessed using the provided API.
+ */
+struct _EDBusServer
+{
+  /*< private >*/
+  GObject parent_instance;
+  EDBusServerPrivate *priv;
+};
+
+/**
+ * EDBusServerClass:
+ * @new_connection: Signal class handler for the #EDBusServer::new-connection signal.
+ *
+ * Class structure for #EDBusServer.
+ */
+struct _EDBusServerClass
+{
+  /*< private >*/
+  GObjectClass parent_class;
+
+  /*< public >*/
+
+  /* Signals */
+  void (*new_connection) (EDBusServer     *server,
+                          EDBusConnection *connection);
+
+  /*< private >*/
+  /* Padding for future expansion */
+  void (*_g_reserved1) (void);
+  void (*_g_reserved2) (void);
+  void (*_g_reserved3) (void);
+  void (*_g_reserved4) (void);
+  void (*_g_reserved5) (void);
+  void (*_g_reserved6) (void);
+  void (*_g_reserved7) (void);
+  void (*_g_reserved8) (void);
+};
+
+GType        e_dbus_server_get_type    (void) G_GNUC_CONST;
+EDBusServer *e_dbus_server_new         (const gchar *address,
+                                        GError      **error);
+const gchar *e_dbus_server_get_address (EDBusServer *server);
+
+G_END_DECLS
+
+#endif /* __E_DBUS_SERVER_H__ */
diff --git a/libedbus/gdbustypes.h b/libedbus/gdbustypes.h
new file mode 100644
index 0000000..9aa811e
--- /dev/null
+++ b/libedbus/gdbustypes.h
@@ -0,0 +1,53 @@
+/* EDBus - GLib D-Bus Library
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#if !defined (__E_DBUS_H_INSIDE__) && !defined (E_DBUS_COMPILATION)
+#error "Only <gdbus/gdbus.h> can be included directly."
+#endif
+
+#ifndef __E_DBUS_TYPES_H__
+#define __E_DBUS_TYPES_H__
+
+#include "gvariant.h"
+#include <gdbus/gdbusenums.h>
+
+G_BEGIN_DECLS
+
+typedef struct _EDBusConnection       EDBusConnection;
+typedef struct _EDBusServer           EDBusServer;
+typedef struct _EDBusProxy            EDBusProxy;
+typedef struct _EDBusMethodInvocation EDBusMethodInvocation;
+
+typedef struct _EDBusInterfaceVTable  EDBusInterfaceVTable;
+typedef struct _EDBusSubtreeVTable    EDBusSubtreeVTable;
+
+typedef struct _EDBusAnnotationInfo   EDBusAnnotationInfo;
+typedef struct _EDBusArgInfo          EDBusArgInfo;
+typedef struct _EDBusMethodInfo       EDBusMethodInfo;
+typedef struct _EDBusSignalInfo       EDBusSignalInfo;
+typedef struct _EDBusPropertyInfo     EDBusPropertyInfo;
+typedef struct _EDBusInterfaceInfo    EDBusInterfaceInfo;
+typedef struct _EDBusNodeInfo         EDBusNodeInfo;
+
+G_END_DECLS
+
+#endif /* __E_DBUS_TYPES_H__ */
diff --git a/libedbus/gvariant-core.c b/libedbus/gvariant-core.c
new file mode 100644
index 0000000..1cec489
--- /dev/null
+++ b/libedbus/gvariant-core.c
@@ -0,0 +1,1509 @@
+/*
+ * Copyright © 2007, 2008 Ryan Lortie
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/**
+ * SECTION: gvariant
+ * @title: EVariant
+ * @short_description: a general purpose variant datatype
+ * @see_also: EVariantType
+ *
+ * #EVariant is a variant datatype; it stores a value along with
+ * information about the type of that value.  The range of possible
+ * values is determined by the type.  The range of possible types is
+ * exactly those types that may be sent over DBus.
+ *
+ * #EVariant instances always have a type and a value (which are given
+ * at construction time).  The type and value of a #EVariant instance
+ * can never change other than by the #EVariant itself being
+ * destroyed.  A #EVariant can not contain a pointer.
+ *
+ * Facilities exist for serialising the value of a #EVariant into a
+ * byte sequence.  A #EVariant can be sent over the bus or be saved to
+ * disk.  Additionally, #EVariant is used as the basis of the
+ * #GSettings persistent storage system.
+ **/
+
+/*
+ * This file is organised into 6 sections
+ *
+ * SECTION 1: structure declaration, condition constants
+ * SECTION 2: allocation/free functions
+ * SECTION 3: condition enabling functions
+ * SECTION 4: condition machinery
+ * SECTION 5: other internal functions
+ * SECTION 6: user-visible functions
+ */
+
+#include "gvariant-private.h"
+#include "gvariant-serialiser.h"
+
+#include <string.h>
+#include <glib.h>
+#include "gbitlock.h"
+
+/* == SECTION 1: structure declaration, condition constants ============== */
+/**
+ * EVariant:
+ *
+ * #EVariant is an opaque data structure and can only be accessed
+ * using the following functions.
+ **/
+struct _EVariant
+{
+  union
+  {
+    struct
+    {
+      EVariant *source;
+      guint8 *data;
+    } serialised;
+
+    struct
+    {
+      EVariant **children;
+      gsize n_children;
+    } tree;
+
+    struct
+    {
+      GDestroyNotify callback;
+      gpointer user_data;
+    } notify;
+  } contents;
+
+  gsize size;
+  EVariantTypeInfo *type;
+  gboolean floating;
+  gint state;
+  gint ref_count;
+};
+
+#define CONDITION_NONE                           0
+#define CONDITION_SOURCE_NATIVE         0x00000001
+#define CONDITION_BECAME_NATIVE         0x00000002
+#define CONDITION_NATIVE                0x00000004
+
+#define CONDITION_SOURCE_TRUSTED        0x00000010
+#define CONDITION_BECAME_TRUSTED        0x00000020
+#define CONDITION_TRUSTED               0x00000040
+
+#define CONDITION_FIXED_SIZE            0x00000100
+#define CONDITION_SIZE_KNOWN            0x00000200
+#define CONDITION_SIZE_VALID            0x00000400
+
+#define CONDITION_SERIALISED            0x00001000
+#define CONDITION_INDEPENDENT           0x00002000
+#define CONDITION_RECONSTRUCTED         0x00004000
+
+#define CONDITION_NOTIFY                0x00010000
+#define CONDITION_LOCKED                0x80000000
+
+static const char * /* debugging only */
+e_variant_state_to_string (guint state)
+{
+  GString *string;
+
+  string = g_string_new (NULL);
+
+#define add(cond,name) if (state & cond) g_string_append (string, name ", ");
+  add (CONDITION_SOURCE_NATIVE,  "source native");
+  add (CONDITION_BECAME_NATIVE,  "became native");
+  add (CONDITION_NATIVE,         "native");
+  add (CONDITION_SOURCE_TRUSTED, "source trusted");
+  add (CONDITION_BECAME_TRUSTED, "became trusted");
+  add (CONDITION_TRUSTED,        "trusted");
+  add (CONDITION_FIXED_SIZE,     "fixed-size");
+  add (CONDITION_SIZE_KNOWN,     "size known");
+  add (CONDITION_SIZE_VALID,     "size valid");
+  add (CONDITION_INDEPENDENT,    "independent");
+  add (CONDITION_SERIALISED,     "serialised");
+  add (CONDITION_RECONSTRUCTED,  "reconstructed");
+  add (CONDITION_NOTIFY,         "notify");
+#undef add
+
+  g_string_truncate (string, string->len - 2);
+  return g_string_free (string, FALSE);
+}
+
+/* == SECTION 2: allocation/free functions =============================== */
+static EVariant *
+e_variant_alloc (EVariantTypeInfo *type,
+                 guint             initial_state)
+{
+  EVariant *new;
+
+  new = g_slice_new (EVariant);
+  new->ref_count = 1;
+  new->type = type;
+  new->floating = TRUE;
+  new->state = initial_state & ~CONDITION_LOCKED;
+
+  return new;
+}
+
+static void
+e_variant_free (EVariant *value)
+{
+  /* free the type info */
+  if (value->type)
+    e_variant_type_info_unref (value->type);
+
+  /* free the data */
+  if (value->state & CONDITION_NOTIFY)
+    value->contents.notify.callback (value->contents.notify.user_data);
+  else if (value->state & CONDITION_SERIALISED)
+    {
+      if (value->state & CONDITION_INDEPENDENT)
+        g_slice_free1 (value->size, value->contents.serialised.data);
+      else
+        e_variant_unref (value->contents.serialised.source);
+
+      if (value->state & CONDITION_RECONSTRUCTED)
+        e_variant_unref (value->contents.serialised.source);
+    }
+  else
+    {
+      EVariant **children;
+      gsize n_children;
+      gsize i;
+
+      children = value->contents.tree.children;
+      n_children = value->contents.tree.n_children;
+
+      for (i = 0; i < n_children; i++)
+        e_variant_unref (children[i]);
+
+      g_slice_free1 (sizeof (EVariant *) * n_children, children);
+    }
+
+  /* free the structure itself */
+  g_slice_free (EVariant, value);
+}
+
+static void
+e_variant_lock (EVariant *value)
+{
+  e_bit_lock (&value->state, 31);
+}
+
+static void
+e_variant_unlock (EVariant *value)
+{
+  e_bit_unlock (&value->state, 31);
+}
+
+/* == SECTION 3: condition enabling functions ============================ */
+static void e_variant_fill_gvs (EVariantSerialised *, gpointer);
+
+static gboolean
+e_variant_enable_size_known (EVariant *value)
+{
+  EVariant **children;
+  gsize n_children;
+
+  children = value->contents.tree.children;
+  n_children = value->contents.tree.n_children;
+  value->size = e_variant_serialiser_needed_size (value->type,
+                                                  &e_variant_fill_gvs,
+                                                  (gpointer *) children,
+                                                  n_children);
+
+  return TRUE;
+}
+
+static gboolean
+e_variant_enable_serialised (EVariant *value)
+{
+  EVariantSerialised gvs;
+  EVariant **children;
+  gsize n_children;
+  gsize i;
+
+  children = value->contents.tree.children;
+  n_children = value->contents.tree.n_children;
+
+  gvs.type_info = value->type;
+  gvs.size = value->size;
+  gvs.data = g_slice_alloc (gvs.size);
+
+  e_variant_serialiser_serialise (gvs, &e_variant_fill_gvs,
+                                  (gpointer *) children, n_children);
+
+  value->contents.serialised.source = NULL;
+  value->contents.serialised.data = gvs.data;
+
+  for (i = 0; i < n_children; i++)
+    e_variant_unref (children[i]);
+  g_slice_free1 (sizeof (EVariant *) * n_children, children);
+
+  return TRUE;
+}
+
+static gboolean
+e_variant_enable_source_native (EVariant *value)
+{
+  return (value->contents.serialised.source->state & CONDITION_NATIVE) != 0;
+}
+
+static gboolean
+e_variant_enable_became_native (EVariant *value)
+{
+  EVariantSerialised gvs;
+
+  gvs.type_info = value->type;
+  gvs.size = value->size;
+  gvs.data = value->contents.serialised.data;
+
+  e_variant_serialised_byteswap (gvs);
+
+  return TRUE;
+}
+
+static gboolean
+e_variant_enable_source_trusted (EVariant *value)
+{
+  return (value->contents.serialised.source->state & CONDITION_TRUSTED) != 0;
+}
+
+static gboolean
+e_variant_enable_became_trusted (EVariant *value)
+{
+  EVariantSerialised gvs;
+
+  gvs.type_info = value->type;
+  gvs.data = value->contents.serialised.data;
+  gvs.size = value->size;
+
+  return e_variant_serialised_is_normal (gvs);
+}
+
+static gboolean
+e_variant_enable_reconstructed (EVariant *value)
+{
+  EVariant *old, *new;
+
+  old = e_variant_alloc (e_variant_type_info_ref (value->type),
+                         value->state & ~CONDITION_LOCKED);
+  value->contents.serialised.source = e_variant_ref_sink (old);
+  old->contents.serialised.source = NULL;
+  old->contents.serialised.data = value->contents.serialised.data;
+  old->size = value->size;
+
+  new = e_variant_deep_copy (old);
+  e_variant_flatten (new);
+
+  /* steal the data from new.  this is very evil. */
+  new->state &= ~CONDITION_INDEPENDENT;
+  new->contents.serialised.source = value;
+  value->contents.serialised.data = new->contents.serialised.data;
+  value->size = new->size;
+
+  e_variant_unref (new);
+
+  return TRUE;
+}
+
+static gboolean
+e_variant_enable_independent (EVariant *value)
+{
+  EVariant *source;
+  gpointer  new;
+
+  source = value->contents.serialised.source;
+  g_assert (source->state & CONDITION_INDEPENDENT);
+
+  new = g_slice_alloc (value->size);
+  memcpy (new, value->contents.serialised.data, value->size);
+
+  /* barrier to ensure byteswap is not in progress */
+  e_variant_lock (source);
+  e_variant_unlock (source);
+
+  /* rare: check if the source became native while we were copying */
+  if (source->state & CONDITION_NATIVE)
+    {
+      /* our data is probably half-and-half */
+      g_slice_free1 (value->size, new);
+
+      return FALSE;
+    }
+
+  value->contents.serialised.source = NULL;
+  value->contents.serialised.data = new;
+  e_variant_unref (source);
+
+  return TRUE;
+}
+
+static gboolean
+e_variant_enable_fixed_size (EVariant *value)
+{
+  gsize fixed_size;
+
+  e_variant_type_info_query (value->type, NULL, &fixed_size);
+
+  return fixed_size != 0;
+}
+
+/* == SECTION 4: condition machinery ===================================== */
+struct precondition_clause
+{
+  guint required;
+  guint forbidden;
+};
+
+struct condition
+{
+  guint    condition;
+  guint    implies;
+  guint    forbids;
+  guint    absence_implies;
+  void     (*assert_invariant) (EVariant *);
+  gboolean (*enable) (EVariant *);
+  struct precondition_clause precondition[4];
+};
+
+struct condition condition_table[] =
+{
+  { CONDITION_SOURCE_NATIVE,
+    CONDITION_NATIVE | CONDITION_SERIALISED,
+    CONDITION_INDEPENDENT | CONDITION_BECAME_NATIVE |
+    CONDITION_RECONSTRUCTED,
+    CONDITION_NONE,
+    NULL, e_variant_enable_source_native,
+    { { CONDITION_NONE, CONDITION_NATIVE | CONDITION_INDEPENDENT    } } },
+
+  { CONDITION_BECAME_NATIVE,
+    CONDITION_NATIVE | CONDITION_INDEPENDENT | CONDITION_SERIALISED,
+    CONDITION_SOURCE_NATIVE | CONDITION_RECONSTRUCTED,
+    CONDITION_NONE,
+    NULL, e_variant_enable_became_native,
+    { { CONDITION_INDEPENDENT | CONDITION_SIZE_VALID,
+        CONDITION_NATIVE                                            } } },
+
+  { CONDITION_NATIVE,
+    CONDITION_NONE,
+    CONDITION_NONE,
+    CONDITION_SERIALISED,
+    NULL, NULL,
+    { { CONDITION_SOURCE_NATIVE                                     },
+      { CONDITION_BECAME_NATIVE                                     },
+      { CONDITION_RECONSTRUCTED                                     } } },
+
+  { CONDITION_SOURCE_TRUSTED,
+    CONDITION_TRUSTED | CONDITION_SERIALISED,
+    CONDITION_RECONSTRUCTED,
+    CONDITION_NONE,
+    NULL, e_variant_enable_source_trusted,
+    { { CONDITION_NONE, CONDITION_TRUSTED | CONDITION_INDEPENDENT   } } },
+
+  { CONDITION_BECAME_TRUSTED,
+    CONDITION_TRUSTED | CONDITION_SERIALISED,
+    CONDITION_SOURCE_TRUSTED | CONDITION_RECONSTRUCTED,
+    CONDITION_NONE,
+    NULL, e_variant_enable_became_trusted,
+    { { CONDITION_SERIALISED, CONDITION_TRUSTED                     } } },
+
+  { CONDITION_TRUSTED,
+    CONDITION_NONE,
+    CONDITION_NONE,
+    CONDITION_NONE,
+    NULL, NULL,
+    { { CONDITION_SOURCE_TRUSTED                                    },
+      { CONDITION_BECAME_TRUSTED                                    },
+      { CONDITION_RECONSTRUCTED                                     } } },
+
+  { CONDITION_SERIALISED,
+    CONDITION_SIZE_KNOWN,
+    CONDITION_NONE,
+    CONDITION_NATIVE | CONDITION_INDEPENDENT,
+    NULL, e_variant_enable_serialised,
+    { { CONDITION_SIZE_KNOWN                                        } } },
+
+  { CONDITION_SIZE_KNOWN,
+    CONDITION_NONE,
+    CONDITION_NOTIFY,
+    CONDITION_NONE,
+    NULL, e_variant_enable_size_known,
+    { { CONDITION_NONE, CONDITION_SIZE_KNOWN                        } } },
+
+  { CONDITION_SIZE_VALID,
+    CONDITION_SIZE_KNOWN,
+    CONDITION_NONE,
+    CONDITION_NONE,
+    NULL, NULL,
+    { { CONDITION_SIZE_KNOWN | CONDITION_FIXED_SIZE                 },
+      { CONDITION_SIZE_KNOWN | CONDITION_TRUSTED                    },
+      { CONDITION_SIZE_KNOWN | CONDITION_NATIVE                     } } },
+
+  { CONDITION_INDEPENDENT,
+    CONDITION_NONE,
+    CONDITION_NONE,
+    CONDITION_SERIALISED,
+    NULL, e_variant_enable_independent,
+    { { CONDITION_SERIALISED, CONDITION_NATIVE                      } } },
+
+  { CONDITION_RECONSTRUCTED,
+    CONDITION_TRUSTED | CONDITION_NATIVE | CONDITION_INDEPENDENT,
+    CONDITION_BECAME_NATIVE | CONDITION_BECAME_TRUSTED,
+    CONDITION_NONE,
+    NULL, e_variant_enable_reconstructed,
+    { { CONDITION_SERIALISED,
+        CONDITION_NATIVE | CONDITION_TRUSTED | CONDITION_FIXED_SIZE } } },
+
+  { CONDITION_FIXED_SIZE,
+    CONDITION_NONE,
+    CONDITION_NONE,
+    CONDITION_NONE,
+    NULL, e_variant_enable_fixed_size,
+    { { CONDITION_NONE, CONDITION_FIXED_SIZE                        } } },
+
+  { CONDITION_NOTIFY,
+    CONDITION_NONE,
+    CONDITION_NOTIFY - 1,
+    CONDITION_NONE,
+    NULL, NULL,
+    { } },
+
+  { }
+};
+
+static gboolean
+e_variant_state_is_valid (guint state)
+{
+  struct condition *c;
+
+  for (c = condition_table; c->condition; c++)
+    {
+      if (state & c->condition)
+        {
+          if (~state & c->implies)
+            return FALSE;
+
+          if (state & c->forbids)
+            return FALSE;
+        }
+      else
+        {
+          if (~state & c->absence_implies)
+            return FALSE;
+        }
+    }
+
+  return TRUE;
+}
+
+/*
+ * e_variant_assert_invariant:
+ * @value: a #EVariant instance to check
+ *
+ * This function asserts the class invariant on a #EVariant instance.
+ * Any detected problems will result in an assertion failure.
+ *
+ * This function is potentially very slow.
+ *
+ * This function never fails.
+ */
+void
+e_variant_assert_invariant (EVariant *value)
+{
+  if (value->state & CONDITION_NOTIFY)
+    return;
+
+  e_variant_lock (value);
+
+  g_assert_cmpint (value->ref_count, >, 0);
+
+  if G_UNLIKELY (!e_variant_state_is_valid (value->state & ~CONDITION_LOCKED))
+    g_critical ("instance %p in invalid state: %s",
+                value, e_variant_state_to_string (value->state));
+
+  if (value->state & CONDITION_SERIALISED)
+    {
+      if (value->state & CONDITION_RECONSTRUCTED)
+        e_variant_assert_invariant (value->contents.serialised.source);
+
+      if (!(value->state & CONDITION_INDEPENDENT))
+        e_variant_assert_invariant (value->contents.serialised.source);
+    }
+  else
+    {
+      gsize i;
+
+      for (i = 0; i < value->contents.tree.n_children; i++)
+        {
+          g_assert_cmpint (value->state & CONDITION_TRUSTED, <=,
+                           value->contents.tree.children[i]->state &
+                           CONDITION_TRUSTED);
+
+          g_assert (!value->contents.tree.children[i]->floating);
+          e_variant_assert_invariant (value->contents.tree.children[i]);
+        }
+    }
+
+  e_variant_unlock (value);
+}
+
+/* how many bits are in 'reqd' but not 'have'?
+ */
+static int
+bits_missing (guint have,
+              guint reqd)
+{
+  guint count = 0;
+
+  reqd &= ~have;
+  while (reqd && ++count)
+    reqd &= reqd - 1;
+
+  return count;
+}
+
+static gboolean
+e_variant_try_unlocked (EVariant *value,
+                        guint     conditions)
+{
+  struct precondition_clause *p;
+  struct condition *c;
+  int max_missing;
+
+  /* attempt to enable each missing condition */
+  for (c = condition_table; c->condition; c++)
+    if ((value->state & c->condition) < (conditions & c->condition))
+      {
+        /* prefer preconditon clauses with the fewest false terms */
+        for (max_missing = 0; max_missing < 10; max_missing++)
+          for (p = c->precondition; p->required || p->forbidden; p++)
+            if (!(value->state & p->forbidden) &&
+                bits_missing (value->state, p->required) < max_missing &&
+                max_missing && e_variant_try_unlocked (value, p->required))
+              goto attempt_enable;
+
+        return FALSE;
+
+       attempt_enable:
+        if (c->enable && !c->enable (value))
+          return FALSE;
+
+        value->state |= c->condition;
+      }
+
+  if (~value->state & conditions)
+    g_error ("was %x %x", value->state, conditions);
+
+  g_assert (!(~value->state & conditions));
+
+  return TRUE;
+}
+
+static gboolean
+e_variant_try_enabling_conditions (EVariant *value,
+                                   guint     conditions)
+{
+  gboolean success;
+
+  e_variant_assert_invariant (value);
+
+  if ((value->state & conditions) == conditions)
+    return TRUE;
+
+  e_variant_lock (value);
+  success = e_variant_try_unlocked (value, conditions);
+  e_variant_unlock (value);
+  e_variant_assert_invariant (value);
+
+  return success;
+}
+
+static void
+e_variant_require_conditions (EVariant *value,
+                              guint     condition_set)
+{
+  if G_UNLIKELY (!e_variant_try_enabling_conditions (value, condition_set))
+    g_error ("instance %p unable to enable '%s' from '%s'\n",
+             value, e_variant_state_to_string (condition_set),
+             e_variant_state_to_string (value->state));
+}
+
+static gboolean
+e_variant_forbid_conditions (EVariant *value,
+                             guint     condition_set)
+{
+  e_variant_assert_invariant (value);
+
+  if (value->state & condition_set)
+    return FALSE;
+
+  e_variant_lock (value);
+
+  if (value->state & condition_set)
+    {
+      e_variant_unlock (value);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+EVariant *
+e_variant_load_fixed (const EVariantType *type,
+                      gconstpointer       data,
+                      gsize               n_items)
+{
+  EVariantTypeInfo *info;
+  gsize fixed_size;
+  EVariant *new;
+
+  info = e_variant_type_info_get (type);
+  if (e_variant_type_is_array (type))
+    {
+      e_variant_type_info_query_element (info, NULL, &fixed_size);
+      fixed_size *= n_items;
+    }
+  else
+    e_variant_type_info_query (info, NULL, &fixed_size);
+  g_assert (fixed_size);
+
+  /* TODO: can't be trusted yet since there may be non-zero
+   *       padding between the elements.  can fix this with
+   *       some sort of intelligent zero-inserting memdup.
+   */
+  new = e_variant_alloc (info,
+                         CONDITION_INDEPENDENT | CONDITION_NATIVE |
+                         CONDITION_SERIALISED | CONDITION_SIZE_KNOWN);
+  new->contents.serialised.source = NULL;
+  new->contents.serialised.data = g_slice_alloc (fixed_size);
+  memcpy (new->contents.serialised.data, data, fixed_size);
+  new->size = fixed_size;
+
+  e_variant_assert_invariant (new);
+
+  return new;
+}
+
+/* == SECTION 5: other internal functions ================================ */
+static EVariantSerialised
+e_variant_get_gvs (EVariant  *value,
+                   EVariant **source)
+{
+  EVariantSerialised gvs = { value->type };
+
+  g_assert (value->state & CONDITION_SERIALISED);
+  e_variant_require_conditions (value, CONDITION_SIZE_VALID);
+
+  if (e_variant_forbid_conditions (value, CONDITION_INDEPENDENT))
+    {
+      /* dependent */
+      gvs.data = value->contents.serialised.data;
+
+      if (source)
+        *source = e_variant_ref (value->contents.serialised.source);
+
+      e_variant_unlock (value);
+    }
+  else
+    {
+      /* independent */
+      gvs.data = value->contents.serialised.data;
+
+      if (source)
+        *source = e_variant_ref (value);
+
+      e_variant_unlock (value);
+    }
+
+  gvs.size = value->size;
+
+  return gvs;
+}
+
+/*
+ * e_variant_fill_gvs:
+ * @serialised: the #EVariantSerialised to fill
+ * @data: our #EVariant instance
+ *
+ * Utility function used as a callback from the serialiser to get
+ * information about a given #EVariant instance (in @data).
+ */
+static void
+e_variant_fill_gvs (EVariantSerialised *serialised,
+                    gpointer            data)
+{
+  EVariant *value = data;
+
+  e_variant_assert_invariant (value);
+
+  e_variant_require_conditions (value, CONDITION_SIZE_VALID);
+
+  if (serialised->type_info == NULL)
+    serialised->type_info = value->type;
+
+  if (serialised->size == 0)
+    serialised->size = value->size;
+
+  g_assert (serialised->type_info == value->type);
+  g_assert (serialised->size == value->size);
+
+  if (serialised->data && serialised->size)
+    e_variant_store (value, serialised->data);
+}
+
+/*
+ * e_variant_get_zeros:
+ * @size: a size, in bytes
+ * @returns: a new reference to a #EVariant
+ *
+ * Creates a #EVariant with no type that contains at least @size bytes
+ * worth of zeros.  The instance will live forever.
+ *
+ * This is required for the case where deserialisation of a fixed-size
+ * value from a non-fixed-sized container fails.  The fixed-sized
+ * value needs to have zero-filled data (in case this data is
+ * requested).  This data must also outlive the child since the parent
+ * from which that child was taken may have been flattened (in which
+ * case the user expects the child's data to live as long as the
+ * parent).
+ *
+ * There are less-permanent ways of doing this (ie: somehow
+ * associating the zeros with the parent) but this way is easy and it
+ * works fine for now.
+ */
+static EVariant *
+e_variant_get_zeros (gsize size)
+{
+  static GStaticMutex lock = G_STATIC_MUTEX_INIT;
+  static GSList *zeros;
+  EVariant *value;
+
+  /* there's actually no sense in storing the entire linked list since
+   * we will always use the first item in the list, but it prevents
+   * valgrind from complaining.
+   */
+
+  if (size < 4096)
+    size = 4096;
+
+  g_static_mutex_lock (&lock);
+  if (zeros)
+    {
+      value = zeros->data;
+
+      if (value->size < size)
+        value = NULL;
+    }
+  else
+    value = NULL;
+
+  if (value == NULL)
+    {
+      size--;
+      size |= size >> 1; size |= size >> 2; size |= size >> 4;
+      size |= size >> 8; size |= size >> 16;
+      size |= size >> 16; size |= size >> 16;
+      size++;
+
+      value = e_variant_alloc (NULL,
+                               CONDITION_SERIALISED | CONDITION_SIZE_KNOWN |
+                               CONDITION_NATIVE | CONDITION_TRUSTED |
+                               CONDITION_INDEPENDENT | CONDITION_SIZE_VALID);
+      value->contents.serialised.source = NULL;
+      value->contents.serialised.data = g_malloc0 (size);
+      value->size = size;
+
+      zeros = g_slist_prepend (zeros, e_variant_ref_sink (value));
+    }
+  g_static_mutex_unlock (&lock);
+
+  return e_variant_ref (value);
+}
+
+/*
+ * e_variant_apply_flags:
+ * @value: a fresh #EVariant instance
+ * @flags: various load flags
+ *
+ * This function is the common code used to apply flags (normalise,
+ * byteswap, etc) to fresh #EVariant instances created using one of
+ * the load functions.
+ */
+static EVariant *
+e_variant_apply_flags (EVariant      *value,
+                       EVariantFlags  flags)
+{
+  guint16 byte_order = flags;
+
+  if (byte_order == 0)
+    byte_order = G_BYTE_ORDER;
+
+  g_assert (byte_order == G_LITTLE_ENDIAN ||
+            byte_order == G_BIG_ENDIAN);
+
+  if (byte_order == G_BYTE_ORDER)
+    value->state |= CONDITION_NATIVE;
+
+  else if (~flags & E_VARIANT_LAZY_BYTESWAP)
+    e_variant_require_conditions (value, CONDITION_NATIVE);
+
+  e_variant_assert_invariant (value);
+
+  return value;
+}
+
+gboolean
+e_variant_is_trusted (EVariant *value)
+{
+  return !!(value->state & CONDITION_TRUSTED);
+}
+
+gboolean
+e_variant_is_normal_ (EVariant *value)
+{
+  return e_variant_serialised_is_normal (e_variant_get_gvs (value, NULL));
+}
+
+/* == SECTION 6: user-visibile functions ================================= */
+/**
+ * e_variant_get_type:
+ * @value: a #EVariant
+ * @returns: a #EVariantType
+ *
+ * Determines the type of @value.
+ *
+ * The return value is valid for the lifetime of @value and must not
+ * be freed.
+ */
+const EVariantType *
+e_variant_get_type (EVariant *value)
+{
+  g_return_val_if_fail (value != NULL, NULL);
+  e_variant_assert_invariant (value);
+
+  return E_VARIANT_TYPE (e_variant_type_info_get_type_string (value->type));
+}
+
+/**
+ * e_variant_n_children:
+ * @value: a container #EVariant
+ * @returns: the number of children in the container
+ *
+ * Determines the number of children in a container #EVariant
+ * instance.  This includes variants, maybes, arrays, tuples and
+ * dictionary entries.  It is an error to call this function on any
+ * other type of #EVariant.
+ *
+ * For variants, the return value is always 1.  For maybes, it is
+ * always zero or one.  For arrays, it is the length of the array.
+ * For tuples it is the number of tuple items (which depends
+ * only on the type).  For dictionary entries, it is always 2.
+ *
+ * This function never fails.
+ * TS
+ **/
+gsize
+e_variant_n_children (EVariant *value)
+{
+  gsize n_children;
+
+  g_return_val_if_fail (value != NULL, 0);
+  e_variant_assert_invariant (value);
+
+  if (e_variant_forbid_conditions (value, CONDITION_SERIALISED))
+    {
+      n_children = value->contents.tree.n_children;
+      e_variant_unlock (value);
+    }
+  else
+    {
+      EVariantSerialised gvs;
+      EVariant *source;
+
+      gvs = e_variant_get_gvs (value, &source);
+      n_children = e_variant_serialised_n_children (gvs);
+      e_variant_unref (source);
+    }
+
+  return n_children;
+}
+
+/**
+ * e_variant_get_child_value:
+ * @value: a container #EVariant
+ * @index: the index of the child to fetch
+ * @returns: the child at the specified index
+ *
+ * Reads a child item out of a container #EVariant instance.  This
+ * includes variants, maybes, arrays, tuple and dictionary
+ * entries.  It is an error to call this function on any other type of
+ * #EVariant.
+ *
+ * It is an error if @index is greater than the number of child items
+ * in the container.  See e_variant_n_children().
+ *
+ * This function never fails.
+ **/
+EVariant *
+e_variant_get_child_value (EVariant *value,
+                           gsize     index)
+{
+  EVariant *child;
+
+  g_return_val_if_fail (value != NULL, NULL);
+  e_variant_assert_invariant (value);
+
+  if (e_variant_forbid_conditions (value, CONDITION_SERIALISED))
+    {
+      if G_UNLIKELY (index >= value->contents.tree.n_children)
+        g_error ("Attempt to access item %" G_GSIZE_FORMAT
+                 " in a container with only %" G_GSIZE_FORMAT
+                 " items", index, value->contents.tree.n_children);
+
+      child = e_variant_ref (value->contents.tree.children[index]);
+      e_variant_unlock (value);
+    }
+  else
+    {
+      EVariantSerialised gvs;
+      EVariant *source;
+
+      gvs = e_variant_get_gvs (value, &source);
+      gvs = e_variant_serialised_get_child (gvs, index);
+
+      child = e_variant_alloc (gvs.type_info,
+                               CONDITION_SERIALISED | CONDITION_SIZE_KNOWN);
+      child->type = gvs.type_info;
+      child->contents.serialised.source = source;
+      child->contents.serialised.data = gvs.data;
+      child->size = gvs.size;
+      child->floating = FALSE;
+
+      if (gvs.data == NULL)
+        {
+          /* not actually using source data -- release it */
+          e_variant_unref (child->contents.serialised.source);
+          child->contents.serialised.source = NULL;
+
+          if (gvs.size)
+            {
+              EVariant *zeros;
+              gpointer data;
+
+              g_assert (!((source->state | value->state)
+                          & CONDITION_TRUSTED));
+
+              zeros = e_variant_get_zeros (gvs.size);
+              data = zeros->contents.serialised.data;
+
+              child->contents.serialised.source = zeros;
+              child->contents.serialised.data = data;
+
+              child->state |= CONDITION_FIXED_SIZE | CONDITION_TRUSTED;
+            }
+          else
+            child->state |= CONDITION_INDEPENDENT;
+
+          child->state |= CONDITION_NATIVE | CONDITION_SIZE_VALID;
+        }
+
+      /* inherit 'native' and 'trusted' attributes */
+      child->state |= (source->state | value->state) &
+                      (CONDITION_NATIVE | CONDITION_TRUSTED);
+    }
+
+  e_variant_assert_invariant (child);
+
+  return child;
+}
+
+/**
+ * e_variant_get_size:
+ * @value: a #EVariant instance
+ * @returns: the serialised size of @value
+ *
+ * Determines the number of bytes that would be required to store
+ * @value with e_variant_store().
+ *
+ * In the case that @value is already in serialised form or the size
+ * has already been calculated (ie: this function has been called
+ * before) then this function is O(1).  Otherwise, the size is
+ * calculated, an operation which is approximately O(n) in the number
+ * of values involved.
+ *
+ * This function never fails.
+ **/
+gsize
+e_variant_get_size (EVariant *value)
+{
+  g_return_val_if_fail (value != NULL, 0);
+
+  e_variant_require_conditions (value, CONDITION_SIZE_VALID);
+
+  return value->size;
+}
+
+/**
+ * e_variant_get_data:
+ * @value: a #EVariant instance
+ * @returns: the serialised form of @value
+ *
+ * Returns a pointer to the serialised form of a #EVariant instance.
+ * The returned data is in machine native byte order but may not be in
+ * fully-normalised form if read from an untrusted source.  The
+ * returned data must not be freed; it remains valid for as long as
+ * @value exists.
+ *
+ * In the case that @value is already in serialised form, this
+ * function is O(1).  If the value is not already in serialised form,
+ * serialisation occurs implicitly and is approximately O(n) in the
+ * size of the result.
+ *
+ * This function never fails.
+ **/
+gconstpointer
+e_variant_get_data (EVariant *value)
+{
+  g_return_val_if_fail (value != NULL, NULL);
+
+  e_variant_require_conditions (value, CONDITION_NATIVE | CONDITION_SERIALISED);
+
+  return value->contents.serialised.data;
+}
+
+/**
+ * e_variant_store:
+ * @value: the #EVariant to store
+ * @data: the location to store the serialised data at
+ *
+ * Stores the serialised form of @variant at @data.  @data should be
+ * serialised enough.  See e_variant_get_size().
+ *
+ * The stored data is in machine native byte order but may not be in
+ * fully-normalised form if read from an untrusted source.  See
+ * e_variant_normalise() for a solution.
+ *
+ * This function is approximately O(n) in the size of @data.
+ *
+ * This function never fails.
+ **/
+void
+e_variant_store (EVariant *value,
+                 gpointer  data)
+{
+  g_return_if_fail (value != NULL);
+
+  e_variant_assert_invariant (value);
+
+  e_variant_require_conditions (value, CONDITION_SIZE_VALID | CONDITION_NATIVE);
+
+  if (e_variant_forbid_conditions (value, CONDITION_SERIALISED))
+    {
+      EVariantSerialised gvs;
+      EVariant **children;
+      gsize n_children;
+
+      gvs.type_info = value->type;
+      gvs.data = data;
+      gvs.size = value->size;
+
+      children = value->contents.tree.children;
+      n_children = value->contents.tree.n_children;
+
+      /* XXX we hold the lock for an awful long time here... */
+      e_variant_serialiser_serialise (gvs,
+                                      &e_variant_fill_gvs,
+                                      (gpointer *) children,
+                                      n_children);
+      e_variant_unlock (value);
+    }
+  else
+    {
+      EVariantSerialised gvs;
+      EVariant *source;
+
+      gvs = e_variant_get_gvs (value, &source);
+      memcpy (data, gvs.data, gvs.size);
+      e_variant_unref (source);
+  }
+}
+
+/**
+ * e_variant_get_fixed:
+ * @value: a #EVariant
+ * @size: the size of @value
+ * @returns: a pointer to the fixed-sized data
+ *
+ * Gets a pointer to the data of a fixed sized #EVariant instance.
+ * This pointer can be treated as a pointer to the equivalent C
+ * stucture type and accessed directly.  The data is in machine byte
+ * order.
+ *
+ * @size must be equal to the fixed size of the type of @value.  It
+ * isn't used for anything, but serves as a sanity check to ensure the
+ * user of this function will be able to make sense of the data they
+ * receive a pointer to.
+ *
+ * This function may return %NULL if @size is zero.
+ **/
+gconstpointer
+e_variant_get_fixed (EVariant *value,
+                     gsize     size)
+{
+  gsize fixed_size;
+
+  g_return_val_if_fail (value != NULL, NULL);
+
+  e_variant_assert_invariant (value);
+
+  e_variant_type_info_query (value->type, NULL, &fixed_size);
+  g_assert (fixed_size);
+
+  g_assert_cmpint (size, ==, fixed_size);
+
+  return e_variant_get_data (value);
+}
+
+/**
+ * e_variant_get_fixed_array:
+ * @value: an array #EVariant
+ * @elem_size: the size of one array element
+ * @length: a pointer to the length of the array, or %NULL
+ * @returns: a pointer to the array data
+ *
+ * Gets a pointer to the data of an array of fixed sized #EVariant
+ * instances.  This pointer can be treated as a pointer to an array of
+ * the equivalent C type and accessed directly.  The data is
+ * in machine byte order.
+ *
+ * @elem_size must be equal to the fixed size of the element type of
+ * @value.  It isn't used for anything, but serves as a sanity check
+ * to ensure the user of this function will be able to make sense of
+ * the data they receive a pointer to.
+ *
+ * @length is set equal to the number of items in the array, so that
+ * the size of the memory region returned is @elem_size times @length.
+ *
+ * This function may return %NULL if either @elem_size or @length is
+ * zero.
+ */
+gconstpointer
+e_variant_get_fixed_array (EVariant *value,
+                           gsize     elem_size,
+                           gsize    *length)
+{
+  gsize fixed_elem_size;
+
+  g_return_val_if_fail (value != NULL, NULL);
+
+  e_variant_assert_invariant (value);
+
+  /* unsupported: maybes are treated as arrays of size zero or one */
+  e_variant_type_info_query_element (value->type, NULL, &fixed_elem_size);
+  g_assert (fixed_elem_size);
+
+  g_assert_cmpint (elem_size, ==, fixed_elem_size);
+
+  if (length != NULL)
+    *length = e_variant_n_children (value);
+
+  return e_variant_get_data (value);
+}
+
+/**
+ * e_variant_unref:
+ * @value: a #EVariant
+ *
+ * Decreases the reference count of @variant.  When its reference
+ * count drops to 0, the memory used by the variant is freed.
+ **/
+void
+e_variant_unref (EVariant *value)
+{
+  g_return_if_fail (value != NULL);
+
+  e_variant_assert_invariant (value);
+
+  if (g_atomic_int_dec_and_test (&value->ref_count))
+    e_variant_free (value);
+}
+
+/**
+ * e_variant_ref:
+ * @value: a #EVariant
+ * @returns: the same @variant
+ *
+ * Increases the reference count of @variant.
+ **/
+EVariant *
+e_variant_ref (EVariant *value)
+{
+  g_return_val_if_fail (value != NULL, NULL);
+
+  e_variant_assert_invariant (value);
+
+  g_atomic_int_inc (&value->ref_count);
+
+  return value;
+}
+
+/**
+ * e_variant_ref_sink:
+ * @value: a #EVariant
+ * @returns: the same @variant
+ *
+ * If @value is floating, mark it as no longer floating.  If it is not
+ * floating, increase its reference count.
+ **/
+EVariant *
+e_variant_ref_sink (EVariant *value)
+{
+  g_return_val_if_fail (value != NULL, NULL);
+
+  e_variant_assert_invariant (value);
+
+  e_variant_ref (value);
+  if (g_atomic_int_compare_and_exchange (&value->floating, 1, 0))
+    e_variant_unref (value);
+
+  return value;
+}
+
+/* private */
+EVariant *
+e_variant_new_tree (const EVariantType  *type,
+                    EVariant           **children,
+                    gsize                n_children,
+                    gboolean             trusted)
+{
+  EVariant *new;
+
+  new = e_variant_alloc (e_variant_type_info_get (type),
+                         CONDITION_INDEPENDENT | CONDITION_NATIVE);
+  new->contents.tree.children = children;
+  new->contents.tree.n_children = n_children;
+  new->size = 0;
+
+  if (trusted)
+    new->state |= CONDITION_TRUSTED;
+
+  e_variant_assert_invariant (new);
+
+  return new;
+}
+
+/**
+ * e_variant_from_slice:
+ * @type: the #EVariantType of the new variant
+ * @slice: a pointer to a GSlice-allocated region
+ * @size: the size of @slice
+ * @flags: zero or more #EVariantFlags
+ * @returns: a new #EVariant instance
+ *
+ * Creates a #EVariant instance from a memory slice.  Ownership of the
+ * memory slice is assumed.  This function allows efficiently creating
+ * #EVariant instances with data that is, for example, read over a
+ * socket.
+ *
+ * If @type is %NULL then @data is assumed to have the type
+ * %E_VARIANT_TYPE_VARIANT and the return value is the value extracted
+ * from that variant.
+ *
+ * This function never fails.
+ **/
+EVariant *
+e_variant_from_slice (const EVariantType *type,
+                      gpointer            slice,
+                      gsize               size,
+                      EVariantFlags       flags)
+{
+  EVariant *new;
+
+  g_return_val_if_fail (slice != NULL || size == 0, NULL);
+
+  if (type == NULL)
+    {
+      EVariant *variant;
+
+      variant = e_variant_from_slice (E_VARIANT_TYPE_VARIANT,
+                                      slice, size, flags);
+      new = e_variant_get_variant (variant);
+      e_variant_unref (variant);
+
+      return new;
+    }
+  else
+    {
+      new = e_variant_alloc (e_variant_type_info_get (type),
+                             CONDITION_SERIALISED | CONDITION_INDEPENDENT |
+                             CONDITION_SIZE_KNOWN);
+
+      new->contents.serialised.source = NULL;
+      new->contents.serialised.data = slice;
+      new->floating = FALSE;
+      new->size = size;
+
+      return e_variant_apply_flags (new, flags);
+    }
+}
+
+/**
+ * e_variant_from_data:
+ * @type: the #EVariantType of the new variant
+ * @data: a pointer to the serialised data
+ * @size: the size of @data
+ * @flags: zero or more #EVariantFlags
+ * @notify: a function to call when @data is no longer needed
+ * @user_data: a #gpointer to pass to @notify
+ * @returns: a new #EVariant instance
+ *
+ * Creates a #EVariant instance from serialised data.  The data is not
+ * copied.  When the data is no longer required (which may be before
+ * or after the return value is freed) @notify is called.  @notify may
+ * even be called before this function returns.
+ *
+ * If @type is %NULL then @data is assumed to have the type
+ * %E_VARIANT_TYPE_VARIANT and the return value is the value extracted
+ * from that variant.
+ *
+ * This function never fails.
+ **/
+EVariant *
+e_variant_from_data (const EVariantType *type,
+                          gconstpointer       data,
+                          gsize               size,
+                          EVariantFlags       flags,
+                          GDestroyNotify      notify,
+                          gpointer            user_data)
+{
+  EVariant *new;
+
+  g_return_val_if_fail (data != NULL || size == 0, NULL);
+
+  if (type == NULL)
+    {
+      EVariant *variant;
+
+      variant = e_variant_from_data (E_VARIANT_TYPE_VARIANT,
+                                     data, size, flags, notify, user_data);
+      new = e_variant_get_variant (variant);
+      e_variant_unref (variant);
+
+      return new;
+    }
+  else
+    {
+      EVariant *marker;
+
+      marker = e_variant_alloc (NULL, CONDITION_NOTIFY);
+      marker->contents.notify.callback = notify;
+      marker->contents.notify.user_data = user_data;
+
+      new = e_variant_alloc (e_variant_type_info_get (type),
+                             CONDITION_SERIALISED | CONDITION_SIZE_KNOWN);
+      new->contents.serialised.source = marker;
+      new->contents.serialised.data = (gpointer) data;
+      new->floating = FALSE;
+      new->size = size;
+
+      return e_variant_apply_flags (new, flags);
+    }
+}
+
+/**
+ * e_variant_load:
+ * @type: the #EVariantType of the new variant, or %NULL
+ * @data: the serialised #EVariant data to load
+ * @size: the size of @data
+ * @flags: zero or more #EVariantFlags
+ * @returns: a new #EVariant instance
+ *
+ * Creates a new #EVariant instance.  @data is copied.  For a more
+ * efficient way to create #EVariant instances, see
+ * e_variant_from_slice() or e_variant_from_data().
+ *
+ * If @type is non-%NULL then it specifies the type of the
+ * #EVariant contained in the serialise data.  If @type is
+ * %NULL then the serialised data is assumed to have type
+ * "v" and instead of returning the variant itself, the
+ * contents of the variant is returned.  This provides a
+ * simple way to store data along with its type.
+ *
+ * This function is O(n) in the size of @data.
+ *
+ * This function never fails.
+ **/
+EVariant *
+e_variant_load (const EVariantType *type,
+                gconstpointer       data,
+                gsize               size,
+                EVariantFlags       flags)
+{
+  EVariant *new;
+
+  g_return_val_if_fail (data != NULL || size == 0, NULL);
+
+  if (type == NULL)
+    {
+      EVariant *variant;
+
+      variant = e_variant_load (E_VARIANT_TYPE_VARIANT, data, size, flags);
+      new = e_variant_get_variant (variant);
+      e_variant_unref (variant);
+
+      return new;
+    }
+  else
+    {
+      gpointer slice;
+
+      slice = g_slice_alloc (size);
+      memcpy (slice, data, size);
+
+      return e_variant_from_slice (type, slice, size, flags);
+    }
+}
+
+/**
+ * e_variant_classify:
+ * @value: a #EVariant
+ * @returns: the #EVariantClass of @value
+ *
+ * Returns the type class of @value.  This function is equivalent to
+ * calling e_variant_get_type() followed by
+ * e_variant_type_get_class().
+ **/
+EVariantClass
+e_variant_classify (EVariant *value)
+{
+  g_return_val_if_fail (value != NULL, 0);
+  return e_variant_type_info_get_type_char (value->type);
+}
+
+#include <glib-object.h>
+
+GType
+e_variant_get_gtype (void)
+{
+  static GType type_id = 0;
+
+  if (!type_id)
+    type_id = g_boxed_type_register_static (g_intern_static_string ("EVariant"),
+                                            (GBoxedCopyFunc) e_variant_ref,
+                                            (GBoxedFreeFunc) e_variant_unref);
+
+  return type_id;
+}
diff --git a/libedbus/gvariant-printer.c b/libedbus/gvariant-printer.c
new file mode 100644
index 0000000..f7806d8
--- /dev/null
+++ b/libedbus/gvariant-printer.c
@@ -0,0 +1,318 @@
+/*
+ * Copyright © 2009 Sam Thursfield
+ * Copyright © 2009 Codethink Limited
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Sam Thursfield
+ *          Ryan Lortie <desrt desrt ca>
+ */
+
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <glib.h>
+#include "gvariant.h"
+
+/**
+ * e_variant_print:
+ * @value: a #EVariant
+ * @type_annotate: %TRUE if type information should be included in
+ *                 the output
+ * @returns: a newly-allocated string holding the result.
+ *
+ * Pretty-prints @value in the format understood by e_variant_parse().
+ *
+ * If @type_annotate is %TRUE, then type information is included in
+ * the output.
+ */
+gchar *
+e_variant_print (EVariant *value,
+                 gboolean type_annotate)
+{
+  return g_string_free (e_variant_print_string (value, NULL, type_annotate),
+                        FALSE);
+};
+
+/**
+ * e_variant_print_string:
+ * @value: a #EVariant
+ * @string: a #GString, or %NULL
+ * @type_annotate: %TRUE if type information should be included in
+ *                 the output
+ * @returns: a #GString containing the string
+ *
+ * Behaves as e_variant_print(), but operates on a #GString.
+ *
+ * If @string is non-%NULL then it is appended to and returned.  Else,
+ * a new empty #GString is allocated and it is returned.
+ **/
+GString *
+e_variant_print_string (EVariant *value,
+                        GString *string,
+                        gboolean type_annotate)
+{
+  const EVariantType *type;
+
+  type = e_variant_get_type (value);
+
+  if G_UNLIKELY (string == NULL)
+      string = g_string_new (NULL);
+
+  switch (e_variant_classify (value))
+  {
+    case E_VARIANT_CLASS_ARRAY:
+      {
+        EVariantIter iter;
+
+        if (e_variant_iter_init (&iter, value))
+          {
+            EVariant *element;
+
+            g_string_append_c (string, '[');
+
+            /* only type annotate the first element (if requested) */
+            while ((element = e_variant_iter_next_value (&iter)))
+              {
+                e_variant_print_string (element, string, type_annotate);
+                g_string_append (string, ", ");
+                type_annotate = FALSE;
+              }
+            g_string_truncate (string, string->len - 2);
+
+            g_string_append_c (string, ']');
+          }
+        else
+          {
+            /* if there are no elements then we must type
+             * annotate the array itself (if requested)
+             */
+            if (type_annotate)
+              g_string_append_printf (string, "@%s ",
+                                      e_variant_get_type_string (value));
+            g_string_append (string, "[]");
+          }
+        break;
+      }
+
+    case E_VARIANT_CLASS_VARIANT:
+      {
+        EVariant *child = e_variant_get_variant (value);
+
+        /* Always annotate types in nested variants, because they are
+         * (by nature) of variable type.
+         */
+        g_string_append_c (string, '<');
+        e_variant_print_string (child, string, TRUE);
+        g_string_append_c (string, '>');
+
+        e_variant_unref (child);
+        break;
+      }
+
+    case E_VARIANT_CLASS_TUPLE:
+      {
+        EVariantIter iter;
+
+        g_string_append_c (string, '(');
+        if (e_variant_iter_init (&iter, value))
+          {
+            EVariant *element;
+
+            while ((element = e_variant_iter_next_value (&iter)))
+              {
+                e_variant_print_string (element, string, type_annotate);
+                g_string_append (string, ", ");
+              }
+            g_string_truncate (string, string->len - 2);
+          }
+        g_string_append_c (string, ')');
+
+        break;
+      }
+
+    case E_VARIANT_CLASS_DICT_ENTRY:
+      {
+        EVariantIter iter;
+        EVariant *element;
+
+        g_string_append_c (string, '{');
+
+        if (e_variant_iter_init (&iter, value))
+          {
+            while ((element = e_variant_iter_next_value (&iter)))
+              {
+                e_variant_print_string (element, string, type_annotate);
+
+                g_string_append_c (string, ':');
+
+                element = e_variant_iter_next_value (&iter);
+                e_variant_print_string (element, string, type_annotate);
+                g_string_append (string, ", ");
+              }
+            g_string_truncate (string, string->len - 2);
+
+            g_string_append_c (string, '}');
+          }
+
+        break;
+      }
+
+    case E_VARIANT_CLASS_BOOLEAN:
+      if (e_variant_get_boolean (value))
+        g_string_append (string, "true");
+      else
+        g_string_append (string, "false");
+      break;
+
+    /*case E_VARIANT_CLASS_MAYBE:
+      {
+        if (e_variant_n_children (value))
+          {
+            EVariant *element;
+
+            g_string_append (string, "<maybe>");
+            e_variant_markup_newline (string, newlines);
+
+            element = e_variant_get_child_value (value, 0);
+            e_variant_markup_print (element, string,
+                                    newlines, indentation, tabstop);
+            e_variant_unref (element);
+
+            e_variant_markup_indent (string, indentation - tabstop);
+            g_string_append (string, "</maybe>");
+          }
+        else
+          g_string_append_printf (string, "<nothing type='%s'/>",
+                                  e_variant_get_type_string (value));
+
+        break;
+      }*/
+
+    case E_VARIANT_CLASS_STRING:
+      {
+        const gchar *str = e_variant_get_string (value, NULL);
+        gchar *escaped = g_strescape (str, NULL);
+
+        g_string_append_printf (string, "\"%s\"", escaped);
+
+        g_free (escaped);
+        break;
+      }
+
+    case E_VARIANT_CLASS_BYTE:
+      if (type_annotate)
+        g_string_append (string, "byte ");
+      g_string_append_printf (string, "0x%02x",
+                              e_variant_get_byte (value));
+      break;
+
+    case E_VARIANT_CLASS_INT16:
+      if (type_annotate)
+        g_string_append (string, "int16 ");
+      g_string_append_printf (string, "%"G_GINT16_FORMAT,
+                              e_variant_get_int16 (value));
+      break;
+
+    case E_VARIANT_CLASS_UINT16:
+      if (type_annotate)
+        g_string_append (string, "uint16 ");
+      g_string_append_printf (string, "%"G_GUINT16_FORMAT,
+                              e_variant_get_uint16 (value));
+      break;
+
+    case E_VARIANT_CLASS_INT32:
+      /* Never annotate this type because it is the default for numbers
+       * (and this is a *pretty* printer)
+       */
+      g_string_append_printf (string, "%"G_GINT32_FORMAT,
+                              e_variant_get_int32 (value));
+      break;
+
+    case E_VARIANT_CLASS_HANDLE:
+      if (type_annotate)
+        g_string_append (string, "handle ");
+      g_string_append_printf (string, "%"G_GINT32_FORMAT,
+                              e_variant_get_handle (value));
+      break;
+
+    case E_VARIANT_CLASS_UINT32:
+      if (type_annotate)
+        g_string_append (string, "uint32 ");
+      g_string_append_printf (string, "%"G_GUINT32_FORMAT,
+                              e_variant_get_uint32 (value));
+      break;
+
+    case E_VARIANT_CLASS_INT64:
+      if (type_annotate)
+        g_string_append (string, "int64 ");
+      g_string_append_printf (string, "%"G_GINT64_FORMAT,
+                              e_variant_get_int64 (value));
+      break;
+
+    case E_VARIANT_CLASS_UINT64:
+      if (type_annotate)
+        g_string_append (string, "uint64 ");
+      g_string_append_printf (string, "%"G_GUINT64_FORMAT,
+                              e_variant_get_uint64 (value));
+      break;
+
+    case E_VARIANT_CLASS_DOUBLE:
+      {
+        gchar buffer[100];
+        gint i;
+
+        g_ascii_dtostr (buffer, sizeof buffer, e_variant_get_double (value));
+
+        for (i = 0; buffer[i]; i++)
+          if (buffer[i] == '.' || buffer[i] == 'e' ||
+              buffer[i] == 'n' || buffer[i] == 'N')
+            break;
+
+        /* if there is no '.' or 'e' in the float then add one */
+        if (buffer[i] == '\0')
+          {
+            buffer[i++] = '.';
+            buffer[i++] = '0';
+            buffer[i++] = '\0';
+          }
+
+        g_string_append (string, buffer);
+        break;
+      }
+
+    case E_VARIANT_CLASS_OBJECT_PATH:
+      if (type_annotate)
+        g_string_append (string, "objectpath ");
+      g_string_append_printf (string, "\"%s\"",
+                              e_variant_get_string (value, NULL));
+      break;
+
+    case E_VARIANT_CLASS_SIGNATURE:
+      if (type_annotate)
+        g_string_append (string, "signature");
+      g_string_append_printf (string, "\"%s\"",
+                              e_variant_get_string (value, NULL));
+      break;
+
+    default:
+      g_error ("e_variant_print: sorry... not handled yet: %s",
+               e_variant_get_type_string (value));
+  }
+
+  return string;
+};
diff --git a/libedbus/gvariant-private.h b/libedbus/gvariant-private.h
new file mode 100644
index 0000000..01f876c
--- /dev/null
+++ b/libedbus/gvariant-private.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright © 2007, 2008 Ryan Lortie
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __E_VARIANT_PRIVATE_H__
+#define __E_VARIANT_PRIVATE_H__
+
+#include "gvarianttypeinfo.h"
+#include "gvariant.h"
+
+/* gvariant-core.c */
+EVariant *                      e_variant_new_tree                      (const EVariantType  *type,
+                                                                         EVariant           **children,
+                                                                         gsize                n_children,
+                                                                         gboolean             trusted);
+void                            e_variant_assert_invariant              (EVariant            *value);
+gboolean                        e_variant_is_trusted                    (EVariant            *value);
+void                            e_variant_dump_data                     (EVariant            *value);
+EVariant *                      e_variant_load_fixed                    (const EVariantType  *type,
+                                                                         gconstpointer        data,
+                                                                         gsize                n_items);
+gboolean                        e_variant_iter_should_free              (EVariantIter        *iter);
+EVariant *                      e_variant_deep_copy                     (EVariant            *value);
+
+/* do not use -- only for test cases */
+gboolean                        e_variant_is_normal_                    (EVariant            *value);
+
+#endif /* __E_VARIANT_PRIVATE_H__ */
diff --git a/libedbus/gvariant-serialiser.c b/libedbus/gvariant-serialiser.c
new file mode 100644
index 0000000..0314205
--- /dev/null
+++ b/libedbus/gvariant-serialiser.c
@@ -0,0 +1,1653 @@
+/*
+ * Copyright © 2007, 2008 Ryan Lortie
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+/* Prologue {{{1 */
+#include "gvariant.h"
+#include "gvariant-serialiser.h"
+
+#include <glib/gtestutils.h>
+#include <glib/gstrfuncs.h>
+#include <glib/gtypes.h>
+
+#include <string.h>
+
+/* EVariantSerialiser
+ *
+ * After this prologue section, this file has roughly 2 parts.
+ *
+ * The first part is split up into sections according to various
+ * container types.  Maybe, Array, Tuple, Variant.  The Maybe and Array
+ * sections are subdivided for element types being fixed or
+ * variable-sized types.
+ *
+ * Each section documents the format of that particular type of
+ * container and implements 5 functions for dealing with it:
+ *
+ *  n_children:
+ *    - determines (according to serialised data) how many child values
+ *      are inside a particular container value.
+ *
+ *  get_child:
+ *    - gets the type of and the serialised data corresponding to a
+ *      given child value within the container value.
+ *
+ *  needed_size:
+ *    - determines how much space would be required to serialise a
+ *      container of this type, containing the given children so that
+ *      buffers can be preallocated before serialising.
+ *
+ *  serialise:
+ *    - write the serialised data for a container of this type,
+ *      containing the given children, to a buffer.
+ *
+ *  is_normal:
+ *    - check the given data to ensure that it is in normal form.  For a
+ *      given set of child values, there is exactly one normal form for
+ *      the serialised data of a container.  Other forms are possible
+ *      while maintaining the same children (for example, by inserting
+ *      something other than zero bytes as padding) but only one form is
+ *      the normal form.
+ *
+ * The second part contains the main entry point for each of the above 5
+ * functions and logic to dispatch it to the handler for the appropriate
+ * container type code.
+ *
+ * The second part also contains a routine to byteswap serialised
+ * values.  This code makes use of the n_children() and get_child()
+ * functions above to do its work so no extra support is needed on a
+ * per-container-type basis.
+ *
+ * There is also additional code for checking for normal form.  All
+ * numeric types are always in normal form since the full range of
+ * values is permitted (eg: 0 to 255 is a valid byte).  Special checks
+ * need to be performed for booleans (only 0 or 1 allowed), strings
+ * (properly nul-terminated) and object paths and signature strings
+ * (meeting the DBus specification requirements).
+ */
+
+/* < private >
+ * EVariantSerialised:
+ * @type_info: the #EVariantTypeInfo of this value
+ * @data: the serialised data of this value, or %NULL
+ * @size: the size of this value
+ *
+ * A structure representing a EVariant in serialised form.  This
+ * structure is used with #EVariantSerialisedFiller functions and as the
+ * primary interface to the serialiser.  See #EVariantSerialisedFiller
+ * for a description of its use there.
+ *
+ * When used with the serialiser API functions, the following invariants
+ * apply to all #EVariantTypeSerialised structures passed to and
+ * returned from the serialiser.
+ *
+ * @type_info must be non-%NULL.
+ *
+ * @data must be properly aligned for the type described by @type_info.
+ *
+ * If @type_info describes a fixed-sized type then @size must always be
+ * equal to the fixed size of that type.
+ *
+ * For fixed-sized types (and only fixed-sized types), @data may be
+ * %NULL even if @size is non-zero.  This happens when a framing error
+ * occurs while attempting to extract a fixed-sized value out of a
+ * variable-sized container.  There is no data to return for the
+ * fixed-sized type, yet @size must be non-zero.  The effect of this
+ * combination should be as if @data were a pointer to an
+ * appropriately-sized zero-filled region.
+ */
+
+/* < private >
+ * e_variant_serialised_check:
+ * @serialised: a #EVariantSerialised struct
+ *
+ * Checks @serialised for validity according to the invariants described
+ * above.
+ */
+static void
+e_variant_serialised_check (EVariantSerialised serialised)
+{
+  gsize fixed_size;
+  guint alignment;
+
+  g_assert (serialised.type_info != NULL);
+  e_variant_type_info_query (serialised.type_info, &alignment, &fixed_size);
+
+  if (fixed_size)
+    g_assert_cmpint (serialised.size, ==, fixed_size);
+  else
+    g_assert (serialised.size == 0 || serialised.data != NULL);
+
+  g_assert_cmpint (alignment & (gsize) serialised.data, ==, 0);
+}
+
+/* < private >
+ * EVariantSerialisedFiller:
+ * @serialised: a #EVariantSerialised instance to fill
+ * @data: data from the children array
+ *
+ * This function is called back from e_variant_serialiser_needed_size()
+ * and e_variant_serialiser_serialise().  It fills in missing details
+ * from a partially-complete #EVariantSerialised.
+ *
+ * The @data parameter passed back to the function is one of the items
+ * that was passed to the serialiser in the @children array.  It
+ * represents a single child item of the container that is being
+ * serialised.  The information filled in to @serialised is the
+ * information for this child.
+ *
+ * If the @type_info field of @serialised is %NULL then the callback
+ * function must set it to the type information corresponding to the
+ * type of the child.  No reference should be added.  If it is non-%NULL
+ * then the callback should assert that it is equal to the actual type
+ * of the child.
+ *
+ * If the @size field is zero then the callback must fill it in with the
+ * required amount of space to store the serialised form of the child.
+ * If it is non-zero then the callback should assert that it is equal to
+ * the needed size of the child.
+ *
+ * If @data is non-%NULL then it points to a space that is properly
+ * aligned for and large enough to store the serialised data of the
+ * child.  The callback must store the serialised form of the child at
+ * @data.
+ *
+ * If the child value is another container then the callback will likely
+ * recurse back into the serialiser by calling
+ * e_variant_serialiser_needed_size() to determine @size and
+ * e_variant_serialiser_serialise() to write to @data.
+ */
+
+/* PART 1: Container types {{{1
+ *
+ * This section contains the serialiser implementation functions for
+ * each container type.
+ */
+
+/* Maybe {{{2
+ *
+ * Maybe types are handled depending on if the element type of the maybe
+ * type is a fixed-sized or variable-sized type.  Although all maybe
+ * types themselves are variable-sized types, herein, a maybe value with
+ * a fixed-sized element type is called a "fixed-sized maybe" for
+ * convenience and a maybe value with a variable-sized element type is
+ * called a "variable-sized maybe".
+ */
+
+/* Fixed-sized Maybe {{{3
+ *
+ * The size of a maybe value with a fixed-sized element type is either 0
+ * or equal to the fixed size of its element type.  The case where the
+ * size of the maybe value is zero corresponds to the "Nothing" case and
+ * the case where the size of the maybe value is equal to the fixed size
+ * of the element type corresponds to the "Just" case; in that case, the
+ * serialised data of the child value forms the entire serialised data
+ * of the maybe value.
+ *
+ * In the event that a fixed-sized maybe value is presented with a size
+ * that is not equal to the fixed size of the element type then the
+ * value must be taken to be "Nothing".
+ */
+
+static gsize
+gvs_fixed_sized_maybe_n_children (EVariantSerialised value)
+{
+  gsize element_fixed_size;
+
+  e_variant_type_info_query_element (value.type_info, NULL,
+                                     &element_fixed_size);
+
+  return (element_fixed_size == value.size) ? 1 : 0;
+}
+
+static EVariantSerialised
+gvs_fixed_sized_maybe_get_child (EVariantSerialised value,
+                                 gsize              index_)
+{
+  /* the child has the same bounds as the
+   * container, so just update the type.
+   */
+  value.type_info = e_variant_type_info_element (value.type_info);
+  e_variant_type_info_ref (value.type_info);
+
+  return value;
+}
+
+static gsize
+gvs_fixed_sized_maybe_needed_size (EVariantTypeInfo         *type_info,
+                                   EVariantSerialisedFiller  gvs_filler,
+                                   const gpointer           *children,
+                                   gsize                     n_children)
+{
+  if (n_children)
+    {
+      gsize element_fixed_size;
+
+      e_variant_type_info_query_element (type_info, NULL,
+                                         &element_fixed_size);
+
+      return element_fixed_size;
+    }
+  else
+    return 0;
+}
+
+static void
+gvs_fixed_sized_maybe_serialise (EVariantSerialised        value,
+                                 EVariantSerialisedFiller  gvs_filler,
+                                 const gpointer           *children,
+                                 gsize                     n_children)
+{
+  if (n_children)
+    {
+      EVariantSerialised child = { NULL, value.data, value.size };
+
+      gvs_filler (&child, children[0]);
+    }
+}
+
+static gboolean
+gvs_fixed_sized_maybe_is_normal (EVariantSerialised value)
+{
+  if (value.size > 0)
+    {
+      gsize element_fixed_size;
+
+      e_variant_type_info_query_element (value.type_info,
+                                         NULL, &element_fixed_size);
+
+      if (value.size != element_fixed_size)
+        return FALSE;
+
+      /* proper element size: "Just".  recurse to the child. */
+      value.type_info = e_variant_type_info_element (value.type_info);
+
+      return e_variant_serialised_is_normal (value);
+    }
+
+  /* size of 0: "Nothing" */
+  return TRUE;
+}
+
+/* Variable-sized Maybe
+ *
+ * The size of a maybe value with a variable-sized element type is
+ * either 0 or strictly greater than 0.  The case where the size of the
+ * maybe value is zero corresponds to the "Nothing" case and the case
+ * where the size of the maybe value is greater than zero corresponds to
+ * the "Just" case; in that case, the serialised data of the child value
+ * forms the first part of the serialised data of the maybe value and is
+ * followed by a single zero byte.  This zero byte is always appended,
+ * regardless of any zero bytes that may already be at the end of the
+ * serialised ata of the child value.
+ */
+
+static gsize
+gvs_variable_sized_maybe_n_children (EVariantSerialised value)
+{
+  return (value.size > 0) ? 1 : 0;
+}
+
+static EVariantSerialised
+gvs_variable_sized_maybe_get_child (EVariantSerialised value,
+                                    gsize              index_)
+{
+  /* remove the padding byte and update the type. */
+  value.type_info = e_variant_type_info_element (value.type_info);
+  e_variant_type_info_ref (value.type_info);
+  value.size--;
+
+  /* if it's zero-sized then it may as well be NULL */
+  if (value.size == 0)
+    value.data = NULL;
+
+  return value;
+}
+
+static gsize
+gvs_variable_sized_maybe_needed_size (EVariantTypeInfo         *type_info,
+                                      EVariantSerialisedFiller  gvs_filler,
+                                      const gpointer           *children,
+                                      gsize                     n_children)
+{
+  if (n_children)
+    {
+      EVariantSerialised child = {  };
+
+      gvs_filler (&child, children[0]);
+
+      return child.size + 1;
+    }
+  else
+    return 0;
+}
+
+static void
+gvs_variable_sized_maybe_serialise (EVariantSerialised        value,
+                                    EVariantSerialisedFiller  gvs_filler,
+                                    const gpointer           *children,
+                                    gsize                     n_children)
+{
+  if (n_children)
+    {
+      EVariantSerialised child = { NULL, value.data, value.size - 1 };
+
+      /* write the data for the child.  */
+      gvs_filler (&child, children[0]);
+      value.data[child.size] = '\0';
+    }
+}
+
+static gboolean
+gvs_variable_sized_maybe_is_normal (EVariantSerialised value)
+{
+  if (value.size == 0)
+    return TRUE;
+
+  if (value.data[value.size - 1] != '\0')
+    return FALSE;
+
+  value.type_info = e_variant_type_info_element (value.type_info);
+  value.size--;
+
+  return e_variant_serialised_is_normal (value);
+}
+
+/* Arrays {{{2
+ *
+ * Just as with maybe types, array types are handled depending on if the
+ * element type of the array type is a fixed-sized or variable-sized
+ * type.  Similar to maybe types, for convenience, an array value with a
+ * fixed-sized element type is called a "fixed-sized array" and an array
+ * value with a variable-sized element type is called a "variable sized
+ * array".
+ */
+
+/* Fixed-sized Array {{{3
+ *
+ * For fixed sized arrays, the serialised data is simply a concatenation
+ * of the serialised data of each element, in order.  Since fixed-sized
+ * values always have a fixed size that is a multiple of their alignment
+ * requirement no extra padding is required.
+ *
+ * In the event that a fixed-sized array is presented with a size that
+ * is not an integer multiple of the element size then the value of the
+ * array must be taken as being empty.
+ */
+
+static gsize
+gvs_fixed_sized_array_n_children (EVariantSerialised value)
+{
+  gsize element_fixed_size;
+
+  e_variant_type_info_query_element (value.type_info, NULL,
+                                     &element_fixed_size);
+
+  if (value.size % element_fixed_size == 0)
+    return value.size / element_fixed_size;
+
+  return 0;
+}
+
+static EVariantSerialised
+gvs_fixed_sized_array_get_child (EVariantSerialised value,
+                                 gsize              index_)
+{
+  EVariantSerialised child = {  };
+
+  child.type_info = e_variant_type_info_element (value.type_info);
+  e_variant_type_info_query (child.type_info, NULL, &child.size);
+  child.data = value.data + (child.size * index_);
+  e_variant_type_info_ref (child.type_info);
+
+  return child;
+}
+
+static gsize
+gvs_fixed_sized_array_needed_size (EVariantTypeInfo         *type_info,
+                                   EVariantSerialisedFiller  gvs_filler,
+                                   const gpointer           *children,
+                                   gsize                     n_children)
+{
+  gsize element_fixed_size;
+
+  e_variant_type_info_query_element (type_info, NULL, &element_fixed_size);
+
+  return element_fixed_size * n_children;
+}
+
+static void
+gvs_fixed_sized_array_serialise (EVariantSerialised        value,
+                                 EVariantSerialisedFiller  gvs_filler,
+                                 const gpointer           *children,
+                                 gsize                     n_children)
+{
+  EVariantSerialised child = {  };
+  gsize i;
+
+  child.type_info = e_variant_type_info_element (value.type_info);
+  e_variant_type_info_query (child.type_info, NULL, &child.size);
+  child.data = value.data;
+
+  for (i = 0; i < n_children; i++)
+    {
+      gvs_filler (&child, children[i]);
+      child.data += child.size;
+    }
+}
+
+static gboolean
+gvs_fixed_sized_array_is_normal (EVariantSerialised value)
+{
+  EVariantSerialised child = {  };
+
+  child.type_info = e_variant_type_info_element (value.type_info);
+  e_variant_type_info_query (child.type_info, NULL, &child.size);
+
+  if (value.size % child.size != 0)
+    return FALSE;
+
+  for (child.data = value.data;
+       child.data < value.data + value.size;
+       child.data += child.size)
+    {
+      if (!e_variant_serialised_is_normal (child))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+/* Variable-sized Array {{{3
+ *
+ * Variable sized arrays, containing variable-sized elements, must be
+ * able to determine the boundaries between the elements.  The items
+ * cannot simply be concatenated.  Additionally, we are faced with the
+ * fact that non-fixed-sized values do not neccessarily have a size that
+ * is a multiple of their alignment requirement, so we may need to
+ * insert zero-filled padding.
+ *
+ * While it is possible to find the start of an item by starting from
+ * the end of the item before it and padding for alignment, it is not
+ * generally possible to do the reverse operation.  For this reason, we
+ * record the end point of each element in the array.
+ *
+ * EVariant works in terms of "offsets".  An offset is a pointer to a
+ * boundary between two bytes.  In 4 bytes of serialised data, there
+ * would be 5 possible offsets: one at the start ('0'), one between each
+ * pair of adjacent bytes ('1', '2', '3') and one at the end ('4').
+ *
+ * The numeric value of an offset is an unsigned integer given relative
+ * to the start of the serialised data of the array.  Offsets are always
+ * stored in little endian byte order and are always only as big as they
+ * need to be.  For example, in 255 bytes of serialised data, there are
+ * 256 offsets.  All possibilities can be stored in an 8 bit unsigned
+ * integer.  In 256 bytes of serialised data, however, there are 257
+ * possible offsets so 16 bit integers must be used.  The size of an
+ * offset is always a power of 2.
+ *
+ * The offsets are stored at the end of the serialised data of the
+ * array.  They are simply concatenated on without any particular
+ * alignment.  The size of the offsets is included in the size of the
+ * serialised data for purposes of determining the size of the offsets.
+ * This presents a possibly ambiguity; in certain cases, a particular
+ * value of array could have two different serialised forms.
+ *
+ * Imagine an array containing a single string of 253 bytes in length
+ * (so, 254 bytes including the nul terminator).  Now the offset must be
+ * written.  If an 8 bit offset is written, it will bring the size of
+ * the array's serialised data to 255 -- which means that the use of an
+ * 8 bit offset was valid.  If a 16 bit offset is used then the total
+ * size of the array will be 256 -- which means that the use of a 16 bit
+ * offset was valid.  Although both of these will be accepted by the
+ * deserialiser, only the smaller of the two is considered to be in
+ * normal form and that is the one that the serialiser must produce.
+ */
+
+static inline gsize
+gvs_read_unaligned_le (guchar *bytes,
+                       guint   size)
+{
+  union
+  {
+    guchar bytes[GLIB_SIZEOF_SIZE_T];
+    gsize integer;
+  } tmpvalue;
+
+  tmpvalue.integer = 0;
+  memcpy (&tmpvalue.bytes, bytes, size);
+
+  return GSIZE_FROM_LE (tmpvalue.integer);
+}
+
+static inline void
+gvs_write_unaligned_le (guchar *bytes,
+                        gsize   value,
+                        guint   size)
+{
+  union
+  {
+    guchar bytes[GLIB_SIZEOF_SIZE_T];
+    gsize integer;
+  } tmpvalue;
+
+  tmpvalue.integer = GSIZE_TO_LE (value);
+  memcpy (bytes, &tmpvalue.bytes, size);
+}
+
+static guint
+gvs_get_offset_size (gsize size)
+{
+  if (size > G_MAXUINT32)
+    return 8;
+
+  else if (size > G_MAXUINT16)
+    return 4;
+
+  else if (size > G_MAXUINT8)
+    return 2;
+
+  else if (size > 0)
+    return 1;
+
+  return 0;
+}
+
+static gsize
+gvs_calculate_total_size (gsize body_size,
+                          gsize offsets)
+{
+  if (body_size + 1 * offsets <= G_MAXUINT8)
+    return body_size + 1 * offsets;
+
+  if (body_size + 2 * offsets <= G_MAXUINT16)
+    return body_size + 2 * offsets;
+
+  if (body_size + 4 * offsets <= G_MAXUINT32)
+    return body_size + 4 * offsets;
+
+  return body_size + 8 * offsets;
+}
+
+static gsize
+gvs_variable_sized_array_n_children (EVariantSerialised value)
+{
+  gsize offsets_array_size;
+  gsize offset_size;
+  gsize last_end;
+
+  if (value.size == 0)
+    return 0;
+
+  offset_size = gvs_get_offset_size (value.size);
+
+  last_end = gvs_read_unaligned_le (value.data + value.size -
+                                    offset_size, offset_size);
+
+  if (last_end > value.size)
+    return 0;
+
+  offsets_array_size = value.size - last_end;
+
+  if (offsets_array_size % offset_size)
+    return 0;
+
+  return offsets_array_size / offset_size;
+}
+
+static EVariantSerialised
+gvs_variable_sized_array_get_child (EVariantSerialised value,
+                                    gsize              index_)
+{
+  EVariantSerialised child = {  };
+  gsize offset_size;
+  gsize last_end;
+  gsize start;
+  gsize end;
+
+  child.type_info = e_variant_type_info_element (value.type_info);
+  e_variant_type_info_ref (child.type_info);
+
+  offset_size = gvs_get_offset_size (value.size);
+
+  last_end = gvs_read_unaligned_le (value.data + value.size -
+                                    offset_size, offset_size);
+
+  if (index_ > 0)
+    {
+      guint alignment;
+
+      start = gvs_read_unaligned_le (value.data + last_end +
+                                     (offset_size * (index_ - 1)),
+                                     offset_size);
+
+      e_variant_type_info_query (child.type_info, &alignment, NULL);
+      start += (-start) & alignment;
+    }
+  else
+    start = 0;
+
+  end = gvs_read_unaligned_le (value.data + last_end +
+                               (offset_size * index_),
+                               offset_size);
+
+  if (start < end && end <= value.size)
+    {
+      child.data = value.data + start;
+      child.size = end - start;
+    }
+
+  return child;
+}
+
+static gsize
+gvs_variable_sized_array_needed_size (EVariantTypeInfo         *type_info,
+                                      EVariantSerialisedFiller  gvs_filler,
+                                      const gpointer           *children,
+                                      gsize                     n_children)
+{
+  guint alignment;
+  gsize offset;
+  gsize i;
+
+  e_variant_type_info_query (type_info, &alignment, NULL);
+  offset = 0;
+
+  for (i = 0; i < n_children; i++)
+    {
+      EVariantSerialised child = {  };
+
+      offset += (-offset) & alignment;
+      gvs_filler (&child, children[i]);
+      offset += child.size;
+    }
+
+  return gvs_calculate_total_size (offset, n_children);
+}
+
+static void
+gvs_variable_sized_array_serialise (EVariantSerialised        value,
+                                    EVariantSerialisedFiller  gvs_filler,
+                                    const gpointer           *children,
+                                    gsize                     n_children)
+{
+  guchar *offset_ptr;
+  gsize offset_size;
+  guint alignment;
+  gsize offset;
+  gsize i;
+
+  e_variant_type_info_query (value.type_info, &alignment, NULL);
+  offset_size = gvs_get_offset_size (value.size);
+  offset = 0;
+
+  offset_ptr = value.data + value.size - offset_size * n_children;
+
+  for (i = 0; i < n_children; i++)
+    {
+      EVariantSerialised child = {  };
+
+      while (offset & alignment)
+        value.data[offset++] = '\0';
+
+      child.data = value.data + offset;
+      gvs_filler (&child, children[i]);
+      offset += child.size;
+
+      gvs_write_unaligned_le (offset_ptr, offset, offset_size);
+      offset_ptr += offset_size;
+    }
+}
+
+static gboolean
+gvs_variable_sized_array_is_normal (EVariantSerialised value)
+{
+  EVariantSerialised child = {  };
+  gsize offsets_array_size;
+  guchar *offsets_array;
+  guint offset_size;
+  guint alignment;
+  gsize last_end;
+  gsize length;
+  gsize offset;
+  gsize i;
+
+  if (value.size == 0)
+    return TRUE;
+
+  offset_size = gvs_get_offset_size (value.size);
+  last_end = gvs_read_unaligned_le (value.data + value.size -
+                                    offset_size, offset_size);
+
+  if (last_end > value.size)
+    return FALSE;
+
+  offsets_array_size = value.size - last_end;
+
+  if (offsets_array_size % offset_size)
+    return FALSE;
+
+  offsets_array = value.data + value.size - offsets_array_size;
+  length = offsets_array_size / offset_size;
+
+  if (length == 0)
+    return FALSE;
+
+  child.type_info = e_variant_type_info_element (value.type_info);
+  e_variant_type_info_query (child.type_info, &alignment, NULL);
+  offset = 0;
+
+  for (i = 0; i < length; i++)
+    {
+      gsize this_end;
+
+      this_end = gvs_read_unaligned_le (offsets_array + offset_size * i,
+                                        offset_size);
+
+      if (this_end < offset || this_end > last_end)
+        return FALSE;
+
+      while (offset & alignment)
+        {
+          if (!(offset < this_end && value.data[offset] == '\0'))
+            return FALSE;
+          offset++;
+        }
+
+      child.data = value.data + offset;
+      child.size = this_end - offset;
+
+      if (child.size == 0)
+        child.data = NULL;
+
+      if (!e_variant_serialised_is_normal (child))
+        return FALSE;
+
+      offset = this_end;
+    }
+
+  g_assert (offset == last_end);
+
+  return TRUE;
+}
+
+/* Tuples {{{2
+ *
+ * Since tuples can contain a mix of variable- and fixed-sized items,
+ * they are, in terms of serialisation, a hybrid of variable-sized and
+ * fixed-sized arrays.
+ *
+ * Offsets are only stored for variable-sized items.  Also, since the
+ * number of items in a tuple is known from its type, we are able to
+ * know exactly how many offsets to expect in the serialised data (and
+ * therefore how much space is taken up by the offset array).  This
+ * means that we know where the end of the serialised data for the last
+ * item is -- we can just subtract the size of the offset array from the
+ * total size of the tuple.  For this reason, the last item in the tuple
+ * doesn't need an offset stored.
+ *
+ * Tuple offsets are stored in reverse.  This design choice allows
+ * iterator-based deserialisers to be more efficient.
+ *
+ * Most of the "heavy lifting" here is handled by the EVariantTypeInfo
+ * for the tuple.  See the notes in gvarianttypeinfo.h.
+ */
+
+static gsize
+gvs_tuple_n_children (EVariantSerialised value)
+{
+  return e_variant_type_info_n_members (value.type_info);
+}
+
+static EVariantSerialised
+gvs_tuple_get_child (EVariantSerialised value,
+                     gsize              index_)
+{
+  const EVariantMemberInfo *member_info;
+  EVariantSerialised child = {  };
+  gsize offset_size;
+  gsize start, end;
+
+  member_info = e_variant_type_info_member_info (value.type_info, index_);
+  child.type_info = e_variant_type_info_ref (member_info->type_info);
+  offset_size = gvs_get_offset_size (value.size);
+
+  /* tuples are the only (potentially) fixed-sized containers, so the
+   * only ones that have to deal with the possibility of having %NULL
+   * data with a non-zero %size if errors occured elsewhere.
+   */
+  if G_UNLIKELY (value.data == NULL && value.size != 0)
+    {
+      e_variant_type_info_query (child.type_info, NULL, &child.size);
+
+      /* this can only happen in fixed-sized tuples,
+       * so the child must also be fixed sized.
+       */
+      g_assert (child.size != 0);
+      child.data = NULL;
+
+      return child;
+    }
+
+  if (member_info->ending_type == E_VARIANT_MEMBER_ENDING_OFFSET)
+    {
+      if (offset_size * (member_info->i + 2) > value.size)
+        return child;
+    }
+  else
+    {
+      if (offset_size * (member_info->i + 1) > value.size)
+        {
+          /* if the child is fixed size, return its size */
+          e_variant_type_info_query (child.type_info, NULL, &child.size);
+
+          return child;
+        }
+    }
+
+  if (member_info->i + 1)
+    start = gvs_read_unaligned_le (value.data + value.size -
+                                   offset_size * (member_info->i + 1),
+                                   offset_size);
+  else
+    start = 0;
+
+  start += member_info->a;
+  start &= member_info->b;
+  start |= member_info->c;
+
+  if (member_info->ending_type == E_VARIANT_MEMBER_ENDING_LAST)
+    end = value.size - offset_size * (member_info->i + 1);
+
+  else if (member_info->ending_type == E_VARIANT_MEMBER_ENDING_FIXED)
+    {
+      gsize fixed_size;
+
+      e_variant_type_info_query (child.type_info, NULL, &fixed_size);
+      end = start + fixed_size;
+      child.size = fixed_size;
+    }
+
+  else /* E_VARIANT_MEMEBER_ENDING_OFFSET */
+    end = gvs_read_unaligned_le (value.data + value.size -
+                                 offset_size * (member_info->i + 2),
+                                 offset_size);
+
+  if (start < end && end <= value.size)
+    {
+      child.data = value.data + start;
+      child.size = end - start;
+    }
+
+  return child;
+}
+
+static gsize
+gvs_tuple_needed_size (EVariantTypeInfo         *type_info,
+                       EVariantSerialisedFiller  gvs_filler,
+                       const gpointer           *children,
+                       gsize                     n_children)
+{
+  const EVariantMemberInfo *member_info = NULL;
+  gsize fixed_size;
+  gsize offset;
+  gsize i;
+
+  e_variant_type_info_query (type_info, NULL, &fixed_size);
+
+  if (fixed_size)
+    return fixed_size;
+
+  offset = 0;
+
+  for (i = 0; i < n_children; i++)
+    {
+      guint alignment;
+
+      member_info = e_variant_type_info_member_info (type_info, i);
+      e_variant_type_info_query (member_info->type_info,
+                                 &alignment, &fixed_size);
+      offset += (-offset) & alignment;
+
+      if (fixed_size)
+        offset += fixed_size;
+      else
+        {
+          EVariantSerialised child = {  };
+
+          gvs_filler (&child, children[i]);
+          offset += child.size;
+        }
+    }
+
+  return gvs_calculate_total_size (offset, member_info->i + 1);
+}
+
+static void
+gvs_tuple_serialise (EVariantSerialised        value,
+                     EVariantSerialisedFiller  gvs_filler,
+                     const gpointer           *children,
+                     gsize                     n_children)
+{
+  gsize offset_size;
+  gsize offset;
+  gsize i;
+
+  offset_size = gvs_get_offset_size (value.size);
+  offset = 0;
+
+  for (i = 0; i < n_children; i++)
+    {
+      const EVariantMemberInfo *member_info;
+      EVariantSerialised child = {  };
+      guint alignment;
+
+      member_info = e_variant_type_info_member_info (value.type_info, i);
+      e_variant_type_info_query (member_info->type_info, &alignment, NULL);
+
+      while (offset & alignment)
+        value.data[offset++] = '\0';
+
+      child.data = value.data + offset;
+      gvs_filler (&child, children[i]);
+      offset += child.size;
+
+      if (member_info->ending_type == E_VARIANT_MEMBER_ENDING_OFFSET)
+        {
+          value.size -= offset_size;
+          gvs_write_unaligned_le (value.data + value.size,
+                                  offset, offset_size);
+        }
+    }
+
+  while (offset < value.size)
+    value.data[offset++] = '\0';
+}
+
+static gboolean
+gvs_tuple_is_normal (EVariantSerialised value)
+{
+  guint offset_size;
+  gsize offset_ptr;
+  gsize length;
+  gsize offset;
+  gsize i;
+
+  offset_size = gvs_get_offset_size (value.size);
+  length = e_variant_type_info_n_members (value.type_info);
+  offset_ptr = value.size;
+  offset = 0;
+
+  for (i = 0; i < length; i++)
+    {
+      const EVariantMemberInfo *member_info;
+      EVariantSerialised child;
+      gsize fixed_size;
+      guint alignment;
+      gsize end;
+
+      member_info = e_variant_type_info_member_info (value.type_info, i);
+      child.type_info = member_info->type_info;
+
+      e_variant_type_info_query (child.type_info, &alignment, &fixed_size);
+
+      while (offset & alignment)
+        {
+          if (offset > value.size || value.data[offset] != '\0')
+            return FALSE;
+          offset++;
+        }
+
+      child.data = value.data + offset;
+
+      switch (member_info->ending_type)
+        {
+        case E_VARIANT_MEMBER_ENDING_FIXED:
+          end = offset + fixed_size;
+          break;
+
+        case E_VARIANT_MEMBER_ENDING_LAST:
+          end = offset_ptr;
+          break;
+
+        case E_VARIANT_MEMBER_ENDING_OFFSET:
+          offset_ptr -= offset_size;
+
+          if (offset_ptr < offset)
+            return FALSE;
+
+          end = gvs_read_unaligned_le (value.data + offset_ptr, offset_size);
+          break;
+        }
+
+      if (end < offset || end > offset_ptr)
+        return FALSE;
+
+      child.size = end - offset;
+
+      if (child.size == 0)
+        child.data = NULL;
+
+      if (!e_variant_serialised_is_normal (child))
+        return FALSE;
+
+      offset = end;
+    }
+
+  {
+    gsize fixed_size;
+    guint alignment;
+
+    e_variant_type_info_query (value.type_info, &alignment, &fixed_size);
+
+    if (fixed_size)
+      {
+        g_assert (fixed_size == value.size);
+        g_assert (offset_ptr == value.size);
+
+        if (i == 0)
+          {
+            if (value.data[offset++] != '\0')
+              return FALSE;
+          }
+        else
+          {
+            while (offset & alignment)
+              if (value.data[offset++] != '\0')
+                return FALSE;
+          }
+
+        g_assert (offset == value.size);
+      }
+  }
+
+  return offset_ptr == offset;
+}
+
+/* Variants {{{2
+ *
+ * Variants are stored by storing the serialised data of the child,
+ * followed by a '\0' character, followed by the type string of the
+ * child.
+ *
+ * In the case that a value is presented that contains no '\0'
+ * character, or doesn't have a single well-formed definite type string
+ * following that character, the variant must be taken as containing the
+ * unit tuple: ().
+ */
+
+static inline gsize
+gvs_variant_n_children (EVariantSerialised value)
+{
+  return 1;
+}
+
+static inline EVariantSerialised
+gvs_variant_get_child (EVariantSerialised value,
+                       gsize              index_)
+{
+  EVariantSerialised child = {  };
+
+  /* NOTE: not O(1) and impossible for it to be... */
+  if (value.size)
+    {
+
+      /* find '\0' character */
+      for (child.size = value.size - 1; child.size; child.size--)
+        if (value.data[child.size] == '\0')
+          break;
+
+      /* ensure we didn't just hit the start of the string */
+      if (value.data[child.size] == '\0')
+        {
+          const gchar *type_string = (gchar *) &value.data[child.size + 1];
+          const gchar *limit = (gchar *) &value.data[value.size];
+          const gchar *end;
+
+          if (e_variant_type_string_scan (type_string, limit, &end) &&
+              end == limit)
+            {
+              const EVariantType *type = (EVariantType *) type_string;
+
+              if (e_variant_type_is_definite (type))
+                {
+                  gsize fixed_size;
+
+                  child.type_info = e_variant_type_info_get (type);
+
+                  if (child.size != 0)
+                    /* only set to non-%NULL if size > 0 */
+                    child.data = value.data;
+
+                  e_variant_type_info_query (child.type_info,
+                                             NULL, &fixed_size);
+
+                  if (!fixed_size || fixed_size == child.size)
+                    return child;
+
+                  e_variant_type_info_unref (child.type_info);
+                }
+            }
+        }
+    }
+
+  child.type_info = e_variant_type_info_get (E_VARIANT_TYPE_UNIT);
+  child.data = NULL;
+  child.size = 1;
+
+  return child;
+}
+
+static inline gsize
+gvs_variant_needed_size (EVariantTypeInfo         *type_info,
+                         EVariantSerialisedFiller  gvs_filler,
+                         const gpointer           *children,
+                         gsize                     n_children)
+{
+  EVariantSerialised child = {  };
+  const gchar *type_string;
+
+  gvs_filler (&child, children[0]);
+  type_string = e_variant_type_info_get_type_string (child.type_info);
+
+  return child.size + 1 + strlen (type_string);
+}
+
+static inline void
+gvs_variant_serialise (EVariantSerialised        value,
+                       EVariantSerialisedFiller  gvs_filler,
+                       const gpointer           *children,
+                       gsize                     n_children)
+{
+  EVariantSerialised child = {  };
+  const gchar *type_string;
+
+  child.data = value.data;
+
+  gvs_filler (&child, children[0]);
+  type_string = e_variant_type_info_get_type_string (child.type_info);
+  value.data[child.size] = '\0';
+  memcpy (value.data + child.size + 1, type_string, strlen (type_string));
+}
+
+static inline gboolean
+gvs_variant_is_normal (EVariantSerialised value)
+{
+  EVariantSerialised child;
+  gboolean normal;
+
+  child = gvs_variant_get_child (value, 0);
+
+  normal = (child.data != NULL || child.size == 0) &&
+           e_variant_serialised_is_normal (child);
+
+  e_variant_type_info_unref (child.type_info);
+
+  return normal;
+}
+
+
+
+/* PART 2: Serialiser API {{{1
+ *
+ * This is the implementation of the API of the serialiser as advertised
+ * in gvariant-serialiser.h.
+ */
+
+/* Dispatch Utilities {{{2
+ *
+ * These macros allow a given function (for example,
+ * e_variant_serialiser_serialise) to be dispatched to the appropriate
+ * type-specific function above (fixed/variable-sized maybe,
+ * fixed/variable-sized array, tuple or variant).
+ */
+#define DISPATCH_FIXED(type_info, before, after) \
+  {                                                     \
+    gsize fixed_size;                                   \
+                                                        \
+    e_variant_type_info_query_element (type_info, NULL, \
+                                       &fixed_size);    \
+                                                        \
+    if (fixed_size)                                     \
+      {                                                 \
+        before ## fixed_sized ## after                  \
+      }                                                 \
+    else                                                \
+      {                                                 \
+        before ## variable_sized ## after               \
+      }                                                 \
+  }
+
+#define DISPATCH_CASES(type_info, before, after) \
+  switch (e_variant_type_info_get_type_char (type_info))        \
+    {                                                           \
+      case E_VARIANT_TYPE_INFO_CHAR_MAYBE:                      \
+        DISPATCH_FIXED (type_info, before, _maybe ## after)     \
+                                                                \
+      case E_VARIANT_TYPE_INFO_CHAR_ARRAY:                      \
+        DISPATCH_FIXED (type_info, before, _array ## after)     \
+                                                                \
+      case E_VARIANT_TYPE_INFO_CHAR_DICT_ENTRY:                 \
+      case E_VARIANT_TYPE_INFO_CHAR_TUPLE:                      \
+        {                                                       \
+          before ## tuple ## after                              \
+        }                                                       \
+                                                                \
+      case E_VARIANT_TYPE_INFO_CHAR_VARIANT:                    \
+        {                                                       \
+          before ## variant ## after                            \
+        }                                                       \
+    }
+
+/* Serialiser entry points {{{2
+ *
+ * These are the functions that are called in order for the serialiser
+ * to do its thing.
+ */
+
+/* < private >
+ * e_variant_serialised_n_children:
+ * @serialised: a #EVariantSerialised
+ * @returns: the number of children
+ *
+ * For serialised data that represents a container value (maybes,
+ * tuples, arrays, variants), determine how many child items are inside
+ * that container.
+ */
+gsize
+e_variant_serialised_n_children (EVariantSerialised serialised)
+{
+  e_variant_serialised_check (serialised);
+
+  DISPATCH_CASES (serialised.type_info,
+
+                  return gvs_/**/,/**/_n_children (serialised);
+
+                 )
+  g_assert_not_reached ();
+}
+
+/* < private >
+ * e_variant_serialised_get_child:
+ * @serialised: a #EVariantSerialised
+ * @index_: the index of the child to fetch
+ * @returns: a #EVariantSerialised for the child
+ *
+ * Extracts a child from a serialised data representing a container
+ * value.
+ *
+ * It is an error to call this function with an index out of bounds.
+ *
+ * If the result .data == %NULL and .size > 0 then there has been an
+ * error extracting the requested fixed-sized value.  This number of
+ * zero bytes needs to be allocated instead.
+ *
+ * In the case that .data == %NULL and .size == 0 then a zero-sized
+ * item of a variable-sized type is being returned.
+ *
+ * .data is never non-%NULL if size is 0.
+ */
+EVariantSerialised
+e_variant_serialised_get_child (EVariantSerialised serialised,
+                                gsize              index_)
+{
+  EVariantSerialised child;
+
+  e_variant_serialised_check (serialised);
+
+  if G_LIKELY (index_ < e_variant_serialised_n_children (serialised))
+    {
+      DISPATCH_CASES (serialised.type_info,
+
+                      child = gvs_/**/,/**/_get_child (serialised, index_);
+                      g_assert (child.size || child.data == NULL);
+                      e_variant_serialised_check (child);
+                      return child;
+
+                     )
+      g_assert_not_reached ();
+    }
+
+  g_error ("Attempt to access item %"G_GSIZE_FORMAT
+           " in a container with only %"G_GSIZE_FORMAT" items",
+           index_, e_variant_serialised_n_children (serialised));
+}
+
+/* < private >
+ * e_variant_serialiser_serialise:
+ * @serialised: a #EVariantSerialised, properly set up
+ * @gvs_filler: the filler function
+ * @children: an array of child items
+ * @n_children: the size of @children
+ *
+ * Writes data in serialised form.
+ *
+ * The type_info field of @serialised must be filled in to type info for
+ * the type that we are serialising.
+ *
+ * The size field of @serialised must be filled in with the value
+ * returned by a previous call to e_variant_serialiser_needed_size().
+ *
+ * The data field of @serialised must be a pointer to a properly-aligned
+ * memory region large enough to serialise into (ie: at least as big as
+ * the size field).
+ *
+ * This function is only resonsible for serialising the top-level
+ * container.  @gvs_filler is called on each child of the container in
+ * order for all of the data of that child to be filled in.
+ */
+void
+e_variant_serialiser_serialise (EVariantSerialised        serialised,
+                                EVariantSerialisedFiller  gvs_filler,
+                                const gpointer           *children,
+                                gsize                     n_children)
+{
+  e_variant_serialised_check (serialised);
+
+  DISPATCH_CASES (serialised.type_info,
+
+                  gvs_/**/,/**/_serialise (serialised, gvs_filler,
+                                           children, n_children);
+                  return;
+
+                 )
+  g_assert_not_reached ();
+}
+
+/* < private >
+ * e_variant_serialiser_needed_size:
+ * @type_info: the type to serialise for
+ * @gvs_filler: the filler function
+ * @children: an array of child items
+ * @n_children: the size of @children
+ *
+ * Determines how much memory would be needed to serialise this value.
+ *
+ * This function is only resonsible for performing calculations for the
+ * top-level container.  @gvs_filler is called on each child of the
+ * container in order to determine its size.
+ */
+gsize
+e_variant_serialiser_needed_size (EVariantTypeInfo         *type_info,
+                                  EVariantSerialisedFiller  gvs_filler,
+                                  const gpointer           *children,
+                                  gsize                     n_children)
+{
+  DISPATCH_CASES (type_info,
+
+                  return gvs_/**/,/**/_needed_size (type_info, gvs_filler,
+                                                    children, n_children);
+
+                 )
+  g_assert_not_reached ();
+}
+
+/* Byteswapping {{{2 */
+
+/* < private >
+ * e_variant_serialised_byteswap:
+ * @value: a #EVariantSerialised
+ *
+ * Byte-swap serialised data.  The result of this function is only
+ * well-defined if the data is in normal form.
+ */
+void
+e_variant_serialised_byteswap (EVariantSerialised serialised)
+{
+  gsize fixed_size;
+  guint alignment;
+
+  e_variant_serialised_check (serialised);
+
+  if (!serialised.data)
+    return;
+
+  /* the types we potentially need to byteswap are
+   * exactly those with alignment requirements.
+   */
+  e_variant_type_info_query (serialised.type_info, &alignment, &fixed_size);
+  if (!alignment)
+    return;
+
+  /* if fixed size and alignment are equal then we are down
+   * to the base integer type and we should swap it.  the
+   * only exception to this is if we have a tuple with a
+   * single item, and then swapping it will be OK anyway.
+   */
+  if (alignment + 1 == fixed_size)
+    {
+      switch (fixed_size)
+      {
+        case 2:
+          {
+            guint16 *ptr = (guint16 *) serialised.data;
+
+            g_assert_cmpint (serialised.size, ==, 2);
+            *ptr = GUINT16_SWAP_LE_BE (*ptr);
+          }
+          return;
+
+        case 4:
+          {
+            guint32 *ptr = (guint32 *) serialised.data;
+
+            g_assert_cmpint (serialised.size, ==, 4);
+            *ptr = GUINT32_SWAP_LE_BE (*ptr);
+          }
+          return;
+
+        case 8:
+          {
+            guint64 *ptr = (guint64 *) serialised.data;
+
+            g_assert_cmpint (serialised.size, ==, 8);
+            *ptr = GUINT64_SWAP_LE_BE (*ptr);
+          }
+          return;
+
+        default:
+          g_assert_not_reached ();
+      }
+    }
+
+  /* else, we have a container that potentially contains
+   * some children that need to be byteswapped.
+   */
+  else
+    {
+      gsize children, i;
+
+      children = e_variant_serialised_n_children (serialised);
+      for (i = 0; i < children; i++)
+        {
+          EVariantSerialised child;
+
+          child = e_variant_serialised_get_child (serialised, i);
+          e_variant_serialised_byteswap (child);
+          e_variant_type_info_unref (child.type_info);
+        }
+    }
+}
+
+/* Normal form checking {{{2 */
+
+/* < private >
+ * e_variant_serialised_is_normal:
+ * @serialised: a #EVariantSerialised
+ *
+ * Determines, recursively if @serialised is in normal form.  There is
+ * precisely one normal form of serialised data for each possible value.
+ *
+ * It is possible that multiple byte sequences form the serialised data
+ * for a given value if, for example, the padding bytes are filled in
+ * with something other than zeros, but only one form is the normal
+ * form.
+ */
+gboolean
+e_variant_serialised_is_normal (EVariantSerialised serialised)
+{
+  DISPATCH_CASES (serialised.type_info,
+
+                  return gvs_/**/,/**/_is_normal (serialised);
+
+                 )
+
+  /* some hard-coded terminal cases */
+  switch (e_variant_type_info_get_type_char (serialised.type_info))
+    {
+    case 'b': /* boolean */
+      return serialised.data[0] < 2;
+
+    case 's': /* string */
+      return e_variant_serialiser_is_string (serialised.data,
+                                             serialised.size);
+
+    case 'o':
+      return e_variant_serialiser_is_object_path (serialised.data,
+                                                  serialised.size);
+
+    case 'g':
+      return e_variant_serialiser_is_signature (serialised.data,
+                                                serialised.size);
+
+    default:
+      /* all of the other types are fixed-sized numerical types for
+       * which all possible values are valid (including various NaN
+       * representations for floating point values).
+       */
+      return TRUE;
+    }
+}
+
+/* Validity-checking functions {{{2
+ *
+ * Checks if strings, object paths and signature strings are valid.
+ */
+
+/* < private >
+ * e_variant_serialiser_is_string:
+ * @data: a possible string
+ * @size: the size of @data
+ *
+ * Ensures that @data is a valid string with a nul terminator at the end
+ * and no nul bytes embedded.
+ */
+gboolean
+e_variant_serialiser_is_string (gconstpointer data,
+                                gsize         size)
+{
+  const gchar *string = data;
+
+  if (size == 0)
+    return FALSE;
+
+  if (string[size - 1] != '\0')
+    return FALSE;
+
+  return strlen (string) == size - 1;
+}
+
+/* < private >
+ * e_variant_serialiser_is_object_path:
+ * @data: a possible DBus object path
+ * @size: the size of @data
+ *
+ * Performs the checks for being a valid string.
+ *
+ * Also, ensures that @data is a valid DBus object path, as per the DBus
+ * specification.
+ */
+gboolean
+e_variant_serialiser_is_object_path (gconstpointer data,
+                                     gsize         size)
+{
+  const gchar *string = data;
+  gsize i;
+
+  if (!e_variant_serialiser_is_string (data, size))
+    return FALSE;
+
+  /* The path must begin with an ASCII '/' (integer 47) character */
+  if (string[0] != '/')
+    return FALSE;
+
+  for (i = 1; string[i]; i++)
+    /* Each element must only contain the ASCII characters
+     * "[A-Z][a-z][0-9]_"
+     */
+    if (g_ascii_isalnum (string[i]) || string[i] == '_')
+      ;
+
+    /* must consist of elements separated by slash characters. */
+    else if (string[i] == '/')
+      {
+        /* No element may be the empty string. */
+        /* Multiple '/' characters cannot occur in sequence. */
+        if (string[i - 1] == '/')
+          return FALSE;
+      }
+
+    else
+      return FALSE;
+
+  /* A trailing '/' character is not allowed unless the path is the
+   * root path (a single '/' character).
+   */
+  if (i > 1 && string[i - 1] == '/')
+    return FALSE;
+
+  return TRUE;
+}
+
+/* < private >
+ * e_variant_serialiser_is_signature:
+ * @data: a possible DBus signature
+ * @size: the size of @data
+ *
+ * Performs the checks for being a valid string.
+ *
+ * Also, ensures that @data is a valid DBus type signature, as per the
+ * DBus specification.
+ */
+gboolean
+e_variant_serialiser_is_signature (gconstpointer data,
+                                   gsize         size)
+{
+  const gchar *string = data;
+  gsize first_invalid;
+
+  if (!e_variant_serialiser_is_string (data, size))
+    return FALSE;
+
+  /* make sure no non-definite characters appear */
+  first_invalid = strspn (string, "ybnqiuxthdvasog(){}");
+  if (string[first_invalid])
+    return FALSE;
+
+  /* make sure each type string is well-formed */
+  while (*string)
+    if (!e_variant_type_string_scan (string, NULL, &string))
+      return FALSE;
+
+  return TRUE;
+}
+
+/* vim:set foldmethod=marker: */
diff --git a/libedbus/gvariant-serialiser.h b/libedbus/gvariant-serialiser.h
new file mode 100644
index 0000000..3f2a76c
--- /dev/null
+++ b/libedbus/gvariant-serialiser.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright © 2007, 2008 Ryan Lortie
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#ifndef __E_VARIANT_SERIALISER_H__
+#define __E_VARIANT_SERIALISER_H__
+
+#include "gvarianttypeinfo.h"
+
+typedef struct
+{
+  EVariantTypeInfo *type_info;
+  guchar           *data;
+  gsize             size;
+} EVariantSerialised;
+
+/* deserialisation */
+G_GNUC_INTERNAL
+gsize                           e_variant_serialised_n_children         (EVariantSerialised        container);
+G_GNUC_INTERNAL
+EVariantSerialised              e_variant_serialised_get_child          (EVariantSerialised        container,
+                                                                         gsize                     index);
+
+/* serialisation */
+typedef void                  (*EVariantSerialisedFiller)               (EVariantSerialised       *serialised,
+                                                                         gpointer                  data);
+
+G_GNUC_INTERNAL
+gsize                           e_variant_serialiser_needed_size        (EVariantTypeInfo         *info,
+                                                                         EVariantSerialisedFiller  gsv_filler,
+                                                                         const gpointer           *children,
+                                                                         gsize                     n_children);
+
+G_GNUC_INTERNAL
+void                            e_variant_serialiser_serialise          (EVariantSerialised        container,
+                                                                         EVariantSerialisedFiller  gsv_filler,
+                                                                         const gpointer           *children,
+                                                                         gsize                     n_children);
+
+/* misc */
+G_GNUC_INTERNAL
+gboolean                        e_variant_serialised_is_normal          (EVariantSerialised        value);
+G_GNUC_INTERNAL
+void                            e_variant_serialised_byteswap           (EVariantSerialised        value);
+
+/* validation of strings */
+G_GNUC_INTERNAL
+gboolean                        e_variant_serialiser_is_string          (gconstpointer             data,
+                                                                         gsize                     size);
+G_GNUC_INTERNAL
+gboolean                        e_variant_serialiser_is_object_path     (gconstpointer             data,
+                                                                         gsize                     size);
+G_GNUC_INTERNAL
+gboolean                        e_variant_serialiser_is_signature       (gconstpointer             data,
+                                                                         gsize                     size);
+
+#endif /* __E_VARIANT_SERIALISER_H__ */
diff --git a/libedbus/gvariant-util.c b/libedbus/gvariant-util.c
new file mode 100644
index 0000000..6571d4b
--- /dev/null
+++ b/libedbus/gvariant-util.c
@@ -0,0 +1,1902 @@
+/*
+ * Copyright © 2007, 2008 Ryan Lortie
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <string.h>
+#include <glib.h>
+
+#include "gvariant-private.h"
+
+/**
+ * EVariantIter:
+ *
+ * An opaque structure type used to iterate over a container #EVariant
+ * instance.
+ *
+ * The iter must be initialised with a call to e_variant_iter_init()
+ * before using it.  After that, e_variant_iter_next() will return the
+ * child values, in order.
+ *
+ * The iter may maintain a reference to the container #EVariant until
+ * e_variant_iter_next() returns %NULL.  For this reason, it is
+ * essential that you call e_variant_iter_next() until %NULL is
+ * returned.  If you want to abort iterating part way through then use
+ * e_variant_iter_cancel().
+ */
+typedef struct
+{
+  EVariant *value;
+  EVariant *child;
+  gsize length;
+  gsize offset;
+  gboolean cancelled;
+} EVariantIterReal;
+
+/**
+ * e_variant_iter_init:
+ * @iter: a #EVariantIter
+ * @value: a container #EVariant instance
+ * @returns: the number of items in the container
+ *
+ * Initialises the fields of a #EVariantIter and perpare to iterate
+ * over the contents of @value.
+ *
+ * @iter is allowed to be completely uninitialised prior to this call;
+ * it does not, for example, have to be cleared to zeros.  For this
+ * reason, if @iter was already being used, you should first cancel it
+ * with e_variant_iter_cancel().
+ *
+ * After this call, @iter holds a reference to @value.  The reference
+ * will be automatically dropped once all values have been iterated
+ * over or manually by calling e_variant_iter_cancel().
+ *
+ * This function returns the number of times that
+ * e_variant_iter_next() will return non-%NULL.  You're not expected to
+ * use this value, but it's there incase you wanted to know.
+ **/
+gsize
+e_variant_iter_init (EVariantIter *iter,
+                     EVariant     *value)
+{
+  EVariantIterReal *real = (EVariantIterReal *) iter;
+
+  g_return_val_if_fail (iter != NULL, 0);
+  g_return_val_if_fail (value != NULL, 0);
+
+  g_assert (sizeof (EVariantIterReal) <= sizeof (EVariantIter));
+
+  real->cancelled = FALSE;
+  real->length = e_variant_n_children (value);
+  real->offset = 0;
+  real->child = NULL;
+
+  if (real->length)
+    real->value = e_variant_ref (value);
+  else
+    real->value = NULL;
+
+  return real->length;
+}
+
+/**
+ * e_variant_iter_next_value:
+ * @iter: a #EVariantIter
+ * @returns: a #EVariant for the next child
+ *
+ * Retreives the next child value from @iter.  In the event that no
+ * more child values exist, %NULL is returned and @iter drops its
+ * reference to the value that it was created with.
+ *
+ * The return value of this function is internally cached by the
+ * @iter, so you don't have to unref it when you're done.  For this
+ * reason, thought, it is important to ensure that you call
+ * e_variant_iter_next() one last time, even if you know the number of
+ * items in the container.
+ *
+ * It is permissable to call this function on a cancelled iter, in
+ * which case %NULL is returned and nothing else happens.
+ **/
+EVariant *
+e_variant_iter_next_value (EVariantIter *iter)
+{
+  EVariantIterReal *real = (EVariantIterReal *) iter;
+
+  g_return_val_if_fail (iter != NULL, NULL);
+
+  if (real->child)
+    {
+      e_variant_unref (real->child);
+      real->child = NULL;
+    }
+
+  if (real->value == NULL)
+    return NULL;
+
+  real->child = e_variant_get_child_value (real->value, real->offset++);
+
+  if (real->offset == real->length)
+    {
+      e_variant_unref (real->value);
+      real->value = NULL;
+    }
+
+  return real->child;
+}
+
+/**
+ * e_variant_iter_cancel:
+ * @iter: a #EVariantIter
+ *
+ * Causes @iter to drop its reference to the container that it was
+ * created with.  You need to call this on an iter if, for some
+ * reason, you stopped iterating before reading the end.
+ *
+ * You do not need to call this in the normal case of visiting all of
+ * the elements.  In this case, the reference will be automatically
+ * dropped by e_variant_iter_next() just before it returns %NULL.
+ *
+ * It is permissable to call this function more than once on the same
+ * iter.  It is permissable to call this function after the last
+ * value.
+ **/
+void
+e_variant_iter_cancel (EVariantIter *iter)
+{
+  EVariantIterReal *real = (EVariantIterReal *) iter;
+
+  g_return_if_fail (iter != NULL);
+
+  real->cancelled = TRUE;
+
+  if (real->value)
+    {
+      e_variant_unref (real->value);
+      real->value = NULL;
+    }
+
+  if (real->child)
+    {
+      e_variant_unref (real->child);
+      real->child = NULL;
+    }
+}
+
+/**
+ * e_variant_iter_was_cancelled:
+ * @iter: a #EVariantIter
+ * @returns: %TRUE if e_variant_iter_cancel() was called
+ *
+ * Determines if e_variant_iter_cancel() was called on @iter.
+ **/
+gboolean
+e_variant_iter_was_cancelled (EVariantIter *iter)
+{
+  EVariantIterReal *real = (EVariantIterReal *) iter;
+
+  g_return_val_if_fail (iter != NULL, FALSE);
+
+  return real->cancelled;
+}
+
+/* private */
+gboolean
+e_variant_iter_should_free (EVariantIter *iter)
+{
+  EVariantIterReal *real = (EVariantIterReal *) iter;
+
+  return real->child != NULL;
+}
+
+/**
+ * e_variant_has_type:
+ * @value: a #EVariant instance
+ * @pattern: a #EVariantType
+ * @returns: %TRUE if the type of @value matches @type
+ *
+ * Checks if a value has a type matching the provided type.
+ **/
+gboolean
+e_variant_has_type (EVariant           *value,
+                    const EVariantType *type)
+{
+  g_return_val_if_fail (value != NULL, FALSE);
+  g_return_val_if_fail (value != NULL, FALSE);
+
+  return e_variant_type_is_subtype_of (e_variant_get_type (value), type);
+}
+
+/**
+ * e_variant_new_boolean:
+ * @boolean: a #gboolean value
+ * @returns: a new boolean #EVariant instance
+ *
+ * Creates a new boolean #EVariant instance -- either %TRUE or %FALSE.
+ **/
+EVariant *
+e_variant_new_boolean (gboolean boolean)
+{
+  guint8 byte = !!boolean;
+
+  return e_variant_load (E_VARIANT_TYPE_BOOLEAN,
+                         &byte, 1, E_VARIANT_TRUSTED);
+}
+
+/**
+ * e_variant_new_byte:
+ * @byte: a #guint8 value
+ * @returns: a new byte #EVariant instance
+ *
+ * Creates a new byte #EVariant instance.
+ **/
+EVariant *
+e_variant_new_byte (guint8 byte)
+{
+  return e_variant_load (E_VARIANT_TYPE_BYTE,
+                         &byte, 1, E_VARIANT_TRUSTED);
+}
+
+/**
+ * e_variant_new_int16:
+ * @int16: a #gint16 value
+ * @returns: a new int16 #EVariant instance
+ *
+ * Creates a new int16 #EVariant instance.
+ **/
+EVariant *
+e_variant_new_int16 (gint16 int16)
+{
+  return e_variant_load (E_VARIANT_TYPE_INT16,
+                         &int16, 2, E_VARIANT_TRUSTED);
+}
+
+/**
+ * e_variant_new_uint16:
+ * @uint16: a #guint16 value
+ * @returns: a new uint16 #EVariant instance
+ *
+ * Creates a new uint16 #EVariant instance.
+ **/
+EVariant *
+e_variant_new_uint16 (guint16 uint16)
+{
+  return e_variant_load (E_VARIANT_TYPE_UINT16,
+                         &uint16, 2, E_VARIANT_TRUSTED);
+}
+
+/**
+ * e_variant_new_int32:
+ * @int32: a #gint32 value
+ * @returns: a new int32 #EVariant instance
+ *
+ * Creates a new int32 #EVariant instance.
+ **/
+EVariant *
+e_variant_new_int32 (gint32 int32)
+{
+  return e_variant_load (E_VARIANT_TYPE_INT32,
+                         &int32, 4, E_VARIANT_TRUSTED);
+}
+
+/**
+ * e_variant_new_handle:
+ * @handle: a #gint32 value
+ * @returns: a new handle #EVariant instance
+ *
+ * Creates a new int32 #EVariant instance.
+ **/
+EVariant *
+e_variant_new_handle (gint32 handle)
+{
+  return e_variant_load (E_VARIANT_TYPE_HANDLE,
+                         &handle, 4, E_VARIANT_TRUSTED);
+}
+
+/**
+ * e_variant_new_uint32:
+ * @uint32: a #guint32 value
+ * @returns: a new uint32 #EVariant instance
+ *
+ * Creates a new uint32 #EVariant instance.
+ **/
+EVariant *
+e_variant_new_uint32 (guint32 uint32)
+{
+  return e_variant_load (E_VARIANT_TYPE_UINT32,
+                         &uint32, 4, E_VARIANT_TRUSTED);
+}
+
+/**
+ * e_variant_new_int64:
+ * @int64: a #gint64 value
+ * @returns: a new int64 #EVariant instance
+ *
+ * Creates a new int64 #EVariant instance.
+ **/
+EVariant *
+e_variant_new_int64 (gint64 int64)
+{
+  return e_variant_load (E_VARIANT_TYPE_INT64,
+                         &int64, 8, E_VARIANT_TRUSTED);
+}
+
+/**
+ * e_variant_new_uint64:
+ * @uint64: a #guint64 value
+ * @returns: a new uint64 #EVariant instance
+ *
+ * Creates a new uint64 #EVariant instance.
+ **/
+EVariant *
+e_variant_new_uint64 (guint64 uint64)
+{
+  return e_variant_load (E_VARIANT_TYPE_UINT64,
+                         &uint64, 8, E_VARIANT_TRUSTED);
+}
+
+/**
+ * e_variant_new_double:
+ * @floating: a #gdouble floating point value
+ * @returns: a new double #EVariant instance
+ *
+ * Creates a new double #EVariant instance.
+ **/
+EVariant *
+e_variant_new_double (gdouble floating)
+{
+  return e_variant_load (E_VARIANT_TYPE_DOUBLE,
+                         &floating, 8, E_VARIANT_TRUSTED);
+}
+
+/**
+ * e_variant_new_string:
+ * @string: a normal C nul-terminated string
+ * @returns: a new string #EVariant instance
+ *
+ * Creates a string #EVariant with the contents of @string.
+ **/
+EVariant *
+e_variant_new_string (const gchar *string)
+{
+  return e_variant_load (E_VARIANT_TYPE_STRING,
+                         string, strlen (string) + 1, E_VARIANT_TRUSTED);
+}
+
+/**
+ * e_variant_new_object_path:
+ * @string: a normal C nul-terminated string
+ * @returns: a new object path #EVariant instance
+ *
+ * Creates a DBus object path #EVariant with the contents of @string.
+ * @string must be a valid DBus object path.  Use
+ * e_variant_is_object_path() if you're not sure.
+ **/
+EVariant *
+e_variant_new_object_path (const gchar *string)
+{
+  g_return_val_if_fail (e_variant_is_object_path (string), NULL);
+
+  return e_variant_load (E_VARIANT_TYPE_OBJECT_PATH,
+                         string, strlen (string) + 1,
+                         E_VARIANT_TRUSTED);
+}
+
+/**
+ * e_variant_is_object_path:
+ * @string: a normal C nul-terminated string
+ * @returns: %TRUE if @string is a DBus object path
+ *
+ * Determines if a given string is a valid DBus object path.  You
+ * should ensure that a string is a valid DBus object path before
+ * passing it to e_variant_new_object_path().
+ *
+ * A valid object path starts with '/' followed by zero or more
+ * sequences of characters separated by '/' characters.  Each sequence
+ * must contain only the characters "[A-Z][a-z][0-9]_".  No sequence
+ * (including the one following the final '/' character) may be empty.
+ **/
+gboolean
+e_variant_is_object_path (const gchar *string)
+{
+  /* according to DBus specification */
+  gsize i;
+
+  g_return_val_if_fail (string != NULL, FALSE);
+
+  /* The path must begin with an ASCII '/' (integer 47) character */
+  if (string[0] != '/')
+    return FALSE;
+
+  for (i = 1; string[i]; i++)
+    /* Each element must only contain the ASCII characters
+     * "[A-Z][a-z][0-9]_"
+     */
+    if (g_ascii_isalnum (string[i]) || string[i] == '_')
+      ;
+
+    /* must consist of elements separated by slash characters. */
+    else if (string[i] == '/')
+      {
+        /* No element may be the empty string. */
+        /* Multiple '/' characters cannot occur in sequence. */
+        if (string[i - 1] == '/')
+          return FALSE;
+      }
+
+    else
+      return FALSE;
+
+  /* A trailing '/' character is not allowed unless the path is the
+   * root path (a single '/' character).
+   */
+  if (i > 1 && string[i - 1] == '/')
+    return FALSE;
+
+  return TRUE;
+}
+
+/**
+ * e_variant_new_signature:
+ * @string: a normal C nul-terminated string
+ * @returns: a new signature #EVariant instance
+ *
+ * Creates a DBus type signature #EVariant with the contents of
+ * @string.  @string must be a valid DBus type signature.  Use
+ * e_variant_is_signature() if you're not sure.
+ **/
+EVariant *
+e_variant_new_signature (const gchar *string)
+{
+  g_return_val_if_fail (e_variant_is_signature (string), NULL);
+
+  return e_variant_load (E_VARIANT_TYPE_SIGNATURE,
+                         string, strlen (string) + 1,
+                         E_VARIANT_TRUSTED);
+}
+
+/**
+ * e_variant_is_signature:
+ * @string: a normal C nul-terminated string
+ * @returns: %TRUE if @string is a DBus type signature
+ *
+ * Determines if a given string is a valid DBus type signature.  You
+ * should ensure that a string is a valid DBus object path before
+ * passing it to e_variant_new_signature().
+ *
+ * DBus type signatures consist of zero or more definite #EVariantType
+ * strings in sequence.
+ **/
+gboolean
+e_variant_is_signature (const gchar *string)
+{
+  gsize first_invalid;
+
+  g_return_val_if_fail (string != NULL, FALSE);
+
+  /* make sure no non-definite characters appear */
+  first_invalid = strspn (string, "ybnqihuxtdvmasog(){}");
+  if (string[first_invalid])
+    return FALSE;
+
+  /* make sure each type string is well-formed */
+  while (*string)
+    if (!e_variant_type_string_scan (string, NULL, &string))
+      return FALSE;
+
+  return TRUE;
+}
+
+/**
+ * e_variant_new_variant:
+ * @value: a #GVariance instance
+ * @returns: a new variant #EVariant instance
+ *
+ * Boxes @value.  The result is a #EVariant instance representing a
+ * variant containing the original value.
+ **/
+EVariant *
+e_variant_new_variant (EVariant *value)
+{
+  EVariant **children;
+
+  g_return_val_if_fail (value != NULL, NULL);
+
+  children = g_slice_new (EVariant *);
+  children[0] = e_variant_ref_sink (value);
+
+  return e_variant_new_tree (E_VARIANT_TYPE_VARIANT,
+                             children, 1,
+                             e_variant_is_trusted (value));
+}
+
+/**
+ * e_variant_get_boolean:
+ * @value: a boolean #EVariant instance
+ * @returns: %TRUE or %FALSE
+ *
+ * Returns the boolean value of @value.
+ *
+ * It is an error to call this function with a @value of any type
+ * other than %E_VARIANT_TYPE_BOOLEAN.
+ **/
+gboolean
+e_variant_get_boolean (EVariant *value)
+{
+  guint8 byte;
+
+  g_return_val_if_fail (value != NULL, FALSE);
+  g_assert (e_variant_has_type (value, E_VARIANT_TYPE_BOOLEAN));
+  e_variant_store (value, &byte);
+
+  return byte;
+}
+
+/**
+ * e_variant_get_byte:
+ * @value: a byte #EVariant instance
+ * @returns: a #guchar
+ *
+ * Returns the byte value of @value.
+ *
+ * It is an error to call this function with a @value of any type
+ * other than %E_VARIANT_TYPE_BYTE.
+ **/
+guint8
+e_variant_get_byte (EVariant *value)
+{
+  guint8 byte;
+
+  g_return_val_if_fail (value != NULL, 0);
+  g_assert (e_variant_has_type (value, E_VARIANT_TYPE_BYTE));
+  e_variant_store (value, &byte);
+
+  return byte;
+}
+
+/**
+ * e_variant_get_int16:
+ * @value: a int16 #EVariant instance
+ * @returns: a #gint16
+ *
+ * Returns the 16-bit signed integer value of @value.
+ *
+ * It is an error to call this function with a @value of any type
+ * other than %E_VARIANT_TYPE_INT16.
+ **/
+gint16
+e_variant_get_int16 (EVariant *value)
+{
+  gint16 int16;
+
+  g_return_val_if_fail (value != NULL, 0);
+  g_assert (e_variant_has_type (value, E_VARIANT_TYPE_INT16));
+  e_variant_store (value, &int16);
+
+  return int16;
+}
+
+/**
+ * e_variant_get_uint16:
+ * @value: a uint16 #EVariant instance
+ * @returns: a #guint16
+ *
+ * Returns the 16-bit unsigned integer value of @value.
+ *
+ * It is an error to call this function with a @value of any type
+ * other than %E_VARIANT_TYPE_UINT16.
+ **/
+guint16
+e_variant_get_uint16 (EVariant *value)
+{
+  guint16 uint16;
+
+  g_return_val_if_fail (value != NULL, 0);
+  g_assert (e_variant_has_type (value, E_VARIANT_TYPE_UINT16));
+  e_variant_store (value, &uint16);
+
+  return uint16;
+}
+
+/**
+ * e_variant_get_int32:
+ * @value: a int32 #EVariant instance
+ * @returns: a #gint32
+ *
+ * Returns the 32-bit signed integer value of @value.
+ *
+ * It is an error to call this function with a @value of any type
+ * other than %E_VARIANT_TYPE_INT32.
+ **/
+gint32
+e_variant_get_int32 (EVariant *value)
+{
+  gint32 int32;
+
+  g_return_val_if_fail (value != NULL, 0);
+  g_assert (e_variant_has_type (value, E_VARIANT_TYPE_INT32));
+  e_variant_store (value, &int32);
+
+  return int32;
+}
+
+/**
+ * e_variant_get_handle:
+ * @value: a handle #EVariant instance
+ * @returns: a #gint32
+ *
+ * Returns the 32-bit signed integer value of @value.
+ *
+ * It is an error to call this function with a @value of any type
+ * other than %E_VARIANT_TYPE_HANDLE.
+ **/
+gint32
+e_variant_get_handle (EVariant *value)
+{
+  gint32 int32;
+
+  g_return_val_if_fail (value != NULL, 0);
+  g_assert (e_variant_has_type (value, E_VARIANT_TYPE_HANDLE));
+  e_variant_store (value, &int32);
+
+  return int32;
+}
+
+/**
+ * e_variant_get_uint32:
+ * @value: a uint32 #EVariant instance
+ * @returns: a #guint32
+ *
+ * Returns the 32-bit unsigned integer value of @value.
+ *
+ * It is an error to call this function with a @value of any type
+ * other than %E_VARIANT_TYPE_UINT32.
+ **/
+guint32
+e_variant_get_uint32 (EVariant *value)
+{
+  guint32 uint32;
+
+  g_return_val_if_fail (value != NULL, 0);
+  g_assert (e_variant_has_type (value, E_VARIANT_TYPE_UINT32));
+  e_variant_store (value, &uint32);
+
+  return uint32;
+}
+
+/**
+ * e_variant_get_int64:
+ * @value: a int64 #EVariant instance
+ * @returns: a #gint64
+ *
+ * Returns the 64-bit signed integer value of @value.
+ *
+ * It is an error to call this function with a @value of any type
+ * other than %E_VARIANT_TYPE_INT64.
+ **/
+gint64
+e_variant_get_int64 (EVariant *value)
+{
+  gint64 int64;
+
+  g_return_val_if_fail (value != NULL, 0);
+  g_assert (e_variant_has_type (value, E_VARIANT_TYPE_INT64));
+  e_variant_store (value, &int64);
+
+  return int64;
+}
+
+/**
+ * e_variant_get_uint64:
+ * @value: a uint64 #EVariant instance
+ * @returns: a #guint64
+ *
+ * Returns the 64-bit unsigned integer value of @value.
+ *
+ * It is an error to call this function with a @value of any type
+ * other than %E_VARIANT_TYPE_UINT64.
+ **/
+guint64
+e_variant_get_uint64 (EVariant *value)
+{
+  guint64 uint64;
+
+  g_return_val_if_fail (value != NULL, 0);
+  g_assert (e_variant_has_type (value, E_VARIANT_TYPE_UINT64));
+  e_variant_store (value, &uint64);
+
+  return uint64;
+}
+
+/**
+ * e_variant_get_double:
+ * @value: a double #EVariant instance
+ * @returns: a #gdouble
+ *
+ * Returns the double precision floating point value of @value.
+ *
+ * It is an error to call this function with a @value of any type
+ * other than %E_VARIANT_TYPE_DOUBLE.
+ **/
+gdouble
+e_variant_get_double (EVariant *value)
+{
+  gdouble floating;
+
+  g_return_val_if_fail (value != NULL, 0);
+  g_assert (e_variant_has_type (value, E_VARIANT_TYPE_DOUBLE));
+  e_variant_store (value, &floating);
+
+  return floating;
+}
+
+/**
+ * e_variant_get_string:
+ * @value: a string #EVariant instance
+ * @length: a pointer to a #gsize, to store the length
+ * @returns: the constant string
+ *
+ * Returns the string value of a #EVariant instance with a string
+ * type.  This includes the types %E_VARIANT_TYPE_STRING,
+ * %E_VARIANT_TYPE_OBJECT_PATH and %E_VARIANT_TYPE_SIGNATURE.
+ *
+ * If @length is non-%NULL then the length of the string (in bytes) is
+ * returned there.  For trusted values, this information is already
+ * known.  For untrusted values, a strlen() will be performed.
+ *
+ * It is an error to call this function with a @value of any type
+ * other than those three.
+ *
+ * The return value remains valid as long as @value exists.
+ **/
+const gchar *
+e_variant_get_string (EVariant *value,
+                      gsize    *length)
+{
+  EVariantClass class;
+  gboolean valid_string;
+  const gchar *string;
+  gssize size;
+
+  g_return_val_if_fail (value != NULL, NULL);
+  class = e_variant_classify (value);
+  g_return_val_if_fail (class == E_VARIANT_CLASS_STRING ||
+                        class == E_VARIANT_CLASS_OBJECT_PATH ||
+                        class == E_VARIANT_CLASS_SIGNATURE, NULL);
+
+  string = e_variant_get_data (value);
+  size = e_variant_get_size (value);
+
+  if (e_variant_is_trusted (value))
+    {
+      if (length)
+        *length = size - 1;
+
+      return string;
+    }
+
+  valid_string = string != NULL && size > 0 && string[size - 1] == '\0';
+
+  switch (class)
+  {
+    case E_VARIANT_CLASS_STRING:
+      if (valid_string)
+        break;
+
+      if (length)
+        *length = 0;
+
+      return "";
+
+    case E_VARIANT_CLASS_OBJECT_PATH:
+      if (valid_string && e_variant_is_object_path (string))
+        break;
+
+      if (length)
+        *length = 1;
+
+      return "/";
+
+    case E_VARIANT_CLASS_SIGNATURE:
+      if (valid_string && e_variant_is_signature (string))
+        break;
+
+      if (length)
+        *length = 0;
+
+      return "";
+
+    default:
+      g_assert_not_reached ();
+  }
+
+  if (length)
+    *length = strlen (string);
+
+  return string;
+}
+
+/**
+ * e_variant_dup_string:
+ * @value: a string #EVariant instance
+ * @length: a pointer to a #gsize, to store the length
+ * @returns: a newly allocated string
+ *
+ * Similar to e_variant_get_string() except that instead of returning
+ * a constant string, the string is duplicated.
+ *
+ * The return value must be freed using g_free().
+ **/
+gchar *
+e_variant_dup_string (EVariant *value,
+                      gsize    *length)
+{
+  EVariantClass class;
+  int size;
+
+  g_return_val_if_fail (value != NULL, NULL);
+  class = e_variant_classify (value);
+  g_return_val_if_fail (class == E_VARIANT_CLASS_STRING ||
+                        class == E_VARIANT_CLASS_OBJECT_PATH ||
+                        class == E_VARIANT_CLASS_SIGNATURE, NULL);
+
+  size = e_variant_get_size (value);
+
+  if (length)
+    *length = size - 1;
+
+  return g_memdup (e_variant_get_data (value), size);
+}
+
+/**
+ * e_variant_get_variant:
+ * @value: a variant #GVariance instance
+ * @returns: the item contained in the variant
+ *
+ * Unboxes @value.  The result is the #EVariant instance that was
+ * contained in @value.
+ **/
+EVariant *
+e_variant_get_variant (EVariant *value)
+{
+  g_return_val_if_fail (value != NULL, NULL);
+  g_assert (e_variant_has_type (value, E_VARIANT_TYPE_VARIANT));
+
+  return e_variant_get_child_value (value, 0);
+}
+
+/**
+ * EVariantBuilder:
+ *
+ * An opaque type used to build container #EVariant instances one
+ * child value at a time.
+ */
+struct _EVariantBuilder
+{
+  EVariantBuilder *parent;
+
+  EVariantClass container_class;
+  EVariantType *type;
+  const EVariantType *expected;
+  /* expected2 is always definite.  it is set if adding a second
+   * element to an array (or open sub-builder thereof).  it is
+   * required to ensure the following works correctly:
+   *
+   *   b = new(array, "a**");
+   *   sb = open(b, array, "ai");
+   *   b = close (sb);
+   *
+   *   sb = open (b, array, "a*");   <-- valid
+   *   add (sb, "u", 1234);          <-- must fail here
+   *
+   * the 'valid' call is valid since the "a*" is no more general than
+   * the element type of "aa*" (in fact, it is exactly equal) but the
+   * 'must fail' call still needs to know that it requires element
+   * type 'i'.  this requires keeping track of the two types
+   * separately.
+   */
+  const EVariantType *expected2;
+  gsize min_items;
+  gsize max_items;
+
+  EVariant **children;
+  gsize children_allocated;
+  gsize offset;
+  int has_child : 1;
+  int trusted : 1;
+};
+
+/**
+ * E_VARIANT_BUILDER_ERROR:
+ *
+ * Error domain for #EVariantBuilder. Errors in this domain will be
+ * from the #EVariantBuilderError enumeration.  See #GError for
+ * information on error domains.
+ **/
+/**
+ * EVariantBuilderError:
+ * @E_VARIANT_BUILDER_ERROR_TOO_MANY: too many items have been added
+ * (returned by e_variant_builder_check_add())
+ * @E_VARIANT_BUILDER_ERROR_TOO_FEW: too few items have been added
+ * (returned by e_variant_builder_check_end())
+ * @E_VARIANT_BUILDER_ERROR_INFER: unable to infer the type of an
+ * array or maybe (returned by e_variant_builder_check_end())
+ * @E_VARIANT_BUILDER_ERROR_TYPE: the value is of the incorrect type
+ * (returned by e_variant_builder_check_add())
+ *
+ * Errors codes returned by e_variant_builder_check_add() and
+ * e_variant_builder_check_end().
+ */
+
+static void
+e_variant_builder_resize (EVariantBuilder *builder,
+                          int              new_allocated)
+{
+  EVariant **new_children;
+  int i;
+
+  g_assert_cmpint (builder->offset, <=, new_allocated);
+
+  new_children = g_slice_alloc (sizeof (EVariant *) * new_allocated);
+
+  for (i = 0; i < builder->offset; i++)
+    new_children[i] = builder->children[i];
+
+  g_slice_free1 (sizeof (EVariant **) * builder->children_allocated,
+                 builder->children);
+  builder->children = new_children;
+  builder->children_allocated = new_allocated;
+}
+
+/**
+ * e_variant_builder_add_value:
+ * @builder: a #EVariantBuilder
+ * @value: a #EVariant
+ *
+ * Adds @value to @builder.
+ *
+ * It is an error to call this function if @builder has an outstanding
+ * child.  It is an error to call this function in any case that
+ * e_variant_builder_check_add() would return %FALSE.
+ **/
+void
+e_variant_builder_add_value (EVariantBuilder *builder,
+                             EVariant        *value)
+{
+  g_return_if_fail (builder != NULL && value != NULL);
+  g_return_if_fail (e_variant_builder_check_add (builder,
+                                                 e_variant_get_type (value),
+                                                 NULL));
+
+  builder->trusted &= e_variant_is_trusted (value);
+
+  if (builder->container_class == E_VARIANT_CLASS_TUPLE ||
+      builder->container_class == E_VARIANT_CLASS_DICT_ENTRY)
+    {
+      if (builder->expected)
+        builder->expected = e_variant_type_next (builder->expected);
+
+      if (builder->expected2)
+        builder->expected2 = e_variant_type_next (builder->expected2);
+    }
+
+  if (builder->container_class == E_VARIANT_CLASS_ARRAY &&
+      builder->expected2 == NULL)
+    builder->expected2 = e_variant_get_type (value);
+
+  if (builder->offset == builder->children_allocated)
+    e_variant_builder_resize (builder, builder->children_allocated * 2);
+
+  builder->children[builder->offset++] = e_variant_ref_sink (value);
+}
+
+/**
+ * e_variant_builder_open:
+ * @parent: a #EVariantBuilder
+ * @type: a #EVariantType, or %NULL
+ * @returns: a new (child) #EVariantBuilder
+ *
+ * Opens a subcontainer inside the given @parent.
+ *
+ * It is not permissible to use any other builder calls with @parent
+ * until @e_variant_builder_close() is called on the return value of
+ * this function.
+ *
+ * It is an error to call this function if @parent has an outstanding
+ * child.  It is an error to call this function in any case that
+ * e_variant_builder_check_add() would return %FALSE.  It is an error
+ * to call this function in any case that it's an error to call
+ * e_variant_builder_new().
+ *
+ * If @type is %NULL and @parent was given type information, that
+ * information is passed down to the subcontainer and constrains what
+ * values may be added to it.
+ **/
+EVariantBuilder *
+e_variant_builder_open (EVariantBuilder    *parent,
+                        const EVariantType *type)
+{
+  EVariantBuilder *child;
+
+  g_return_val_if_fail (parent != NULL && type != NULL, NULL);
+  g_return_val_if_fail (e_variant_builder_check_add (parent, type, NULL),
+                        NULL);
+  g_return_val_if_fail (!parent->has_child, NULL);
+
+  child = e_variant_builder_new (type);
+
+  if (parent->expected2)
+    {
+      if (e_variant_type_is_maybe (type) || e_variant_type_is_array (type))
+        child->expected2 = e_variant_type_element (parent->expected2);
+
+      if (e_variant_type_is_tuple (type) ||
+          e_variant_type_is_dict_entry (type))
+        child->expected2 = e_variant_type_first (parent->expected2);
+
+      /* in variant case, we don't want to propagate the type */
+    }
+
+  parent->has_child = TRUE;
+  child->parent = parent;
+
+  return child;
+}
+
+/**
+ * e_variant_builder_close:
+ * @child: a #EVariantBuilder
+ * @returns: the original parent of @child
+ *
+ * This function closes a builder that was created with a call to
+ * e_variant_builder_open().
+ *
+ * It is an error to call this function on a builder that was created
+ * using e_variant_builder_new().  It is an error to call this
+ * function if @child has an outstanding child.  It is an error to
+ * call this function in any case that e_variant_builder_check_end()
+ * would return %FALSE.
+ **/
+EVariantBuilder *
+e_variant_builder_close (EVariantBuilder *child)
+{
+  EVariantBuilder *parent;
+  EVariant *value;
+
+  g_return_val_if_fail (child != NULL, NULL);
+  g_return_val_if_fail (child->has_child == FALSE, NULL);
+  g_return_val_if_fail (child->parent != NULL, NULL);
+  g_assert (child->parent->has_child);
+
+  parent = child->parent;
+  parent->has_child = FALSE;
+  parent = child->parent;
+  child->parent = NULL;
+
+  value = e_variant_builder_end (child);
+  e_variant_builder_add_value (parent, value);
+
+  return parent;
+}
+
+/**
+ * e_variant_builder_new:
+ * @tclass: a container #EVariantClass
+ * @type: a type contained in @tclass, or %NULL
+ * @returns: a #EVariantBuilder
+ *
+ * Creates a new #EVariantBuilder.
+ *
+ * @tclass must be specified and must be a container type.
+ *
+ * If @type is given, it constrains the child values that it is
+ * permissible to add.  If @tclass is not %E_VARIANT_CLASS_VARIANT
+ * then @type must be contained in @tclass and will match the type of
+ * the final value.  If @tclass is %E_VARIANT_CLASS_VARIANT then
+ * @type must match the value that must be added to the variant.
+ *
+ * After the builder is created, values are added using
+ * e_variant_builder_add_value().
+ *
+ * After all the child values are added, e_variant_builder_end() ends
+ * the process.
+ **/
+EVariantBuilder *
+e_variant_builder_new (const EVariantType *type)
+{
+  EVariantBuilder *builder;
+
+  g_return_val_if_fail (type != NULL, NULL);
+  g_return_val_if_fail (e_variant_type_is_container (type), NULL);
+
+  builder = g_slice_new (EVariantBuilder);
+  builder->parent = NULL;
+  builder->offset = 0;
+  builder->has_child = FALSE;
+  builder->type = e_variant_type_copy (type);
+  builder->expected = NULL;
+  builder->trusted = TRUE;
+  builder->expected2 = NULL;
+
+  switch (*(const gchar *) type)
+    {
+    case E_VARIANT_CLASS_VARIANT:
+      builder->container_class = E_VARIANT_CLASS_VARIANT;
+      builder->children_allocated = 1;
+      builder->expected = NULL;
+      builder->min_items = 1;
+      builder->max_items = 1;
+      break;
+
+    case E_VARIANT_CLASS_ARRAY:
+      builder->container_class = E_VARIANT_CLASS_ARRAY;
+      builder->children_allocated = 8;
+      builder->expected = e_variant_type_element (builder->type);
+      builder->min_items = 0;
+      builder->max_items = -1;
+      break;
+
+    case E_VARIANT_CLASS_MAYBE:
+      builder->container_class = E_VARIANT_CLASS_MAYBE;
+      builder->children_allocated = 1;
+      builder->expected = e_variant_type_element (builder->type);
+      builder->min_items = 0;
+      builder->max_items = 1;
+      break;
+
+    case E_VARIANT_CLASS_DICT_ENTRY:
+      builder->container_class = E_VARIANT_CLASS_DICT_ENTRY;
+      builder->children_allocated = 2;
+      builder->expected = e_variant_type_key (builder->type);
+      builder->min_items = 2;
+      builder->max_items = 2;
+      break;
+
+    case 'r': /* E_VARIANT_TYPE_TUPLE was given */
+      builder->container_class = E_VARIANT_CLASS_TUPLE;
+      builder->children_allocated = 8;
+      builder->expected = NULL;
+      builder->min_items = 0;
+      builder->max_items = -1;
+      break;
+
+    case E_VARIANT_CLASS_TUPLE: /* a definite tuple type was given */
+      builder->container_class = E_VARIANT_CLASS_TUPLE;
+      builder->children_allocated = e_variant_type_n_items (type);
+      builder->expected = e_variant_type_first (builder->type);
+      builder->min_items = builder->children_allocated;
+      builder->max_items = builder->children_allocated;
+      break;
+
+    default:
+      g_assert_not_reached ();
+   }
+
+  builder->children = g_slice_alloc (sizeof (EVariant *) *
+                                     builder->children_allocated);
+
+  return builder;
+}
+
+/**
+ * e_variant_builder_end:
+ * @builder: a #EVariantBuilder
+ * @returns: a new, floating, #EVariant
+ *
+ * Ends the builder process and returns the constructed value.
+ *
+ * It is an error to call this function on a #EVariantBuilder created
+ * by a call to e_variant_builder_open().  It is an error to call this
+ * function if @builder has an outstanding child.  It is an error to
+ * call this function in any case that e_variant_builder_check_end()
+ * would return %FALSE.
+ **/
+EVariant *
+e_variant_builder_end (EVariantBuilder *builder)
+{
+  EVariantType *my_type;
+  EVariant *value;
+
+  g_return_val_if_fail (builder != NULL, NULL);
+  g_return_val_if_fail (builder->parent == NULL, NULL);
+  g_return_val_if_fail (e_variant_builder_check_end (builder, NULL), NULL);
+
+  e_variant_builder_resize (builder, builder->offset);
+
+  if (e_variant_type_is_definite (builder->type))
+    {
+      my_type = e_variant_type_copy (builder->type);
+    }
+  else
+    {
+      switch (builder->container_class)
+        {
+        case E_VARIANT_CLASS_MAYBE:
+          {
+            const EVariantType *child_type;
+
+            child_type = e_variant_get_type (builder->children[0]);
+            my_type = e_variant_type_new_maybe (child_type);
+          }
+          break;
+
+        case E_VARIANT_CLASS_ARRAY:
+          {
+            const EVariantType *child_type;
+
+            child_type = e_variant_get_type (builder->children[0]);
+            my_type = e_variant_type_new_array (child_type);
+          }
+          break;
+
+        case E_VARIANT_CLASS_TUPLE:
+          {
+            const EVariantType **types;
+            gint i;
+
+            types = g_new (const EVariantType *, builder->offset);
+            for (i = 0; i < builder->offset; i++)
+              types[i] = e_variant_get_type (builder->children[i]);
+            my_type = e_variant_type_new_tuple (types, i);
+            g_free (types);
+          }
+          break;
+
+        case E_VARIANT_CLASS_DICT_ENTRY:
+          {
+            const EVariantType *key_type, *value_type;
+
+            key_type = e_variant_get_type (builder->children[0]);
+            value_type = e_variant_get_type (builder->children[1]);
+            my_type = e_variant_type_new_dict_entry (key_type, value_type);
+          }
+        break;
+
+        case E_VARIANT_CLASS_VARIANT:
+          /* 'v' is surely a definite type, so this should never be hit */
+        default:
+          g_assert_not_reached ();
+        }
+    }
+
+  value = e_variant_new_tree (my_type, builder->children,
+                              builder->offset, builder->trusted);
+
+  e_variant_type_free (builder->type);
+  g_slice_free (EVariantBuilder, builder);
+  e_variant_type_free (my_type);
+
+  return value;
+}
+
+/**
+ * e_variant_builder_check_end:
+ * @builder: a #EVariantBuilder
+ * @error: a #GError
+ * @returns: %TRUE if ending is safe
+ *
+ * Checks if a call to e_variant_builder_end() or
+ * e_variant_builder_close() would succeed.
+ *
+ * It is an error to call this function if @builder has a child (ie:
+ * e_variant_builder_open() has been used on @builder and
+ * e_variant_builder_close() has not yet been called).
+ *
+ * This function checks that a sufficient number of items have been
+ * added to the builder.  For dictionary entries, for example, it
+ * ensures that 2 items were added.
+ *
+ * This function also checks that array and maybe builders that were
+ * created without definite type information contain at least one item
+ * (without which it would be impossible to infer the definite type).
+ *
+ * If some sort of error (either too few items were added or type
+ * inference is not possible) prevents the builder from being ended
+ * then %FALSE is returned and @error is set.
+ **/
+gboolean
+e_variant_builder_check_end (EVariantBuilder  *builder,
+                             GError          **error)
+{
+  g_return_val_if_fail (builder != NULL, FALSE);
+  g_return_val_if_fail (builder->has_child == FALSE, FALSE);
+
+  /* this function needs to check two things:
+   *
+   * 1) that we have the number of items required
+   * 2) in the case of an array or maybe type, either:
+   *      a) we have a definite type already
+   *      b) we have an item from which to infer the type
+   */
+
+  if (builder->offset < builder->min_items)
+    {
+      gchar *type_str;
+
+      type_str = e_variant_type_dup_string (builder->type);
+      g_set_error (error, E_VARIANT_BUILDER_ERROR,
+                   E_VARIANT_BUILDER_ERROR_TOO_FEW,
+                   "this container (type '%s') must contain %"G_GSIZE_FORMAT
+                   " values but only %"G_GSIZE_FORMAT "have been given",
+                   type_str, builder->min_items, builder->offset);
+      g_free (type_str);
+
+      return FALSE;
+    }
+
+  if (!e_variant_type_is_definite (builder->type) &&
+      (builder->container_class == E_VARIANT_CLASS_MAYBE ||
+       builder->container_class == E_VARIANT_CLASS_ARRAY) &&
+      builder->offset == 0)
+    {
+      g_set_error (error, E_VARIANT_BUILDER_ERROR,
+                   E_VARIANT_BUILDER_ERROR_INFER,
+                   "unable to infer type with no values present");
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+/**
+ * e_variant_builder_check_add:
+ * @builder: a #EVariantBuilder
+ * @tclass: a #EVariantClass
+ * @type: a #EVariantType, or %NULL
+ * @error: a #GError
+ * @returns: %TRUE if adding is safe
+ *
+ * Does all sorts of checks to ensure that it is safe to call
+ * e_variant_builder_add() or e_variant_builder_open().
+ *
+ * It is an error to call this function if @builder has a child (ie:
+ * e_variant_builder_open() has been used on @builder and
+ * e_variant_builder_close() has not yet been called).
+ *
+ * It is an error to call this function with an invalid @tclass
+ * (including %E_VARIANT_CLASS_INVALID) or a class that's not the
+ * smallest class for some definite type (for example,
+ * %E_VARIANT_CLASS_ALL).
+ *
+ * If @type is non-%NULL this function first checks that it is a
+ * member of @tclass (except, as with e_variant_builder_new(), if
+ * @tclass is %E_VARIANT_CLASS_VARIANT then any @type is OK).
+ *
+ * The function then checks if any child of class @tclass (and type
+ * @type, if given) would be suitable for adding to the builder.  If
+ * @type is non-%NULL and is non-definite then all definite types
+ * matching @type must be suitable for adding (ie: @type must be equal
+ * to or less general than the type expected by the builder).
+ *
+ * In the case of an array that already has at least one item in it,
+ * this function performs an additional check to ensure that @tclass
+ * and @type match the items already in the array.  @type, if given,
+ * need not be definite in order for this check to pass.
+ *
+ * Errors are flagged in the event that the builder contains too many
+ * items or the addition would cause a type error.
+ *
+ * If @tclass is specified and is a container type and @type is not
+ * given then there is no guarantee that adding a value of that class
+ * would not fail.  Calling e_variant_builder_open() with that @tclass
+ * (and @type as %NULL) would succeed, though.
+ *
+ * In the case that any error is detected @error is set and %FALSE is
+ * returned.
+ **/
+gboolean
+e_variant_builder_check_add (EVariantBuilder     *builder,
+                             const EVariantType  *type,
+                             GError             **error)
+{
+  g_return_val_if_fail (builder != NULL, FALSE);
+  g_return_val_if_fail (type != NULL, FALSE);
+  g_return_val_if_fail (builder->has_child == FALSE, FALSE);
+
+  /* this function needs to check two things:
+   *
+   * 1) that we have not exceeded the number of allowable items
+   * 2) that the incoming type matches the expected types like so:
+   *
+   *      expected2 <= type <= expected
+   *
+   * but since expected2 or expected could be %NULL, we need explicit checks:
+   *
+   *   type <= expected
+   *   expected2 <= type
+   *
+   * (we already know expected2 <= expected)
+   */
+
+  if (builder->offset == builder->max_items)
+    {
+      gchar *type_str;
+
+      type_str = e_variant_type_dup_string (builder->type);
+      g_set_error (error, E_VARIANT_BUILDER_ERROR,
+                   E_VARIANT_BUILDER_ERROR_TOO_MANY,
+                   "this container (type '%s') may not contain more than"
+                   " %"G_GSIZE_FORMAT " values", type_str, builder->offset);
+      g_free (type_str);
+
+      return FALSE;
+    }
+
+  /* type <= expected */
+  if (builder->expected &&
+      !e_variant_type_is_subtype_of (type, builder->expected))
+    {
+      gchar *expected_str, *type_str;
+
+      expected_str = e_variant_type_dup_string (builder->expected);
+      type_str = e_variant_type_dup_string (type);
+      g_set_error (error, E_VARIANT_BUILDER_ERROR,
+                   E_VARIANT_BUILDER_ERROR_TYPE,
+                   "type '%s' does not match expected type '%s'",
+                   type_str, expected_str);
+      g_free (expected_str);
+      g_free (type_str);
+      return FALSE;
+    }
+
+  /* expected2 <= type */
+  if (builder->expected2 &&
+      !e_variant_type_is_subtype_of (builder->expected2, type))
+    {
+      g_set_error (error, E_VARIANT_BUILDER_ERROR,
+                   E_VARIANT_BUILDER_ERROR_TYPE,
+                   "all elements in an array must have the same type");
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+/**
+ * e_variant_builder_cancel:
+ * @builder: a #EVariantBuilder
+ *
+ * Cancels the build process.  All memory associated with @builder is
+ * freed.  If the builder was created with e_variant_builder_open()
+ * then all ancestors are also freed.
+ **/
+void
+e_variant_builder_cancel (EVariantBuilder *builder)
+{
+  EVariantBuilder *parent;
+
+  g_return_if_fail (builder != NULL);
+
+  do
+    {
+      gsize i;
+
+      for (i = 0; i < builder->offset; i++)
+        e_variant_unref (builder->children[i]);
+
+      g_slice_free1 (sizeof (EVariant *) * builder->children_allocated,
+                     builder->children);
+
+      if (builder->type)
+        e_variant_type_free (builder->type);
+
+      parent = builder->parent;
+      g_slice_free (EVariantBuilder, builder);
+    }
+  while ((builder = parent));
+}
+
+/**
+ * e_variant_flatten:
+ * @value: a #EVariant instance
+ *
+ * Flattens @value.
+ *
+ * This is a strange function with no direct effects but some
+ * noteworthy side-effects.  Essentially, it ensures that @value is in
+ * its most favourable form.  This involves ensuring that the value is
+ * serialised and in machine byte order.  The investment of time now
+ * can pay off by allowing shorter access times for future calls and
+ * typically results in a reduction of memory consumption.
+ *
+ * A value received over the network or read from the disk in machine
+ * byte order is already flattened.
+ *
+ * Some of the effects of this call are that any future accesses to
+ * the data of @value (or children taken from it after flattening)
+ * will occur in O(1) time.  Also, any data accessed from such a child
+ * value will continue to be valid even after the child has been
+ * destroyed, as long as @value still exists (since the contents of
+ * the children are now serialised as part of the parent).
+ **/
+void
+e_variant_flatten (EVariant *value)
+{
+  g_return_if_fail (value != NULL);
+  e_variant_get_data (value);
+}
+
+/**
+ * e_variant_get_type_string:
+ * @value: a #EVariant
+ * @returns: the type string for the type of @value
+ *
+ * Returns the type string of @value.  Unlike the result of calling
+ * e_variant_type_peek_string(), this string is nul-terminated.  This
+ * string belongs to #EVariant and must not be freed.
+ **/
+const gchar *
+e_variant_get_type_string (EVariant *value)
+{
+  g_return_val_if_fail (value != NULL, NULL);
+  return (const gchar *) e_variant_get_type (value);
+}
+
+/**
+ * e_variant_is_basic:
+ * @value: a #EVariant
+ * @returns: %TRUE if @value has a basic type
+ *
+ * Determines if @value has a basic type.  Values with basic types may
+ * be used as the keys of dictionary entries.
+ *
+ * This function is the exact opposite of e_variant_is_container().
+ **/
+gboolean
+e_variant_is_basic (EVariant *value)
+{
+  g_return_val_if_fail (value != NULL, FALSE);
+  return e_variant_type_is_basic (e_variant_get_type (value));
+}
+
+/**
+ * e_variant_is_container:
+ * @value: a #EVariant
+ * @returns: %TRUE if @value has a basic type
+ *
+ * Determines if @value has a container type.  Values with container
+ * types may be used with the functions e_variant_n_children() and
+ * e_variant_get_child().
+ *
+ * This function is the exact opposite of e_variant_is_basic().
+ **/
+gboolean
+e_variant_is_container (EVariant *value)
+{
+  g_return_val_if_fail (value != NULL, FALSE);
+  return e_variant_type_is_container (e_variant_get_type (value));
+}
+
+#include <stdio.h>
+void
+e_variant_dump_data (EVariant *value)
+{
+  const guchar *data;
+  const guchar *end;
+  char row[3*16+2];
+  gsize data_size;
+  gsize i;
+
+  g_return_if_fail (value != NULL);
+  data_size = e_variant_get_size (value);
+
+  g_debug ("EVariant at %p (type '%s', %" G_GSIZE_FORMAT " bytes):",
+           value, e_variant_get_type_string (value), data_size);
+
+  data = e_variant_get_data (value);
+  end = data + data_size;
+
+  i = 0;
+  row[3*16+1] = '\0';
+  while (data < end || (i & 15))
+    {
+      if ((i & 15) == (((gsize) data) & 15) && data < end)
+        sprintf (&row[3 * (i & 15) + (i & 8)/8], "%02x  ", *data++);
+      else
+        sprintf (&row[3 * (i & 15) + (i & 8)/8], "    ");
+
+      if ((++i & 15) == 0)
+        {
+          g_debug ("   %s", row);
+          memset (row, 'q', 3 * 16 + 1);
+        }
+    }
+
+  g_debug ("==");
+}
+
+EVariant *
+e_variant_deep_copy (EVariant *value)
+{
+  switch (e_variant_classify (value))
+  {
+    case E_VARIANT_CLASS_BOOLEAN:
+      return e_variant_new_boolean (e_variant_get_boolean (value));
+
+    case E_VARIANT_CLASS_BYTE:
+      return e_variant_new_byte (e_variant_get_byte (value));
+
+    case E_VARIANT_CLASS_INT16:
+      return e_variant_new_int16 (e_variant_get_int16 (value));
+
+    case E_VARIANT_CLASS_UINT16:
+      return e_variant_new_uint16 (e_variant_get_uint16 (value));
+
+    case E_VARIANT_CLASS_INT32:
+      return e_variant_new_int32 (e_variant_get_int32 (value));
+
+    case E_VARIANT_CLASS_UINT32:
+      return e_variant_new_uint32 (e_variant_get_uint32 (value));
+
+    case E_VARIANT_CLASS_INT64:
+      return e_variant_new_int64 (e_variant_get_int64 (value));
+
+    case E_VARIANT_CLASS_UINT64:
+      return e_variant_new_uint64 (e_variant_get_uint64 (value));
+
+    case E_VARIANT_CLASS_DOUBLE:
+      return e_variant_new_double (e_variant_get_double (value));
+
+    case E_VARIANT_CLASS_STRING:
+      return e_variant_new_string (e_variant_get_string (value, NULL));
+
+    case E_VARIANT_CLASS_OBJECT_PATH:
+      return e_variant_new_object_path (e_variant_get_string (value, NULL));
+
+    case E_VARIANT_CLASS_SIGNATURE:
+      return e_variant_new_signature (e_variant_get_string (value, NULL));
+
+    case E_VARIANT_CLASS_VARIANT:
+      {
+        EVariant *inside, *new;
+
+        inside = e_variant_get_variant (value);
+        new = e_variant_new_variant (e_variant_deep_copy (inside));
+        e_variant_unref (inside);
+
+        return new;
+      }
+
+    case E_VARIANT_CLASS_HANDLE:
+      return e_variant_new_handle (e_variant_get_handle (value));
+
+    case E_VARIANT_CLASS_MAYBE:
+    case E_VARIANT_CLASS_ARRAY:
+    case E_VARIANT_CLASS_TUPLE:
+    case E_VARIANT_CLASS_DICT_ENTRY:
+      {
+        EVariantBuilder *builder;
+        EVariantIter iter;
+        EVariant *child;
+
+        builder = e_variant_builder_new (e_variant_get_type (value));
+        e_variant_iter_init (&iter, value);
+
+        while ((child = e_variant_iter_next_value (&iter)))
+          e_variant_builder_add_value (builder, e_variant_deep_copy (child));
+
+        return e_variant_builder_end (builder);
+      }
+
+    default:
+      g_assert_not_reached ();
+  }
+}
+
+/**
+ * e_variant_new_strv:
+ * @strv: an array of strings
+ * @length: the length of @strv, or -1
+ * @returns: a new floating #EVariant instance
+ *
+ * Constructs an array of strings #EVariant from the given array of
+ * strings.
+ *
+ * If @length is not -1 then it gives the maximum length of @strv.  In
+ * any case, a %NULL pointer in @strv is taken as a terminator.
+ **/
+EVariant *
+e_variant_new_strv (const gchar * const *strv,
+                    gint                 length)
+{
+  EVariantBuilder *builder;
+
+  g_return_val_if_fail (strv != NULL || length == 0, NULL);
+
+  builder = e_variant_builder_new (E_VARIANT_TYPE ("as"));
+  while (length-- && *strv)
+    e_variant_builder_add (builder, "s", *strv++);
+
+  return e_variant_builder_end (builder);
+}
+
+/**
+ * e_variant_get_strv:
+ * @value: an array of strings #EVariant
+ * @length: the length of the result, or %NULL
+ * @returns: an array of constant strings
+ *
+ * Gets the contents of an array of strings #EVariant.  This call
+ * makes a shallow copy; the return result should be released with
+ * g_free(), but the individual strings must not be modified.
+ *
+ * If @length is non-%NULL then the number of elements in the result
+ * is stored there.  In any case, the resulting array will be
+ * %NULL-terminated.
+ *
+ * For an empty array, @length will be set to 0 and a pointer to a
+ * %NULL pointer will be returned.
+ **/
+const gchar **
+e_variant_get_strv (EVariant *value,
+                    gint     *length)
+{
+  const gchar **result;
+  gint my_length;
+  gint i;
+
+  g_return_val_if_fail (value != NULL, NULL);
+  g_return_val_if_fail (e_variant_has_type (value, E_VARIANT_TYPE ("as")),
+                        NULL);
+
+  e_variant_flatten (value);
+
+  my_length = e_variant_n_children (value);
+  result = g_new (const gchar *, my_length + 1);
+
+  if (length)
+    *length = my_length;
+
+  for (i = 0; i < my_length; i++)
+    {
+      EVariant *child = e_variant_get_child_value (value, i);
+      result[i] = e_variant_get_string (child, NULL);
+      e_variant_unref (child);
+    }
+  result[i] = NULL;
+
+  return result;
+}
+
+/**
+ * e_variant_dup_strv:
+ * @value: an array of strings #EVariant
+ * @length: the length of the result, or %NULL
+ * @returns: an array of constant strings
+ *
+ * Gets the contents of an array of strings #EVariant.  This call
+ * makes a deep copy; the return result should be released with
+ * g_strfreev().
+ *
+ * If @length is non-%NULL then the number of elements in the result
+ * is stored there.  In any case, the resulting array will be
+ * %NULL-terminated.
+ *
+ * For an empty array, @length will be set to 0 and a pointer to a
+ * %NULL pointer will be returned.
+ **/
+gchar **
+e_variant_dup_strv (EVariant *value,
+                    gint     *length)
+{
+  gchar **result;
+  gint my_length;
+  gint i;
+
+  g_return_val_if_fail (value != NULL, NULL);
+  g_return_val_if_fail (e_variant_has_type (value, E_VARIANT_TYPE ("as")),
+                        NULL);
+
+  e_variant_flatten (value);
+
+  my_length = e_variant_n_children (value);
+  result = g_new (gchar *, my_length + 1);
+
+  if (length)
+    *length = my_length;
+
+  for (i = 0; i < my_length; i++)
+    {
+      EVariant *child = e_variant_get_child_value (value, i);
+      result[i] = e_variant_dup_string (child, NULL);
+      e_variant_unref (child);
+    }
+  result[i] = NULL;
+
+  return result;
+}
+
+/**
+ * e_variant_lookup_value:
+ * @dictionary: a #EVariant dictionary, keyed by strings
+ * @key: a string to lookup in the dictionary
+ * @returns: the value corresponding to @key, or %NULL
+
+ * Looks up @key in @dictionary.  This is essentially a convenience
+ * function for dealing with the extremely common case of a dictionary
+ * keyed by strings.
+ *
+ * In the case that the key is found, the corresponding value is
+ * returned; not the dictionary entry.  If the key is not found then
+ * this function returns %NULL.
+ **/
+EVariant *
+e_variant_lookup_value (EVariant    *dictionary,
+                        const gchar *key)
+{
+  EVariantIter iter;
+  const gchar *_key;
+  EVariant *value;
+  EVariant *result = NULL;
+
+  g_return_val_if_fail (dictionary != NULL, NULL);
+  g_return_val_if_fail (key != NULL, NULL);
+
+  e_variant_iter_init (&iter, dictionary);
+  while (e_variant_iter_next (&iter, "{&s*}", &_key, &value))
+    if (strcmp (_key, key) == 0)
+      {
+        result = e_variant_ref (value);
+        e_variant_iter_cancel (&iter);
+      }
+
+  return result;
+}
+
+/**
+ * e_variant_from_file:
+ * @type: the #EVariantType of the new variant
+ * @filename: the filename to load from
+ * @flags: zero or more #EVariantFlags
+ * @error: a pointer to a #GError, or %NULL
+ * @returns: a new #EVariant instance, or %NULL
+ *
+ * Utility function to load a #EVariant from the contents of a file,
+ * using a #GMappedFile.
+ *
+ * This function attempts to open @filename using #GMappedFile and then
+ * calls e_variant_from_data() on the result.  As with that function,
+ * @type may be %NULL.
+ **/
+EVariant *
+e_variant_from_file (const EVariantType *type,
+                     const gchar        *filename,
+                     EVariantFlags       flags,
+                     GError             **error)
+{
+  GMappedFile *mapped;
+  gconstpointer data;
+  gsize size;
+
+  g_return_val_if_fail (filename != NULL, NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  mapped = g_mapped_file_new (filename, FALSE, error);
+
+  if (mapped == NULL)
+    return NULL;
+
+  data = g_mapped_file_get_contents (mapped);
+  size = g_mapped_file_get_length (mapped);
+
+  if (size == 0)
+  /* #595535 */
+    data = NULL;
+
+  return e_variant_from_data (type, data, size, flags,
+                              (GDestroyNotify) g_mapped_file_unref,
+                              mapped);
+}
diff --git a/libedbus/gvariant-valist.c b/libedbus/gvariant-valist.c
new file mode 100644
index 0000000..f036af9
--- /dev/null
+++ b/libedbus/gvariant-valist.c
@@ -0,0 +1,1572 @@
+/*
+ * Copyright © 2007, 2008 Ryan Lortie
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "gvariant-private.h"
+
+#include <glib/gtestutils.h>
+#include <glib/gmessages.h>
+#include <glib/gstrfuncs.h>
+#include <string.h>
+
+/**
+ * e_variant_format_string_scan:
+ * @string: a string that may be prefixed with a format string
+ * @limit: a pointer to the end of @string
+ * @endptr: location to store the end pointer, or %NULL
+ * @returns: %TRUE if there was a valid format string
+ *
+ * Checks the string pointed to by @string for starting with a properly
+ * formed #EVariant varargs format string.  If no valid format string is
+ * found then %FALSE is returned.
+ *
+ * If @string does start with a valid format string and @endptr is
+ * non-%NULL then it is updated to point to the first character after
+ * the format string.  If @endptr is %NULL and there is any character
+ * following the format string then this call returns %FALSE.  Another
+ * way of saying this is that if @endptr is %NULL then @string is
+ * checked to contain exactly one valid format string and nothing else.
+ *
+ * If @limit is non-%NULL then @limit (and any charater after it) will
+ * not be accessed and the effect is otherwise equivalent to if the
+ * character at @limit were nul.
+ *
+ * All valid #EVariantType strings are also valid format strings.  See
+ * e_variant_type_string_is_valid().
+ *
+ * Additionally, any type string contained in the format string may be
+ * prefixed with a '@' character.  Nested '@' characters may not
+ * appear.
+ *
+ * Additionally, any fixed-width type may be prefixed with a '&'
+ * character.  No wildcard type is a fixed-width type.  Like '@', '&'
+ * characters may not be nested.
+ *
+ * Additionally, any array of fixed-width types may be prefixed with a
+ * '&'.
+ *
+ * Additionally, '&s', '^as' and '^a&s' may appear.
+ *
+ * No '@', '&' or '^' character, however, may appear as part of an array
+ * type.
+ *
+ * Currently, there are no other permissible format strings.  Others
+ * may be added in the future.
+ *
+ * For an explanation of what these strings mean, see e_variant_new()
+ * and e_variant_get().
+ **/
+gboolean
+e_variant_format_string_scan (const gchar  *string,
+                              const gchar  *limit,
+                              const gchar **endptr)
+{
+  const gchar *start;
+
+  if (string == limit || *string == '\0')
+    return FALSE;
+
+  switch (*string++)
+    {
+    case '(':
+      while (string == limit || *string != ')')
+        if (!e_variant_format_string_scan (string, limit, &string))
+          return FALSE;
+
+      string++;
+      break;
+
+    case '{':
+      if (string != limit && (*string == '@' || *string == '&'))
+        string++;
+
+      if (string == limit || *string == '\0' ||                    /* { */
+          !strchr ("bynqihuxtdsog?", *string++) ||                  /* key */
+          !e_variant_format_string_scan (string, limit, &string) ||/* value */
+          string == limit || *string++ != '}')                     /* } */
+        return FALSE;
+
+      break;
+
+    case 'm':
+      return e_variant_format_string_scan (string, limit, endptr); /* tcall */
+
+    case 'a':
+    case '@':
+      return e_variant_type_string_scan (string, limit, endptr);   /* tcall */
+
+    case '&':
+      start = string;
+
+      if (!e_variant_type_string_scan (string, limit, &string))
+        return FALSE;
+
+      if (start + 1 == string && *start == 's')
+        break;
+
+      if (*start == 'a')
+        start++;
+
+      while (start != string)
+        if (!strchr ("bynqihuxtd(){}", *start++))
+          return FALSE;
+
+      break;
+
+    case '^':
+      if (string == limit || *string++ != 'a')
+        return FALSE;
+
+      if (string != limit && *string == '&')
+        string++;
+
+      if (string == limit || *string++ != 's')
+        return FALSE;
+
+      break;
+
+    case 'b': case 'y': case 'n': case 'q': case 'i': case 'u':
+    case 'x': case 't': case 'd': case 's': case 'o': case 'g':
+    case 'v': case '*': case '?': case 'r': case 'h':
+      break;
+
+    default:
+      return FALSE;
+  }
+
+  if (endptr != NULL)
+    *endptr = string;
+  else
+    {
+      if (string != limit && *string != '\0')
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+/**
+ * e_variant_format_string_scan_type:
+ * @string: a string that may be prefixed with a format string
+ * @limit: a pointer to the end of @string
+ * @endptr: location to store the end pointer, or %NULL
+ * @returns: a #EVariantType if there was a valid format string
+ *
+ * If @string starts with a valid format string then this function will
+ * return the type that the format string corresponds to.  Otherwise
+ * this function returns %NULL.
+ *
+ * The returned type must be freed by the caller.
+ *
+ * This function is otherwise exactly like
+ * e_variant_format_string_scan().
+ **/
+EVariantType *
+e_variant_format_string_scan_type (const gchar  *string,
+                                   const gchar  *limit,
+                                   const gchar **endptr)
+{
+  const gchar *my_end;
+  gchar *dest;
+  gchar *new;
+
+  if (endptr == NULL)
+    endptr = &my_end;
+
+  if (!e_variant_format_string_scan (string, limit, endptr))
+    return NULL;
+
+  dest = new = g_malloc (*endptr - string + 1);
+  while (string != *endptr)
+    {
+      if (*string != '@' && *string != '&' && *string != '^')
+        *dest++ = *string;
+      string++;
+    }
+  *dest = '\0';
+
+  return (EVariantType *) E_VARIANT_TYPE (new);
+}
+
+static EVariant *
+e_variant_valist_new (const gchar **format_string,
+                      va_list      *app)
+{
+  switch (**format_string)
+  {
+    case 'b':
+      (*format_string)++;
+      return e_variant_new_boolean (va_arg (*app, gboolean));
+
+    case 'y':
+      (*format_string)++;
+      return e_variant_new_byte (va_arg (*app, guint));
+
+    case 'n':
+      (*format_string)++;
+      return e_variant_new_int16 (va_arg (*app, gint));
+
+    case 'q':
+      (*format_string)++;
+      return e_variant_new_uint16 (va_arg (*app, guint));
+
+    case 'i':
+      (*format_string)++;
+      return e_variant_new_int32 (va_arg (*app, gint));
+
+    case 'h':
+      (*format_string)++;
+      return e_variant_new_handle (va_arg (*app, gint));
+
+    case 'u':
+      (*format_string)++;
+      return e_variant_new_uint32 (va_arg (*app, guint));
+
+    case 'x':
+      (*format_string)++;
+      return e_variant_new_int64 (va_arg (*app, gint64));
+
+    case 't':
+      (*format_string)++;
+      return e_variant_new_uint64 (va_arg (*app, guint64));
+
+    case 'd':
+      (*format_string)++;
+      return e_variant_new_double (va_arg (*app, gdouble));
+
+    case 's':
+      (*format_string)++;
+      return e_variant_new_string (va_arg (*app, const gchar *));
+
+    case 'o':
+      (*format_string)++;
+      return e_variant_new_object_path (va_arg (*app, const gchar *));
+
+    case 'g':
+      (*format_string)++;
+      return e_variant_new_signature (va_arg (*app, const gchar *));
+
+    case 'v':
+      (*format_string)++;
+      return e_variant_new_variant (va_arg (*app, EVariant *));
+
+    case '@':
+    case '*':
+    case '?':
+    case 'r':
+      e_variant_format_string_scan (*format_string, NULL, format_string);
+      return va_arg (*app, EVariant *);
+
+    case '^':
+      {
+        const gchar *string = (*format_string + 1);
+
+        if (string[0] == 'a' &&
+            (string[1] == 's' ||
+             (string[1] == '&' && string[2] == 's')))
+          {
+            const gchar * const *ptr;
+
+            *format_string += 3 + (string[1] == '&');
+            ptr = va_arg (*app, const gchar * const *);
+
+            return e_variant_new_strv (ptr, -1);
+          }
+
+        g_error ("Currently, only ^as and ^a&s are supported");
+      }
+
+    case '&':
+      {
+        const gchar *string = (*format_string + 1);
+        gconstpointer ptr;
+        gint n_items = 0;
+
+        ptr = va_arg (*app, gconstpointer);
+
+        switch (*string++)
+          {
+          case 's': /* '&s' */
+            /* for building, just the same as normal 's' */
+            *format_string += 2;
+            return e_variant_new_string (ptr);
+
+          case 'a':
+            n_items = va_arg (*app, gint);
+
+            if (n_items < 0)
+              g_error ("size of -1 can only be specified for string arrays");
+
+            /* fall through */
+
+          default:
+            {
+              EVariantType *type;
+              EVariant *value;
+
+              type = e_variant_format_string_scan_type (*format_string,
+                                                        NULL, format_string);
+              g_assert (e_variant_type_is_definite (type));
+
+              value = e_variant_load_fixed (type, ptr, n_items);
+              e_variant_type_free (type);
+
+              return value;
+            }
+          }
+      }
+
+    case 'a':
+      e_variant_format_string_scan (*format_string, NULL, format_string);
+      return e_variant_builder_end (va_arg (*app, EVariantBuilder *));
+
+    case 'm':
+      {
+        EVariantBuilder *builder;
+        const gchar *string;
+        EVariantType *type;
+        EVariant *value;
+
+        string = (*format_string) + 1;
+        type = e_variant_format_string_scan_type (*format_string,
+                                                  NULL, format_string);
+        builder = e_variant_builder_new (type);
+        e_variant_type_free (type);
+
+        switch (*string)
+        {
+          case 's':
+            if ((string = va_arg (*app, const gchar *)))
+              e_variant_builder_add_value (builder,
+                                           e_variant_new_string (string));
+            break;
+
+          case 'o':
+            if ((string = va_arg (*app, const gchar *)))
+              e_variant_builder_add_value (builder,
+                                           e_variant_new_object_path (string));
+            break;
+
+          case 'g':
+            if ((string = va_arg (*app, const gchar *)))
+              e_variant_builder_add_value (builder,
+                                           e_variant_new_signature (string));
+            break;
+
+          case '@':
+          case '*':
+          case '?':
+          case 'r':
+            if ((value = va_arg (*app, EVariant *)))
+              e_variant_builder_add_value (builder, value);
+            break;
+
+          case 'v':
+            if ((value = va_arg (*app, EVariant *)))
+              e_variant_builder_add_value (builder,
+                                           e_variant_new_variant (value));
+            break;
+
+          case '&':
+            {
+              gconstpointer ptr;
+
+              if ((ptr = va_arg (*app, gconstpointer)))
+                {
+                  EVariantType *type;
+                  gsize n_items;
+
+                  type = e_variant_format_string_scan_type (string,
+                                                            NULL, NULL);
+                  g_assert (e_variant_type_is_definite (type));
+
+                  if (e_variant_type_is_array (type))
+                    n_items = va_arg (*app, gsize);
+                  else
+                    n_items = 0;
+
+                  e_variant_builder_add_value (builder,
+                                               e_variant_load_fixed (type,
+                                                                     ptr,
+                                                                     n_items));
+                  e_variant_type_free (type);
+                }
+              break;
+            }
+
+          default:
+            {
+              gboolean *just;
+
+              just = va_arg (*app, gboolean *);
+
+              if (just != NULL)
+                {
+                  /* non-NULL, so consume the arguments */
+                  value = e_variant_valist_new (&string, app);
+                  g_assert (string == *format_string);
+
+                  e_variant_ref_sink (value);
+
+                  if (*just)
+                    /* only put in the maybe if just was TRUE */
+                    e_variant_builder_add_value (builder, value);
+
+                  e_variant_unref (value);
+                }
+            }
+        }
+
+        return e_variant_builder_end (builder);
+      }
+
+    case '(':
+    case '{':
+      {
+        EVariantBuilder *builder;
+
+        if (**format_string == '(')
+          builder = e_variant_builder_new (E_VARIANT_TYPE_TUPLE);
+        else
+          builder = e_variant_builder_new (E_VARIANT_TYPE_DICT_ENTRY);
+
+        (*format_string)++;                                          /* '(' */
+        while (**format_string != ')' && **format_string != '}')
+          {
+            EVariant *value;
+
+            value = e_variant_valist_new (format_string, app);
+            e_variant_builder_add_value (builder, value);
+          }
+        (*format_string)++;                                          /* ')' */
+
+        return e_variant_builder_end (builder);
+      }
+
+    default:
+      g_assert_not_reached ();
+  }
+}
+
+static void
+e_variant_valist_get (EVariant     *value,
+                      gboolean      free,
+                      const gchar **format_string,
+                      va_list      *app)
+{
+  switch ((*format_string)[0])
+  {
+    case 'b':
+      {
+        gboolean *ptr = va_arg (*app, gboolean *);
+
+        if (ptr)
+          {
+            if (value)
+              *ptr = e_variant_get_boolean (value);
+            else
+              *ptr = FALSE;
+          }
+
+        (*format_string)++;
+        return;
+      }
+
+    case 'y':
+      {
+        guchar *ptr = va_arg (*app, guchar *);
+
+        if (ptr)
+          {
+            if (value)
+              *ptr = e_variant_get_byte (value);
+            else
+              *ptr = '\0';
+          }
+
+        (*format_string)++;
+        return;
+      }
+
+    case 'n':
+      {
+        gint16 *ptr = va_arg (*app, gint16 *);
+
+        if (ptr)
+          {
+            if (value)
+              *ptr = e_variant_get_int16 (value);
+            else
+              *ptr = 0;
+          }
+
+        (*format_string)++;
+        return;
+      }
+
+    case 'q':
+      {
+        guint16 *ptr = va_arg (*app, guint16 *);
+
+        if (ptr)
+          {
+            if (value)
+              *ptr = e_variant_get_uint16 (value);
+            else
+              *ptr = 0;
+          }
+
+        (*format_string)++;
+        return;
+      }
+
+    case 'i':
+      {
+        gint32 *ptr = va_arg (*app, gint32 *);
+
+        if (ptr)
+          {
+            if (value)
+              *ptr = e_variant_get_int32 (value);
+            else
+              *ptr = 0;
+          }
+
+        (*format_string)++;
+        return;
+      }
+
+    case 'h':
+      {
+        gint32 *ptr = va_arg (*app, gint32 *);
+
+        if (ptr)
+          {
+            if (value)
+              *ptr = e_variant_get_handle (value);
+            else
+              *ptr = 0;
+          }
+
+        (*format_string)++;
+        return;
+      }
+
+    case 'u':
+      {
+        guint32 *ptr = va_arg (*app, guint32 *);
+
+        if (ptr)
+          {
+            if (value)
+              *ptr = e_variant_get_uint32 (value);
+            else
+              *ptr = 0;
+          }
+
+        (*format_string)++;
+        return;
+      }
+
+    case 'x':
+      {
+        gint64 *ptr = va_arg (*app, gint64 *);
+
+        if (ptr)
+          {
+            if (value)
+              *ptr = e_variant_get_int64 (value);
+            else
+              *ptr = 0;
+          }
+
+        (*format_string)++;
+        return;
+      }
+
+    case 't':
+      {
+        guint64 *ptr = va_arg (*app, guint64 *);
+
+        if (ptr)
+          {
+            if (value)
+              *ptr = e_variant_get_uint64 (value);
+            else
+              *ptr = 0;
+          }
+
+        (*format_string)++;
+        return;
+      }
+
+    case 'd':
+      {
+        gdouble *ptr = va_arg (*app, gdouble *);
+
+        if (ptr)
+          {
+            if (value)
+              *ptr = e_variant_get_double (value);
+            else
+              *ptr = 0.;
+          }
+
+        (*format_string)++;
+        return;
+      }
+
+    case 's':
+    case 'o':
+    case 'g':
+      {
+        gchar **ptr = va_arg (*app, gchar **);
+
+        if (ptr)
+          {
+            if (free && *ptr)
+              g_free (*ptr);
+
+            if (value)
+              *ptr = e_variant_dup_string (value, NULL);
+            else
+              *ptr = NULL;
+          }
+
+        (*format_string)++;
+        return;
+      }
+
+    case 'v':
+      {
+        EVariant **ptr = va_arg (*app, EVariant **);
+
+        if (ptr)
+          {
+            if (free && *ptr)
+              e_variant_unref (*ptr);
+
+            if (value)
+              *ptr = e_variant_get_variant (value);
+            else
+              *ptr = NULL;
+          }
+
+        (*format_string)++;
+        return;
+      }
+
+    case '@':
+    case '*':
+    case '?':
+    case 'r':
+      {
+        EVariant **ptr = va_arg (*app, EVariant **);
+
+        if (ptr)
+          {
+            if (free && *ptr)
+              e_variant_unref (*ptr);
+
+            if (value)
+              *ptr = e_variant_ref (value);
+            else
+              *ptr = NULL;
+          }
+
+        e_variant_format_string_scan (*format_string, NULL, format_string);
+        return;
+      }
+
+    case 'a':
+      {
+        EVariantIter *ptr = va_arg (*app, EVariantIter *);
+
+        if (ptr)
+          {
+            if (free)
+              e_variant_iter_cancel (ptr);
+
+            if (value)
+              e_variant_iter_init (ptr, value);
+            else
+              {
+                memset (ptr, 0, sizeof (EVariantIter));
+                e_variant_iter_cancel (ptr);
+              }
+          }
+
+        e_variant_format_string_scan (*format_string, NULL, format_string);
+        return;
+      }
+
+    case '^':
+      {
+        const gchar *string = (*format_string + 1);
+
+        if (string[0] == 'a')
+          {
+            gpointer *ptr;
+
+            ptr = va_arg (*app, gpointer *);
+
+            if (string[1] == 's')
+              {
+                *format_string += 3;
+
+                if (free && *ptr)
+                  g_strfreev (*ptr);
+
+                if (value)
+                  *ptr = e_variant_dup_strv (value, NULL);
+                else
+                  *ptr = NULL;
+
+                return;
+              }
+
+            if (string[1] == '&' && string[2] == 's')
+              {
+                *format_string += 4;
+
+                if (free && *ptr)
+                  g_free (*ptr);
+
+                if (value)
+                  *ptr = e_variant_get_strv (value, NULL);
+                else
+                  *ptr = NULL;
+
+                return;
+              }
+          }
+
+        g_error ("Currently, only ^as and ^a&s are supported");
+      }
+
+
+    case '&':
+      {
+        gconstpointer *ptr;
+
+        ptr = va_arg (*app, gconstpointer *);
+
+        if ((*format_string)[1] == 's') /* '&s' */
+          {
+            /* cannot just get_data() like below.
+             * might not be nul-terminated */
+            if (ptr)
+              {
+                if (value)
+                  *ptr = e_variant_get_string (value, NULL);
+                else
+                  *ptr = NULL;
+              }
+
+            (*format_string) += 2;
+            return;
+          }
+
+        if ((*format_string)[1] == 'a') /* '&a..' */
+          {
+            gint *n_items = va_arg (*app, gint *);
+
+            if (n_items)
+              {
+                if (value)
+                  *n_items = e_variant_n_children (value);
+                else
+                  *n_items = 0;
+              }
+          }
+
+        if (ptr)
+          {
+            if (value)
+              *ptr = e_variant_get_data (value);
+            else
+              *ptr = NULL;
+          }
+
+        e_variant_format_string_scan (*format_string, NULL, format_string);
+        return;
+      }
+
+    case 'm':
+      {
+        EVariant *just;
+
+        if (value && e_variant_n_children (value))
+          just = e_variant_get_child_value (value, 0);
+        else
+          just = NULL;
+
+        /* skip the 'm', check the next character */
+        if (!strchr ("sogv *?r&", *++(*format_string)))
+          {
+            gboolean *ptr = va_arg (*app, gboolean *);
+
+            if (ptr)
+              {
+                /* only free the args if *ptr was TRUE from last time.
+                 * else, last iteration was 'None' -> nothing to free. */
+                e_variant_valist_get (just, free && *ptr,
+                                      format_string, app);
+                *ptr = just != NULL;
+              }
+            else
+              e_variant_format_string_scan (*format_string,
+                                            NULL, format_string);
+          }
+        else
+          e_variant_valist_get (just, free, format_string, app);
+
+        if (just)
+          e_variant_unref (just);
+        return;
+      }
+
+    case '(':
+    case '{':
+      {
+        EVariantIter iter;
+        EVariant *child;
+        char end_char;
+
+        if (**format_string == '(')
+          end_char = ')';
+        else
+          end_char = '}';
+
+        if (value)
+          e_variant_iter_init (&iter, value);
+
+        (*format_string)++;                                          /* '(' */
+        while (**format_string != ')' && **format_string != '}')
+          {
+            if (value)
+              {
+                child = e_variant_iter_next_value (&iter);
+                g_assert (child != NULL);
+              }
+            else
+              child = NULL;
+
+            e_variant_valist_get (child, free, format_string, app);
+          }
+        (*format_string)++;                                          /* ')' */
+
+        if (value)
+          {
+            child = e_variant_iter_next_value (&iter);
+            g_assert (child == NULL);
+          }
+
+        return;
+      }
+
+    default:
+      g_assert_not_reached ();
+  }
+}
+
+/**
+ * e_variant_new:
+ * @format_string: a #EVariant format string
+ * @...: arguments, as per @format_string
+ * @returns: a new floating #EVariant instance
+ *
+ * Creates a new #EVariant instance.
+ *
+ * Think of this function as an analogue to g_strdup_printf().
+ *
+ * The type of the created instance and the arguments that are
+ * expected by this function are determined by @format_string.  In the
+ * most simple case, @format_string is exactly equal to a definite
+ * #EVariantType type string and the result is of that type.  All
+ * exceptions to this case are explicitly mentioned below.
+ *
+ * The arguments that this function collects are determined by
+ * scanning @format_string from start to end.  Brackets do not impact
+ * the collection of arguments (except when following 'a', 'm' or '@';
+ * see below).  Each other character that is encountered will result
+ * in an argument being collected.
+ *
+ * Arguments for the base types are expected as follows:
+ * <informaltable>
+ *   <tgroup cols='3'>
+ *     <colspec align='center'/>
+ *     <colspec align='center'/>
+ *     <colspec align='left'/>
+ *     <thead>
+ *       <row>
+ *       <entry>Character</entry>
+ *       <entry>Argument Type</entry>
+ *       <entry>Notes</entry>
+ *       </row>
+ *     </thead>
+ *     <tbody>
+ *       <row>
+ *         <entry><literal>b</literal></entry>
+ *         <entry>#gboolean</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>y</literal></entry>
+ *         <entry>#guchar</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>n</literal></entry>
+ *         <entry>#gint16</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>q</literal></entry>
+ *         <entry>#guint16</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>i</literal></entry>
+ *         <entry>#gint32</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>u</literal></entry>
+ *         <entry>#guint32</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>x</literal></entry>
+ *           <entry>#gint64</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>t</literal></entry>
+ *         <entry>#guint64</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>d</literal></entry>
+ *         <entry>#gdouble</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>s</literal></entry>
+ *         <entry>const #gchar *</entry>
+ *         <entry>must be non-%NULL</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>o</literal></entry>
+ *         <entry>const #gchar *</entry>
+ *         <entry>must be non-%NULL and a valid DBus object path</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>g</literal></entry>
+ *         <entry>const #gchar *</entry>
+ *         <entry>must be non-%NULL and a valid DBus signature string</entry>
+ *       </row>
+ *     </tbody>
+ *   </tgroup>
+ * </informaltable>
+ *
+ * If a 'v' character is encountered in @format_string then a
+ * (#EVariant *) is collected which must be non-%NULL and must point
+ * to a valid #EVariant instance.
+ *
+ * If an array type is encountered in @format_string, a
+ * (#EVariantBuilder *) is collected and has e_variant_builder_end()
+ * called on it.  The type of the array has no impact on argument
+ * collection but is checked against the type of the array and can be
+ * used to infer the type of an empty array.  As a special exception,
+ * if %NULL is given then an empty array of the given type will be
+ * used.
+ *
+ * If a maybe type is encountered in @format_string, then the expected
+ * arguments vary depending on the type.
+ *
+ * <informaltable>
+ *   <tgroup cols='3'>
+ *     <colspec align='center'/>
+ *     <colspec align='center'/>
+ *     <colspec align='left'/>
+ *     <thead>
+ *       <row>
+ *       <entry>Format string</entry>
+ *       <entry>Argument Type</entry>
+ *       <entry>Notes</entry>
+ *       </row>
+ *     </thead>
+ *     <tbody>
+ *       <row>
+ *         <entry>ms</entry>
+ *         <entry>const #gchar *</entry>
+ *         <entry>use %NULL for Nothing</entry>
+ *       </row>
+ *       <row>
+ *         <entry>mo</entry>
+ *         <entry>const #gchar *</entry>
+ *         <entry>use %NULL for Nothing</entry>
+ *       </row>
+ *       <row>
+ *         <entry>mg</entry>
+ *         <entry>const #gchar *</entry>
+ *         <entry>use %NULL for Nothing</entry>
+ *       </row>
+ *       <row>
+ *         <entry>mv</entry>
+ *         <entry>#EVariant *</entry>
+ *         <entry>use %NULL for Nothing</entry>
+ *       </row>
+ *       <row>
+ *         <entry>m *</entry>
+ *         <entry>#EVariant *</entry>
+ *         <entry>use %NULL for Nothing</entry>
+ *       </row>
+ *       <row>
+ *         <entry>otherwise</entry>
+ *         <entry>#gboolean</entry>
+ *         <entry>
+ *           use %FALSE for Nothing.  If %TRUE is given then the
+ *           arguments will be collected, after the #gboolean, exactly
+ *           as they would be if there were no 'm'.
+ *         </entry>
+ *       </row>
+ *     </tbody>
+ *   </tgroup>
+ * </informaltable>
+ *
+ * If a '@' character is encountered in @format_string, followed by a
+ * type string, then a (#EVariant *) is collected which must be
+ * non-%NULL and must point to a valid #EVariant instance.  This
+ * #EVariant is inserted directly at the given position.  The given
+ * #EVariant must match the provided format string.
+ *
+ * '*' means exactly the same as '@*'. 'r' means exactly the same as
+ * '@r'.  '?' means exactly the same as '@?'.
+ *
+ * The first character of the format string must not be '*' '?' '@' or
+ * 'r'; in essence, a new #EVariant must always be constructed by this
+ * function (and not merely passed through it unmodified).
+ *
+ * Please note that the syntax of the format string is very likely to
+ * be extended in the future.
+ **/
+EVariant *
+e_variant_new (const gchar *format_string,
+               ...)
+{
+  EVariant *value;
+  va_list ap;
+
+  g_assert (format_string != NULL);
+  g_assert (strchr ("*? r", format_string[0]) == NULL);
+
+  va_start (ap, format_string);
+  value = e_variant_new_va (NULL, format_string, NULL, &ap);
+  va_end (ap);
+
+  return value;
+}
+
+/**
+ * e_variant_new_va:
+ * @must_be_null: %NULL (for future expansion)
+ * @format_string: a string that is prefixed with a format string
+ * @endptr: location to store the end pointer, or %NULL
+ * @app: a pointer to a #va_list
+ * @returns: a new, usually floating, #EVariant
+ *
+ * This function is intended to be used by libraries based on
+ * #EVariant that want to provide e_variant_new()-like functionality
+ * to their users.
+ *
+ * The API is more general than e_variant_new() to allow a wider range
+ * of possible uses.
+
+ * @format_string must still point to a valid format string, but it only
+ * need to be nul-terminated if @endptr is %NULL.  If @endptr is
+ * non-%NULL then it is updated to point to the first character past the
+ * end of the format string.
+ *
+ * @app is a pointer to a #va_list.  The arguments, according to
+ * @format_string, are collected from this #va_list and the list is left
+ * pointing to the argument following the last.
+ *
+ * These two generalisations allow mixing of multiple calls to
+ * e_variant_new_va() and e_variant_get_va() within a single actual
+ * varargs call by the user.
+ *
+ * The return value will be floating if it was a newly created EVariant
+ * instance (for example, if the format string was "(ii)").  In the case
+ * that the format_string was '*', '?', 'r', or a format starting with
+ * '@' then the collected #EVariant pointer will be returned unmodified,
+ * without adding any additional references.
+ *
+ * In order to behave correctly in all cases it is necessary for the
+ * calling function to e_variant_ref_sink() the return result before
+ * returning control to the user that originally provided the pointer.
+ * At this point, the caller will have their own full reference to the
+ * result.  This can also be done by adding the result to a container,
+ * or by passing it to another e_variant_new() call.
+ **/
+EVariant *
+e_variant_new_va (gpointer      must_be_null,
+                  const gchar  *format_string,
+                  const gchar **endptr,
+                  va_list      *app)
+{
+  EVariant *value;
+
+  g_return_val_if_fail (format_string != NULL, NULL);
+  g_return_val_if_fail (must_be_null == NULL, NULL);
+  g_return_val_if_fail (app != NULL, NULL);
+
+  value = e_variant_valist_new (&format_string, app);
+
+  if (endptr != NULL)
+    *endptr = format_string;
+  else
+    g_assert (*format_string == '\0');
+
+  e_variant_flatten (value);
+
+  return value;
+}
+
+/**
+ * e_variant_get:
+ * @value: a #EVariant instance
+ * @format_string: a #EVariant format string
+ * @...: arguments, as per @format_string
+ *
+ * Deconstructs a #EVariant instance.
+ *
+ * Think of this function as an analogue to scanf().
+ *
+ * The arguments that are expected by this function are entirely
+ * determined by @format_string.  @format_string also restricts the
+ * permissible types of @value.  It is an error to give a value with
+ * an incompatible type.
+ *
+ * In the most simple case, @format_string is exactly equal to a
+ * definite #EVariantType type string and @value must have that type.
+ * All exceptions to this case are explicitly mentioned below.
+ *
+ * The arguments that this function collects are determined by
+ * scanning @format_string from start to end.  Brackets do not impact
+ * the collection of arguments (except when following 'a', 'm' or '@';
+ * see below).  Each other character that is encountered will result
+ * in an argument being collected.
+ *
+ * All arguments are pointer types.  Any pointer may be %NULL and in
+ * that case, collection will be supressed for that position (ie: you
+ * can use a %NULL pointer if you don't care about the value of a
+ * particular child of a complex value).
+ *
+ * Arguments for the base types are expected as follows:
+ * <informaltable>
+ *   <tgroup cols='2'>
+ *     <colspec align='center'/>
+ *     <colspec align='center'/>
+ *     <thead>
+ *       <row>
+ *         <entry>Character</entry>
+ *         <entry>Argument Type</entry>
+ *       </row>
+ *     </thead>
+ *     <tbody>
+ *       <row>
+ *         <entry><literal>b</literal></entry>
+ *         <entry>#gboolean*</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>y</literal></entry>
+ *         <entry>#guchar*</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>n</literal></entry>
+ *         <entry>#gint16*</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>q</literal></entry>
+ *         <entry>#guint16*</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>i</literal></entry>
+ *         <entry>#gint32*</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>u</literal></entry>
+ *         <entry>#guint32*</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>x</literal></entry>
+ *         <entry>#gint64*</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>t</literal></entry>
+ *         <entry>#guint64*</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>d</literal></entry>
+ *         <entry>#gdouble*</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>s</literal></entry>
+ *         <entry>const #gchar *</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>o</literal></entry>
+ *         <entry>const #gchar *</entry>
+ *       </row>
+ *       <row>
+ *         <entry><literal>g</literal></entry>
+ *         <entry>const #gchar *</entry>
+ *       </row>
+ *     </tbody>
+ *   </tgroup>
+ * </informaltable>
+ *
+ * If a 'v' character is encountered in @format_string then a
+ * (#EVariant **) is collected.  If this is non-%NULL then it is set
+ * to a new reference to a #EVariant.  You must call e_variant_unref()
+ * on it later.
+ *
+ * If an array type is encountered in @format_string, a
+ * (#EVariantIter *) is collected.  If this is non-%NULL then the
+ * #EVariantIter at which it points will be initialised for iterating
+ * over the array.  The array must be iterated (or the iter cancelled)
+ * in order to avoid leaking memory.
+ *
+ * If a maybe type is encountered in @format_string, then the expected
+ * arguments vary depending on the type.
+ *
+ * <informaltable>
+ *   <tgroup cols='3'>
+ *     <colspec align='center'/>
+ *     <colspec align='center'/>
+ *     <colspec align='left'/>
+ *     <thead>
+ *       <row>
+ *       <entry>Format string</entry>
+ *       <entry>Argument Type</entry>
+ *       <entry>Notes</entry>
+ *       </row>
+ *     </thead>
+ *     <tbody>
+ *       <row>
+ *         <entry>ms</entry>
+ *         <entry>const #gchar *</entry>
+ *         <entry>set to %NULL in case of Nothing</entry>
+ *       </row>
+ *       <row>
+ *         <entry>mo</entry>
+ *         <entry>const #gchar *</entry>
+ *         <entry>set to %NULL in case of Nothing</entry>
+ *       </row>
+ *       <row>
+ *         <entry>mg</entry>
+ *         <entry>const #gchar *</entry>
+ *         <entry>set to %NULL in case of Nothing</entry>
+ *       </row>
+ *       <row>
+ *         <entry>mv</entry>
+ *         <entry>#EVariant *</entry>
+ *         <entry>set to %NULL in case of Nothing</entry>
+ *       </row>
+ *       <row>
+ *         <entry>m *</entry>
+ *         <entry>#EVariant *</entry>
+ *         <entry>set to %NULL in case of Nothing</entry>
+ *       </row>
+ *       <row>
+ *         <entry>otherwise</entry>
+ *         <entry>#gboolean*</entry>
+ *         <entry>
+ *           If %NULL is given then the entire value is ignored.
+ *           Else, the #gboolean is set to %TRUE to indicate that the
+ *           maybe is non-Nothing or %FALSE to indicate Nothing.  In
+ *           the %TRUE case, the normal arguments (as per the format
+ *           string) are collected and handled normally.  In the
+ *           %FALSE case, the normal arguments are collected and
+ *           ignored.  This allows the same set of arguments to be
+ *           given without knowing the content of the value ahead of
+ *           time.
+ *         </entry>
+ *       </row>
+ *     </tbody>
+ *   </tgroup>
+ * </informaltable>
+ *
+ * If a '@' character is encountered in @format_string, followed by a
+ * type string, then a (#EVariant **) is collected.  If this is
+ * non-%NULL then it is set to a new reference to a #EVariant the
+ * corresponds to the value at the current position.  You must call
+ * e_variant_unref() on it later.  The returned value will match the
+ * type string provided.
+ *
+ * '*' means exactly the same as '@*'. 'r' means exactly the same as
+ * '@r'.  '?' means exactly the same as '@?'.
+ *
+ * Please note that the syntax of the format string is very likely to
+ * be extended in the future.
+ **/
+void
+e_variant_get (EVariant    *value,
+               const gchar *format_string,
+               ...)
+{
+  va_list ap;
+
+  va_start (ap, format_string);
+  e_variant_get_va (value, NULL, format_string, NULL, &ap);
+  va_end (ap);
+}
+
+/**
+ * e_variant_get_va:
+ * @value: a #EVariant
+ * @must_be_null: %NULL (for future expansion)
+ * @format_string: a string that is prefixed with a format string
+ * @endptr: location to store the end pointer, or %NULL
+ * @app: a pointer to a #va_list
+ *
+ * This function is intended to be used by libraries based on #EVariant
+ * that want to provide e_variant_get()-like functionality to their
+ * users.
+ *
+ * The API is more general than e_variant_get() to allow a wider range
+ * of possible uses.
+ *
+ * @format_string must still point to a valid format string, but it only
+ * need to be nul-terminated if @endptr is %NULL.  If @endptr is
+ * non-%NULL then it is updated to point to the first character past the
+ * end of the format string.
+ *
+ * @app is a pointer to a #va_list.  The arguments, according to
+ * @format_string, are collected from this #va_list and the list is left
+ * pointing to the argument following the last.
+ *
+ * These two generalisations allow mixing of multiple calls to
+ * e_variant_new_va() and e_variant_get_va() within a single actual
+ * varargs call by the user.
+ **/
+void
+e_variant_get_va (EVariant     *value,
+                  gpointer      must_be_null,
+                  const gchar  *format_string,
+                  const gchar **endptr,
+                  va_list      *app)
+{
+  g_return_if_fail (format_string != NULL);
+  g_return_if_fail (must_be_null == NULL);
+  g_return_if_fail (value != NULL);
+  g_return_if_fail (app != NULL);
+
+  {
+    EVariantType *type;
+
+    type = e_variant_format_string_scan_type (format_string, NULL, endptr);
+    g_return_if_fail (type != NULL);
+    g_return_if_fail (e_variant_has_type (value, type));
+    e_variant_type_free (type);
+  }
+
+  e_variant_flatten (value);
+  e_variant_valist_get (value, FALSE, &format_string, app);
+}
+
+/**
+ * e_variant_iter_next:
+ * @iter: a #EVariantIter
+ * @format_string: a format string
+ * @...: arguments, as per @format_string
+ * @returns: %TRUE if a child was fetched or %FALSE if not
+ *
+ * Retreives the next child value from @iter and deconstructs it
+ * according to @format_string.  This call is sort of like calling
+ * e_variant_iter_next() and e_variant_get().
+ *
+ * This function does something else, though: on all but the first
+ * call (including on the last call, which returns %FALSE) the values
+ * allocated by the previous call will be freed.  This allows you to
+ * iterate without ever freeing anything yourself.  In the case of
+ * #EVariant * arguments, they are unref'd and in the case of
+ * #EVariantIter arguments, they are cancelled.
+ *
+ * Note that strings are not freed since (as with e_variant_get())
+ * they are constant pointers to internal #EVariant data.
+ *
+ * This function might be used as follows:
+ *
+ * <programlisting>
+ * {
+ *   const gchar *key, *value;
+ *   EVariantIter iter;
+ *   ...
+ *
+ *   while (e_variant_iter_next (iter, "{ss}", &key, &value))
+ *     printf ("dict['%s'] = '%s'\n", key, value);
+ * }
+ * </programlisting>
+ **/
+gboolean
+e_variant_iter_next (EVariantIter *iter,
+                     const gchar  *format_string,
+                     ...)
+{
+  gboolean free_args;
+  EVariant *next;
+  va_list ap;
+
+  free_args = e_variant_iter_should_free (iter);
+  next = e_variant_iter_next_value (iter);
+  /* g_assert (free_args || next != NULL);
+   * XXX this fails on empty iters */
+
+  if (next)
+    e_variant_flatten (next);
+
+  va_start (ap, format_string);
+  e_variant_valist_get (next, free_args, &format_string, &ap);
+  g_assert (*format_string == '\0');
+  va_end (ap);
+
+  return next != NULL;
+}
+
+/**
+ * e_variant_builder_add:
+ * @builder: a #EVariantBuilder
+ * @format_string: a #EVariant varargs format string
+ * @...: arguments, as per @format_string
+ *
+ * Adds to a #EVariantBuilder.
+ *
+ * This call is a convenience wrapper that is exactly equivalent to
+ * calling e_variant_new() followed by e_variant_builder_add_value().
+ *
+ * This function might be used as follows:
+ *
+ * <programlisting>
+ * EVariant *
+ * make_pointless_dictionary (void)
+ * {
+ *   EVariantBuilder *builder;
+ *   int i;
+ *
+ *   builder = e_variant_builder_new (E_VARIANT_TYPE_CLASS_ARRAY,
+ *                                    NULL);
+ *   for (i = 0; i < 16; i++)
+ *     {
+ *       char buf[3];
+ *
+ *       sprintf (buf, "%d", i);
+ *       e_variant_builder_add (builder, "{is}", i, buf);
+ *     }
+ *
+ *   return e_variant_builder_end (builder);
+ * }
+ * </programlisting>
+ **/
+void
+e_variant_builder_add (EVariantBuilder *builder,
+                       const gchar     *format_string,
+                       ...)
+{
+  EVariant *variant;
+  va_list ap;
+
+  va_start (ap, format_string);
+  variant = e_variant_new_va (NULL, format_string, NULL, &ap);
+  va_end (ap);
+
+  e_variant_builder_add_value (builder, variant);
+}
+
+/**
+ * e_variant_get_child:
+ * @value: a container #EVariant
+ * @index: the index of the child to deconstruct
+ * @format_string: a #EVariant format string
+ * @...: arguments, as per @format_string
+ *
+ * Reads a child item out of a container #EVariant instance and
+ * deconstructs it according to @format_string.  This call is
+ * essentially a combination of e_variant_get_child_value() and
+ * e_variant_get().
+ **/
+void
+e_variant_get_child (EVariant    *value,
+                     gint         index,
+                     const gchar *format_string,
+                     ...)
+{
+  EVariant *child;
+  va_list ap;
+
+  e_variant_flatten (value);
+  child = e_variant_get_child_value (value, index);
+
+  va_start (ap, format_string);
+  e_variant_get_va (child, NULL, format_string, NULL, &ap);
+  va_end (ap);
+
+  e_variant_unref (child);
+}
+
+/**
+ * e_variant_lookup:
+ * @dictionary: a #EVariant dictionary, keyed by strings
+ * @key: a string to lookup in the dictionary
+ * @format_string: a #EVariant format string, or %NULL
+ * @...: arguments, as per @format_string
+ * @returns: %TRUE if @key was found, else %FALSE
+ *
+ * Looks up @key in @dictionary and deconstructs it according to
+ * @format_string.  This call is essentially a combination of
+ * e_variant_lookup_value() and e_variant_get().
+ *
+ * If @key is not found, then no deconstruction occurs (ie: the argument
+ * list is left untouched) and %FALSE is returned.  If @key is found
+ * then %TRUE is returned.
+ *
+ * As a special case, if @format_string is %NULL then the lookup occurs
+ * but no deconstruction is preformed.  This is useful for checking (via
+ * the return value) if a key is in the dictionary or not.
+ **/
+gboolean
+e_variant_lookup (EVariant    *dictionary,
+                  const gchar *key,
+                  const gchar *format_string,
+                  ...)
+{
+  EVariant *child;
+  va_list ap;
+
+  if (format_string)
+    e_variant_flatten (dictionary);
+
+  child = e_variant_lookup_value (dictionary, key);
+
+  if (child == NULL)
+    return FALSE;
+
+  if (format_string)
+    {
+      va_start (ap, format_string);
+      e_variant_get_va (child, NULL, format_string, NULL, &ap);
+      va_end (ap);
+    }
+
+  e_variant_unref (child);
+
+  return TRUE;
+}
diff --git a/libedbus/gvariant.h b/libedbus/gvariant.h
new file mode 100644
index 0000000..abde203
--- /dev/null
+++ b/libedbus/gvariant.h
@@ -0,0 +1,307 @@
+/*
+ * Copyright © 2007, 2008 Ryan Lortie
+ * Copyright © 2009 Codethink Limited
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __E_VARIANT_H__
+#define __E_VARIANT_H__
+
+#include "gvarianttype.h"
+#include <glib/gstring.h>
+#include <glib/gmarkup.h>
+#include <glib/gerror.h>
+#include <glib-object.h>
+#include <stdarg.h>
+
+typedef struct _EVariant        EVariant;
+typedef struct _EVariantIter    EVariantIter;
+typedef struct _EVariantBuilder EVariantBuilder;
+typedef enum   _EVariantClass   EVariantClass;
+
+/* compatibility bits yanked from other pieces of glib */
+#ifndef GSIZE_FROM_LE
+#  define GSIZE_FROM_LE(val)      (GSIZE_TO_LE (val))
+#  define GSSIZE_FROM_LE(val)     (GSSIZE_TO_LE (val))
+// tad of a hack for now, but safe I hope
+#  if GLIB_SIZEOF_SIZE_T == 4
+#    define GSIZE_TO_LE(val)   ((gsize) GUINT32_TO_LE (val))
+#  elif GLIB_SIZEOF_SIZE_T == 8
+#    define GSIZE_TO_LE(val)   ((gsize) GUINT64_TO_LE (val))
+#  else
+#    error "Weirdo architecture - needs an updated glib"
+#  endif
+#endif
+#ifndef E_TYPE_VARIANT
+#  define        E_TYPE_VARIANT (e_variant_get_gtype ())
+GType   e_variant_get_gtype     (void)  G_GNUC_CONST;
+#endif
+
+enum _EVariantClass
+{
+  E_VARIANT_CLASS_BOOLEAN       = 'b',
+  E_VARIANT_CLASS_BYTE          = 'y',
+  E_VARIANT_CLASS_INT16         = 'n',
+  E_VARIANT_CLASS_UINT16        = 'q',
+  E_VARIANT_CLASS_INT32         = 'i',
+  E_VARIANT_CLASS_UINT32        = 'u',
+  E_VARIANT_CLASS_INT64         = 'x',
+  E_VARIANT_CLASS_UINT64        = 't',
+  E_VARIANT_CLASS_HANDLE        = 'h',
+  E_VARIANT_CLASS_DOUBLE        = 'd',
+  E_VARIANT_CLASS_STRING        = 's',
+  E_VARIANT_CLASS_OBJECT_PATH   = 'o',
+  E_VARIANT_CLASS_SIGNATURE     = 'g',
+  E_VARIANT_CLASS_VARIANT       = 'v',
+  E_VARIANT_CLASS_MAYBE         = 'm',
+  E_VARIANT_CLASS_ARRAY         = 'a',
+  E_VARIANT_CLASS_TUPLE         = '(',
+  E_VARIANT_CLASS_DICT_ENTRY    = '{'
+};
+
+struct _EVariantIter
+{
+  gpointer priv[8];
+};
+
+G_BEGIN_DECLS
+
+EVariant *                      e_variant_ref                           (EVariant             *value);
+EVariant *                      e_variant_ref_sink                      (EVariant             *value);
+void                            e_variant_unref                         (EVariant             *value);
+void                            e_variant_flatten                       (EVariant             *value);
+
+const EVariantType *            e_variant_get_type                      (EVariant             *value);
+const gchar *                   e_variant_get_type_string               (EVariant             *value);
+gboolean                        e_variant_is_basic                      (EVariant             *value);
+gboolean                        e_variant_is_container                  (EVariant             *value);
+gboolean                        e_variant_has_type                      (EVariant             *value,
+                                                                         const EVariantType   *pattern);
+
+/* varargs construct/deconstruct */
+EVariant *                      e_variant_new                           (const gchar          *format_string,
+                                                                         ...);
+void                            e_variant_get                           (EVariant             *value,
+                                                                         const gchar          *format_string,
+                                                                         ...);
+
+gboolean                        e_variant_format_string_scan            (const gchar          *string,
+                                                                         const gchar          *limit,
+                                                                         const gchar         **endptr);
+EVariantType *                  e_variant_format_string_scan_type       (const gchar          *string,
+                                                                         const gchar          *limit,
+                                                                         const gchar         **endptr);
+EVariant *                      e_variant_new_va                        (gpointer              must_be_null,
+                                                                         const gchar          *format_string,
+                                                                         const gchar         **endptr,
+                                                                         va_list              *app);
+void                            e_variant_get_va                        (EVariant             *value,
+                                                                         gpointer              must_be_null,
+                                                                         const gchar          *format_string,
+                                                                         const gchar         **endptr,
+                                                                         va_list              *app);
+
+/* constructors */
+EVariant *                      e_variant_new_boolean                   (gboolean              boolean);
+EVariant *                      e_variant_new_byte                      (guint8                byte);
+EVariant *                      e_variant_new_uint16                    (guint16               uint16);
+EVariant *                      e_variant_new_int16                     (gint16                int16);
+EVariant *                      e_variant_new_uint32                    (guint32               uint32);
+EVariant *                      e_variant_new_int32                     (gint32                int32);
+EVariant *                      e_variant_new_uint64                    (guint64               uint64);
+EVariant *                      e_variant_new_int64                     (gint64                int64);
+EVariant *                      e_variant_new_double                    (gdouble               floating);
+EVariant *                      e_variant_new_string                    (const gchar          *string);
+EVariant *                      e_variant_new_object_path               (const gchar          *string);
+gboolean                        e_variant_is_object_path                (const gchar          *string);
+EVariant *                      e_variant_new_signature                 (const gchar          *string);
+gboolean                        e_variant_is_signature                  (const gchar          *string);
+EVariant *                      e_variant_new_variant                   (EVariant             *value);
+EVariant *                      e_variant_new_handle                    (gint32                handle);
+EVariant *                      e_variant_new_strv                      (const gchar * const  *strv,
+                                                                         gint                  length);
+
+/* deconstructors */
+gboolean                        e_variant_get_boolean                   (EVariant             *value);
+guint8                          e_variant_get_byte                      (EVariant             *value);
+guint16                         e_variant_get_uint16                    (EVariant             *value);
+gint16                          e_variant_get_int16                     (EVariant             *value);
+guint32                         e_variant_get_uint32                    (EVariant             *value);
+gint32                          e_variant_get_int32                     (EVariant             *value);
+guint64                         e_variant_get_uint64                    (EVariant             *value);
+gint64                          e_variant_get_int64                     (EVariant             *value);
+gdouble                         e_variant_get_double                    (EVariant             *value);
+const gchar *                   e_variant_get_string                    (EVariant             *value,
+                                                                         gsize                *length);
+gchar *                         e_variant_dup_string                    (EVariant             *value,
+                                                                         gsize                *length);
+const gchar **                  e_variant_get_strv                      (EVariant             *value,
+                                                                         gint                 *length);
+gchar **                        e_variant_dup_strv                      (EVariant             *value,
+                                                                         gint                 *length);
+EVariant *                      e_variant_get_variant                   (EVariant             *value);
+gint32                          e_variant_get_handle                    (EVariant             *value);
+gconstpointer                   e_variant_get_fixed                     (EVariant             *value,
+                                                                         gsize                 size);
+gconstpointer                   e_variant_get_fixed_array               (EVariant             *value,
+                                                                         gsize                 elem_size,
+                                                                         gsize                *length);
+gsize                           e_variant_n_children                    (EVariant             *value);
+EVariant *                      e_variant_get_child_value               (EVariant             *value,
+                                                                         gsize                 index);
+void                            e_variant_get_child                     (EVariant             *value,
+                                                                         gint                  index,
+                                                                         const gchar          *format_string,
+                                                                         ...);
+EVariant *                      e_variant_lookup_value                  (EVariant             *dictionary,
+                                                                         const gchar          *key);
+gboolean                        e_variant_lookup                        (EVariant             *dictionary,
+                                                                         const gchar          *key,
+                                                                         const gchar          *format_string,
+                                                                         ...);
+
+/* EVariantIter */
+gsize                           e_variant_iter_init                     (EVariantIter         *iter,
+                                                                         EVariant             *value);
+EVariant *                      e_variant_iter_next_value               (EVariantIter         *iter);
+void                            e_variant_iter_cancel                   (EVariantIter         *iter);
+gboolean                        e_variant_iter_was_cancelled            (EVariantIter         *iter);
+gboolean                        e_variant_iter_next                     (EVariantIter         *iter,
+                                                                         const gchar          *format_string,
+                                                                         ...);
+
+/* EVariantBuilder */
+void                            e_variant_builder_add_value             (EVariantBuilder      *builder,
+                                                                         EVariant             *value);
+void                            e_variant_builder_add                   (EVariantBuilder      *builder,
+                                                                         const gchar          *format_string,
+                                                                         ...);
+EVariantBuilder *               e_variant_builder_open                  (EVariantBuilder      *parent,
+                                                                         const EVariantType   *type);
+EVariantBuilder *               e_variant_builder_close                 (EVariantBuilder      *child);
+gboolean                        e_variant_builder_check_add             (EVariantBuilder      *builder,
+                                                                         const EVariantType   *type,
+                                                                         GError              **error);
+gboolean                        e_variant_builder_check_end             (EVariantBuilder      *builder,
+                                                                         GError              **error);
+EVariantBuilder *               e_variant_builder_new                   (const EVariantType   *type);
+EVariant *                      e_variant_builder_end                   (EVariantBuilder      *builder);
+void                            e_variant_builder_cancel                (EVariantBuilder      *builder);
+
+#define E_VARIANT_BUILDER_ERROR \
+    (g_quark_from_static_string ("g-variant-builder-error-quark"))
+
+typedef enum
+{
+  E_VARIANT_BUILDER_ERROR_TOO_MANY,
+  E_VARIANT_BUILDER_ERROR_TOO_FEW,
+  E_VARIANT_BUILDER_ERROR_INFER,
+  E_VARIANT_BUILDER_ERROR_TYPE
+} EVariantBuilderError;
+
+/* text printing/parsing */
+typedef struct
+{
+  const gchar *start;
+  const gchar *end;
+  gchar *error;
+} EVariantParseError;
+
+gchar *                         e_variant_print                         (EVariant             *value,
+                                                                         gboolean              type_annotate);
+GString *                       e_variant_print_string                  (EVariant             *value,
+                                                                         GString              *string,
+                                                                         gboolean              type_annotate);
+EVariant *                      e_variant_parse                         (const gchar          *text,
+                                                                         gint                  text_length,
+                                                                         const EVariantType   *type,
+                                                                         GError              **error);
+EVariant *                      e_variant_parse_full                    (const gchar          *text,
+                                                                         const gchar          *limit,
+                                                                         const gchar         **endptr,
+                                                                         const EVariantType   *type,
+                                                                         EVariantParseError   *error);
+EVariant *                      e_variant_new_parsed                    (const gchar          *format,
+                                                                         ...);
+EVariant *                      e_variant_new_parsed_va                 (const gchar          *format,
+                                                                         va_list              *app);
+
+/* markup printing/parsing */
+gchar *                         e_variant_markup_print                  (EVariant             *value,
+                                                                         gboolean              newlines,
+                                                                         gint                  indentation,
+                                                                         gint                  tabstop);
+GString *                       e_variant_markup_print_string           (EVariant             *value,
+                                                                         GString              *string,
+                                                                         gboolean              newlines,
+                                                                         gint                  indentation,
+                                                                         gint                  tabstop);
+void                            e_variant_markup_subparser_start        (GMarkupParseContext  *context,
+                                                                         const EVariantType   *type);
+EVariant *                      e_variant_markup_subparser_end          (GMarkupParseContext  *context,
+                                                                         GError              **error);
+GMarkupParseContext *           e_variant_markup_parse_context_new      (GMarkupParseFlags     flags,
+                                                                         const EVariantType   *type);
+EVariant *                      e_variant_markup_parse_context_end      (GMarkupParseContext  *context,
+                                                                         GError              **error);
+EVariant *                      e_variant_markup_parse                  (const gchar          *text,
+                                                                         gssize                text_len,
+                                                                         const EVariantType   *type,
+                                                                         GError              **error);
+
+/* load/store serialised format */
+typedef enum
+{
+  E_VARIANT_LITTLE_ENDIAN       = G_LITTLE_ENDIAN,
+  E_VARIANT_BIG_ENDIAN          = G_BIG_ENDIAN,
+  E_VARIANT_TRUSTED             = 0x00010000,
+  E_VARIANT_LAZY_BYTESWAP       = 0x00020000,
+} EVariantFlags;
+
+EVariant *                      e_variant_load                          (const EVariantType  *type,
+                                                                         gconstpointer        data,
+                                                                         gsize                size,
+                                                                         EVariantFlags        flags);
+EVariant *                      e_variant_from_slice                    (const EVariantType  *type,
+                                                                         gpointer             slice,
+                                                                         gsize                size,
+                                                                         EVariantFlags        flags);
+EVariant *                      e_variant_from_data                     (const EVariantType  *type,
+                                                                         gconstpointer        data,
+                                                                         gsize                size,
+                                                                         EVariantFlags        flags,
+                                                                         GDestroyNotify       notify,
+                                                                         gpointer             user_data);
+EVariant *                      e_variant_from_file                     (const EVariantType  *type,
+                                                                         const gchar         *filename,
+                                                                         EVariantFlags        flags,
+                                                                         GError              **error);
+
+void                            e_variant_store                         (EVariant            *value,
+                                                                         gpointer             data);
+gconstpointer                   e_variant_get_data                      (EVariant            *value);
+gsize                           e_variant_get_size                      (EVariant            *value);
+
+#define E_VARIANT_JUST ((gboolean *) "truetrue")
+
+EVariantClass                   e_variant_classify                      (EVariant            *value);
+
+#define e_variant_get_type_class e_variant_classify
+
+G_END_DECLS
+
+#endif /* __E_VARIANT_H__ */
diff --git a/libedbus/gvarianttype.c b/libedbus/gvarianttype.c
new file mode 100644
index 0000000..3df9b88
--- /dev/null
+++ b/libedbus/gvarianttype.c
@@ -0,0 +1,1470 @@
+/*
+ * Copyright © 2007, 2008 Ryan Lortie
+ * Copyright © 2009, 2010 Codethink Limited
+ *
+ * This library 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 licence, or (at your option) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#include "gvarianttype.h"
+
+#include <glib/gtestutils.h>
+#include <glib/gstrfuncs.h>
+
+#include <string.h>
+
+/**
+ * SECTION: gvarianttype
+ * @title: EVariantType
+ * @short_description: introduction to the EVariant type system
+ * @see_also: #EVariantType, #EVariant
+ *
+ * This section introduces the EVariant type system.  It is based, in
+ * large part, on the DBus type system, with two major changes and some minor
+ * lifting of restrictions.  The <ulink
+ * url='http://dbus.freedesktop.org/doc/dbus-specification.html'>DBus
+ * specification</ulink>, therefore, provides a significant amount of
+ * information that is useful when working with EVariant.
+ *
+ * The first major change with respect to the DBus type system is the
+ * introduction of maybe (or "nullable") types.  Any type in EVariant can be
+ * converted to a maybe type, in which case, "nothing" (or "null") becomes a
+ * valid value.  Maybe types have been added by introducing the
+ * character "<literal>m</literal>" to type strings.
+ *
+ * The second major change is that the EVariant type system supports the
+ * concept of "indefinite types" -- types that are less specific than
+ * the normal types found in DBus.  For example, it is possible to speak
+ * of "an array of any type" in EVariant, where the DBus type system
+ * would require you to speak of "an array of integers" or "an array of
+ * strings".  Indefinite types have been added by introducing the
+ * characters "<literal>*</literal>", "<literal>?</literal>" and
+ * "<literal>r</literal>" to type strings.
+ *
+ * Finally, all arbitrary restrictions relating to the complexity of
+ * types are lifted along with the restriction that dictionary entries
+ * may only appear nested inside of arrays.
+ *
+ * Just as in DBus, EVariant types are described with strings ("type
+ * strings").  Subject to the differences mentioned above, these strings
+ * are of the same form as those found in DBus.  Note, however: DBus
+ * always works in terms of messages and therefore individual type
+ * strings appear nowhere in its interface.  Instead, "signatures"
+ * are a concatenation of the strings of the type of each argument in a
+ * message.  EVariant deals with single values directly so EVariant type
+ * strings always describe the type of exactly one value.  This means
+ * that a DBus signature string is generally not a valid EVariant type
+ * string -- except in the case that it is the signature of a message
+ * containing exactly one argument.
+ *
+ * An indefinite type is similar in spirit to what may be called an
+ * abstract type in other type systems.  No value can exist that has an
+ * indefinite type as its type, but values can exist that have types
+ * that are subtypes of indefinite types.  That is to say,
+ * e_variant_get_type() will never return an indefinite type, but
+ * calling e_variant_is_a() with an indefinite type may return %TRUE.
+ * For example, you can not have a value that represents "an array of no
+ * particular type", but you can have an "array of integers" which
+ * certainly matches the type of "an array of no particular type", since
+ * "array of integers" is a subtype of "array of no particular type".
+ *
+ * This is similar to how instances of abstract classes may not
+ * directly exist in other type systems, but instances of their
+ * non-abstract subtypes may.  For example, in GTK, no object that has
+ * the type of #GtkBin can exist (since #GtkBin is an abstract class),
+ * but a #GtkWindow can certainly be instantiated, and you would say
+ * that the #GtkWindow is a #GtkBin (since #GtkWindow is a subclass of
+ * #GtkBin).
+ *
+ * A detailed description of EVariant type strings is given here:
+ *
+ * <refsect2 id='gvariant-typestrings'>
+ *  <title>EVariant Type Strings</title>
+ *  <para>
+ *   A EVariant type string can be any of the following:
+ *  </para>
+ *  <itemizedlist>
+ *   <listitem>
+ *    <para>
+ *     any basic type string (listed below)
+ *    </para>
+ *   </listitem>
+ *   <listitem>
+ *    <para>
+ *     "<literal>v</literal>", "<literal>r</literal>" or
+ *     "<literal>*</literal>"
+ *    </para>
+ *   </listitem>
+ *   <listitem>
+ *    <para>
+ *     one of the characters '<literal>a</literal>' or
+ *     '<literal>m</literal>', followed by another type string
+ *    </para>
+ *   </listitem>
+ *   <listitem>
+ *    <para>
+ *     the character '<literal>(</literal>', followed by a concatenation
+ *     of zero or more other type strings, followed by the character
+ *     '<literal>)</literal>'
+ *    </para>
+ *   </listitem>
+ *   <listitem>
+ *    <para>
+ *     the character '<literal>{</literal>', followed by a basic type
+ *     string (see below), followed by another type string, followed by
+ *     the character '<literal>}</literal>'
+ *    </para>
+ *   </listitem>
+ *  </itemizedlist>
+ *  <para>
+ *   A basic type string describes a basic type (as per
+ *   e_variant_type_is_basic()) and is always a single
+ *   character in length.  The valid basic type strings are
+ *   "<literal>b</literal>", "<literal>y</literal>",
+ *   "<literal>n</literal>", "<literal>q</literal>",
+ *   "<literal>i</literal>", "<literal>u</literal>",
+ *   "<literal>x</literal>", "<literal>t</literal>",
+ *   "<literal>h</literal>", "<literal>d</literal>",
+ *   "<literal>s</literal>", "<literal>o</literal>",
+ *   "<literal>g</literal>" and "<literal>?</literal>".
+ *  </para>
+ *  <para>
+ *   The above definition is recursive to arbitrary depth.
+ *   "<literal>aaaaai</literal>" and "<literal>(ui(nq((y)))s)</literal>"
+ *   are both valid type strings, as is
+ *   "<literal>a(aa(ui)(qna{ya(yd)}))</literal>".
+ *  </para>
+ *  <para>
+ *   The meaning of each of the characters is as follows:
+ *  </para>
+ *  <informaltable>
+ *   <tgroup cols='2'>
+ *    <tbody>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <emphasis role='strong'>Character</emphasis>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        <emphasis role='strong'>Meaning</emphasis>
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>b</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        the type string of %E_VARIANT_TYPE_BOOLEAN; a boolean value.
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>y</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        the type string of %E_VARIANT_TYPE_BYTE; a byte.
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>n</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        the type string of %E_VARIANT_TYPE_INT16; a signed 16 bit
+ *        integer.
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>q</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        the type string of %E_VARIANT_TYPE_UINT16; an unsigned 16 bit
+ *        integer.
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>i</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        the type string of %E_VARIANT_TYPE_INT32; a signed 32 bit
+ *        integer.
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>u</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        the type string of %E_VARIANT_TYPE_UINT32; an unsigned 32 bit
+ *        integer.
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>x</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        the type string of %E_VARIANT_TYPE_INT64; a signed 64 bit
+ *        integer.
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>t</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        the type string of %E_VARIANT_TYPE_UINT64; an unsigned 64 bit
+ *        integer.
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>h</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        the type string of %E_VARIANT_TYPE_HANDLE; a signed 32 bit
+ *        value that, by convention, is used as an index into an array
+ *        of file descriptors that are sent alongside a DBus message.
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>d</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        the type string of %E_VARIANT_TYPE_DOUBLE; a double precision
+ *        floating point value.
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>s</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        the type string of %E_VARIANT_TYPE_STRING; a string.
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>o</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        the type string of %E_VARIANT_TYPE_OBJECT_PATH; a string in
+ *        the form of a DBus object path.
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>g</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        the type string of %E_VARIANT_TYPE_STRING; a string in the
+ *        form of a DBus type signature.
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>?</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        the type string of %E_VARIANT_TYPE_BASIC; an indefinite type
+ *        that is a supertype of any of the basic types.
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>v</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        the type string of %E_VARIANT_TYPE_VARIANT; a container type
+ *        that contain any other type of value.
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>a</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        used as a prefix on another type string to mean an array of
+ *        that type; the type string "<literal>ai</literal>", for
+ *        example, is the type of an array of 32 bit signed integers.
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>m</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        used as a prefix on another type string to mean a "maybe", or
+ *        "nullable", version of that type; the type string
+ *        "<literal>ms</literal>", for example, is the type of a value
+ *        that maybe contains a string, or maybe contains nothing.
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>()</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        used to enclose zero or more other concatenated type strings
+ *        to create a tuple type; the type string
+ *        "<literal>(is)</literal>", for example, is the type of a pair
+ *        of an integer and a string.
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>r</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        the type string of %E_VARIANT_TYPE_TUPLE; an indefinite type
+ *        that is a supertype of any tuple type, regardless of the
+ *        number of items.
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>{}</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        used to enclose a basic type string concatenated with another
+ *        type string to create a dictionary entry type, which usually
+ *        appears inside of an array to form a dictionary; the type
+ *        string "<literal>a{sd}</literal>", for example, is the type of
+ *        a dictionary that maps strings to double precision floating
+ *        point values.
+ *       </para>
+ *       <para>
+ *        The first type (the basic type) is the key type and the second
+ *        type is the value type.  The reason that the first type is
+ *        restricted to being a basic type is so that it can easily be
+ *        hashed.
+ *       </para>
+ *      </entry>
+ *     </row>
+ *     <row>
+ *      <entry>
+ *       <para>
+ *        <literal>*</literal>
+ *       </para>
+ *      </entry>
+ *      <entry>
+ *       <para>
+ *        the type string of %E_VARIANT_TYPE_ANY; the indefinite type
+ *        that is a supertype of all types.  Note that, as with all type
+ *        strings, this character represents exactly one type.  It
+ *        cannot be used inside of tuples to mean "any number of items".
+ *       </para>
+ *      </entry>
+ *     </row>
+ *    </tbody>
+ *   </tgroup>
+ *  </informaltable>
+ *  <para>
+ *   Any type string of a container that contains an indefinite type is,
+ *   itself, an indefinite type.  For example, the type string
+ *   "<literal>a*</literal>" (corresponding to %E_VARIANT_TYPE_ARRAY) is
+ *   an indefinite type that is a supertype of every array type.
+ *   "<literal>(*s)</literal>" is a supertype of all tuples that
+ *   contain exactly two items where the second item is a string.
+ *  </para>
+ *  <para>
+ *   "<literal>a{?*}</literal>" is an indefinite type that is a
+ *   supertype of all arrays containing dictionary entries where the key
+ *   is any basic type and the value is any type at all.  This is, by
+ *   definition, a dictionary, so this type string corresponds to
+ *   %E_VARIANT_TYPE_DICTIONARY.  Note that, due to the restriction that
+ *   the key of a dictionary entry must be a basic type,
+ *   "<literal>{**}</literal>" is not a valid type string.
+ *  </para>
+ * </refsect2>
+ */
+
+
+static gboolean
+e_variant_type_check (const EVariantType *type)
+{
+  const gchar *type_string;
+
+  if (type == NULL)
+    return FALSE;
+
+  type_string = (const gchar *) type;
+#ifndef G_DISABLE_CHECKS
+  return e_variant_type_string_scan (type_string, NULL, NULL);
+#else
+  return TRUE;
+#endif
+}
+
+/**
+ * e_variant_type_string_scan:
+ * @string: a pointer to any string
+ * @limit: the end of @string, or %NULL
+ * @endptr: location to store the end pointer, or %NULL
+ * @returns: %TRUE if a valid type string was found
+ *
+ * Scan for a single complete and valid EVariant type string in @string.
+ * The memory pointed to by @limit (or bytes beyond it) is never
+ * accessed.
+ *
+ * If a valid type string is found, @endptr is updated to point to the
+ * first character past the end of the string that was found and %TRUE
+ * is returned.
+ *
+ * If there is no valid type string starting at @string, or if the type
+ * string does not end before @limit then %FALSE is returned.
+ *
+ * For the simple case of checking if a string is a valid type string,
+ * see e_variant_type_string_is_valid().
+ *
+ * Since: 2.24
+ **/
+gboolean
+e_variant_type_string_scan (const gchar  *string,
+                            const gchar  *limit,
+                            const gchar **endptr)
+{
+  g_return_val_if_fail (string != NULL, FALSE);
+
+  if (string == limit || *string == '\0')
+    return FALSE;
+
+  switch (*string++)
+    {
+    case '(':
+      while (string == limit || *string != ')')
+        if (!e_variant_type_string_scan (string, limit, &string))
+          return FALSE;
+
+      string++;
+      break;
+
+    case '{':
+      if (string == limit || *string == '\0' ||                    /* { */
+          !strchr ("bynqihuxtdsog?", *string++) ||                 /* key */
+          !e_variant_type_string_scan (string, limit, &string) ||  /* value */
+          string == limit || *string++ != '}')                     /* } */
+        return FALSE;
+
+      break;
+
+    case 'm': case 'a':
+      return e_variant_type_string_scan (string, limit, endptr);
+
+    case 'b': case 'y': case 'n': case 'q': case 'i': case 'u':
+    case 'x': case 't': case 'd': case 's': case 'o': case 'g':
+    case 'v': case 'r': case '*': case '?': case 'h':
+      break;
+
+    default:
+      return FALSE;
+    }
+
+  if (endptr != NULL)
+    *endptr = string;
+
+  return TRUE;
+}
+
+/**
+ * e_variant_type_string_is_valid:
+ * @type_string: a pointer to any string
+ * @returns: %TRUE if @type_string is exactly one valid type string
+ *
+ * Checks if @type_string is a valid EVariant type string.  This call is
+ * equivalent to calling e_variant_type_string_scan() and confirming
+ * that the following character is a nul terminator.
+ *
+ * Since 2.24
+ **/
+gboolean
+e_variant_type_string_is_valid (const gchar *type_string)
+{
+  const gchar *endptr;
+
+  g_return_val_if_fail (type_string != NULL, FALSE);
+
+  if (!e_variant_type_string_scan (type_string, NULL, &endptr))
+    return FALSE;
+
+  return *endptr == '\0';
+}
+
+/**
+ * e_variant_type_free:
+ * @type: a #EVariantType, or %NULL
+ *
+ * Frees a #EVariantType that was allocated with
+ * e_variant_type_copy(), e_variant_type_new() or one of the container
+ * type constructor functions.
+ *
+ * In the case that @type is %NULL, this function does nothing.
+ *
+ * Since 2.24
+ **/
+void
+e_variant_type_free (EVariantType *type)
+{
+  g_return_if_fail (type == NULL || e_variant_type_check (type));
+
+  g_free (type);
+}
+
+/**
+ * e_variant_type_copy:
+ * @type: a #EVariantType
+ * @returns: a new #EVariantType
+ *
+ * Makes a copy of a #EVariantType.  It is appropriate to call
+ * e_variant_type_free() on the return value.  @type may not be %NULL.
+ *
+ * Since 2.24
+ **/
+EVariantType *
+e_variant_type_copy (const EVariantType *type)
+{
+  gsize length;
+  gchar *new;
+
+  g_return_val_if_fail (e_variant_type_check (type), NULL);
+
+  length = e_variant_type_get_string_length (type);
+  new = g_malloc (length + 1);
+
+  memcpy (new, type, length);
+  new[length] = '\0';
+
+  return (EVariantType *) new;
+}
+
+/**
+ * e_variant_type_new:
+ * @type_string: a valid EVariant type string
+ * @returns: a new #EVariantType
+ *
+ * Creates a new #EVariantType corresponding to the type string given
+ * by @type_string.  It is appropriate to call e_variant_type_free() on
+ * the return value.
+ *
+ * It is a programmer error to call this function with an invalid type
+ * string.  Use e_variant_type_string_is_valid() if you are unsure.
+ *
+ * Since: 2.24
+ */
+EVariantType *
+e_variant_type_new (const gchar *type_string)
+{
+  g_return_val_if_fail (type_string != NULL, NULL);
+
+  return e_variant_type_copy (E_VARIANT_TYPE (type_string));
+}
+
+/**
+ * e_variant_type_get_string_length:
+ * @type: a #EVariantType
+ * @returns: the length of the corresponding type string
+ *
+ * Returns the length of the type string corresponding to the given
+ * @type.  This function must be used to determine the valid extent of
+ * the memory region returned by e_variant_type_peek_string().
+ *
+ * Since 2.24
+ **/
+gsize
+e_variant_type_get_string_length (const EVariantType *type)
+{
+  const gchar *type_string = (const gchar *) type;
+  gint brackets = 0;
+  gsize index = 0;
+
+  g_return_val_if_fail (e_variant_type_check (type), 0);
+
+  do
+    {
+      while (type_string[index] == 'a' || type_string[index] == 'm')
+        index++;
+
+      if (type_string[index] == '(' || type_string[index] == '{')
+        brackets++;
+
+      else if (type_string[index] == ')' || type_string[index] == '}')
+        brackets--;
+
+      index++;
+    }
+  while (brackets);
+
+  return index;
+}
+
+/**
+ * e_variant_type_peek_string:
+ * @type: a #EVariantType
+ * @returns: the corresponding type string (not nul-terminated)
+ *
+ * Returns the type string corresponding to the given @type.  The
+ * result is not nul-terminated; in order to determine its length you
+ * must call e_variant_type_get_string_length().
+ *
+ * To get a nul-terminated string, see e_variant_type_dup_string().
+ *
+ * Since 2.24
+ **/
+const gchar *
+e_variant_type_peek_string (const EVariantType *type)
+{
+  g_return_val_if_fail (e_variant_type_check (type), NULL);
+
+  return (const gchar *) type;
+}
+
+/**
+ * e_variant_type_dup_string:
+ * @type: a #EVariantType
+ * @returns: the corresponding type string
+ *
+ * Returns a newly-allocated copy of the type string corresponding to
+ * @type.  The returned string is nul-terminated.  It is appropriate to
+ * call g_free() on the return value.
+ *
+ * Since 2.24
+ **/
+gchar *
+e_variant_type_dup_string (const EVariantType *type)
+{
+  g_return_val_if_fail (e_variant_type_check (type), NULL);
+
+  return g_strndup (e_variant_type_peek_string (type),
+                    e_variant_type_get_string_length (type));
+}
+
+/**
+ * e_variant_type_is_definite:
+ * @type: a #EVariantType
+ * @returns: %TRUE if @type is definite
+ *
+ * Determines if the given @type is definite (ie: not indefinite).
+ *
+ * A type is definite if its type string does not contain any indefinite
+ * type characters ('*', '?', or 'r').
+ *
+ * A #EVariant instance may not have an indefinite type, so calling
+ * this function on the result of e_variant_get_type() will always
+ * result in %TRUE being returned.  Calling this function on an
+ * indefinite type like %E_VARIANT_TYPE_ARRAY, however, will result in
+ * %FALSE being returned.
+ *
+ * Since 2.24
+ **/
+gboolean
+e_variant_type_is_definite (const EVariantType *type)
+{
+  const gchar *type_string;
+  gsize type_length;
+  gsize i;
+
+  g_return_val_if_fail (e_variant_type_check (type), FALSE);
+
+  type_length = e_variant_type_get_string_length (type);
+  type_string = e_variant_type_peek_string (type);
+
+  for (i = 0; i < type_length; i++)
+    if (type_string[i] == '*' ||
+        type_string[i] == '?' ||
+        type_string[i] == 'r')
+      return FALSE;
+
+  return TRUE;
+}
+
+/**
+ * e_variant_type_is_container:
+ * @type: a #EVariantType
+ * @returns: %TRUE if @type is a container type
+ *
+ * Determines if the given @type is a container type.
+ *
+ * Container types are any array, maybe, tuple, or dictionary
+ * entry types plus the variant type.
+ *
+ * This function returns %TRUE for any indefinite type for which every
+ * definite subtype is a container -- %E_VARIANT_TYPE_ARRAY, for
+ * example.
+ *
+ * Since 2.24
+ **/
+gboolean
+e_variant_type_is_container (const EVariantType *type)
+{
+  gchar first_char;
+
+  g_return_val_if_fail (e_variant_type_check (type), FALSE);
+
+  first_char = e_variant_type_peek_string (type)[0];
+  switch (first_char)
+  {
+    case 'a':
+    case 'm':
+    case 'r':
+    case '(':
+    case '{':
+    case 'v':
+      return TRUE;
+
+    default:
+      return FALSE;
+  }
+}
+
+/**
+ * e_variant_type_is_basic:
+ * @type: a #EVariantType
+ * @returns: %TRUE if @type is a basic type
+ *
+ * Determines if the given @type is a basic type.
+ *
+ * Basic types are booleans, bytes, integers, doubles, strings, object
+ * paths and signatures.
+ *
+ * Only a basic type may be used as the key of a dictionary entry.
+ *
+ * This function returns %FALSE for all indefinite types except
+ * %E_VARIANT_TYPE_BASIC.
+ *
+ * Since 2.24
+ **/
+gboolean
+e_variant_type_is_basic (const EVariantType *type)
+{
+  gchar first_char;
+
+  g_return_val_if_fail (e_variant_type_check (type), FALSE);
+
+  first_char = e_variant_type_peek_string (type)[0];
+  switch (first_char)
+  {
+    case 'b':
+    case 'y':
+    case 'n':
+    case 'q':
+    case 'i':
+    case 'h':
+    case 'u':
+    case 't':
+    case 'x':
+    case 'd':
+    case 's':
+    case 'o':
+    case 'g':
+    case '?':
+      return TRUE;
+
+    default:
+      return FALSE;
+  }
+}
+
+/**
+ * e_variant_type_is_maybe:
+ * @type: a #EVariantType
+ * @returns: %TRUE if @type is a maybe type
+ *
+ * Determines if the given @type is a maybe type.  This is true if the
+ * type string for @type starts with an 'm'.
+ *
+ * This function returns %TRUE for any indefinite type for which every
+ * definite subtype is a maybe type -- %E_VARIANT_TYPE_MAYBE, for
+ * example.
+ *
+ * Since 2.24
+ **/
+gboolean
+e_variant_type_is_maybe (const EVariantType *type)
+{
+  g_return_val_if_fail (e_variant_type_check (type), FALSE);
+
+  return e_variant_type_peek_string (type)[0] == 'm';
+}
+
+/**
+ * e_variant_type_is_array:
+ * @type: a #EVariantType
+ * @returns: %TRUE if @type is an array type
+ *
+ * Determines if the given @type is an array type.  This is true if the
+ * type string for @type starts with an 'a'.
+ *
+ * This function returns %TRUE for any indefinite type for which every
+ * definite subtype is an array type -- %E_VARIANT_TYPE_ARRAY, for
+ * example.
+ *
+ * Since 2.24
+ **/
+gboolean
+e_variant_type_is_array (const EVariantType *type)
+{
+  g_return_val_if_fail (e_variant_type_check (type), FALSE);
+
+  return e_variant_type_peek_string (type)[0] == 'a';
+}
+
+/**
+ * e_variant_type_is_tuple:
+ * @type: a #EVariantType
+ * @returns: %TRUE if @type is a tuple type
+ *
+ * Determines if the given @type is a tuple type.  This is true if the
+ * type string for @type starts with a '(' or if @type is
+ * %E_VARIANT_TYPE_TUPLE.
+ *
+ * This function returns %TRUE for any indefinite type for which every
+ * definite subtype is a tuple type -- %E_VARIANT_TYPE_TUPLE, for
+ * example.
+ *
+ * Since 2.24
+ **/
+gboolean
+e_variant_type_is_tuple (const EVariantType *type)
+{
+  gchar type_char;
+
+  g_return_val_if_fail (e_variant_type_check (type), FALSE);
+
+  type_char = e_variant_type_peek_string (type)[0];
+  return type_char == 'r' || type_char == '(';
+}
+
+/**
+ * e_variant_type_is_dict_entry:
+ * @type: a #EVariantType
+ * @returns: %TRUE if @type is a dictionary entry type
+ *
+ * Determines if the given @type is a dictionary entry type.  This is
+ * true if the type string for @type starts with a '{'.
+ *
+ * This function returns %TRUE for any indefinite type for which every
+ * definite subtype is a dictionary entry type --
+ * %E_VARIANT_TYPE_DICT_ENTRY, for example.
+ *
+ * Since 2.24
+ **/
+gboolean
+e_variant_type_is_dict_entry (const EVariantType *type)
+{
+  g_return_val_if_fail (e_variant_type_check (type), FALSE);
+
+  return e_variant_type_peek_string (type)[0] == '{';
+}
+
+/**
+ * e_variant_type_hash:
+ * @type: a #EVariantType
+ * @returns: the hash value
+ *
+ * Hashes @type.
+ *
+ * The argument type of @type is only #gconstpointer to allow use with
+ * #GHashTable without function pointer casting.  A valid
+ * #EVariantType must be provided.
+ *
+ * Since 2.24
+ **/
+guint
+e_variant_type_hash (gconstpointer type)
+{
+  const gchar *type_string;
+  guint value = 0;
+  gsize length;
+  gsize i;
+
+  g_return_val_if_fail (e_variant_type_check (type), 0);
+
+  type_string = e_variant_type_peek_string (type);
+  length = e_variant_type_get_string_length (type);
+
+  for (i = 0; i < length; i++)
+    value = (value << 5) - value + type_string[i];
+
+  return value;
+}
+
+/**
+ * e_variant_type_equal:
+ * @type1: a #EVariantType
+ * @type2: a #EVariantType
+ * @returns: %TRUE if @type1 and @type2 are exactly equal
+ *
+ * Compares @type1 and @type2 for equality.
+ *
+ * Only returns %TRUE if the types are exactly equal.  Even if one type
+ * is an indefinite type and the other is a subtype of it, %FALSE will
+ * be returned if they are not exactly equal.  If you want to check for
+ * subtypes, use e_variant_type_is_subtype_of().
+ *
+ * The argument types of @type1 and @type2 are only #gconstpointer to
+ * allow use with #GHashTable without function pointer casting.  For
+ * both arguments, a valid #EVariantType must be provided.
+ *
+ * Since 2.24
+ **/
+gboolean
+e_variant_type_equal (gconstpointer type1,
+                      gconstpointer type2)
+{
+  const gchar *string1, *string2;
+  gsize size1, size2;
+
+  g_return_val_if_fail (e_variant_type_check (type1), FALSE);
+  g_return_val_if_fail (e_variant_type_check (type2), FALSE);
+
+  if (type1 == type2)
+    return TRUE;
+
+  size1 = e_variant_type_get_string_length (type1);
+  size2 = e_variant_type_get_string_length (type2);
+
+  if (size1 != size2)
+    return FALSE;
+
+  string1 = e_variant_type_peek_string (type1);
+  string2 = e_variant_type_peek_string (type2);
+
+  return memcmp (string1, string2, size1) == 0;
+}
+
+/**
+ * e_variant_type_is_subtype_of:
+ * @type: a #EVariantType
+ * @supertype: a #EVariantType
+ * @returns: %TRUE if @type is a subtype of @supertype
+ *
+ * Checks if @type is a subtype of @supertype.
+ *
+ * This function returns %TRUE if @type is a subtype of @supertype.  All
+ * types are considered to be subtypes of themselves.  Aside from that,
+ * only indefinite types can have subtypes.
+ *
+ * Since 2.24
+ **/
+gboolean
+e_variant_type_is_subtype_of (const EVariantType *type,
+                              const EVariantType *supertype)
+{
+  const gchar *supertype_string;
+  const gchar *supertype_end;
+  const gchar *type_string;
+
+  g_return_val_if_fail (e_variant_type_check (type), FALSE);
+  g_return_val_if_fail (e_variant_type_check (supertype), FALSE);
+
+  supertype_string = e_variant_type_peek_string (supertype);
+  type_string = e_variant_type_peek_string (type);
+
+  supertype_end = supertype_string +
+                  e_variant_type_get_string_length (supertype);
+
+  /* we know that type and supertype are both well-formed, so it's
+   * safe to treat this merely as a text processing problem.
+   */
+  while (supertype_string < supertype_end)
+    {
+      char supertype_char = *supertype_string++;
+
+      if (supertype_char == *type_string)
+        type_string++;
+
+      else if (*type_string == ')')
+        return FALSE;
+
+      else
+        {
+          const EVariantType *target_type = (EVariantType *) type_string;
+
+          switch (supertype_char)
+            {
+            case 'r':
+              if (!e_variant_type_is_tuple (target_type))
+                return FALSE;
+              break;
+
+            case '*':
+              break;
+
+            case '?':
+              if (!e_variant_type_is_basic (target_type))
+                return FALSE;
+              break;
+
+            default:
+              return FALSE;
+            }
+
+          type_string += e_variant_type_get_string_length (target_type);
+        }
+    }
+
+  return TRUE;
+}
+
+/**
+ * e_variant_type_element:
+ * @type: an array or maybe #EVariantType
+ * @returns: the element type of @type
+ *
+ * Determines the element type of an array or maybe type.
+ *
+ * This function may only be used with array or maybe types.
+ *
+ * Since 2.24
+ **/
+const EVariantType *
+e_variant_type_element (const EVariantType *type)
+{
+  const gchar *type_string;
+
+  g_return_val_if_fail (e_variant_type_check (type), NULL);
+
+  type_string = e_variant_type_peek_string (type);
+
+  g_assert (type_string[0] == 'a' || type_string[0] == 'm');
+
+  return (const EVariantType *) &type_string[1];
+}
+
+/**
+ * e_variant_type_first:
+ * @type: a tuple or dictionary entry #EVariantType
+ * @returns: the first item type of @type, or %NULL
+ *
+ * Determines the first item type of a tuple or dictionary entry
+ * type.
+ *
+ * This function may only be used with tuple or dictionary entry types,
+ * but must not be used with the generic tuple type
+ * %E_VARIANT_TYPE_TUPLE.
+ *
+ * In the case of a dictionary entry type, this returns the type of
+ * the key.
+ *
+ * %NULL is returned in case of @type being %E_VARIANT_TYPE_UNIT.
+ *
+ * This call, together with e_variant_type_next() provides an iterator
+ * interface over tuple and dictionary entry types.
+ *
+ * Since 2.24
+ **/
+const EVariantType *
+e_variant_type_first (const EVariantType *type)
+{
+  const gchar *type_string;
+
+  g_return_val_if_fail (e_variant_type_check (type), NULL);
+
+  type_string = e_variant_type_peek_string (type);
+  g_assert (type_string[0] == '(' || type_string[0] == '{');
+
+  if (type_string[1] == ')')
+    return NULL;
+
+  return (const EVariantType *) &type_string[1];
+}
+
+/**
+ * e_variant_type_next:
+ * @type: a #EVariantType from a previous call
+ * @returns: the next #EVariantType after @type, or %NULL
+ *
+ * Determines the next item type of a tuple or dictionary entry
+ * type.
+ *
+ * @type must be the result of a previous call to
+ * e_variant_type_first() or e_variant_type_next().
+ *
+ * If called on the key type of a dictionary entry then this call
+ * returns the value type.  If called on the value type of a dictionary
+ * entry then this call returns %NULL.
+ *
+ * For tuples, %NULL is returned when @type is the last item in a tuple.
+ *
+ * Since 2.24
+ **/
+const EVariantType *
+e_variant_type_next (const EVariantType *type)
+{
+  const gchar *type_string;
+
+  g_return_val_if_fail (e_variant_type_check (type), NULL);
+
+  type_string = e_variant_type_peek_string (type);
+  type_string += e_variant_type_get_string_length (type);
+
+  if (*type_string == ')' || *type_string == '}')
+    return NULL;
+
+  return (const EVariantType *) type_string;
+}
+
+/**
+ * e_variant_type_n_items:
+ * @type: a tuple or dictionary entry #EVariantType
+ * @returns: the number of items in @type
+ *
+ * Determines the number of items contained in a tuple or
+ * dictionary entry type.
+ *
+ * This function may only be used with tuple or dictionary entry types,
+ * but must not be used with the generic tuple type
+ * %E_VARIANT_TYPE_TUPLE.
+ *
+ * In the case of a dictionary entry type, this function will always
+ * return 2.
+ *
+ * Since 2.24
+ **/
+gsize
+e_variant_type_n_items (const EVariantType *type)
+{
+  gsize count = 0;
+
+  g_return_val_if_fail (e_variant_type_check (type), 0);
+
+  for (type = e_variant_type_first (type);
+       type;
+       type = e_variant_type_next (type))
+    count++;
+
+  return count;
+}
+
+/**
+ * e_variant_type_key:
+ * @type: a dictionary entry #EVariantType
+ * @returns: the key type of the dictionary entry
+ *
+ * Determines the key type of a dictionary entry type.
+ *
+ * This function may only be used with a dictionary entry type.  Other
+ * than the additional restriction, this call is equivalent to
+ * e_variant_type_first().
+ *
+ * Since 2.24
+ **/
+const EVariantType *
+e_variant_type_key (const EVariantType *type)
+{
+  const gchar *type_string;
+
+  g_return_val_if_fail (e_variant_type_check (type), NULL);
+
+  type_string = e_variant_type_peek_string (type);
+  g_assert (type_string[0] == '{');
+
+  return (const EVariantType *) &type_string[1];
+}
+
+/**
+ * e_variant_type_value:
+ * @type: a dictionary entry #EVariantType
+ * @returns: the value type of the dictionary entry
+ *
+ * Determines the value type of a dictionary entry type.
+ *
+ * This function may only be used with a dictionary entry type.
+ *
+ * Since 2.24
+ **/
+const EVariantType *
+e_variant_type_value (const EVariantType *type)
+{
+  const gchar *type_string;
+
+  g_return_val_if_fail (e_variant_type_check (type), NULL);
+
+  type_string = e_variant_type_peek_string (type);
+  g_assert (type_string[0] == '{');
+
+  return e_variant_type_next (e_variant_type_key (type));
+}
+
+/**
+ * e_variant_type_new_tuple:
+ * @items: an array of #EVariantTypes, one for each item
+ * @length: the length of @items, or -1
+ * @returns: a new tuple #EVariantType
+ *
+ * Constructs a new tuple type, from @items.
+ *
+ * @length is the number of items in @items, or -1 to indicate that
+ * @items is %NULL-terminated.
+ *
+ * It is appropriate to call e_variant_type_free() on the return value.
+ *
+ * Since 2.24
+ **/
+static EVariantType *
+e_variant_type_new_tuple_slow (const EVariantType * const *items,
+                               gint                        length)
+{
+  /* the "slow" version is needed in case the static buffer of 1024
+   * bytes is exceeded when running the normal version.  this will
+   * happen only in truly insane code, so it can be slow.
+   */
+  GString *string;
+  gsize i;
+
+  string = g_string_new ("(");
+  for (i = 0; i < length; i++)
+    {
+      const EVariantType *type;
+      gsize size;
+
+      g_return_val_if_fail (e_variant_type_check (items[i]), NULL);
+
+      type = items[i];
+      size = e_variant_type_get_string_length (type);
+      g_string_append_len (string, (const gchar *) type, size);
+    }
+  g_string_append_c (string, ')');
+
+  return (EVariantType *) g_string_free (string, FALSE);
+}
+
+EVariantType *
+e_variant_type_new_tuple (const EVariantType * const *items,
+                          gint                        length)
+{
+  char buffer[1024];
+  gsize offset;
+  gsize i;
+
+  g_return_val_if_fail (length == 0 || items != NULL, NULL);
+
+  if (length < 0)
+    for (length = 0; items[length] != NULL; length++);
+
+  offset = 0;
+  buffer[offset++] = '(';
+
+  for (i = 0; i < length; i++)
+    {
+      const EVariantType *type;
+      gsize size;
+
+      g_return_val_if_fail (e_variant_type_check (items[i]), NULL);
+
+      type = items[i];
+      size = e_variant_type_get_string_length (type);
+
+      if (offset + size >= sizeof buffer) /* leave room for ')' */
+        return e_variant_type_new_tuple_slow (items, length);
+
+      memcpy (&buffer[offset], type, size);
+      offset += size;
+    }
+
+  g_assert (offset < sizeof buffer);
+  buffer[offset++] = ')';
+
+  return (EVariantType *) g_memdup (buffer, offset);
+}
+
+/**
+ * e_variant_type_new_array:
+ * @element: a #EVariantType
+ * @returns: a new array #EVariantType
+ *
+ * Constructs the type corresponding to an array of elements of the
+ * type @type.
+ *
+ * It is appropriate to call e_variant_type_free() on the return value.
+ *
+ * Since 2.24
+ **/
+EVariantType *
+e_variant_type_new_array (const EVariantType *element)
+{
+  gsize size;
+  gchar *new;
+
+  g_return_val_if_fail (e_variant_type_check (element), NULL);
+
+  size = e_variant_type_get_string_length (element);
+  new = g_malloc (size + 1);
+
+  new[0] = 'a';
+  memcpy (new + 1, element, size);
+
+  return (EVariantType *) new;
+}
+
+/**
+ * e_variant_type_new_maybe:
+ * @element: a #EVariantType
+ * @returns: a new maybe #EVariantType
+ *
+ * Constructs the type corresponding to a maybe instance containing
+ * type @type or Nothing.
+ *
+ * It is appropriate to call e_variant_type_free() on the return value.
+ *
+ * Since 2.24
+ **/
+EVariantType *
+e_variant_type_new_maybe (const EVariantType *element)
+{
+  gsize size;
+  gchar *new;
+
+  g_return_val_if_fail (e_variant_type_check (element), NULL);
+
+  size = e_variant_type_get_string_length (element);
+  new = g_malloc (size + 1);
+
+  new[0] = 'm';
+  memcpy (new + 1, element, size);
+
+  return (EVariantType *) new;
+}
+
+/**
+ * e_variant_type_new_dict_entry:
+ * @key: a basic #EVariantType
+ * @value: a #EVariantType
+ * @returns: a new dictionary entry #EVariantType
+ *
+ * Constructs the type corresponding to a dictionary entry with a key
+ * of type @key and a value of type @value.
+ *
+ * It is appropriate to call e_variant_type_free() on the return value.
+ *
+ * Since 2.24
+ **/
+EVariantType *
+e_variant_type_new_dict_entry (const EVariantType *key,
+                               const EVariantType *value)
+{
+  gsize keysize, valsize;
+  gchar *new;
+
+  g_return_val_if_fail (e_variant_type_check (key), NULL);
+  g_return_val_if_fail (e_variant_type_check (value), NULL);
+
+  keysize = e_variant_type_get_string_length (key);
+  valsize = e_variant_type_get_string_length (value);
+
+  new = g_malloc (1 + keysize + valsize + 1);
+
+  new[0] = '{';
+  memcpy (new + 1, key, keysize);
+  memcpy (new + 1 + keysize, value, valsize);
+  new[1 + keysize + valsize] = '}';
+
+  return (EVariantType *) new;
+}
+
+/* private */
+const EVariantType *
+e_variant_type_checked_ (const gchar *type_string)
+{
+  g_return_val_if_fail (e_variant_type_string_is_valid (type_string), NULL);
+  return (const EVariantType *) type_string;
+}
diff --git a/libedbus/gvarianttype.h b/libedbus/gvarianttype.h
new file mode 100644
index 0000000..3d35bfb
--- /dev/null
+++ b/libedbus/gvarianttype.h
@@ -0,0 +1,304 @@
+/*
+ * Copyright © 2007, 2008 Ryan Lortie
+ * Copyright © 2009, 2010 Codethink Limited
+ *
+ * This library 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 licence, or (at your option) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#ifndef __E_VARIANT_TYPE_H__
+#define __E_VARIANT_TYPE_H__
+
+#include <glib/gmessages.h>
+#include <glib/gtypes.h>
+
+G_BEGIN_DECLS
+
+/**
+ * EVariantType:
+ *
+ * A type in the EVariant type system.
+ *
+ * Two types may not be compared by value; use e_variant_type_equal() or
+ * e_variant_type_is_subtype().  May be copied using
+ * e_variant_type_copy() and freed using e_variant_type_free().
+ **/
+typedef struct _EVariantType EVariantType;
+
+/**
+ * E_VARIANT_TYPE_BOOLEAN:
+ *
+ * The type of a value that can be either %TRUE or %FALSE.
+ **/
+#define E_VARIANT_TYPE_BOOLEAN              ((const EVariantType *) "b")
+
+/**
+ * E_VARIANT_TYPE_BYTE:
+ *
+ * The type of an integer value that can range from 0 to 255.
+ **/
+#define E_VARIANT_TYPE_BYTE                 ((const EVariantType *) "y")
+
+/**
+ * E_VARIANT_TYPE_INT16:
+ *
+ * The type of an integer value that can range from -32768 to 32767.
+ **/
+#define E_VARIANT_TYPE_INT16                ((const EVariantType *) "n")
+
+/**
+ * E_VARIANT_TYPE_UINT16:
+ *
+ * The type of an integer value that can range from 0 to 65535.
+ * There were about this many people living in Toronto in the 1870s.
+ **/
+#define E_VARIANT_TYPE_UINT16               ((const EVariantType *) "q")
+
+/**
+ * E_VARIANT_TYPE_INT32:
+ *
+ * The type of an integer value that can range from -2147483648 to
+ * 2147483647.
+ **/
+#define E_VARIANT_TYPE_INT32                ((const EVariantType *) "i")
+
+/**
+ * E_VARIANT_TYPE_UINT32:
+ *
+ * The type of an integer value that can range from 0 to 4294967295.
+ * That's one number for everyone who was around in the late 1970s.
+ **/
+#define E_VARIANT_TYPE_UINT32               ((const EVariantType *) "u")
+
+/**
+ * E_VARIANT_TYPE_INT64:
+ *
+ * The type of an integer value that can range from
+ * -9223372036854775808 to 9223372036854775807.
+ **/
+#define E_VARIANT_TYPE_INT64                ((const EVariantType *) "x")
+
+/**
+ * E_VARIANT_TYPE_UINT64:
+ *
+ * The type of an integer value that can range from 0 to
+ * 18446744073709551616.  That's a really big number, but a Rubik's
+ * cube can have a bit more than twice as many possible positions.
+ **/
+#define E_VARIANT_TYPE_UINT64               ((const EVariantType *) "t")
+
+/**
+ * E_VARIANT_TYPE_DOUBLE:
+ *
+ * The type of a double precision IEEE754 floating point number.
+ * These guys go up to about 1.80e308 (plus and minus) but miss out on
+ * some numbers in between.  In any case, that's far greater than the
+ * estimated number of fundamental particles in the observable
+ * universe.
+ **/
+#define E_VARIANT_TYPE_DOUBLE               ((const EVariantType *) "d")
+
+/**
+ * E_VARIANT_TYPE_STRING:
+ *
+ * The type of a string.  "" is a string.  %NULL is not a string.
+ **/
+#define E_VARIANT_TYPE_STRING               ((const EVariantType *) "s")
+
+/**
+ * E_VARIANT_TYPE_OBJECT_PATH:
+ *
+ * The type of a DBus object reference.  These are strings of a
+ * specific format used to identify objects at a given destination on
+ * the bus.
+ *
+ * If you are not interacting with DBus, then there is no reason to make
+ * use of this type.  If you are, then the DBus specification contains a
+ * precise description of valid object paths.
+ **/
+#define E_VARIANT_TYPE_OBJECT_PATH          ((const EVariantType *) "o")
+
+/**
+ * E_VARIANT_TYPE_SIGNATURE:
+ *
+ * The type of a DBus type signature.  These are strings of a specific
+ * format used as type signatures for DBus methods and messages.
+ *
+ * If you are not interacting with DBus, then there is no reason to make
+ * use of this type.  If you are, then the DBus specification contains a
+ * precise description of valid signature strings.
+ **/
+#define E_VARIANT_TYPE_SIGNATURE            ((const EVariantType *) "g")
+
+/**
+ * E_VARIANT_TYPE_VARIANT:
+ *
+ * The type of a box that contains any other value (including another
+ * variant).
+ **/
+#define E_VARIANT_TYPE_VARIANT              ((const EVariantType *) "v")
+
+/**
+ * E_VARIANT_TYPE_HANDLE:
+ *
+ * The type of a 32bit signed integer value, that by convention, is used
+ * as an index into an array of file descriptors that are sent alongside
+ * a DBus message.
+ *
+ * If you are not interacting with DBus, then there is no reason to make
+ * use of this type.
+ **/
+#define E_VARIANT_TYPE_HANDLE               ((const EVariantType *) "h")
+
+/**
+ * E_VARIANT_TYPE_UNIT:
+ *
+ * The empty tuple type.  Has only one instance.  Known also as "triv"
+ * or "void".
+ **/
+#define E_VARIANT_TYPE_UNIT                 ((const EVariantType *) "()")
+
+/**
+ * E_VARIANT_TYPE_ANY:
+ *
+ * An indefinite type that is a supertype of every type (including
+ * itself).
+ **/
+#define E_VARIANT_TYPE_ANY                  ((const EVariantType *) "*")
+
+/**
+ * E_VARIANT_TYPE_BASIC:
+ *
+ * An indefinite type that is a supertype of every basic (ie:
+ * non-container) type.
+ **/
+#define E_VARIANT_TYPE_BASIC                ((const EVariantType *) "?")
+
+/**
+ * E_VARIANT_TYPE_MAYBE:
+ *
+ * An indefinite type that is a supertype of every maybe type.
+ **/
+#define E_VARIANT_TYPE_MAYBE                ((const EVariantType *) "m*")
+
+/**
+ * E_VARIANT_TYPE_ARRAY:
+ *
+ * An indefinite type that is a supertype of every array type.
+ **/
+#define E_VARIANT_TYPE_ARRAY                ((const EVariantType *) "a*")
+
+/**
+ * E_VARIANT_TYPE_TUPLE:
+ *
+ * An indefinite type that is a supertype of every tuple type,
+ * regardless of the number of items in the tuple.
+ **/
+#define E_VARIANT_TYPE_TUPLE                ((const EVariantType *) "r")
+
+/**
+ * E_VARIANT_TYPE_DICT_ENTRY:
+ *
+ * An indefinite type that is a supertype of every dictionary entry
+ * type.
+ **/
+#define E_VARIANT_TYPE_DICT_ENTRY           ((const EVariantType *) "{?*}")
+
+/**
+ * E_VARIANT_TYPE_DICTIONARY:
+ *
+ * An indefinite type that is a supertype of every dictionary type --
+ * that is, any array type that has an element type equal to any
+ * dictionary entry type.
+ **/
+#define E_VARIANT_TYPE_DICTIONARY           ((const EVariantType *) "a{?*}")
+
+/**
+ * E_VARIANT_TYPE:
+ * @type_string: a well-formed #EVariantType type string
+ *
+ * Converts a string to a const #EVariantType.  Depending on the
+ * current debugging level, this function may perform a runtime check
+ * to ensure that @string is a valid EVariant type string.
+ *
+ * It is always a programmer error to use this macro with an invalid
+ * type string.
+ *
+ * Since 2.24
+ **/
+#ifndef G_DISABLE_CHECKS
+# define E_VARIANT_TYPE(type_string)            (e_variant_type_checked_ ((type_string)))
+#else
+# define E_VARIANT_TYPE(type_string)            ((const EVariantType *) (type_string))
+#endif
+
+/* type string checking */
+gboolean                        e_variant_type_string_is_valid          (const gchar         *type_string);
+gboolean                        e_variant_type_string_scan              (const gchar         *string,
+                                                                         const gchar         *limit,
+                                                                         const gchar        **endptr);
+
+/* create/destroy */
+void                            e_variant_type_free                     (EVariantType        *type);
+EVariantType *                  e_variant_type_copy                     (const EVariantType  *type);
+EVariantType *                  e_variant_type_new                      (const gchar         *type_string);
+
+/* getters */
+gsize                           e_variant_type_get_string_length        (const EVariantType  *type);
+const gchar *                   e_variant_type_peek_string              (const EVariantType  *type);
+gchar *                         e_variant_type_dup_string               (const EVariantType  *type);
+
+/* classification */
+gboolean                        e_variant_type_is_definite              (const EVariantType  *type);
+gboolean                        e_variant_type_is_container             (const EVariantType  *type);
+gboolean                        e_variant_type_is_basic                 (const EVariantType  *type);
+gboolean                        e_variant_type_is_maybe                 (const EVariantType  *type);
+gboolean                        e_variant_type_is_array                 (const EVariantType  *type);
+gboolean                        e_variant_type_is_tuple                 (const EVariantType  *type);
+gboolean                        e_variant_type_is_dict_entry            (const EVariantType  *type);
+
+/* for hash tables */
+guint                           e_variant_type_hash                     (gconstpointer        type);
+gboolean                        e_variant_type_equal                    (gconstpointer        type1,
+                                                                         gconstpointer        type2);
+
+/* subtypes */
+gboolean                        e_variant_type_is_subtype_of            (const EVariantType  *type,
+                                                                         const EVariantType  *supertype);
+
+/* type iterator interface */
+const EVariantType *            e_variant_type_element                  (const EVariantType  *type);
+const EVariantType *            e_variant_type_first                    (const EVariantType  *type);
+const EVariantType *            e_variant_type_next                     (const EVariantType  *type);
+gsize                           e_variant_type_n_items                  (const EVariantType  *type);
+const EVariantType *            e_variant_type_key                      (const EVariantType  *type);
+const EVariantType *            e_variant_type_value                    (const EVariantType  *type);
+
+/* constructors */
+EVariantType *                  e_variant_type_new_array                (const EVariantType  *element);
+EVariantType *                  e_variant_type_new_maybe                (const EVariantType  *element);
+EVariantType *                  e_variant_type_new_tuple                (const EVariantType * const *items,
+                                                                         gint                 length);
+EVariantType *                  e_variant_type_new_dict_entry           (const EVariantType  *key,
+                                                                         const EVariantType  *value);
+
+/*< private >*/
+const EVariantType *            e_variant_type_checked_                 (const gchar *);
+
+G_END_DECLS
+
+#endif /* __E_VARIANT_TYPE_H__ */
diff --git a/libedbus/gvarianttypeinfo.c b/libedbus/gvarianttypeinfo.c
new file mode 100644
index 0000000..b0682e1
--- /dev/null
+++ b/libedbus/gvarianttypeinfo.c
@@ -0,0 +1,854 @@
+/*
+ * Copyright © 2008 Ryan Lortie
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#include "gvarianttypeinfo.h"
+#include <glib.h>
+
+/* < private >
+ * EVariantTypeInfo:
+ *
+ * This structure contains the necessary information to facilitate the
+ * serialisation and fast deserialisation of a given type of EVariant
+ * value.  A EVariant instance holds a pointer to one of these
+ * structures to provide for efficient operation.
+ *
+ * The EVariantTypeInfo structures for all of the base types, plus the
+ * "variant" type are stored in a read-only static array.
+ *
+ * For container types, a hash table and reference counting is used to
+ * ensure that only one of these structures exists for any given type.
+ * In general, a container EVariantTypeInfo will exist for a given type
+ * only if one or more EVariant instances of that type exist or if
+ * another EVariantTypeInfo has that type as a subtype.  For example, if
+ * a process contains a single EVariant instance with type "(asv)", then
+ * container EVariantTypeInfo structures will exist for "(asv)" and
+ * for "as" (note that "s" and "v" always exist in the static array).
+ *
+ * The trickiest part of EVariantTypeInfo (and in fact, the major reason
+ * for its existance) is the storage of somewhat magical constants that
+ * allow for O(1) lookups of items in tuples.  This is described below.
+ *
+ * 'container_class' is set to 'a' or 'r' if the EVariantTypeInfo is
+ * contained inside of an ArrayInfo or TupleInfo, respectively.  This
+ * allows the storage of the necessary additional information.
+ *
+ * 'fixed_size' is set to the fixed size of the type, if applicable, or
+ * 0 otherwise (since no type has a fixed size of 0).
+ *
+ * 'alignment' is set to one less than the alignment requirement for
+ * this type.  This makes many operations much more convenient.
+ */
+struct _EVariantTypeInfo
+{
+  gsize fixed_size;
+  guchar alignment;
+  guchar container_class;
+};
+
+/* Container types are reference counted.  They also need to have their
+ * type string stored explicitly since it is not merely a single letter.
+ */
+typedef struct
+{
+  EVariantTypeInfo info;
+
+  gchar *type_string;
+  gint ref_count;
+} ContainerInfo;
+
+/* For 'array' and 'maybe' types, we store some extra information on the
+ * end of the EVariantTypeInfo struct -- the element type (ie: "s" for
+ * "as").  The container EVariantTypeInfo structure holds a reference to
+ * the element typeinfo.
+ */
+typedef struct
+{
+  ContainerInfo container;
+
+  EVariantTypeInfo *element;
+} ArrayInfo;
+
+/* For 'tuple' and 'dict entry' types, we store extra information for
+ * each member -- its type and how to find it inside the serialised data
+ * in O(1) time using 4 variables -- 'i', 'a', 'b', and 'c'.  See the
+ * comment on EVariantMemberInfo in gvarianttypeinfo.h.
+ */
+typedef struct
+{
+  ContainerInfo container;
+
+  EVariantMemberInfo *members;
+  gsize n_members;
+} TupleInfo;
+
+
+/* Hard-code the base types in a constant array */
+static const EVariantTypeInfo e_variant_type_info_basic_table[24] = {
+#define fixed_aligned(x)  x, x - 1
+#define unaligned         0, 0
+#define aligned(x)        0, x - 1
+  /* 'b' */ { fixed_aligned(1) },   /* boolean */
+  /* 'c' */ { },
+  /* 'd' */ { fixed_aligned(8) },   /* double */
+  /* 'e' */ { },
+  /* 'f' */ { },
+  /* 'g' */ { unaligned        },   /* signature string */
+  /* 'h' */ { fixed_aligned(4) },   /* file handle (int32) */
+  /* 'i' */ { fixed_aligned(4) },   /* int32 */
+  /* 'j' */ { },
+  /* 'k' */ { },
+  /* 'l' */ { },
+  /* 'm' */ { },
+  /* 'n' */ { fixed_aligned(2) },   /* int16 */
+  /* 'o' */ { unaligned        },   /* object path string */
+  /* 'p' */ { },
+  /* 'q' */ { fixed_aligned(2) },   /* uint16 */
+  /* 'r' */ { },
+  /* 's' */ { unaligned        },   /* string */
+  /* 't' */ { fixed_aligned(8) },   /* uint64 */
+  /* 'u' */ { fixed_aligned(4) },   /* uint32 */
+  /* 'v' */ { aligned(8)       },   /* variant */
+  /* 'w' */ { },
+  /* 'x' */ { fixed_aligned(8) },   /* int64 */
+  /* 'y' */ { fixed_aligned(1) },   /* byte */
+#undef fixed_aligned
+#undef unaligned
+#undef aligned
+};
+
+/* We need to have type strings to return for the base types.  We store
+ * those in another array.  Since all base type strings are single
+ * characters this is easy.  By not storing pointers to strings into the
+ * EVariantTypeInfo itself, we save a bunch of relocations.
+ */
+static const char e_variant_type_info_basic_chars[24][2] = {
+  "b", " ", "d", " ", " ", "g", "h", "i", " ", " ", " ", " ",
+  "n", "o", " ", "q", " ", "s", "t", "u", "v", " ", "x", "y"
+};
+
+/* sanity checks to make debugging easier */
+static void
+e_variant_type_info_check (const EVariantTypeInfo *info,
+                           char                    container_class)
+{
+  g_assert (!container_class || info->container_class == container_class);
+
+  /* alignment can only be one of these */
+  g_assert (info->alignment == 0 || info->alignment == 1 ||
+            info->alignment == 3 || info->alignment == 7);
+
+  if (info->container_class)
+    {
+      ContainerInfo *container = (ContainerInfo *) info;
+
+      /* extra checks for containers */
+      g_assert_cmpint (container->ref_count, >, 0);
+      g_assert (container->type_string != NULL);
+    }
+  else
+    {
+      gint index;
+
+      /* if not a container, then ensure that it is a valid member of
+       * the basic types table
+       */
+      index = info - e_variant_type_info_basic_table;
+
+      g_assert (G_N_ELEMENTS (e_variant_type_info_basic_table) == 24);
+      g_assert (G_N_ELEMENTS (e_variant_type_info_basic_chars) == 24);
+      g_assert (0 <= index && index < 24);
+      g_assert (e_variant_type_info_basic_chars[index][0] != ' ');
+    }
+}
+
+/* < private >
+ * e_variant_type_info_get_type_string:
+ * @info: a #EVariantTypeInfo
+ *
+ * Gets the type string for @info.  The string is nul-terminated.
+ */
+const gchar *
+e_variant_type_info_get_type_string (EVariantTypeInfo *info)
+{
+  e_variant_type_info_check (info, 0);
+
+  if (info->container_class)
+    {
+      ContainerInfo *container = (ContainerInfo *) info;
+
+      /* containers have their type string stored inside them */
+      return container->type_string;
+    }
+  else
+    {
+      gint index;
+
+      /* look up the type string in the base type array.  the call to
+       * e_variant_type_info_check() above already ensured validity.
+       */
+      index = info - e_variant_type_info_basic_table;
+
+      return e_variant_type_info_basic_chars[index];
+    }
+}
+
+/* < private >
+ * e_variant_type_info_query:
+ * @info: a #EVariantTypeInfo
+ * @alignment: the location to store the alignment, or %NULL
+ * @fixed_size: the location to store the fixed size, or %NULL
+ *
+ * Queries @info to determine the alignment requirements and fixed size
+ * (if any) of the type.
+ *
+ * @fixed_size, if non-%NULL is set to the fixed size of the type, or 0
+ * to indicate that the type is a variable-sized type.  No type has a
+ * fixed size of 0.
+ *
+ * @alignment, if non-%NULL, is set to one less than the required
+ * alignment of the type.  For example, for a 32bit integer, @alignment
+ * would be set to 3.  This allows you to round an integer up to the
+ * proper alignment by performing the following efficient calculation:
+ *
+ *   offset += ((-offset) & alignment);
+ */
+void
+e_variant_type_info_query (EVariantTypeInfo *info,
+                           guint            *alignment,
+                           gsize            *fixed_size)
+{
+  e_variant_type_info_check (info, 0);
+
+  if (alignment)
+    *alignment = info->alignment;
+
+  if (fixed_size)
+    *fixed_size = info->fixed_size;
+}
+
+/* == array == */
+#define ARRAY_INFO_CLASS 'a'
+static ArrayInfo *
+ARRAY_INFO (EVariantTypeInfo *info)
+{
+  e_variant_type_info_check (info, ARRAY_INFO_CLASS);
+
+  return (ArrayInfo *) info;
+}
+
+static void
+array_info_free (EVariantTypeInfo *info)
+{
+  ArrayInfo *array_info;
+
+  g_assert (info->container_class == ARRAY_INFO_CLASS);
+  array_info = (ArrayInfo *) info;
+
+  e_variant_type_info_unref (array_info->element);
+  g_slice_free (ArrayInfo, array_info);
+}
+
+static ContainerInfo *
+array_info_new (const EVariantType *type)
+{
+  ArrayInfo *info;
+
+  info = g_slice_new (ArrayInfo);
+  info->container.info.container_class = ARRAY_INFO_CLASS;
+
+  info->element = e_variant_type_info_get (e_variant_type_element (type));
+  info->container.info.alignment = info->element->alignment;
+  info->container.info.fixed_size = 0;
+
+  return (ContainerInfo *) info;
+}
+
+/* < private >
+ * e_variant_type_info_element:
+ * @info: a #EVariantTypeInfo for an array or maybe type
+ *
+ * Returns the element type for the array or maybe type.  A reference is
+ * not added, so the caller must add their own.
+ */
+EVariantTypeInfo *
+e_variant_type_info_element (EVariantTypeInfo *info)
+{
+  return ARRAY_INFO (info)->element;
+}
+
+/* < private >
+ * e_variant_type_query_element:
+ * @info: a #EVariantTypeInfo for an array or maybe type
+ * @alignment: the location to store the alignment, or %NULL
+ * @fixed_size: the location to store the fixed size, or %NULL
+ *
+ * Returns the alignment requires and fixed size (if any) for the
+ * element type of the array.  This call is a convenience wrapper around
+ * e_variant_type_info_element() and e_variant_type_info_query().
+ */
+void
+e_variant_type_info_query_element (EVariantTypeInfo *info,
+                                   guint            *alignment,
+                                   gsize            *fixed_size)
+{
+  e_variant_type_info_query (ARRAY_INFO (info)->element,
+                             alignment, fixed_size);
+}
+
+/* == tuple == */
+#define TUPLE_INFO_CLASS 'r'
+static TupleInfo *
+TUPLE_INFO (EVariantTypeInfo *info)
+{
+  e_variant_type_info_check (info, TUPLE_INFO_CLASS);
+
+  return (TupleInfo *) info;
+}
+
+static void
+tuple_info_free (EVariantTypeInfo *info)
+{
+  TupleInfo *tuple_info;
+  gint i;
+
+  g_assert (info->container_class == TUPLE_INFO_CLASS);
+  tuple_info = (TupleInfo *) info;
+
+  for (i = 0; i < tuple_info->n_members; i++)
+    e_variant_type_info_unref (tuple_info->members[i].type_info);
+
+  g_slice_free1 (sizeof (EVariantMemberInfo) * tuple_info->n_members,
+                 tuple_info->members);
+  g_slice_free (TupleInfo, tuple_info);
+}
+
+static void
+tuple_allocate_members (const EVariantType  *type,
+                        EVariantMemberInfo **members,
+                        gsize               *n_members)
+{
+  const EVariantType *item_type;
+  gsize i = 0;
+
+  *n_members = e_variant_type_n_items (type);
+  *members = g_slice_alloc (sizeof (EVariantMemberInfo) * *n_members);
+
+  item_type = e_variant_type_first (type);
+  while (item_type)
+    {
+      EVariantMemberInfo *member = &(*members)[i++];
+
+      member->type_info = e_variant_type_info_get (item_type);
+      item_type = e_variant_type_next (item_type);
+
+      if (member->type_info->fixed_size)
+        member->ending_type = E_VARIANT_MEMBER_ENDING_FIXED;
+      else if (item_type == NULL)
+        member->ending_type = E_VARIANT_MEMBER_ENDING_LAST;
+      else
+        member->ending_type = E_VARIANT_MEMBER_ENDING_OFFSET;
+    }
+
+  g_assert (i == *n_members);
+}
+
+/* this is e_variant_type_info_query for a given member of the tuple.
+ * before the access is done, it is ensured that the item is within
+ * range and %FALSE is returned if not.
+ */
+static gboolean
+tuple_get_item (TupleInfo          *info,
+                EVariantMemberInfo *item,
+                gsize              *d,
+                gsize              *e)
+{
+  if (&info->members[info->n_members] == item)
+    return FALSE;
+
+  *d = item->type_info->alignment;
+  *e = item->type_info->fixed_size;
+  return TRUE;
+}
+
+/* Read the documentation for #EVariantMemberInfo in gvarianttype.h
+ * before attempting to understand this.
+ *
+ * This function adds one set of "magic constant" values (for one item
+ * in the tuple) to the table.
+ *
+ * The algorithm in tuple_generate_table() calculates values of 'a', 'b'
+ * and 'c' for each item, such that the procedure for finding the item
+ * is to start at the end of the previous variable-sized item, add 'a',
+ * then round up to the nearest multiple of 'b', then then add 'c'.
+ * Note that 'b' is stored in the usual "one less than" form.  ie:
+ *
+ *   start = ROUND_UP(prev_end + a, (b + 1)) + c;
+ *
+ * We tweak these values a little to allow for a slightly easier
+ * computation and more compact storage.
+ */
+static void
+tuple_table_append (EVariantMemberInfo **items,
+                    gsize                i,
+                    gsize                a,
+                    gsize                b,
+                    gsize                c)
+{
+  EVariantMemberInfo *item = (*items)++;
+
+  /* We can shift multiples of the alignment size from 'c' into 'a'.
+   * As long as we're shifting whole multiples, it won't affect the
+   * result.  This means that we can take the "aligned" portion off of
+   * 'c' and add it into 'a'.
+   *
+   *  Imagine (for sake of clarity) that ROUND_10 rounds up to the
+   *  nearest 10.  It is clear that:
+   *
+   *   ROUND_10(a) + c == ROUND_10(a + 10*(c / 10)) + (c % 10)
+   *
+   * ie: remove the 10s portion of 'c' and add it onto 'a'.
+   *
+   * To put some numbers on it, imagine we start with a = 34 and c = 27:
+   *
+   *  ROUND_10(34) + 27 = 40 + 27 = 67
+   *
+   * but also, we can split 27 up into 20 and 7 and do this:
+   *
+   *  ROUND_10(34 + 20) + 7 = ROUND_10(54) + 7 = 60 + 7 = 67
+   *                ^^    ^
+   * without affecting the result.  We do that here.
+   *
+   * This reduction in the size of 'c' means that we can store it in a
+   * gchar instead of a gsize.  Due to how the structure is packed, this
+   * ends up saving us 'two pointer sizes' per item in each tuple when
+   * allocating using GSlice.
+   */
+  a += ~b & c;    /* take the "aligned" part of 'c' and add to 'a' */
+  c &= b;         /* chop 'c' to contain only the unaligned part */
+
+
+  /* Finally, we made one last adjustment.  Recall:
+   *
+   *   start = ROUND_UP(prev_end + a, (b + 1)) + c;
+   *
+   * Forgetting the '+ c' for the moment:
+   *
+   *   ROUND_UP(prev_end + a, (b + 1));
+   *
+   * we can do a "round up" operation by adding 1 less than the amount
+   * to round up to, then rounding down.  ie:
+   *
+   *   #define ROUND_UP(x, y)    ROUND_DOWN(x + (y-1), y)
+   *
+   * Of course, for rounding down to a power of two, we can just mask
+   * out the appropriate number of low order bits:
+   *
+   *   #define ROUND_DOWN(x, y)  (x & ~(y - 1))
+   *
+   * Which gives us
+   *
+   *   #define ROUND_UP(x, y)    (x + (y - 1) & ~(y - 1))
+   *
+   * but recall that our alignment value 'b' is already "one less".
+   * This means that to round 'prev_end + a' up to 'b' we can just do:
+   *
+   *   ((prev_end + a) + b) & ~b
+   *
+   * Associativity, and putting the 'c' back on:
+   *
+   *   (prev_end + (a + b)) & ~b + c
+   *
+   * Now, since (a + b) is constant, we can just add 'b' to 'a' now and
+   * store that as the number to add to prev_end.  Then we use ~b as the
+   * number to take a bitwise 'and' with.  Finally, 'c' is added on.
+   *
+   * Note, however, that all the low order bits of the 'aligned' value
+   * are masked out and that all of the high order bits of 'c' have been
+   * "moved" to 'a' (in the previous step).  This means that there are
+   * no overlapping bits in the addition -- so we can do a bitwise 'or'
+   * equivalently.
+   *
+   * This means that we can now compute the start address of a given
+   * item in the tuple using the algorithm given in the documentation
+   * for #EVariantMemberInfo:
+   *
+   *   item_start = ((prev_end + a) & b) | c;
+   */
+
+  item->i = i;
+  item->a = a + b;
+  item->b = ~b;
+  item->c = c;
+}
+
+static gsize
+tuple_align (gsize offset,
+             guint alignment)
+{
+  return offset + ((-offset) & alignment);
+}
+
+/* This function is the heart of the algorithm for calculating 'i', 'a',
+ * 'b' and 'c' for each item in the tuple.
+ *
+ * Imagine we want to find the start of the "i" in the type "(su(qx)ni)".
+ * That's a string followed by a uint32, then a tuple containing a
+ * uint16 and a int64, then an int16, then our "i".  In order to get to
+ * our "i" we:
+ *
+ * Start at the end of the string, align to 4 (for the uint32), add 4.
+ * Align to 8, add 16 (for the tuple).  Align to 2, add 2 (for the
+ * int16).  Then we're there.  It turns out that, given 3 simple rules,
+ * we can flatten this iteration into one addition, one alignment, then
+ * one more addition.
+ *
+ * The loop below plays through each item in the tuple, querying its
+ * alignment and fixed_size into 'd' and 'e', respectively.  At all
+ * times the variables 'a', 'b', and 'c' are maintained such that in
+ * order to get to the current point, you add 'a', align to 'b' then add
+ * 'c'.  'b' is kept in "one less than" form.  For each item, the proper
+ * alignment is applied to find the values of 'a', 'b' and 'c' to get to
+ * the start of that item.  Those values are recorded into the table.
+ * The fixed size of the item (if applicable) is then added on.
+ *
+ * These 3 rules are how 'a', 'b' and 'c' are modified for alignment and
+ * addition of fixed size.  They have been proven correct but are
+ * presented here, without proof:
+ *
+ *  1) in order to "align to 'd'" where 'd' is less than or equal to the
+ *     largest level of alignment seen so far ('b'), you align 'c' to
+ *     'd'.
+ *  2) in order to "align to 'd'" where 'd' is greater than the largest
+ *     level of alignment seen so far, you add 'c' aligned to 'b' to the
+ *     value of 'a', set 'b' to 'd' (ie: increase the 'largest alignment
+ *     seen') and reset 'c' to 0.
+ *  3) in order to "add 'e'", just add 'e' to 'c'.
+ */
+static void
+tuple_generate_table (TupleInfo *info)
+{
+  EVariantMemberInfo *items = info->members;
+  gsize i = -1, a = 0, b = 0, c = 0, d, e;
+
+  /* iterate over each item in the tuple.
+   *   'd' will be the alignment of the item (in one-less form)
+   *   'e' will be the fixed size (or 0 for variable-size items)
+   */
+  while (tuple_get_item (info, items, &d, &e))
+    {
+      /* align to 'd' */
+      if (d <= b)
+        c = tuple_align (c, d);                   /* rule 1 */
+      else
+        a += tuple_align (c, b), b = d, c = 0;    /* rule 2 */
+
+      /* the start of the item is at this point (ie: right after we
+       * have aligned for it).  store this information in the table.
+       */
+      tuple_table_append (&items, i, a, b, c);
+
+      /* "move past" the item by adding in its size. */
+      if (e == 0)
+        /* variable size:
+         *
+         * we'll have an offset stored to mark the end of this item, so
+         * just bump the offset index to give us a new starting point
+         * and reset all the counters.
+         */
+        i++, a = b = c = 0;
+      else
+        /* fixed size */
+        c += e;                                   /* rule 3 */
+    }
+}
+
+static void
+tuple_set_base_info (TupleInfo *info)
+{
+  EVariantTypeInfo *base = &info->container.info;
+
+  if (info->n_members > 0)
+    {
+      EVariantMemberInfo *m;
+
+      /* the alignment requirement of the tuple is the alignment
+       * requirement of its largest item.
+       */
+      base->alignment = 0;
+      for (m = info->members; m < &info->members[info->n_members]; m++)
+        /* can find the max of a list of "one less than" powers of two
+         * by 'or'ing them
+         */
+        base->alignment |= m->type_info->alignment;
+
+      m--; /* take 'm' back to the last item */
+
+      /* the structure only has a fixed size if no variable-size
+       * offsets are stored and the last item is fixed-sized too (since
+       * an offset is never stored for the last item).
+       */
+      if (m->i == -1 && m->type_info->fixed_size)
+        /* in that case, the fixed size can be found by finding the
+         * start of the last item (in the usual way) and adding its
+         * fixed size.
+         *
+         * if a tuple has a fixed size then it is always a multiple of
+         * the alignment requirement (to make packing into arrays
+         * easier) so we round up to that here.
+         */
+        base->fixed_size =
+          tuple_align (((m->a & m->b) | m->c) + m->type_info->fixed_size,
+                       base->alignment);
+      else
+        /* else, the tuple is not fixed size */
+        base->fixed_size = 0;
+    }
+  else
+    {
+      /* the empty tuple: '()'.
+       *
+       * has a size of 1 and an no alignment requirement.
+       *
+       * It has a size of 1 (not 0) for two practical reasons:
+       *
+       *  1) So we can determine how many of them are in an array
+       *     without dividing by zero or without other tricks.
+       *
+       *  2) Even if we had some trick to know the number of items in
+       *     the array (as EVariant did at one time) this would open a
+       *     potential denial of service attack: an attacker could send
+       *     you an extremely small array (in terms of number of bytes)
+       *     containing trillions of zero-sized items.  If you iterated
+       *     over this array you would effectively infinite-loop your
+       *     program.  By forcing a size of at least one, we bound the
+       *     amount of computation done in response to a message to a
+       *     reasonable function of the size of that message.
+       */
+      base->alignment = 0;
+      base->fixed_size = 1;
+    }
+}
+
+static ContainerInfo *
+tuple_info_new (const EVariantType *type)
+{
+  TupleInfo *info;
+
+  info = g_slice_new (TupleInfo);
+  info->container.info.container_class = TUPLE_INFO_CLASS;
+
+  tuple_allocate_members (type, &info->members, &info->n_members);
+  tuple_generate_table (info);
+  tuple_set_base_info (info);
+
+  return (ContainerInfo *) info;
+}
+
+/* < private >
+ * e_variant_type_info_n_members:
+ * @info: a #EVariantTypeInfo for a tuple or dictionary entry type
+ *
+ * Returns the number of members in a tuple or dictionary entry type.
+ * For a dictionary entry this will always be 2.
+ */
+gsize
+e_variant_type_info_n_members (EVariantTypeInfo *info)
+{
+  return TUPLE_INFO (info)->n_members;
+}
+
+/* < private >
+ * e_variant_type_info_member_info:
+ * @info: a #EVariantTypeInfo for a tuple or dictionary entry type
+ * @index: the member to fetch information for
+ *
+ * Returns the #EVariantMemberInfo for a given member.  See
+ * documentation for that structure for why you would want this
+ * information.
+ *
+ * @index must refer to a valid child (ie: strictly less than
+ * e_variant_type_info_n_members() returns).
+ */
+const EVariantMemberInfo *
+e_variant_type_info_member_info (EVariantTypeInfo *info,
+                                 gsize             index)
+{
+  TupleInfo *tuple_info = TUPLE_INFO (info);
+
+  if (index < tuple_info->n_members)
+    return &tuple_info->members[index];
+
+  return NULL;
+}
+
+/* == new/ref/unref == */
+static GStaticRecMutex e_variant_type_info_lock = G_STATIC_REC_MUTEX_INIT;
+static GHashTable *e_variant_type_info_table;
+
+/* < private >
+ * e_variant_type_info_get:
+ * @type: a #EVariantType
+ *
+ * Returns a reference to a #EVariantTypeInfo for @type.
+ *
+ * If an info structure already exists for this type, a new reference is
+ * returned.  If not, the required calculations are performed and a new
+ * info structure is returned.
+ *
+ * It is appropriate to call e_variant_type_info_unref() on the return
+ * value.
+ */
+EVariantTypeInfo *
+e_variant_type_info_get (const EVariantType *type)
+{
+  char type_char;
+
+  type_char = e_variant_type_peek_string (type)[0];
+
+  if (type_char == E_VARIANT_TYPE_INFO_CHAR_MAYBE ||
+      type_char == E_VARIANT_TYPE_INFO_CHAR_ARRAY ||
+      type_char == E_VARIANT_TYPE_INFO_CHAR_TUPLE ||
+      type_char == E_VARIANT_TYPE_INFO_CHAR_DICT_ENTRY)
+    {
+      EVariantTypeInfo *info;
+      gchar *type_string;
+
+      if G_UNLIKELY (e_variant_type_info_table == NULL)
+        e_variant_type_info_table = g_hash_table_new (g_str_hash,
+                                                      g_str_equal);
+
+      type_string = e_variant_type_dup_string (type);
+
+      g_static_rec_mutex_lock (&e_variant_type_info_lock);
+      info = g_hash_table_lookup (e_variant_type_info_table, type_string);
+
+      if (info == NULL)
+        {
+          ContainerInfo *container;
+
+          if (type_char == E_VARIANT_TYPE_INFO_CHAR_MAYBE ||
+              type_char == E_VARIANT_TYPE_INFO_CHAR_ARRAY)
+            {
+              container = array_info_new (type);
+            }
+          else /* tuple or dict entry */
+            {
+              container = tuple_info_new (type);
+            }
+
+          info = (EVariantTypeInfo *) container;
+          container->type_string = type_string;
+          container->ref_count = 1;
+
+          g_hash_table_insert (e_variant_type_info_table, type_string, info);
+          type_string = NULL;
+        }
+      else
+        e_variant_type_info_ref (info);
+
+      g_static_rec_mutex_unlock (&e_variant_type_info_lock);
+      e_variant_type_info_check (info, 0);
+      g_free (type_string);
+
+      return info;
+    }
+  else
+    {
+      const EVariantTypeInfo *info;
+      int index;
+
+      index = type_char - 'b';
+      g_assert (G_N_ELEMENTS (e_variant_type_info_basic_table) == 24);
+      g_assert_cmpint (0, <=, index);
+      g_assert_cmpint (index, <, 24);
+
+      info = e_variant_type_info_basic_table + index;
+      e_variant_type_info_check (info, 0);
+
+      return (EVariantTypeInfo *) info;
+    }
+}
+
+/* < private >
+ * e_variant_type_info_ref:
+ * @info: a #EVariantTypeInfo
+ *
+ * Adds a reference to @info.
+ */
+EVariantTypeInfo *
+e_variant_type_info_ref (EVariantTypeInfo *info)
+{
+  e_variant_type_info_check (info, 0);
+
+  if (info->container_class)
+    {
+      ContainerInfo *container = (ContainerInfo *) info;
+
+      g_assert_cmpint (container->ref_count, >, 0);
+      g_atomic_int_inc (&container->ref_count);
+    }
+
+  return info;
+}
+
+/* < private >
+ * e_variant_type_info_unref:
+ * @info: a #EVariantTypeInfo
+ *
+ * Releases a reference held on @info.  This may result in @info being
+ * freed.
+ */
+void
+e_variant_type_info_unref (EVariantTypeInfo *info)
+{
+  e_variant_type_info_check (info, 0);
+
+  if (info->container_class)
+    {
+      ContainerInfo *container = (ContainerInfo *) info;
+
+      g_static_rec_mutex_lock (&e_variant_type_info_lock);
+      if (g_atomic_int_dec_and_test (&container->ref_count))
+        {
+          g_hash_table_remove (e_variant_type_info_table,
+                               container->type_string);
+	  g_static_rec_mutex_unlock (&e_variant_type_info_lock);
+
+          g_free (container->type_string);
+
+          if (info->container_class == ARRAY_INFO_CLASS)
+            array_info_free (info);
+
+          else if (info->container_class == TUPLE_INFO_CLASS)
+            tuple_info_free (info);
+
+          else
+            g_assert_not_reached ();
+        }
+      else
+	g_static_rec_mutex_unlock (&e_variant_type_info_lock);
+    }
+}
+
+/* used from the test cases */
+#define assert_no_type_infos() \
+  g_assert_cmpint (g_hash_table_size (e_variant_type_info_table), ==, 0)
diff --git a/libedbus/gvarianttypeinfo.h b/libedbus/gvarianttypeinfo.h
new file mode 100644
index 0000000..455272d
--- /dev/null
+++ b/libedbus/gvarianttypeinfo.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright © 2008 Ryan Lortie
+ * Copyright © 2010 Codethink Limited
+ *
+ * This library 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) any later version.
+ *
+ * This library 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 this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Ryan Lortie <desrt desrt ca>
+ */
+
+#ifndef __E_VARIANT_TYPE_INFO_H__
+#define __E_VARIANT_TYPE_INFO_H__
+
+#include "gvarianttype.h"
+
+#define E_VARIANT_TYPE_INFO_CHAR_MAYBE      'm'
+#define E_VARIANT_TYPE_INFO_CHAR_ARRAY      'a'
+#define E_VARIANT_TYPE_INFO_CHAR_TUPLE      '('
+#define E_VARIANT_TYPE_INFO_CHAR_DICT_ENTRY '{'
+#define E_VARIANT_TYPE_INFO_CHAR_VARIANT    'v'
+#define e_variant_type_info_get_type_char(info) \
+  (e_variant_type_info_get_type_string(info)[0])
+
+typedef struct _EVariantTypeInfo EVariantTypeInfo;
+
+/* < private >
+ * EVariantMemberInfo:
+ *
+ * This structure describes how to construct a EVariant instance
+ * corresponding to a given child of a tuple or dictionary entry in a
+ * very short constant time.  It contains the typeinfo of the child,
+ * along with 4 constants that allow the bounds of the child's
+ * serialised data within the container's serialised data to be found
+ * very efficiently.
+ *
+ * Since dictionary entries are serialised as if they were tuples of 2
+ * items, the term "tuple" will be used here in the general sense to
+ * refer to tuples and dictionary entries.
+ *
+ * BACKGROUND:
+ *   The serialised data for a tuple contains an array of "offsets" at
+ *   the end.  There is one "offset" in this array for each
+ *   variable-sized item in the tuple (except for the last one).  The
+ *   offset points to the end point of that item's serialised data.  The
+ *   procedure for finding the start point is described below.  An
+ *   offset is not needed for the last item because the end point of the
+ *   last item is merely the end point of the container itself (after
+ *   the offsets array has been accounted for).  An offset is not needed
+ *   for fixed-sized items (like integers) because, due to their fixed
+ *   size, the end point is a constant addition to the start point.
+ *
+ *   It is clear that the starting point of a given item in the tuple is
+ *   determined by the items that preceed it in the tuple.  Logically,
+ *   the start point of a particular item in a given type of tuple can
+ *   be determined entirely by the end point of the nearest
+ *   variable-sized item that came before it (or from the start of the
+ *   container itself in case there is no preceeding variable-sized
+ *   item).  In the case of "(isis)" for example, in order to find out
+ *   the start point of the last string, one must start at the end point
+ *   of the first string, align to 4 (for the integer's alignment) and
+ *   then add 4 (for storing the integer).  That's the point where the
+ *   string starts (since no special alignment is required for strings).
+ *
+ *   Of course, this process requires iterating over the types in the
+ *   tuple up to the item of interest.  As it turns out, it is possible
+ *   to determine 3 constants 'a', 'b', and 'c' for each item in the
+ *   tuple, such that, given the ending offset of the nearest previous
+ *   variable-sized item (prev_end), a very simple calculation can be
+ *   performed to determine the start of the item of interest.
+ *
+ * The constants in this structure are used as follows:
+ *
+ * First, among the array of offets contained in the tuple, 'i' is the
+ * index of the offset that refers to the end of the variable-sized item
+ * preceeding the item of interest.  If no variable-sized items preceed
+ * this item, then 'i' will be -1.
+ *
+ * Let 'prev_end' be the end offset of the previous item (or 0 in the
+ * case that there was no such item).  The start address of this item
+ * can then be calculate using 'a', 'b', and 'c':
+ *
+ *    item_start = ((prev_end + a) & b) | c;
+ *
+ * For details about how 'a', 'b' and 'c' are calculated, see the
+ * comments at the point of the implementation in gvariantypeinfo.c.
+ *
+ * The end address of the item is then determined in one of three ways,
+ * according to the 'end_type' field.
+ *
+ *   - FIXED: For fixed sized items, the end address is equal to the
+ *     start address plus the fixed size.
+ *
+ *   - LAST: For the last variable sized item in the tuple, the end
+ *     address is equal to the end address of the tuple, minus the size
+ *     of the offset array.
+ *
+ *   - OFFSET: For other variable sized items, the next index past 'i'
+ *     (ie: 'i + 1') must be consulted to find the end of this item.
+ */
+
+typedef struct
+{
+  EVariantTypeInfo *type_info;
+
+  gsize i, a;
+  gint8 b, c;
+
+  guint8 ending_type;
+} EVariantMemberInfo;
+
+#define E_VARIANT_MEMBER_ENDING_FIXED   0
+#define E_VARIANT_MEMBER_ENDING_LAST    1
+#define E_VARIANT_MEMBER_ENDING_OFFSET  2
+
+/* query */
+G_GNUC_INTERNAL
+const gchar *                   e_variant_type_info_get_type_string     (EVariantTypeInfo   *typeinfo);
+
+G_GNUC_INTERNAL
+void                            e_variant_type_info_query               (EVariantTypeInfo   *typeinfo,
+                                                                         guint              *alignment,
+                                                                         gsize              *size);
+
+/* array */
+G_GNUC_INTERNAL
+EVariantTypeInfo *              e_variant_type_info_element             (EVariantTypeInfo   *typeinfo);
+G_GNUC_INTERNAL
+void                            e_variant_type_info_query_element       (EVariantTypeInfo   *typeinfo,
+                                                                         guint              *alignment,
+                                                                         gsize              *size);
+
+/* structure */
+G_GNUC_INTERNAL
+gsize                           e_variant_type_info_n_members           (EVariantTypeInfo   *typeinfo);
+G_GNUC_INTERNAL
+const EVariantMemberInfo *      e_variant_type_info_member_info         (EVariantTypeInfo   *typeinfo,
+                                                                         gsize               index);
+
+/* new/ref/unref */
+G_GNUC_INTERNAL
+EVariantTypeInfo *              e_variant_type_info_get                 (const EVariantType *type);
+G_GNUC_INTERNAL
+EVariantTypeInfo *              e_variant_type_info_ref                 (EVariantTypeInfo   *typeinfo);
+G_GNUC_INTERNAL
+void                            e_variant_type_info_unref               (EVariantTypeInfo   *typeinfo);
+
+#endif /* __E_VARIANT_TYPE_INFO_H__ */



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