[gjs] Bug 580948 - Add DBus support



commit e6466712a28c29f23e1e3e762c68ccc2d8e339a5
Author: Colin Walters <walters verbum org>
Date:   Thu Apr 30 17:50:37 2009 -0400

    Bug 580948 - Add DBus support
    
    This patch merges in code from Litl implementing a DBus module.
    
    At a high level there are four pieces.  First, the "gjs-dbus"
    library is a C support library for DBus.
    Second, the modules/dbus*.[ch] implement parts of the JavaScript
    API in C.  Third, the modules/dbus.js file fills out the rest
    of the JavaScript API and implementation.  Fourth, there are
    tests and examples.
---
 Makefile-modules.am    |   23 +-
 Makefile-test.am       |    5 +
 Makefile.am            |    4 +-
 configure.ac           |    7 +-
 examples/dbus.js       |   35 +
 gjsdbus/dbus-private.h |   73 ++
 gjsdbus/dbus-proxy.c   |  664 +++++++++++
 gjsdbus/dbus-proxy.h   |   74 ++
 gjsdbus/dbus-signals.c | 1308 +++++++++++++++++++++
 gjsdbus/dbus.c         | 2986 ++++++++++++++++++++++++++++++++++++++++++++++++
 gjsdbus/dbus.h         |  235 ++++
 modules/dbus-exports.c | 1858 ++++++++++++++++++++++++++++++
 modules/dbus-exports.h |   37 +
 modules/dbus-values.c  |  973 ++++++++++++++++
 modules/dbus-values.h  |   53 +
 modules/dbus.c         | 1684 +++++++++++++++++++++++++++
 modules/dbus.h         |   39 +
 modules/dbus.js        |  596 ++++++++++
 test/js/testDbus.js    |  957 ++++++++++++++++
 util/log.c             |    3 +
 util/log.h             |    1 +
 21 files changed, 11611 insertions(+), 4 deletions(-)

diff --git a/Makefile-modules.am b/Makefile-modules.am
index 5f96681..5db31bc 100644
--- a/Makefile-modules.am
+++ b/Makefile-modules.am
@@ -4,9 +4,10 @@ dist_gjstweener_DATA =			\
 	modules/tweener/tweenList.js
 
 dist_gjsjs_DATA += modules/lang.js	\
-                   modules/signals.js
+                   modules/signals.js 	\
+		   modules/dbus.js
 
-gjsnative_LTLIBRARIES += console.la debugger.la gi.la mainloop.la
+gjsnative_LTLIBRARIES += console.la debugger.la gi.la mainloop.la dbusNative.la
 
 JS_NATIVE_MODULE_CFLAGS =	\
         $(AM_CFLAGS)		\
@@ -66,3 +67,21 @@ debugger_la_LDFLAGS = 				\
 debugger_la_SOURCES =		\
 	modules/debugger.h	\
 	modules/debugger.c
+
+dbusNative_la_SOURCES =	\
+	modules/dbus-exports.h	\
+	modules/dbus-values.h	\
+	modules/dbus.h		\
+	modules/dbus-exports.c	\
+	modules/dbus-values.c	\
+	modules/dbus.c
+dbusNative_la_CFLAGS = 					\
+	$(JS_NATIVE_MODULE_CFLAGS) 		\
+	$(GJS_DBUS_CFLAGS)
+dbusNative_la_LIBADD = \
+	libgjs-gi.la				\
+	libgjs-dbus.la				\
+	$(JS_NATIVE_MODULE_LIBADD) 		\
+	$(GJS_DBUS_LIBS)
+dbusNative_la_LDFLAGS = 				\
+	$(JS_NATIVE_MODULE_LDFLAGS)
diff --git a/Makefile-test.am b/Makefile-test.am
index 141046b..42d0073 100644
--- a/Makefile-test.am
+++ b/Makefile-test.am
@@ -72,6 +72,11 @@ test:	$(gjsnative_LTLIBRARIES) ${TEST_PROGS}
 
 check:	test
 
+gdb-check gdb-test: $(gjsnative_LTLIBRARIES) ${TEST_PROGS}
+	failed=; for prog in ${TEST_PROGS}; do \
+	  ${TESTS_ENVIRONMENT} libtool --mode=execute gdb --args $$prog; \
+	  done
+
 valgrind-check valgrind-test: $(gjsnative_LTLIBRARIES) ${TEST_PROGS}
 	@test -z "${TEST_PROGS}" || { \
 	  failed=; for prog in ${TEST_PROGS}; do \
diff --git a/Makefile.am b/Makefile.am
index a1bbf38..9f1117d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,5 +1,6 @@
 bin_PROGRAMS =
 lib_LTLIBRARIES =
+noinst_HEADERS =
 noinst_LTLIBRARIES =
 dist_gjsjs_DATA =
 gjsnative_LTLIBRARIES =
@@ -24,7 +25,7 @@ nobase_gjsinclude_HEADERS =	\
 	gjs/jsapi-util.h	\
 	gjs/mem.h		\
 	gjs/native.h
-noinst_HEADERS =		\
+noinst_HEADERS +=		\
 	gjs/context-jsapi.h	\
 	gjs/profiler.h		\
 	util/crash.h		\
@@ -86,6 +87,7 @@ gjstest_files_with_tests += 	\
 	gjs/stack.c				\
 	util/glib.c
 
+include Makefile-gjsdbus.am
 include Makefile-gi.am
 include Makefile-modules.am
 include Makefile-examples.am
diff --git a/configure.ac b/configure.ac
index bbf75cf..b32ebdc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -193,11 +193,13 @@ fi
 common_packages="gobject-2.0 >= gobject_required_version $JS_PACKAGE"
 gjs_packages="gmodule-2.0 $common_packages"
 gjs_gi_packages="gobject-introspection-1.0 >= 0.6.0 $common_packages"
+gjs_dbus_packages="dbus-glib-1 $common_packages"
 # gjs-tests links against everything
 gjstests_packages="$gjstests_packages $gjs_packages"
 
 PKG_CHECK_MODULES([GJS], [$gjs_packages])
 PKG_CHECK_MODULES([GJS_GI], [$gjs_gi_packages])
+PKG_CHECK_MODULES([GJS_DBUS], [$gjs_dbus_packages])
 PKG_CHECK_MODULES([GJSTESTS], [$gjstests_packages])
 
 if test x"$JS_PACKAGE" = x; then
@@ -207,12 +209,15 @@ if test x"$JS_PACKAGE" = x; then
     GJS_LIBS="$GJS_LIBS $JS_LIBS"
     GJS_GI_CFLAGS="$GJS_GI_CFLAGS $JS_CFLAGS"
     GJS_GI_LIBS="$GJS_GI_LIBS $JS_LIBS"
+    GJS_DBUS_CFLAGS="$GJS_DBUS_CFLAGS $JS_CFLAGS"
+    GJS_DBUS_LIBS="$GJS_DBUS_LIBS $JS_LIBS"
     GJSTESTS_CFLAGS="$GJSTESTS_CFLAGS $JS_CFLAGS"
     GJSTESTS_LIBS="$GJSTEST_LIBS $JS_LIBS"
 fi
 
 GJS_CFLAGS="$GJS_CFLAGS $JS_EXTRA_CFLAGS"
 GJS_GI_CFLAGS="$GJS_GI_CFLAGS $JS_EXTRA_CFLAGS"
+GJS_DBUS_CFLAGS="$GJS_DBUS_CFLAGS $JS_EXTRA_CFLAGS"
 GJSTESTS_CFLAGS="$GJSTESTS_CFLAGS $JS_EXTRA_CFLAGS"
 
 # readline
@@ -265,5 +270,5 @@ gjsnativedir="\${libdir}/gjs-1.0"
 AC_SUBST([gjsjsdir])
 AC_SUBST([gjsnativedir])
 
-AC_CONFIG_FILES([Makefile gjs-1.0.pc gjs-gi-1.0.pc])
+AC_CONFIG_FILES([Makefile gjs-1.0.pc gjs-gi-1.0.pc gjs-dbus-1.0.pc])
 AC_OUTPUT
diff --git a/examples/dbus.js b/examples/dbus.js
new file mode 100644
index 0000000..4ca7601
--- /dev/null
+++ b/examples/dbus.js
@@ -0,0 +1,35 @@
+const DBus = imports.dbus;
+const Mainloop = imports.mainloop;
+
+let bus = DBus.session;
+
+var notifyIface = {
+    name: "org.freedesktop.Notifications",
+    methods: [{ name: "Notify",
+                outSignature: "u",
+                inSignature: "susssasa{sv}i"
+              }
+             ]
+};
+
+function Notify() {
+    this._init();
+};
+
+Notify.prototype = {
+     _init: function() {
+         DBus.session.proxifyObject(this, 'org.freedesktop.Notifications', '/org/freedesktop/Notifications');
+     }
+
+};
+DBus.proxifyPrototype(Notify.prototype,
+                      notifyIface);
+
+let notify = new Notify();
+
+notify.NotifyRemote("test", 0, "", "TestNotify", "Hello from Test Notify", [], {}, 0, function(result, excp) { Mainloop.quit('test'); });
+
+Mainloop.run('test');
+
+
+
diff --git a/gjsdbus/dbus-private.h b/gjsdbus/dbus-private.h
new file mode 100644
index 0000000..3accde9
--- /dev/null
+++ b/gjsdbus/dbus-private.h
@@ -0,0 +1,73 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* Copyright 2008 litl, LLC. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#ifndef __GJS_UTIL_DBUS_PRIVATE_H__
+#define __GJS_UTIL_DBUS_PRIVATE_H__
+
+#include <glib.h>
+#include <gjsdbus/dbus.h>
+#include <gjsdbus/dbus-proxy.h>
+
+G_BEGIN_DECLS
+
+typedef struct {
+    DBusBusType bus_type;
+    void *where_connection_was;
+    GjsDBusProxy *driver_proxy;
+    GHashTable *json_ifaces;
+    GSList *name_ownership_monitors;
+    GHashTable *name_watches;
+
+    GSList *all_signal_watchers;
+
+    /* These signal watcher tables are maps from a
+     * string to a GSList of GjsSignalWatcher,
+     * and they are lazily created if a signal watcher
+     * needs to be looked up by the given key.
+     */
+    GHashTable *signal_watchers_by_unique_sender;
+    GHashTable *signal_watchers_by_path;
+    GHashTable *signal_watchers_by_iface;
+    GHashTable *signal_watchers_by_signal;
+    /* These are matching on well-known name only,
+     * or watching all signals
+     */
+    GSList     *signal_watchers_in_no_table;
+
+} GjsDBusInfo;
+
+GjsDBusInfo*      _gjs_dbus_ensure_info                     (DBusConnection *connection);
+void              _gjs_dbus_dispose_info                    (DBusConnection *connection);
+void              _gjs_dbus_process_pending_signal_watchers (DBusConnection *connection,
+                                                             GjsDBusInfo    *info);
+DBusHandlerResult _gjs_dbus_signal_watch_filter_message     (DBusConnection *connection,
+                                                             DBusMessage    *message,
+                                                             void           *data);
+void              _gjs_dbus_set_matching_name_owner_changed (DBusConnection *connection,
+                                                             const char     *bus_name,
+                                                             gboolean        matched);
+void              _gjs_dbus_ensure_connect_idle             (DBusBusType     bus_type);
+DBusConnection*   _gjs_dbus_get_weak_ref                    (DBusBusType     which_bus);
+
+
+G_END_DECLS
+
+#endif  /* __GJS_UTIL_DBUS_PRIVATE_H__ */
diff --git a/gjsdbus/dbus-proxy.c b/gjsdbus/dbus-proxy.c
new file mode 100644
index 0000000..ae3a355
--- /dev/null
+++ b/gjsdbus/dbus-proxy.c
@@ -0,0 +1,664 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* Copyright 2008 litl, LLC. All Rights Reserved. */
+
+#include <config.h>
+
+#include "dbus-proxy.h"
+#include "dbus.h"
+#include <dbus/dbus-glib-lowlevel.h>
+#include <stdarg.h>
+
+#include "util/log.h"
+
+typedef enum {
+    REPLY_CLOSURE_PLAIN,
+    REPLY_CLOSURE_JSON
+} ReplyClosureType;
+
+typedef struct {
+    GjsDBusProxy *proxy;
+    ReplyClosureType type;
+    union {
+        GjsDBusProxyReplyFunc     plain;
+        GjsDBusProxyJsonReplyFunc json;
+    } func;
+    GjsDBusProxyErrorReplyFunc error_func;
+    void *data;
+    /* this is a debug thing; we want to guarantee
+     * we call exactly 1 time either the reply or error
+     * callback.
+     */
+    guint reply_invoked : 1;
+    guint error_invoked : 1;
+} ReplyClosure;
+
+static void     gjs_dbus_proxy_dispose      (GObject               *object);
+static void     gjs_dbus_proxy_finalize     (GObject               *object);
+static GObject* gjs_dbus_proxy_constructor  (GType                  type,
+                                             guint                  n_construct_properties,
+                                             GObjectConstructParam *construct_params);
+static void     gjs_dbus_proxy_get_property (GObject               *object,
+                                             guint                  prop_id,
+                                             GValue                *value,
+                                             GParamSpec            *pspec);
+static void     gjs_dbus_proxy_set_property (GObject               *object,
+                                             guint                  prop_id,
+                                             const GValue          *value,
+                                             GParamSpec            *pspec);
+
+struct _GjsDBusProxy {
+    GObject parent;
+
+    DBusConnection *connection;
+    char *bus_name;
+    char *object_path;
+    char *iface;
+};
+
+struct _GjsDBusProxyClass {
+    GObjectClass parent;
+};
+
+G_DEFINE_TYPE(GjsDBusProxy, gjs_dbus_proxy, G_TYPE_OBJECT);
+
+#if 0
+enum {
+    LAST_SIGNAL
+};
+
+static int signals[LAST_SIGNAL];
+#endif
+
+enum {
+    PROP_0,
+    PROP_CONNECTION,
+    PROP_BUS_NAME,
+    PROP_OBJECT_PATH,
+    PROP_INTERFACE
+};
+
+static void
+gjs_dbus_proxy_init(GjsDBusProxy *proxy)
+{
+
+}
+
+static void
+gjs_dbus_proxy_class_init(GjsDBusProxyClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+    object_class->dispose = gjs_dbus_proxy_dispose;
+    object_class->finalize = gjs_dbus_proxy_finalize;
+
+    object_class->constructor = gjs_dbus_proxy_constructor;
+    object_class->get_property = gjs_dbus_proxy_get_property;
+    object_class->set_property = gjs_dbus_proxy_set_property;
+
+    g_object_class_install_property(object_class,
+                                    PROP_CONNECTION,
+                                    g_param_spec_boxed("connection",
+                                                       "DBusConnection",
+                                                       "Our connection to the bus",
+                                                       DBUS_TYPE_CONNECTION,
+                                                       G_PARAM_READWRITE));
+    g_object_class_install_property(object_class,
+                                    PROP_BUS_NAME,
+                                    g_param_spec_string("bus-name",
+                                                        "Bus Name",
+                                                        "Name of app on the bus",
+                                                        NULL,
+                                                        G_PARAM_READWRITE));
+    g_object_class_install_property(object_class,
+                                    PROP_OBJECT_PATH,
+                                    g_param_spec_string("object-path",
+                                                        "Object Path",
+                                                        "Object's dbus path",
+                                                        NULL,
+                                                        G_PARAM_READWRITE));
+
+    g_object_class_install_property(object_class,
+                                    PROP_INTERFACE,
+                                    g_param_spec_string("interface",
+                                                        "Interface",
+                                                        "Interface to invoke methods on",
+                                                        NULL,
+                                                        G_PARAM_READWRITE));
+}
+
+static void
+gjs_dbus_proxy_dispose(GObject *object)
+{
+    GjsDBusProxy *proxy;
+
+    proxy = GJS_DBUS_PROXY(object);
+
+    if (proxy->connection) {
+        dbus_connection_unref(proxy->connection);
+        proxy->connection = NULL;
+    }
+
+    if (proxy->bus_name) {
+        g_free(proxy->bus_name);
+        proxy->bus_name = NULL;
+    }
+
+    if (proxy->object_path) {
+        g_free(proxy->object_path);
+        proxy->object_path = NULL;
+    }
+
+    if (proxy->iface) {
+        g_free(proxy->iface);
+        proxy->iface = NULL;
+    }
+
+    G_OBJECT_CLASS(gjs_dbus_proxy_parent_class)->dispose(object);
+}
+
+static void
+gjs_dbus_proxy_finalize(GObject *object)
+{
+
+    G_OBJECT_CLASS(gjs_dbus_proxy_parent_class)->finalize(object);
+}
+
+static GObject*
+gjs_dbus_proxy_constructor (GType                  type,
+                            guint                  n_construct_properties,
+                            GObjectConstructParam *construct_params)
+{
+    GObject *object;
+    GjsDBusProxy *proxy;
+
+    object = (* G_OBJECT_CLASS (gjs_dbus_proxy_parent_class)->constructor) (type,
+                                                                            n_construct_properties,
+                                                                            construct_params);
+
+    proxy = GJS_DBUS_PROXY(object);
+
+    return object;
+}
+
+static void
+gjs_dbus_proxy_get_property (GObject     *object,
+                             guint        prop_id,
+                             GValue      *value,
+                             GParamSpec  *pspec)
+{
+    GjsDBusProxy *proxy;
+
+    proxy = GJS_DBUS_PROXY (object);
+
+    switch (prop_id) {
+    case PROP_CONNECTION:
+        g_value_set_boxed(value, proxy->connection);
+        break;
+    case PROP_BUS_NAME:
+        g_value_set_string(value, proxy->bus_name);
+        break;
+    case PROP_OBJECT_PATH:
+        g_value_set_string(value, proxy->object_path);
+        break;
+    case PROP_INTERFACE:
+        g_value_set_string(value, proxy->iface);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+gjs_dbus_proxy_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+    GjsDBusProxy *proxy;
+
+    proxy = GJS_DBUS_PROXY (object);
+
+    switch (prop_id) {
+    case PROP_CONNECTION:
+        if (proxy->connection != NULL) {
+            g_warning("Cannot change GjsDBusProxy::connection after it's set");
+            return;
+        }
+        proxy->connection = dbus_connection_ref(g_value_get_boxed(value));
+        break;
+    case PROP_BUS_NAME:
+        if (proxy->bus_name != NULL) {
+            g_warning("Cannot change GjsDBusProxy::bus-name after it's set");
+            return;
+        }
+        proxy->bus_name = g_value_dup_string(value);
+        break;
+    case PROP_OBJECT_PATH:
+        if (proxy->object_path != NULL) {
+            g_warning("Cannot change GjsDBusProxy::object-path after it's set");
+            return;
+        }
+        proxy->object_path = g_value_dup_string(value);
+        break;
+    case PROP_INTERFACE:
+        if (proxy->iface != NULL) {
+            g_warning("Cannot change GjsDBusProxy::interface after it's set");
+            return;
+        }
+        proxy->iface = g_value_dup_string(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        break;
+    }
+}
+
+/* bus_name can be NULL if not going through a bus, and
+ * iface is allowed to be NULL but likely should not be.
+ */
+GjsDBusProxy*
+gjs_dbus_proxy_new(DBusConnection *connection,
+                   const char     *bus_name,
+                   const char     *object_path,
+                   const char     *iface)
+{
+    GjsDBusProxy *proxy;
+
+    g_return_val_if_fail(connection != NULL, NULL);
+    g_return_val_if_fail(object_path != NULL, NULL);
+
+    proxy = g_object_new(GJS_TYPE_DBUS_PROXY,
+                         "connection", connection,
+                         "bus-name", bus_name,
+                         "object-path", object_path,
+                         "interface", iface,
+                         NULL);
+
+    return proxy;
+}
+
+DBusConnection*
+gjs_dbus_proxy_get_connection(GjsDBusProxy *proxy)
+{
+    return proxy->connection;
+}
+
+const char*
+gjs_dbus_proxy_get_bus_name(GjsDBusProxy *proxy)
+{
+    return proxy->bus_name;
+}
+
+DBusMessage*
+gjs_dbus_proxy_new_method_call(GjsDBusProxy *proxy,
+                               const char   *method_name)
+{
+    DBusMessage *message;
+
+    message = dbus_message_new_method_call(proxy->bus_name,
+                                           proxy->object_path,
+                                           proxy->iface,
+                                           method_name);
+    if (message == NULL)
+        g_error("no memory");
+
+    /* We don't want methods to auto-start services...  if a service
+     * needs starting or restarting, we want to do so explicitly so we
+     * can do it in an orderly and predictable way.
+     */
+    dbus_message_set_auto_start(message, FALSE);
+
+    return message;
+}
+
+DBusMessage*
+gjs_dbus_proxy_new_json_call(GjsDBusProxy    *proxy,
+                             const char      *method_name,
+                             DBusMessageIter *arg_iter,
+                             DBusMessageIter *dict_iter)
+{
+    DBusMessage *message;
+
+    message = gjs_dbus_proxy_new_method_call(proxy, method_name);
+
+    dbus_message_iter_init_append(message, arg_iter);
+    dbus_message_iter_open_container(arg_iter, DBUS_TYPE_ARRAY, "{sv}", dict_iter);
+
+    return message;
+}
+
+static ReplyClosure*
+reply_closure_new(GjsDBusProxy              *proxy,
+                  GjsDBusProxyReplyFunc      plain_func,
+                  GjsDBusProxyJsonReplyFunc  json_func,
+                  GjsDBusProxyErrorReplyFunc error_func,
+                  void                      *data)
+{
+    ReplyClosure *c;
+
+    c = g_slice_new0(ReplyClosure);
+
+    c->proxy = g_object_ref(proxy);
+
+    g_assert(!(plain_func && json_func));
+
+    if (plain_func != NULL) {
+        c->type = REPLY_CLOSURE_PLAIN;
+        c->func.plain = plain_func;
+    } else {
+        c->type = REPLY_CLOSURE_JSON;
+        c->func.json = json_func;
+    }
+
+    c->error_func = error_func;
+    c->data = data;
+
+    return c;
+}
+
+static void
+reply_closure_free(ReplyClosure *c)
+{
+    /* call exactly one of these */
+    g_assert(!(c->error_invoked &&
+               c->reply_invoked));
+
+    if (!(c->error_invoked ||
+          c->reply_invoked)) {
+        c->error_invoked = TRUE;
+        if (c->error_func) {
+            (* c->error_func) (c->proxy, DBUS_ERROR_FAILED,
+                               "Pending call was freed (due to dbus_shutdown() probably) before it was ever notified",
+                               c->data);
+        }
+    }
+
+    g_object_unref(c->proxy);
+    g_slice_free(ReplyClosure, c);
+}
+
+static void
+reply_closure_invoke_error(ReplyClosure *c,
+                           DBusMessage  *reply)
+{
+    g_assert(dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR);
+
+    g_assert(!c->reply_invoked);
+    g_assert(!c->error_invoked);
+
+    c->error_invoked = TRUE;
+
+    if (c->error_func) {
+        DBusError derror;
+
+        dbus_error_init(&derror);
+
+        dbus_set_error_from_message(&derror, reply);
+
+        (* c->error_func) (c->proxy, derror.name,
+                           derror.message,
+                           c->data);
+
+        dbus_error_free(&derror);
+    }
+}
+
+static void
+reply_closure_invoke(ReplyClosure *c,
+                     DBusMessage  *reply)
+{
+    if (c->type == REPLY_CLOSURE_PLAIN) {
+        if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN) {
+            g_assert(!c->reply_invoked);
+            g_assert(!c->error_invoked);
+
+            c->reply_invoked = TRUE;
+
+            if (c->func.plain != NULL) {
+                (* c->func.plain) (c->proxy,
+                                   reply,
+                                   c->data);
+            }
+        } else if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
+            reply_closure_invoke_error(c, reply);
+        } else {
+            g_assert(!c->reply_invoked);
+            g_assert(!c->error_invoked);
+
+            c->error_invoked = TRUE;
+
+            if (c->error_func) {
+                (* c->error_func) (c->proxy, DBUS_ERROR_FAILED,
+                                   "Got weird message type back as a reply",
+                                   c->data);
+            }
+        }
+    } else if (c->type == REPLY_CLOSURE_JSON) {
+        if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN) {
+            if (dbus_message_has_signature(reply, "a{sv}")) {
+                g_assert(!c->reply_invoked);
+                g_assert(!c->error_invoked);
+
+                c->reply_invoked = TRUE;
+
+                if (c->func.json) {
+                    DBusMessageIter arg_iter;
+                    DBusMessageIter dict_iter;
+
+                    dbus_message_iter_init(reply, &arg_iter);
+                    dbus_message_iter_recurse(&arg_iter, &dict_iter);
+
+                    (* c->func.json) (c->proxy,
+                                      reply,
+                                      &dict_iter,
+                                      c->data);
+                }
+            } else {
+                g_assert(!c->reply_invoked);
+                g_assert(!c->error_invoked);
+
+                c->error_invoked = TRUE;
+
+                if (c->error_func) {
+                    (* c->error_func) (c->proxy,
+                                       DBUS_ERROR_FAILED,
+                                       "Message we got back did not have the right signature",
+                                       c->data);
+                }
+            }
+        } else if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
+            reply_closure_invoke_error(c, reply);
+        } else {
+            g_assert(!c->reply_invoked);
+            g_assert(!c->error_invoked);
+
+            c->error_invoked = TRUE;
+
+            if (c->error_func) {
+                (* c->error_func) (c->proxy, DBUS_ERROR_FAILED,
+                                   "Got weird message type back as a reply",
+                                   c->data);
+            }
+        }
+    } else {
+        g_assert_not_reached();
+    }
+}
+
+
+static gboolean
+failed_to_send_idle(void *data)
+{
+    ReplyClosure *c;
+
+    c = data;
+
+    g_assert(!c->reply_invoked);
+    g_assert(!c->error_invoked);
+
+    c->error_invoked = TRUE;
+
+    if (c->error_func) {
+        (* c->error_func) (c->proxy,
+                           DBUS_ERROR_NO_MEMORY,
+                           "Unable to send method call",
+                           c->data);
+    }
+
+    reply_closure_free(c);
+
+    return FALSE;
+}
+
+
+static void
+pending_call_notify(DBusPendingCall *pending,
+                    void            *user_data)
+{
+    DBusMessage *reply;
+    ReplyClosure *c;
+
+    gjs_debug(GJS_DEBUG_DBUS, "GjsDBusProxy received reply to pending call");
+
+    c = user_data;
+
+    /* reply may be NULL if none received? I think it may never be if
+     * we've already been notified, but be safe here.
+     */
+    reply = dbus_pending_call_steal_reply(pending);
+
+    if (reply) {
+        reply_closure_invoke(c, reply);
+
+        dbus_message_unref(reply);
+    } else {
+        /* I think libdbus won't let this happen, but to be safe... */
+        g_assert(!c->reply_invoked);
+        g_assert(!c->error_invoked);
+
+        c->error_invoked = TRUE;
+
+        if (c->error_func) {
+            (* c->error_func) (c->proxy,
+                               DBUS_ERROR_TIMED_OUT,
+                               "Did not receive a reply or error",
+                               c->data);
+        }
+    }
+
+    /* The closure should be freed along with the pending call */
+}
+
+static void
+pending_call_free_data(void *data)
+{
+    ReplyClosure *c = data;
+    reply_closure_free(c);
+}
+
+static void
+gjs_dbus_proxy_send_internal(GjsDBusProxy              *proxy,
+                             DBusMessage               *message,
+                             GjsDBusProxyReplyFunc      plain_func,
+                             GjsDBusProxyJsonReplyFunc  json_func,
+                             GjsDBusProxyErrorReplyFunc error_func,
+                             void                      *data)
+{
+    ReplyClosure *c;
+    DBusPendingCall *pending;
+
+    if (!(plain_func || json_func || error_func)) {
+        /* Fire and forget! */
+
+        gjs_debug(GJS_DEBUG_DBUS, "Firing and forgetting dbus proxy call");
+
+        dbus_connection_send(proxy->connection, message, NULL);
+        return;
+    }
+
+    gjs_debug(GJS_DEBUG_DBUS, 
+              "Sending dbus proxy call %s",
+              dbus_message_get_member(message));
+
+    c = reply_closure_new(proxy, plain_func, json_func, error_func, data);
+    pending = NULL;
+    if (!dbus_connection_send_with_reply(proxy->connection, message, &pending, -1) ||
+        pending == NULL) {
+
+        gjs_debug(GJS_DEBUG_DBUS, "Failed to send call, will report error in idle handler");
+
+        /* Send an error on return to main loop */
+        g_idle_add(failed_to_send_idle, c);
+        return;
+    }
+
+    dbus_pending_call_set_notify(pending, pending_call_notify, c,
+                                 pending_call_free_data);
+
+    dbus_pending_call_unref(pending); /* DBusConnection should still hold a ref until it's completed */
+}
+
+void
+gjs_dbus_proxy_send(GjsDBusProxy              *proxy,
+                    DBusMessage               *message,
+                    GjsDBusProxyReplyFunc      reply_func,
+                    GjsDBusProxyErrorReplyFunc error_func,
+                    void                      *data)
+{
+    gjs_dbus_proxy_send_internal(proxy, message, reply_func, NULL, error_func, data);
+}
+
+static void
+append_entries_from_valist(DBusMessageIter *dict_iter,
+                           const char      *first_key,
+                           va_list          args)
+{
+    const char *key;
+    int dbus_type;
+    void *value_p;
+
+    key = first_key;
+    dbus_type = va_arg(args, int);
+    value_p = va_arg(args, void*);
+
+    gjs_dbus_append_json_entry(dict_iter, key, dbus_type, value_p);
+
+    key = va_arg(args, const char*);
+    while (key != NULL) {
+        dbus_type = va_arg(args, int);
+        value_p = va_arg(args, void*);
+
+        gjs_dbus_append_json_entry(dict_iter, key, dbus_type, value_p);
+
+        key = va_arg(args, const char*);
+    }
+}
+
+void
+gjs_dbus_proxy_call_json_async (GjsDBusProxy              *proxy,
+                                const char                *method_name,
+                                GjsDBusProxyJsonReplyFunc  reply_func,
+                                GjsDBusProxyErrorReplyFunc error_func,
+                                void                      *data,
+                                const char                *first_key,
+                                ...)
+{
+    DBusMessageIter arg_iter, dict_iter;
+    DBusMessage *message;
+    va_list args;
+
+    message = gjs_dbus_proxy_new_json_call(proxy, method_name, &arg_iter, &dict_iter);
+
+    if (first_key != NULL) {
+        va_start(args, first_key);
+        append_entries_from_valist(&dict_iter, first_key, args);
+        va_end(args);
+    }
+
+    dbus_message_iter_close_container(&arg_iter, &dict_iter);
+
+    gjs_dbus_proxy_send_internal(proxy, message, NULL, reply_func, error_func, data);
+
+    dbus_message_unref(message);
+}
diff --git a/gjsdbus/dbus-proxy.h b/gjsdbus/dbus-proxy.h
new file mode 100644
index 0000000..1632da2
--- /dev/null
+++ b/gjsdbus/dbus-proxy.h
@@ -0,0 +1,74 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* Copyright 2008 litl, LLC. All Rights Reserved. */
+
+#ifndef __GJS_UTIL_DBUS_PROXY_H__
+#define __GJS_UTIL_DBUS_PROXY_H__
+
+#include <gio/gio.h>
+#include <dbus/dbus.h>
+
+G_BEGIN_DECLS
+
+
+typedef struct _GjsDBusProxy      GjsDBusProxy;
+typedef struct _GjsDBusProxyClass GjsDBusProxyClass;
+
+typedef void (* GjsDBusProxyReplyFunc)      (GjsDBusProxy    *proxy,
+                                             DBusMessage     *message,
+                                             void            *data);
+typedef void (* GjsDBusProxyJsonReplyFunc)  (GjsDBusProxy    *proxy,
+                                             DBusMessage     *message,
+                                             DBusMessageIter *return_value_iter,
+                                             void            *data);
+typedef void (* GjsDBusProxyErrorReplyFunc) (GjsDBusProxy    *proxy,
+                                             const char      *error_name,
+                                             const char      *error_message,
+                                             void            *data);
+
+#define GJS_TYPE_DBUS_PROXY              (gjs_dbus_proxy_get_type ())
+#define GJS_DBUS_PROXY(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), GJS_TYPE_DBUS_PROXY, GjsDBusProxy))
+#define GJS_DBUS_PROXY_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GJS_TYPE_DBUS_PROXY, GjsDBusProxyClass))
+#define GJS_IS_DBUS_PROXY(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), GJS_TYPE_DBUS_PROXY))
+#define GJS_IS_DBUS_PROXY_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GJS_TYPE_DBUS_PROXY))
+#define GJS_DBUS_PROXY_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GJS_TYPE_DBUS_PROXY, GjsDBusProxyClass))
+
+GType           gjs_dbus_proxy_get_type      (void) G_GNUC_CONST;
+
+
+GjsDBusProxy*   gjs_dbus_proxy_new             (DBusConnection             *connection,
+                                                const char                 *bus_name,
+                                                const char                 *object_path,
+                                                const char                 *iface);
+DBusConnection* gjs_dbus_proxy_get_connection  (GjsDBusProxy               *proxy);
+const char*     gjs_dbus_proxy_get_bus_name    (GjsDBusProxy               *proxy);
+DBusMessage*    gjs_dbus_proxy_new_method_call (GjsDBusProxy               *proxy,
+                                                const char                 *method_name);
+DBusMessage*    gjs_dbus_proxy_new_json_call   (GjsDBusProxy               *proxy,
+                                                const char                 *method_name,
+                                                DBusMessageIter            *arg_iter,
+                                                DBusMessageIter            *dict_iter);
+void            gjs_dbus_proxy_send            (GjsDBusProxy               *proxy,
+                                                DBusMessage                *message,
+                                                GjsDBusProxyReplyFunc       reply_func,
+                                                GjsDBusProxyErrorReplyFunc  error_func,
+                                                void                       *data);
+
+/* varargs are like:
+ *
+ *   key1, dbus_type_1, &value_1,
+ *   key2, dbus_type_2, &value_2,
+ *   NULL
+ *
+ * Basic types only (no arrays)
+ */
+void          gjs_dbus_proxy_call_json_async (GjsDBusProxy              *proxy,
+                                              const char                *method_name,
+                                              GjsDBusProxyJsonReplyFunc  reply_func,
+                                              GjsDBusProxyErrorReplyFunc error_func,
+                                              void                      *data,
+                                              const char                *first_key,
+                                              ...);
+
+G_END_DECLS
+
+#endif  /* __GJS_UTIL_DBUS_PROXY_H__ */
diff --git a/gjsdbus/dbus-signals.c b/gjsdbus/dbus-signals.c
new file mode 100644
index 0000000..cbe2e15
--- /dev/null
+++ b/gjsdbus/dbus-signals.c
@@ -0,0 +1,1308 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* Copyright 2008 litl, LLC. All Rights Reserved. */
+
+#include <config.h>
+
+#include "dbus-private.h"
+#include "util/log.h"
+
+#include <string.h>
+
+#define INVALID_SIGNAL_ID (-1)
+
+typedef struct {
+    DBusBusType            bus_type;
+    int                    refcount;
+    char                  *sender;
+    char                  *path;
+    char                  *iface;
+    char                  *name;
+    GjsDBusSignalHandler   handler;
+    void                  *data;
+    GDestroyNotify         data_dnotify;
+    int                    id;
+    unsigned int           matching : 1;
+    unsigned int           destroyed : 1;
+} GjsSignalWatcher;
+
+static GSList *pending_signal_watchers = NULL;
+
+static void signal_watcher_remove (DBusConnection   *connection,
+                                   GjsDBusInfo      *info,
+                                   GjsSignalWatcher *watcher);
+
+
+static int global_handler_id = 0;
+
+static GjsSignalWatcher*
+signal_watcher_new(DBusBusType                  bus_type,
+                   const char                  *sender,
+                   const char                  *path,
+                   const char                  *iface,
+                   const char                  *name,
+                   GjsDBusSignalHandler         handler,
+                   void                        *data,
+                   GDestroyNotify               data_dnotify)
+{
+    GjsSignalWatcher *watcher;
+
+    watcher = g_slice_new0(GjsSignalWatcher);
+
+    watcher->refcount = 1;
+
+    watcher->bus_type = bus_type;
+    watcher->sender = g_strdup(sender);
+    watcher->path = g_strdup(path);
+    watcher->iface = g_strdup(iface);
+    watcher->name = g_strdup(name);
+    watcher->handler = handler;
+    watcher->id = global_handler_id++;
+    watcher->data = data;
+    watcher->data_dnotify = data_dnotify;
+
+    return watcher;
+}
+
+static void
+signal_watcher_dnotify(GjsSignalWatcher *watcher)
+{
+    if (watcher->data_dnotify != NULL) {
+        (* watcher->data_dnotify) (watcher->data);
+        watcher->data_dnotify = NULL;
+    }
+    watcher->destroyed = TRUE;
+}
+
+static void
+signal_watcher_ref(GjsSignalWatcher *watcher)
+{
+    watcher->refcount += 1;
+}
+
+static void
+signal_watcher_unref(GjsSignalWatcher *watcher)
+{
+    watcher->refcount -= 1;
+
+    if (watcher->refcount == 0) {
+        signal_watcher_dnotify(watcher);
+
+        g_free(watcher->sender);
+        g_free(watcher->path);
+        g_free(watcher->iface);
+        g_free(watcher->name);
+
+        g_slice_free(GjsSignalWatcher, watcher);
+    }
+}
+
+static char*
+signal_watcher_build_match_rule(GjsSignalWatcher *watcher)
+{
+    GString *s;
+
+    s = g_string_new("type='signal'");
+
+    if (watcher->sender) {
+        g_string_append_printf(s, ",sender='%s'", watcher->sender);
+    }
+
+    if (watcher->path) {
+        g_string_append_printf(s, ",path='%s'", watcher->path);
+    }
+
+    if (watcher->iface) {
+        g_string_append_printf(s, ",interface='%s'", watcher->iface);
+    }
+
+    if (watcher->name) {
+        g_string_append_printf(s, ",member='%s'", watcher->name);
+    }
+
+    return g_string_free(s, FALSE);
+}
+
+
+static GSList*
+signal_watcher_table_lookup(GHashTable *table,
+                            const char *key)
+{
+    if (table == NULL) {
+        return NULL;
+    }
+
+    return g_hash_table_lookup(table, key);
+}
+
+static void
+signal_watcher_list_free(void *data)
+{
+    GSList *l = data;
+    while (l != NULL) {
+        GSList *next = l->next;
+        signal_watcher_unref(l->data);
+        g_slist_free_1(l);
+        l = next;
+    }
+}
+
+static void
+signal_watcher_table_add(GHashTable      **table_p,
+                         const char       *key,
+                         GjsSignalWatcher *watcher)
+{
+    GSList *list;
+    char *original_key;
+
+    if (*table_p == NULL) {
+        list = NULL;
+        original_key = g_strdup(key);
+        *table_p = g_hash_table_new_full(g_str_hash,
+                                         g_str_equal,
+                                         g_free,
+                                         signal_watcher_list_free);
+    } else {
+        if (!g_hash_table_lookup_extended(*table_p,
+                                          key,
+                                          (gpointer*)&original_key,
+                                          (gpointer*)&list)) {
+            original_key = g_strdup(key);
+            list = NULL;
+        }
+    }
+
+    list = g_slist_prepend(list, watcher);
+    signal_watcher_ref(watcher);
+
+    g_hash_table_steal(*table_p, key);
+    g_hash_table_insert(*table_p, original_key, list);
+}
+
+static void
+signal_watcher_table_remove(GHashTable       *table,
+                            const char       *key,
+                            GjsSignalWatcher *watcher)
+{
+    GSList *list;
+    GSList *l;
+    char *original_key;
+
+    if (table == NULL)
+        return; /* Never lazily-created the table, nothing ever added */
+
+    if (!g_hash_table_lookup_extended(table,
+                                      key,
+                                      (gpointer*)&original_key,
+                                      (gpointer*)&list)) {
+        return;
+    }
+
+    l = g_slist_find(list, watcher);
+    if (!l)
+        return; /* we don't want to unref if we weren't in this table */
+
+    list = g_slist_delete_link(list, l);
+
+    g_hash_table_steal(table, key);
+    if (list != NULL) {
+        g_hash_table_insert(table, original_key, list);
+    } else {
+        g_free(original_key);
+    }
+
+    signal_watcher_unref(watcher);
+}
+
+static void
+signal_emitter_name_appeared(DBusConnection *connection,
+                             const char     *name,
+                             const char     *new_owner_unique_name,
+                             void           *data)
+{
+    /* We don't need to do anything here, we installed a name watch so
+     * we could call gjs_dbus_get_watched_name_owner() to dispatch
+     * signals, and to get destroy notification on unique names.
+     */
+}
+
+static void
+signal_emitter_name_vanished(DBusConnection *connection,
+                             const char     *name,
+                             const char     *old_owner_unique_name,
+                             void           *data)
+{
+  gjs_debug(GJS_DEBUG_DBUS, "Signal emitter '%s' is now gone",
+            name);
+
+    /* If a watcher is matching on a unique name sender, once the unique
+     * name goes away, the watcher can never see anything so nuke it.
+     */
+    if (*name == ':') {
+        GSList *list;
+        GjsDBusInfo *info;
+
+        info = _gjs_dbus_ensure_info(connection);
+
+        list = signal_watcher_table_lookup(info->signal_watchers_by_unique_sender,
+                                           name);
+
+        if (list == NULL)
+            return;
+
+        /* copy the list since we're about to remove stuff from it
+         * in signal_watcher_remove
+         */
+        list = g_slist_copy(list);
+        while (list != NULL) {
+            signal_watcher_remove(connection, info, list->data);
+            list = g_slist_delete_link(list, list);
+        }
+    }
+}
+
+static GjsDBusWatchNameFuncs signal_emitter_name_funcs = {
+    signal_emitter_name_appeared,
+    signal_emitter_name_vanished
+};
+
+static void
+signal_watcher_set_matching(DBusConnection   *connection,
+                            GjsSignalWatcher *watcher,
+                            gboolean          matching)
+{
+    char *rule;
+
+    if (watcher->matching == (matching != FALSE)) {
+        return;
+    }
+
+    /* Never add match on a destroyed signal watcher */
+    if (watcher->destroyed && matching)
+        return;
+
+    /* We can't affect match rules if not connected */
+    if (!dbus_connection_get_is_connected(connection)) {
+        return;
+    }
+
+    watcher->matching = matching != FALSE;
+
+    rule = signal_watcher_build_match_rule(watcher);
+
+    if (matching)
+        dbus_bus_add_match(connection,
+                           rule, NULL); /* asking for error would make this block */
+    else
+        dbus_bus_remove_match(connection, rule, NULL);
+
+    g_free(rule);
+
+    if (watcher->sender) {
+        /* If the signal is from a well-known name, we have to add
+         * a name watch to know who owns that name.
+         *
+         * If the signal is from a unique name, we want to destroy
+         * the watcher if the unique name goes away
+         */
+        if (matching) {
+            gjs_dbus_watch_name(watcher->bus_type,
+                                watcher->sender,
+                                0,
+                                &signal_emitter_name_funcs,
+                                NULL);
+        } else {
+            gjs_dbus_unwatch_name(watcher->bus_type,
+                                  watcher->sender,
+                                  &signal_emitter_name_funcs,
+                                  NULL);
+        }
+    }
+}
+
+static void
+signal_watcher_add(DBusConnection   *connection,
+                   GjsDBusInfo      *info,
+                   GjsSignalWatcher *watcher)
+{
+    gboolean in_some_table;
+
+    signal_watcher_set_matching(connection, watcher, TRUE);
+
+    info->all_signal_watchers = g_slist_prepend(info->all_signal_watchers, watcher);
+    signal_watcher_ref(watcher);
+
+    in_some_table = FALSE;
+
+    if (watcher->sender && *(watcher->sender) == ':') {
+        signal_watcher_table_add(&info->signal_watchers_by_unique_sender,
+                                 watcher->sender,
+                                 watcher);
+        in_some_table = TRUE;
+    }
+
+    if (watcher->path) {
+        signal_watcher_table_add(&info->signal_watchers_by_path,
+                                 watcher->path,
+                                 watcher);
+        in_some_table = TRUE;
+    }
+
+    if (watcher->iface) {
+        signal_watcher_table_add(&info->signal_watchers_by_iface,
+                                 watcher->iface,
+                                 watcher);
+        in_some_table = TRUE;
+    }
+
+    if (watcher->name) {
+        signal_watcher_table_add(&info->signal_watchers_by_signal,
+                                 watcher->name,
+                                 watcher);
+        in_some_table = TRUE;
+    }
+
+    if (!in_some_table) {
+        info->signal_watchers_in_no_table =
+            g_slist_prepend(info->signal_watchers_in_no_table,
+                            watcher);
+        signal_watcher_ref(watcher);
+    }
+}
+
+static void
+signal_watcher_remove(DBusConnection   *connection,
+                      GjsDBusInfo      *info,
+                      GjsSignalWatcher *watcher)
+{
+    gboolean in_some_table;
+
+    signal_watcher_set_matching(connection, watcher, FALSE);
+
+    info->all_signal_watchers = g_slist_remove(info->all_signal_watchers, watcher);
+
+    in_some_table = FALSE;
+
+    if (watcher->sender && *(watcher->sender) == ':') {
+        signal_watcher_table_remove(info->signal_watchers_by_unique_sender,
+                                    watcher->sender,
+                                    watcher);
+        in_some_table = TRUE;
+    }
+
+    if (watcher->path) {
+        signal_watcher_table_remove(info->signal_watchers_by_path,
+                                    watcher->path,
+                                    watcher);
+        in_some_table = TRUE;
+    }
+
+    if (watcher->iface) {
+        signal_watcher_table_remove(info->signal_watchers_by_iface,
+                                    watcher->iface,
+                                    watcher);
+        in_some_table = TRUE;
+    }
+
+    if (watcher->name) {
+        signal_watcher_table_remove(info->signal_watchers_by_signal,
+                                    watcher->name,
+                                    watcher);
+        in_some_table = TRUE;
+    }
+
+    if (!in_some_table) {
+        info->signal_watchers_in_no_table =
+            g_slist_remove(info->signal_watchers_in_no_table,
+                           watcher);
+        signal_watcher_unref(watcher);
+    }
+
+    /* Destroy-notify before dropping last ref for a little more safety
+     * (avoids "resurrection" issues), and to ensure we call the destroy
+     * notifier even if we don't finish finalizing just yet.
+     */
+    signal_watcher_dnotify(watcher);
+
+    signal_watcher_unref(watcher);
+}
+
+/* This is called before we notify the app that the connection is open,
+ * to add match rules. It must add the match rules, but MUST NOT
+ * invoke application callbacks since the "connection opened"
+ * callback needs to be first.
+ */
+void
+_gjs_dbus_process_pending_signal_watchers(DBusConnection *connection,
+                                          GjsDBusInfo    *info)
+{
+    GSList *remaining;
+
+    remaining = NULL;
+    while (pending_signal_watchers) {
+        GjsSignalWatcher *watcher = pending_signal_watchers->data;
+        pending_signal_watchers = g_slist_delete_link(pending_signal_watchers,
+                                                      pending_signal_watchers);
+
+        if (watcher->bus_type == info->bus_type) {
+            /* Transfer to the non-pending GjsDBusInfo */
+            signal_watcher_add(connection, info, watcher);
+            signal_watcher_unref(watcher);
+        } else {
+            remaining = g_slist_prepend(remaining, watcher);
+        }
+    }
+
+    /* keep the order deterministic by reversing, though I don't know
+     * of a reason it matters.
+     */
+    pending_signal_watchers = g_slist_reverse(remaining);
+}
+
+static void
+signal_watchers_disconnected(DBusConnection *connection,
+                             GjsDBusInfo    *info)
+{
+    /* None should be pending on this bus, because at start of
+     * _gjs_dbus_signal_watch_filter_message() we process all the pending ones.
+     * However there could be stuff in pending_signal_watchers for
+     * another bus. Anyway bottom line we can ignore pending_signal_watchers
+     * in here.
+     */
+    GSList *list;
+    GSList *destroyed;
+
+    /* Build a separate list to destroy to avoid re-entrancy as we are
+     * walking the list
+     */
+    destroyed = NULL;
+    for (list = info->all_signal_watchers;
+         list != NULL;
+         list = list->next) {
+        GjsSignalWatcher *watcher = list->data;
+        if (watcher->sender && *(watcher->sender) == ':') {
+            destroyed = g_slist_prepend(destroyed,
+                                        watcher);
+            signal_watcher_ref(watcher);
+        }
+    }
+
+    while (destroyed != NULL) {
+        GjsSignalWatcher *watcher = destroyed->data;
+        destroyed = g_slist_delete_link(destroyed, destroyed);
+
+        signal_watcher_remove(connection, info, watcher);
+        signal_watcher_unref(watcher);
+    }
+}
+
+static void
+concat_candidates(GSList    **candidates_p,
+                  GHashTable *table,
+                  const char *key)
+{
+    GSList *list;
+
+    list = signal_watcher_table_lookup(table, key);
+    if (list == NULL)
+        return;
+
+    *candidates_p = g_slist_concat(*candidates_p,
+                                   g_slist_copy(list));
+}
+
+static int
+direct_cmp(gconstpointer a,
+           gconstpointer b)
+{
+    /* gcc dislikes pointer math on void* so cast */
+    return ((const char*)a) - ((const char*)b);
+}
+
+static gboolean
+signal_watcher_watches(GjsDBusInfo      *info,
+                       GjsSignalWatcher *watcher,
+                       const char       *sender,
+                       const char       *path,
+                       const char       *iface,
+                       const char       *name)
+{
+    if (watcher->path &&
+        strcmp(watcher->path, path) != 0)
+        return FALSE;
+
+    if (watcher->iface &&
+        strcmp(watcher->iface, iface) != 0)
+        return FALSE;
+
+    if (watcher->name &&
+        strcmp(watcher->name, name) != 0)
+        return FALSE;
+
+    /* "sender" from message is always the unique name, but
+     * watcher may or may not be.
+     */
+
+    if (watcher->sender == NULL)
+        return TRUE;
+
+
+    if (* (watcher->sender) == ':') {
+        return strcmp(watcher->sender, sender) == 0;
+    } else {
+        const char *owner;
+
+        owner = gjs_dbus_get_watched_name_owner(info->bus_type,
+                                                watcher->sender);
+
+        if (owner != NULL &&
+            strcmp(sender, owner) == 0)
+            return TRUE;
+        else
+            return FALSE;
+    }
+}
+
+DBusHandlerResult
+_gjs_dbus_signal_watch_filter_message(DBusConnection *connection,
+                                      DBusMessage    *message,
+                                      void           *data)
+{
+    /* Two things we're looking for
+     * 1) signals
+     * 2) if the sender of a signal watcher is a unique name,
+     *    we want to destroy notify when it vanishes or
+     *    when the bus disconnects.
+     */
+    GjsDBusInfo *info;
+    const char *sender;
+    const char *path;
+    const char *iface;
+    const char *name;
+    GSList *candidates;
+    GjsSignalWatcher *previous;
+
+    info = _gjs_dbus_ensure_info(connection);
+
+    /* Be sure they are all in the lookup tables */
+    _gjs_dbus_process_pending_signal_watchers(connection, info);
+
+    if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL)
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    sender = dbus_message_get_sender(message);
+    path = dbus_message_get_path(message);
+    iface = dbus_message_get_interface(message);
+    name = dbus_message_get_member(message);
+
+    /* libdbus requires path, iface, name. The bus daemon
+     * will always set a sender but some locally-generated
+     * messages (e.g. disconnected) may not have one.
+     */
+    g_assert(path != NULL);
+    g_assert(iface != NULL);
+    g_assert(name != NULL);
+
+    gjs_debug(GJS_DEBUG_DBUS, "Signal from %s %s.%s sender %s",
+            path, iface, name, sender ? sender : "(none)");
+
+    candidates = NULL;
+
+    if (sender != NULL) {
+        concat_candidates(&candidates,
+                          info->signal_watchers_by_unique_sender,
+                          sender);
+    }
+    concat_candidates(&candidates,
+                      info->signal_watchers_by_path,
+                      path);
+    concat_candidates(&candidates,
+                      info->signal_watchers_by_iface,
+                      iface);
+    concat_candidates(&candidates,
+                      info->signal_watchers_by_signal,
+                      name);
+    candidates = g_slist_concat(candidates,
+                                g_slist_copy(info->signal_watchers_in_no_table));
+
+    /* Sort so we can find dups */
+    candidates = g_slist_sort(candidates, direct_cmp);
+
+    previous = NULL;
+    while (candidates != NULL) {
+        GjsSignalWatcher *watcher;
+
+        watcher = candidates->data;
+        candidates = g_slist_delete_link(candidates, candidates);
+
+        if (previous == watcher)
+            continue; /* watcher was in more than one table */
+
+        previous = watcher;
+
+        if (!signal_watcher_watches(info,
+                                    watcher,
+                                    sender, path, iface, name))
+            continue;
+
+        /* destroyed would happen if e.g. removed while we are going
+         * through here.
+         */
+        if (watcher->destroyed)
+            continue;
+
+        /* Invoke the watcher */
+
+        signal_watcher_ref(watcher);
+
+        (* watcher->handler) (connection,
+                              message,
+                              watcher->data);
+
+        signal_watcher_unref(watcher);
+    }
+
+    /* Note that signal watchers can also listen to the disconnected
+     * signal, so we do our special handling of it last
+     */
+    if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) {
+        gjs_debug(GJS_DEBUG_DBUS, "Disconnected in %s", G_STRFUNC);
+
+        signal_watchers_disconnected(connection, info);
+    }
+
+    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+int
+gjs_dbus_watch_signal(DBusBusType                  bus_type,
+                      const char                  *sender,
+                      const char                  *path,
+                      const char                  *iface,
+                      const char                  *name,
+                      GjsDBusSignalHandler         handler,
+                      void                        *data,
+                      GDestroyNotify               data_dnotify)
+{
+    GjsSignalWatcher *watcher;
+    DBusConnection *weak;
+
+    watcher = signal_watcher_new(bus_type, sender, path,
+                                 iface, name, handler,
+                                 data, data_dnotify);
+
+    /* If we're already connected, it's essential to get the
+     * match rule added right away. Otherwise the race-free pattern
+     * is not possible:
+     * 1. Add match rule to monitor state of remote object
+     * 2. Get current state of remote object
+     *
+     * Using the pending_signal_watchers codepath, there's no
+     * notification when the match rule is added so you can't
+     * be sure you get current state *after* that.
+     *
+     * Since we add our match rule here immediately if connected,
+     * then apps can rely on first watching the signal, then
+     * getting current state.
+     *
+     * In the connect idle, we process pending signal watchers
+     * before calling any other app callbacks, so if someone
+     * gets current state on connect, that will be after
+     * all their match rules are added.
+     */
+    weak = _gjs_dbus_get_weak_ref(bus_type);
+    if (weak != NULL) {
+        signal_watcher_add(weak, _gjs_dbus_ensure_info(weak), watcher);
+        signal_watcher_unref(watcher);
+    } else {
+        pending_signal_watchers = g_slist_prepend(pending_signal_watchers, watcher);
+        _gjs_dbus_ensure_connect_idle(bus_type);
+    }
+
+    return watcher->id;
+}
+
+/* Does the watcher match a removal request? */
+static gboolean
+signal_watcher_matches(GjsSignalWatcher     *watcher,
+                       DBusBusType           bus_type,
+                       const char           *sender,
+                       const char           *path,
+                       const char           *iface,
+                       const char           *name,
+                       int                   id,
+                       GjsDBusSignalHandler  handler,
+                       void                 *data)
+{
+    /* If we have an ID, check that first. If it matches, we are
+     * done
+     */
+    if (id != INVALID_SIGNAL_ID && watcher->id == id)
+        return TRUE;
+
+    /* Start with data, most likely thing to not match */
+    if (watcher->data != data)
+        return FALSE;
+
+    /* Second most likely non-match */
+    if (watcher->handler != handler)
+        return FALSE;
+
+    /* Then third, do the more expensive checks */
+
+    if (watcher->bus_type != bus_type)
+        return FALSE;
+
+    if (g_strcmp0(watcher->sender, sender) != 0)
+        return FALSE;
+
+    if (g_strcmp0(watcher->path, path) != 0)
+        return FALSE;
+
+    if (g_strcmp0(watcher->iface, iface) != 0)
+        return FALSE;
+
+    if (g_strcmp0(watcher->name, name) != 0)
+        return FALSE;
+
+    return TRUE;
+}
+
+static void
+unwatch_signal(DBusBusType                  bus_type,
+               const char                  *sender,
+               const char                  *path,
+               const char                  *iface,
+               const char                  *name,
+               int                          id,
+               GjsDBusSignalHandler         handler,
+               void                        *data)
+{
+    GSList *list;
+    DBusConnection *weak;
+    GjsDBusInfo *info;
+
+    /* Always remove only ONE watcher (the first one we find) */
+
+    weak = _gjs_dbus_get_weak_ref(bus_type);
+
+    /* First see if it's still pending */
+    for (list = pending_signal_watchers;
+         list != NULL;
+         list = list->next) {
+        if (signal_watcher_matches(list->data,
+                                   bus_type,
+                                   sender,
+                                   path,
+                                   iface,
+                                   name,
+                                   id,
+                                   handler,
+                                   data)) {
+            GjsSignalWatcher *watcher = list->data;
+            pending_signal_watchers = g_slist_remove_link(pending_signal_watchers,
+                                                          list);
+
+            if (weak != NULL)
+                signal_watcher_set_matching(weak, watcher, FALSE);
+
+            signal_watcher_dnotify(watcher); /* destroy even if we don't finalize */
+            signal_watcher_unref(watcher);
+            return;
+        }
+    }
+
+    /* If not pending, and no bus connection, it can't exist */
+    if (weak == NULL) {
+        /* don't warn on nonexistent, since a vanishing bus name could
+         * have nuked it outside the app's control.
+         */
+        return;
+    }
+
+    info = _gjs_dbus_ensure_info(weak);
+
+    for (list = info->all_signal_watchers;
+         list != NULL;
+         list = list->next) {
+        if (signal_watcher_matches(list->data,
+                                   bus_type,
+                                   sender,
+                                   path,
+                                   iface,
+                                   name,
+                                   id,
+                                   handler,
+                                   data)) {
+            signal_watcher_remove(weak, info, list->data);
+            /* note that "list" node is now invalid */
+            return;
+        }
+    }
+
+    /* don't warn on nonexistent, since a vanishing bus name could
+     * have nuked it outside the app's control. Just do nothing.
+     */
+}
+
+void
+gjs_dbus_unwatch_signal(DBusBusType                  bus_type,
+                        const char                  *sender,
+                        const char                  *path,
+                        const char                  *iface,
+                        const char                  *name,
+                        GjsDBusSignalHandler         handler,
+                        void                        *data)
+{
+    unwatch_signal(bus_type,
+                   sender,
+                   path,
+                   iface,
+                   name,
+                   INVALID_SIGNAL_ID,
+                   handler,
+                   data);
+}
+
+void
+gjs_dbus_unwatch_signal_by_id(DBusBusType                  bus_type,
+                              int                          id)
+{
+    unwatch_signal(bus_type,
+                   NULL,
+                   NULL,
+                   NULL,
+                   NULL,
+                   id,
+                   (GjsDBusSignalHandler)NULL,
+                   NULL);
+}
+
+#if GJS_BUILD_TESTS
+
+#include "dbus-proxy.h"
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+
+static pid_t test_service_pid = 0;
+static GjsDBusProxy *test_service_proxy = NULL;
+
+static GMainLoop *outer_loop = NULL;
+static GMainLoop *inner_loop = NULL;
+
+static int n_running_children = 0;
+
+typedef struct {
+    const char *sender;
+    const char *path;
+    const char *iface;
+    const char *member;
+} SignalWatchTest;
+
+static SignalWatchTest watch_tests[] = {
+    { NULL, NULL, NULL, NULL },
+    { "com.litl.TestService", NULL, NULL, NULL },
+    { NULL, "/com/litl/test/object42", NULL, NULL },
+    { NULL, NULL, "com.litl.TestIface", NULL },
+    { NULL, NULL, NULL, "TheSignal" }
+};
+
+static void do_test_service_child (void);
+
+/* quit when all children are gone */
+static void
+another_child_down(void)
+{
+    g_assert(n_running_children > 0);
+    n_running_children -= 1;
+
+    if (n_running_children == 0) {
+        g_main_loop_quit(outer_loop);
+    }
+}
+
+/* This test function doesn't really test anything, just sets up
+ * for the following one
+ */
+static void
+fork_test_signal_service(void)
+{
+    pid_t child_pid;
+
+    /* it would break to fork after we already connected */
+    g_assert(_gjs_dbus_get_weak_ref(DBUS_BUS_SESSION) == NULL);
+    g_assert(_gjs_dbus_get_weak_ref(DBUS_BUS_SYSTEM) == NULL);
+    g_assert(test_service_pid == 0);
+
+    child_pid = fork();
+
+    if (child_pid == -1) {
+        g_error("Failed to fork dbus service");
+    } else if (child_pid > 0) {
+        /* We are the parent */
+        test_service_pid = child_pid;
+        n_running_children += 1;
+
+        return;
+    }
+
+    /* we are the child, set up a service for main test process to talk to */
+
+    do_test_service_child();
+}
+
+static void
+kill_child(void)
+{
+    if (kill(test_service_pid, SIGTERM) < 0) {
+        g_error("Test service was no longer around... it must have failed somehow (%s)",
+                strerror(errno));
+    }
+
+    /* We will quit main loop when we see the child go away */
+}
+
+static int signal_received_count = 0;
+static int destroy_notify_count = 0;
+
+static void
+the_destroy_notifier(void *data)
+{
+    gjs_debug(GJS_DEBUG_DBUS, "got destroy notification on signal watch");
+    destroy_notify_count += 1;
+}
+
+static void
+the_destroy_notifier_that_quits(void *data)
+{
+    the_destroy_notifier(data);
+    g_main_loop_quit(inner_loop);
+}
+
+static void
+expect_receive_signal_handler(DBusConnection *connection,
+                              DBusMessage    *message,
+                              void           *data)
+{
+    gjs_debug(GJS_DEBUG_DBUS, "dbus signal watch handler called");
+
+    g_assert(dbus_message_is_signal(message,
+                                    "com.litl.TestIface",
+                                    "TheSignal"));
+
+    signal_received_count += 1;
+
+    g_main_loop_quit(inner_loop);
+}
+
+static void
+test_match_combo(const char *sender,
+                 const char *path,
+                 const char *iface,
+                 const char *member)
+{
+    signal_received_count = 0;
+    destroy_notify_count = 0;
+
+    gjs_debug(GJS_DEBUG_DBUS, "Watching %s %s %s %s",
+              sender,
+              path,
+              iface,
+              member);
+
+    gjs_dbus_watch_signal(DBUS_BUS_SESSION,
+                          sender,
+                          path,
+                          iface,
+                          member,
+                          expect_receive_signal_handler,
+                          GINT_TO_POINTER(1),
+                          the_destroy_notifier);
+
+    gjs_dbus_proxy_call_json_async(test_service_proxy,
+                                   "emitTheSignal",
+                                   NULL,
+                                   NULL,
+                                   NULL,
+                                   NULL);
+    g_main_loop_run(inner_loop);
+
+    g_assert(signal_received_count == 1);
+    g_assert(destroy_notify_count == 0);
+
+    gjs_dbus_unwatch_signal(DBUS_BUS_SESSION,
+                            sender,
+                            path,
+                            iface,
+                            member,
+                            expect_receive_signal_handler,
+                            GINT_TO_POINTER(1));
+
+    g_assert(destroy_notify_count == 1);
+}
+
+static gboolean
+run_signal_tests_idle(void *data)
+{
+    int i;
+    const char *unique_name;
+
+    for (i = 0; i < (int) G_N_ELEMENTS(watch_tests); ++i) {
+        SignalWatchTest *test = &watch_tests[i];
+
+        test_match_combo(test->sender,
+                         test->path,
+                         test->iface,
+                         test->member);
+    }
+
+    /* Now try on the unique bus name */
+
+    unique_name = gjs_dbus_proxy_get_bus_name(test_service_proxy);
+
+    test_match_combo(unique_name,
+                     NULL, NULL, NULL);
+
+    /* Now test we get destroy notify when the unique name disappears
+     * on killing the child.
+     */
+    signal_received_count = 0;
+    destroy_notify_count = 0;
+
+    gjs_debug(GJS_DEBUG_DBUS, "Watching unique name %s",
+              unique_name);
+
+    gjs_dbus_watch_signal(DBUS_BUS_SESSION,
+                          unique_name,
+                          NULL, NULL, NULL,
+                          expect_receive_signal_handler,
+                          GINT_TO_POINTER(1),
+                          the_destroy_notifier_that_quits);
+
+    /* kill owner of unique_name */
+    kill_child();
+
+    /* wait for destroy notify */
+    g_main_loop_run(inner_loop);
+
+    g_assert(signal_received_count == 0);
+    /* roundabout way to write == 1 that gives more info on fail */
+    g_assert(destroy_notify_count > 0);
+    g_assert(destroy_notify_count < 2);
+
+    gjs_dbus_unwatch_signal(DBUS_BUS_SESSION,
+                            unique_name,
+                            NULL, NULL, NULL,
+                            expect_receive_signal_handler,
+                            GINT_TO_POINTER(1));
+
+    g_assert(signal_received_count == 0);
+    g_assert(destroy_notify_count == 1);
+
+    /* remove idle */
+    return FALSE;
+}
+
+static void
+on_test_service_appeared(DBusConnection *connection,
+                         const char     *name,
+                         const char     *new_owner_unique_name,
+                         void           *data)
+{
+    gjs_debug(GJS_DEBUG_DBUS, "%s appeared",
+              name);
+
+    inner_loop = g_main_loop_new(NULL, FALSE);
+
+    test_service_proxy =
+        gjs_dbus_proxy_new(connection, new_owner_unique_name,
+                           "/com/litl/test/object42",
+                           "com.litl.TestIface");
+
+    g_idle_add(run_signal_tests_idle, NULL);
+}
+
+static void
+on_test_service_vanished(DBusConnection *connection,
+                         const char     *name,
+                         const char     *old_owner_unique_name,
+                         void           *data)
+{
+    gjs_debug(GJS_DEBUG_DBUS, "%s vanished", name);
+
+    another_child_down();
+}
+
+static GjsDBusWatchNameFuncs watch_test_service_funcs = {
+    on_test_service_appeared,
+    on_test_service_vanished
+};
+
+void
+bigtest_test_func_util_dbus_signals_client(void)
+{
+    pid_t result;
+    int status;
+
+    /* See comment in dbus.c above the g_test_trap_fork()
+     * there on why we have to do this.
+     */
+    if (!g_test_trap_fork(0, 0)) {
+        /* We are the parent */
+        g_test_trap_assert_passed();
+        return;
+    }
+
+    /* All this code runs in a child process */
+
+    fork_test_signal_service();
+
+    g_type_init();
+
+    /* We rely on the child-forking test functions being called first */
+    g_assert(test_service_pid != 0);
+
+    gjs_dbus_watch_name(DBUS_BUS_SESSION,
+                        "com.litl.TestService",
+                        0,
+                        &watch_test_service_funcs,
+                        NULL);
+
+    outer_loop = g_main_loop_new(NULL, FALSE);
+
+    g_main_loop_run(outer_loop);
+
+    if (test_service_proxy != NULL)
+        g_object_unref(test_service_proxy);
+
+    gjs_debug(GJS_DEBUG_DBUS,
+              "waitpid() for first child");
+
+    result = waitpid(test_service_pid, &status, 0);
+    if (result < 0) {
+        g_error("Failed to waitpid() for forked child: %s", strerror(errno));
+    }
+
+    if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
+        g_error("Forked dbus service child exited with error code %d", WEXITSTATUS(status));
+    }
+
+    if (WIFSIGNALED(status) && WTERMSIG(status) != SIGTERM) {
+        g_error("Forked dbus service child exited on wrong signal number %d", WTERMSIG(status));
+    }
+
+    gjs_debug(GJS_DEBUG_DBUS, "dbus signals test completed");
+
+    /* We want to kill dbus so the weak refs are NULL to start the
+     * next dbus-related test, which allows those tests
+     * to fork new child processes.
+     */
+    _gjs_dbus_dispose_info(_gjs_dbus_get_weak_ref(DBUS_BUS_SESSION));
+    dbus_shutdown();
+
+    gjs_debug(GJS_DEBUG_DBUS, "dbus shut down");
+
+    /* FIXME this is only here because we've forked */
+    exit(0);
+}
+
+/*
+ * Child service that emits signals
+ */
+
+static gboolean currently_have_test_service = FALSE;
+static GObject *test_service_object = NULL;
+
+static void
+test_service_emit_the_signal(DBusConnection  *connection,
+                             DBusMessage     *message,
+                             DBusMessageIter *in_iter,
+                             DBusMessageIter *out_iter,
+                             void            *data,
+                             DBusError       *error)
+{
+    DBusMessage *signal;
+
+    signal = dbus_message_new_signal("/com/litl/test/object42",
+                                     "com.litl.TestIface",
+                                     "TheSignal");
+    dbus_connection_send(connection, signal, NULL);
+    dbus_message_unref(signal);
+}
+
+static GjsDBusJsonMethod test_service_methods[] = {
+    { "emitTheSignal", test_service_emit_the_signal, NULL }
+};
+
+static void
+on_test_service_acquired(DBusConnection *connection,
+                         const char     *name,
+                         void           *data)
+{
+    g_assert(!currently_have_test_service);
+    currently_have_test_service = TRUE;
+
+    gjs_debug(GJS_DEBUG_DBUS, "com.litl.TestService acquired by child");
+
+    gjs_dbus_register_json(connection,
+                           "com.litl.TestIface",
+                           test_service_methods,
+                           G_N_ELEMENTS(test_service_methods));
+
+    test_service_object = g_object_new(G_TYPE_OBJECT, NULL);
+
+    gjs_dbus_register_g_object(connection,
+                               "/com/litl/test/object42",
+                               test_service_object,
+                               "com.litl.TestIface");
+}
+
+static void
+on_test_service_lost(DBusConnection *connection,
+                     const char     *name,
+                     void           *data)
+{
+    g_assert(currently_have_test_service);
+    currently_have_test_service = FALSE;
+
+    gjs_debug(GJS_DEBUG_DBUS, "com.litl.TestService lost by child");
+
+    gjs_dbus_unregister_g_object(connection,
+                                 "/com/litl/test/object42");
+
+    gjs_dbus_unregister_json(connection,
+                             "com.litl.TestIface");
+}
+
+static GjsDBusNameOwnerFuncs test_service_funcs = {
+    "com.litl.TestService",
+    DBUS_BUS_SESSION,
+    on_test_service_acquired,
+    on_test_service_lost
+};
+
+static void
+do_test_service_child(void)
+{
+    GMainLoop *loop;
+
+    g_type_init();
+
+    loop = g_main_loop_new(NULL, FALSE);
+
+    gjs_dbus_acquire_name(DBUS_BUS_SESSION,
+                          &test_service_funcs,
+                          NULL);
+
+    g_main_loop_run(loop);
+
+    /* Don't return to the test program main() */
+    exit(0);
+}
+
+#endif /* GJS_BUILD_TESTS */
diff --git a/gjsdbus/dbus.c b/gjsdbus/dbus.c
new file mode 100644
index 0000000..51fd897
--- /dev/null
+++ b/gjsdbus/dbus.c
@@ -0,0 +1,2986 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* Copyright 2008 litl, LLC. All Rights Reserved. */
+
+#include <config.h>
+
+#include "dbus.h"
+
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "gjsdbus/dbus-private.h"
+#include "gjsdbus/dbus-proxy.h"
+#include "glib.h"
+
+#include "util/log.h"
+
+typedef struct {
+    const GjsDBusConnectFuncs *funcs;
+    void *data;
+    unsigned int opened : 1;
+} ConnectFuncs;
+
+typedef enum {
+    NAME_NOT_REQUESTED,
+    NAME_PRIMARY_OWNER,
+    NAME_IN_QUEUE,
+    NAME_NOT_OWNED
+} NameOwnershipState;
+
+typedef struct {
+    char *name;
+    const GjsDBusJsonMethod *methods;
+    int n_methods;
+} GjsJsonIface;
+
+typedef struct {
+    DBusBusType bus_type;
+    /* If prev_state != state then we may need to notify */
+    NameOwnershipState prev_state;
+    NameOwnershipState state;
+    const GjsDBusNameOwnerFuncs *funcs;
+    void *data;
+    unsigned int id;
+} GjsNameOwnershipMonitor;
+
+typedef struct {
+    char *name;
+    char *current_owner;
+    GSList *watchers;
+} GjsNameWatch;
+
+typedef struct {
+    GjsDBusWatchNameFlags flags;
+    const GjsDBusWatchNameFuncs *funcs;
+    void *data;
+    DBusBusType bus_type;
+    GjsNameWatch *watch;
+    guint notify_idle;
+    int refcount;
+    guint destroyed : 1;
+} GjsNameWatcher;
+
+typedef struct {
+    DBusBusType bus_type;
+    char *name;
+    GjsNameWatcher *watcher;
+} GjsPendingNameWatcher;
+
+static DBusConnection *session_bus_weak_ref = NULL;
+static GSList *session_bus_weak_refs = NULL;
+static DBusConnection *system_bus_weak_ref = NULL;
+static GSList *system_bus_weak_refs = NULL;
+static guint session_connect_idle_id = 0;
+static guint system_connect_idle_id = 0;
+static GSList *all_connect_funcs = NULL;
+
+static GSList *pending_name_ownership_monitors = NULL;
+static GSList *pending_name_watchers = NULL;
+
+#define GJS_DBUS_NAME_OWNER_MONITOR_INVALID_ID 0
+
+static unsigned int global_monitor_id = 0;
+
+static DBusHandlerResult disconnect_filter_message             (DBusConnection   *connection,
+                                                                DBusMessage      *message,
+                                                                void             *data);
+static DBusHandlerResult name_ownership_monitor_filter_message (DBusConnection   *connection,
+                                                                DBusMessage      *message,
+                                                                void             *data);
+static void              process_name_ownership_monitors       (DBusConnection   *connection,
+                                                                GjsDBusInfo      *info);
+static void              name_watch_remove_watcher             (GjsNameWatch     *watch,
+                                                                GjsNameWatcher   *watcher);
+static DBusHandlerResult name_watch_filter_message             (DBusConnection   *connection,
+                                                                DBusMessage      *message,
+                                                                void             *data);
+static void              process_pending_name_watchers         (DBusConnection   *connection,
+                                                                GjsDBusInfo      *info);
+static void              json_iface_free                       (GjsJsonIface     *iface);
+static void              info_free                             (GjsDBusInfo      *info);
+static gboolean          notify_watcher_name_appeared          (gpointer data);
+
+static dbus_int32_t info_slot = -1;
+GjsDBusInfo*
+_gjs_dbus_ensure_info(DBusConnection *connection)
+{
+    GjsDBusInfo *info;
+
+    dbus_connection_allocate_data_slot(&info_slot);
+
+    info = dbus_connection_get_data(connection, info_slot);
+
+    if (info == NULL) {
+        info = g_slice_new0(GjsDBusInfo);
+
+        info->where_connection_was = connection;
+
+        if (connection == session_bus_weak_ref)
+            info->bus_type = DBUS_BUS_SESSION;
+        else if (connection == system_bus_weak_ref)
+            info->bus_type = DBUS_BUS_SYSTEM;
+        else
+            g_error("Unknown bus type opened in %s", __FILE__);
+
+        info->json_ifaces = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                                  NULL, (GFreeFunc) json_iface_free);
+        info->name_watches = g_hash_table_new(g_str_hash, g_str_equal);
+        dbus_connection_set_data(connection, info_slot, info, (DBusFreeFunction) info_free);
+
+        dbus_connection_add_filter(connection, name_ownership_monitor_filter_message,
+                                   NULL, NULL);
+        dbus_connection_add_filter(connection, name_watch_filter_message,
+                                   NULL, NULL);
+        dbus_connection_add_filter(connection, _gjs_dbus_signal_watch_filter_message,
+                                   NULL, NULL);
+
+        /* Important: disconnect_filter_message() must be LAST so
+         * it runs last when the disconnect message arrives.
+         */
+        dbus_connection_add_filter(connection, disconnect_filter_message,
+                                   NULL, NULL);
+
+        /* caution, this could get circular if proxy_new() goes back around
+         * and tries to use dbus.c - but we'll fix it when it happens.
+         * Also, this refs the connection ...
+         */
+        info->driver_proxy =
+            gjs_dbus_proxy_new(connection,
+                               DBUS_SERVICE_DBUS,
+                               DBUS_PATH_DBUS,
+                               DBUS_INTERFACE_DBUS);
+    }
+
+    return info;
+}
+
+void
+_gjs_dbus_dispose_info(DBusConnection *connection)
+{
+    GjsDBusInfo *info;
+
+    if (info_slot < 0)
+        return;
+
+    info = dbus_connection_get_data(connection, info_slot);
+
+    if (info != NULL) {
+
+        gjs_debug(GJS_DEBUG_DBUS, "Disposing info on connection %p",
+                connection);
+
+        /* the driver proxy refs the connection, we want
+         * to break that cycle.
+         */
+        g_object_unref(info->driver_proxy);
+        info->driver_proxy = NULL;
+
+        dbus_connection_set_data(connection, info_slot, NULL, NULL);
+
+        dbus_connection_free_data_slot(&info_slot);
+    }
+}
+
+DBusConnection*
+_gjs_dbus_get_weak_ref(DBusBusType which_bus)
+{
+    if (which_bus == DBUS_BUS_SESSION) {
+        return session_bus_weak_ref;
+    } else if (which_bus == DBUS_BUS_SYSTEM) {
+        return system_bus_weak_ref;
+    } else {
+        g_assert_not_reached();
+    }
+}
+
+static DBusHandlerResult
+disconnect_filter_message(DBusConnection   *connection,
+                          DBusMessage      *message,
+                          void             *data)
+{
+    /* We should be running after all other filters */
+    if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) {
+        gjs_debug(GJS_DEBUG_DBUS, "Disconnected in %s", G_STRFUNC);
+
+        _gjs_dbus_dispose_info(connection);
+
+        if (session_bus_weak_ref == connection)
+            session_bus_weak_ref = NULL;
+
+        if (system_bus_weak_ref == connection)
+            system_bus_weak_ref = NULL;
+    }
+
+    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static DBusConnection*
+try_connecting(DBusBusType which_bus)
+{
+
+    DBusGConnection *gconnection;
+    DBusConnection *connection;
+    GError *error;
+
+    connection = _gjs_dbus_get_weak_ref(which_bus);
+    if (connection != NULL)
+        return connection;
+
+    gjs_debug(GJS_DEBUG_DBUS, "trying to connect to message bus");
+
+    error = NULL;
+    gconnection = dbus_g_bus_get(which_bus,
+                                 &error);
+    if (gconnection == NULL) {
+        gjs_debug(GJS_DEBUG_DBUS, "bus connection failed: %s",
+                error->message);
+        g_error_free(error);
+        return NULL;
+    }
+
+    connection = dbus_g_connection_get_connection(gconnection);
+
+    /* Disable this because all our apps will be well-behaved! */
+    dbus_connection_set_exit_on_disconnect(connection, FALSE);
+
+    if (which_bus == DBUS_BUS_SESSION &&
+        session_bus_weak_ref == NULL) {
+        GSList *l;
+        session_bus_weak_ref = connection;
+        for (l = session_bus_weak_refs; l != NULL; l = l->next) {
+            DBusConnection **connection_p = l->data;
+            *connection_p = session_bus_weak_ref;
+        }
+    } else if (which_bus == DBUS_BUS_SYSTEM &&
+               system_bus_weak_ref == NULL) {
+        GSList *l;
+        system_bus_weak_ref = connection;
+        for (l = system_bus_weak_refs; l != NULL; l = l->next) {
+            DBusConnection **connection_p = l->data;
+            *connection_p = system_bus_weak_ref;
+        }
+    }
+
+    dbus_g_connection_unref(gconnection); /* rely on libdbus holding a ref */
+
+    gjs_debug(GJS_DEBUG_DBUS, "Successfully connected");
+
+    return connection;
+}
+
+static gboolean
+connect_idle(void *data)
+{
+    GSList *l;
+    DBusConnection *connection;
+    GjsDBusInfo *info;
+    DBusBusType bus_type;
+
+    bus_type = GPOINTER_TO_INT(data);
+
+    if (bus_type == DBUS_BUS_SESSION)
+        session_connect_idle_id = 0;
+    else if (bus_type == DBUS_BUS_SYSTEM)
+        system_connect_idle_id = 0;
+    else
+        g_assert_not_reached();
+
+    gjs_debug(GJS_DEBUG_DBUS, "connection idle with %d connect listeners to traverse", g_slist_length(all_connect_funcs));
+
+    connection = try_connecting(bus_type);
+    if (connection == NULL) {
+        if (bus_type == DBUS_BUS_SESSION) {
+            g_printerr("Lost connection to session bus, exiting\n");
+            exit(1);
+        } else {
+            /* Here it would theoretically make sense to reinstall the
+             * idle as a timeout or something, but we don't for now,
+             * just wait for something to trigger a reconnect. It is
+             * not a situation that should happen in reality (we won't
+             * restart the system bus without rebooting).
+             */
+        }
+        return FALSE;
+    }
+
+    info = _gjs_dbus_ensure_info(connection);
+
+    /* We first need to call AddMatch on all signal watchers.
+     * This is so if on connect, the app calls methods to get
+     * the state the signal notifies the app of changes in,
+     * the match rule is added before the "get current state"
+     * methods are called. Otherwise there's a race where
+     * a signal can be missed between a "get current state" method
+     * call reply and the AddMatch.
+     */
+    _gjs_dbus_process_pending_signal_watchers(connection, info);
+
+    /* We want the app to see notification of connection opening,
+     * THEN other notifications, so notify it's open first.
+     */
+
+    for (l = all_connect_funcs; l != NULL; l = l->next) {
+        ConnectFuncs *f;
+        f = l->data;
+
+        if (!f->opened && f->funcs->which_bus == bus_type) {
+            f->opened = TRUE;
+            (* f->funcs->opened) (connection, f->data);
+        }
+    }
+
+    /* These two invoke application callbacks, unlike
+     * _gjs_dbus_process_pending_signal_watchers(), so should come after
+     * the above calls to the "connection opened" callbacks.
+     */
+
+    process_name_ownership_monitors(connection, info);
+
+    process_pending_name_watchers(connection, info);
+
+    return FALSE;
+}
+
+void
+_gjs_dbus_ensure_connect_idle(DBusBusType bus_type)
+{
+    if (bus_type == DBUS_BUS_SESSION) {
+        if (session_connect_idle_id == 0) {
+            session_connect_idle_id = g_idle_add(connect_idle, GINT_TO_POINTER(bus_type));
+        }
+    } else if (bus_type == DBUS_BUS_SYSTEM) {
+        if (system_connect_idle_id == 0) {
+            system_connect_idle_id = g_idle_add(connect_idle, GINT_TO_POINTER(bus_type));
+        }
+    } else {
+        g_assert_not_reached();
+    }
+}
+
+static void
+internal_add_connect_funcs(const GjsDBusConnectFuncs *funcs,
+                           void                      *data,
+                           gboolean                   sync_notify)
+{
+    ConnectFuncs *f;
+
+    f = g_slice_new0(ConnectFuncs);
+    f->funcs = funcs;
+    f->data = data;
+    f->opened = FALSE;
+
+    all_connect_funcs = g_slist_prepend(all_connect_funcs, f);
+
+    _gjs_dbus_ensure_connect_idle(f->funcs->which_bus);
+
+    if (sync_notify) {
+        /* sync_notify means IF we are already connected
+         * (we have a weak ref != NULL) then notify
+         * right away before we return.
+         */
+        DBusConnection *connection;
+
+        connection = _gjs_dbus_get_weak_ref(f->funcs->which_bus);
+
+        if (connection != NULL && !f->opened) {
+            f->opened = TRUE;
+            (* f->funcs->opened) (connection, f->data);
+        }
+    }
+}
+
+/* this should guarantee that the funcs are only called async, which is why
+ * it does not try_connecting right away; the idea is to defer to inside the
+ * main loop.
+ */
+void
+gjs_dbus_add_connect_funcs(const GjsDBusConnectFuncs *funcs,
+                           void                      *data)
+{
+    internal_add_connect_funcs(funcs, data, FALSE);
+}
+
+/* The sync_notify flavor calls the open notification right away if
+ * we are already connected.
+ */
+void
+gjs_dbus_add_connect_funcs_sync_notify(const GjsDBusConnectFuncs *funcs,
+                                       void                      *data)
+{
+    internal_add_connect_funcs(funcs, data, TRUE);
+}
+
+void
+gjs_dbus_remove_connect_funcs(const GjsDBusConnectFuncs *funcs,
+                              void                      *data)
+{
+    ConnectFuncs *f;
+    GSList *l;
+
+    f = NULL;
+    for (l = all_connect_funcs; l != NULL; l = l->next) {
+        f = l->data;
+
+        if (f->funcs == funcs &&
+            f->data == data)
+            break;
+    }
+
+    if (l == NULL) {
+        g_warning("Could not find functions matching %p %p", funcs, data);
+        return;
+    }
+    g_assert(l->data == f);
+
+    all_connect_funcs = g_slist_delete_link(all_connect_funcs, l);
+    g_slice_free(ConnectFuncs, f);
+}
+
+void
+gjs_dbus_add_bus_weakref(DBusBusType      which_bus,
+                         DBusConnection **connection_p)
+{
+    if (which_bus == DBUS_BUS_SESSION) {
+        *connection_p = session_bus_weak_ref;
+        session_bus_weak_refs = g_slist_prepend(session_bus_weak_refs, connection_p);
+    } else if (which_bus == DBUS_BUS_SYSTEM) {
+        *connection_p = system_bus_weak_ref;
+        system_bus_weak_refs = g_slist_prepend(system_bus_weak_refs, connection_p);
+    } else {
+        g_assert_not_reached();
+    }
+
+    _gjs_dbus_ensure_connect_idle(which_bus);
+}
+
+void
+gjs_dbus_remove_bus_weakref(DBusBusType      which_bus,
+                            DBusConnection **connection_p)
+{
+    if (which_bus == DBUS_BUS_SESSION) {
+        *connection_p = NULL;
+        session_bus_weak_refs = g_slist_remove(session_bus_weak_refs, connection_p);
+    } else if (which_bus == DBUS_BUS_SYSTEM) {
+        *connection_p = NULL;
+        system_bus_weak_refs = g_slist_remove(system_bus_weak_refs, connection_p);
+    } else {
+        g_assert_not_reached();
+    }
+}
+
+void
+gjs_dbus_try_connecting_now(DBusBusType which_bus)
+{
+    try_connecting(which_bus);
+}
+
+static GjsJsonIface*
+json_iface_new(const char *name,
+               const GjsDBusJsonMethod *methods,
+               int n_methods)
+{
+    GjsJsonIface *iface;
+
+    iface = g_slice_new0(GjsJsonIface);
+    iface->name = g_strdup(name);
+    iface->methods = methods;
+    iface->n_methods = n_methods;
+
+    return iface;
+}
+
+static void
+json_iface_free(GjsJsonIface *iface)
+{
+    g_free(iface->name);
+    g_slice_free(GjsJsonIface, iface);
+}
+
+static GjsNameOwnershipMonitor*
+name_ownership_monitor_new(DBusBusType                  bus_type,
+                           const GjsDBusNameOwnerFuncs *funcs,
+                           void                        *data)
+{
+    GjsNameOwnershipMonitor *monitor;
+
+    monitor = g_slice_new0(GjsNameOwnershipMonitor);
+    monitor->bus_type = bus_type;
+    monitor->prev_state = NAME_NOT_REQUESTED;
+    monitor->state = NAME_NOT_REQUESTED;
+    monitor->funcs = funcs;
+    monitor->data = data;
+    monitor->id = ++global_monitor_id;
+
+    return  monitor;
+}
+
+static void
+name_ownership_monitor_free(GjsNameOwnershipMonitor *monitor)
+{
+
+    g_slice_free(GjsNameOwnershipMonitor, monitor);
+}
+
+static GjsNameWatch*
+name_watch_new(const char *name)
+{
+    GjsNameWatch *watch;
+
+    watch = g_slice_new0(GjsNameWatch);
+    watch->name = g_strdup(name);
+
+    /* For unique names, we assume the owner is itself,
+     * so we default to "exists" and maybe emit "vanished",
+     * while with well-known names we do the opposite.
+     */
+    if (*watch->name == ':') {
+        watch->current_owner = g_strdup(watch->name);
+    }
+
+    return watch;
+}
+
+static void
+name_watch_free(GjsNameWatch *watch)
+{
+    g_assert(watch->watchers == NULL);
+
+    g_free(watch->name);
+    g_free(watch->current_owner);
+    g_slice_free(GjsNameWatch, watch);
+}
+
+static GjsNameWatcher*
+name_watcher_new(GjsDBusWatchNameFlags        flags,
+                 const GjsDBusWatchNameFuncs *funcs,
+                 void                        *data,
+                 DBusBusType                  bus_type)
+{
+    GjsNameWatcher *watcher;
+
+    watcher = g_slice_new0(GjsNameWatcher);
+    watcher->flags = flags;
+    watcher->funcs = funcs;
+    watcher->data = data;
+    watcher->bus_type = bus_type;
+    watcher->watch = NULL;
+    watcher->refcount = 1;
+
+    return watcher;
+}
+
+static void
+name_watcher_ref(GjsNameWatcher *watcher)
+{
+    watcher->refcount += 1;
+}
+
+static void
+name_watcher_unref(GjsNameWatcher *watcher)
+{
+    watcher->refcount -= 1;
+
+    if (watcher->refcount == 0)
+        g_slice_free(GjsNameWatcher, watcher);
+}
+
+static void
+info_free(GjsDBusInfo *info)
+{
+    void *key;
+    void *value;
+
+    gjs_debug(GJS_DEBUG_DBUS, "Destroy notify invoked on bus connection info for %p",
+            info->where_connection_was);
+
+    if (info->where_connection_was == session_bus_weak_ref)
+        session_bus_weak_ref = NULL;
+
+    if (info->where_connection_was == system_bus_weak_ref)
+        system_bus_weak_ref = NULL;
+
+    /* This could create some strange re-entrancy so do it first.
+     * If we processed a disconnect message, this should have been done
+     * already at that time, but if we were finalized without that,
+     * it may not have been.
+     */
+    if (info->driver_proxy != NULL) {
+        g_object_unref(info->driver_proxy);
+        info->driver_proxy = NULL;
+    }
+
+    while (info->name_ownership_monitors != NULL) {
+        name_ownership_monitor_free(info->name_ownership_monitors->data);
+        info->name_ownership_monitors = g_slist_remove(info->name_ownership_monitors,
+                                                       info->name_ownership_monitors->data);
+    }
+
+    {
+      GHashTableIter iter;
+      g_hash_table_iter_init (&iter, info->name_watches);
+      while (g_hash_table_iter_next (&iter, &key, &value)) {
+        GjsNameWatch *watch = value;
+
+        g_hash_table_iter_steal (&iter);
+
+        while (watch->watchers) {
+            name_watch_remove_watcher(watch, watch->watchers->data);
+        }
+
+        name_watch_free(watch);
+      }
+    }
+
+    if (info->signal_watchers_by_unique_sender) {
+        g_hash_table_destroy(info->signal_watchers_by_unique_sender);
+    }
+
+    if (info->signal_watchers_by_path) {
+        g_hash_table_destroy(info->signal_watchers_by_path);
+    }
+
+    if (info->signal_watchers_by_iface) {
+        g_hash_table_destroy(info->signal_watchers_by_iface);
+    }
+
+    if (info->signal_watchers_by_signal) {
+        g_hash_table_destroy(info->signal_watchers_by_signal);
+    }
+
+    g_hash_table_destroy(info->name_watches);
+    g_hash_table_destroy(info->json_ifaces);
+    g_slice_free(GjsDBusInfo, info);
+}
+
+static DBusHandlerResult
+name_ownership_monitor_filter_message(DBusConnection *connection,
+                                      DBusMessage    *message,
+                                      void           *data)
+{
+    GjsDBusInfo *info;
+    gboolean states_changed;
+
+    info = _gjs_dbus_ensure_info(connection);
+
+    states_changed = FALSE;
+
+    if (dbus_message_is_signal(message, DBUS_INTERFACE_DBUS, "NameLost") &&
+        dbus_message_has_sender(message, DBUS_SERVICE_DBUS)) {
+        const char *name = NULL;
+        if (dbus_message_get_args(message, NULL,
+                                  DBUS_TYPE_STRING, &name,
+                                  DBUS_TYPE_INVALID)) {
+            GSList *l;
+
+            gjs_debug(GJS_DEBUG_DBUS, "Lost name %s", name);
+
+            for (l = info->name_ownership_monitors; l != NULL; l = l->next) {
+                GjsNameOwnershipMonitor *monitor;
+
+                monitor = l->data;
+
+                if (monitor->state == NAME_PRIMARY_OWNER &&
+                    strcmp(name, monitor->funcs->name) == 0) {
+                    monitor->prev_state = monitor->state;
+                    monitor->state = NAME_NOT_OWNED;
+                    states_changed = TRUE;
+                    /* keep going, don't break, there may be more matches */
+                }
+            }
+        } else {
+            gjs_debug(GJS_DEBUG_DBUS, "NameLost has wrong arguments???");
+        }
+    } else if (dbus_message_is_signal(message, DBUS_INTERFACE_DBUS, "NameAcquired") &&
+               dbus_message_has_sender(message, DBUS_SERVICE_DBUS)) {
+        const char *name = NULL;
+        if (dbus_message_get_args(message, NULL,
+                                  DBUS_TYPE_STRING, &name,
+                                  DBUS_TYPE_INVALID)) {
+            GSList *l;
+
+            gjs_debug(GJS_DEBUG_DBUS, "Acquired name %s", name);
+
+            for (l = info->name_ownership_monitors; l != NULL; l = l->next) {
+                GjsNameOwnershipMonitor *monitor;
+
+                monitor = l->data;
+
+                if (monitor->state != NAME_PRIMARY_OWNER &&
+                    strcmp(name, monitor->funcs->name) == 0) {
+                    monitor->prev_state = monitor->state;
+                    monitor->state = NAME_PRIMARY_OWNER;
+                    states_changed = TRUE;
+                    /* keep going, don't break, there may be more matches */
+                }
+            }
+        } else {
+            gjs_debug(GJS_DEBUG_DBUS, "NameAcquired has wrong arguments???");
+        }
+    } else if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) {
+        GSList *l;
+
+        gjs_debug(GJS_DEBUG_DBUS, "Disconnected in %s", G_STRFUNC);
+
+        for (l = info->name_ownership_monitors; l != NULL; l = l->next) {
+            GjsNameOwnershipMonitor *monitor;
+
+            monitor = l->data;
+
+            if (monitor->state != NAME_NOT_REQUESTED) {
+                /* Set things up to re-request the name */
+                monitor->prev_state = monitor->state;
+                monitor->state = NAME_NOT_REQUESTED;
+                states_changed = TRUE;
+            }
+        }
+
+        /* FIXME move the monitors back to the pending list so they'll be found on reconnect */
+    }
+
+    if (states_changed)
+        process_name_ownership_monitors(connection, info);
+
+    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static void
+process_name_ownership_monitors(DBusConnection *connection,
+                                GjsDBusInfo    *info)
+{
+    GSList *l;
+    gboolean connected;
+    GSList *still_pending;
+
+    /* First pull anything out of pending queue */
+
+    still_pending = NULL;
+    while (pending_name_ownership_monitors != NULL) {
+        GjsNameOwnershipMonitor *monitor;
+
+        monitor = pending_name_ownership_monitors->data;
+        pending_name_ownership_monitors =
+            g_slist_remove(pending_name_ownership_monitors,
+                           pending_name_ownership_monitors->data);
+
+        if (monitor->bus_type == info->bus_type) {
+            info->name_ownership_monitors =
+                g_slist_prepend(info->name_ownership_monitors,
+                                monitor);
+        } else {
+            still_pending = g_slist_prepend(still_pending, monitor);
+        }
+    }
+    g_assert(pending_name_ownership_monitors == NULL);
+    pending_name_ownership_monitors = still_pending;
+
+    /* Now send notifications to the app */
+
+    connected = dbus_connection_get_is_connected(connection);
+
+    if (connected) {
+        for (l = info->name_ownership_monitors; l != NULL; l = l->next) {
+            GjsNameOwnershipMonitor *monitor;
+
+            monitor = l->data;
+
+            if (monitor->state == NAME_NOT_REQUESTED) {
+                int result;
+                unsigned int flags;
+                DBusError derror;
+
+                flags = DBUS_NAME_FLAG_ALLOW_REPLACEMENT;
+                if (monitor->funcs->type == GJS_DBUS_NAME_SINGLE_INSTANCE)
+                    flags |= DBUS_NAME_FLAG_DO_NOT_QUEUE;
+
+                dbus_error_init(&derror);
+                result = dbus_bus_request_name(connection,
+                                               monitor->funcs->name,
+                                               flags,
+                                               &derror);
+
+                /* log 'error' word only when one occurred */
+                if (derror.message != NULL) {
+                    gjs_debug(GJS_DEBUG_DBUS, "Requested name %s result %d error %s",
+                            monitor->funcs->name, result, derror.message);
+                } else {
+                    gjs_debug(GJS_DEBUG_DBUS, "Requested name %s result %d",
+                            monitor->funcs->name, result);
+                }
+
+                dbus_error_free(&derror);
+
+                /* An important feature of this code is that we always
+                 * transition from NOT_REQUESTED to something else when
+                 * a name monitor is first added, so we always notify
+                 * the app either "acquired" or "lost" and don't
+                 * leave the app in limbo.
+                 *
+                 * This means the app can "get going" when it gets the name
+                 * and exit when it loses it, and that will just work
+                 * since one or the other will always happen on startup.
+                 */
+
+                monitor->prev_state = monitor->state;
+
+                if (result == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER ||
+                    result == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER) {
+                    monitor->state = NAME_PRIMARY_OWNER;
+                } else if (result == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
+                    monitor->state = NAME_IN_QUEUE;
+                } else if (result == DBUS_REQUEST_NAME_REPLY_EXISTS) {
+                    monitor->state = NAME_NOT_OWNED;
+                } else {
+                    /* reply code we don't understand? */
+                    monitor->state = NAME_NOT_OWNED;
+                }
+            }
+        }
+    }
+
+    /* Do notifications with a list copy for extra safety
+     * (for true safety we also need to refcount each monitor
+     * and have a "destroyed" flag)
+     */
+    l = g_slist_copy(info->name_ownership_monitors);
+    while (l != NULL) {
+        GjsNameOwnershipMonitor *monitor;
+
+        monitor = l->data;
+        l = g_slist_remove(l, l->data);
+
+        if (monitor->prev_state != monitor->state) {
+            monitor->prev_state = monitor->state;
+
+            if (monitor->state == NAME_PRIMARY_OWNER) {
+                gjs_debug(GJS_DEBUG_DBUS, "Notifying acquired %s",
+                        monitor->funcs->name);
+                (* monitor->funcs->acquired) (connection, monitor->funcs->name, monitor->data);
+            } else if (monitor->state != NAME_PRIMARY_OWNER) {
+                gjs_debug(GJS_DEBUG_DBUS, "Notifying lost %s",
+                        monitor->funcs->name);
+                (* monitor->funcs->lost) (connection, monitor->funcs->name, monitor->data);
+            }
+        }
+    }
+}
+
+unsigned int
+gjs_dbus_acquire_name (DBusBusType                  bus_type,
+                       const GjsDBusNameOwnerFuncs *funcs,
+                       void                        *data)
+{
+    GjsNameOwnershipMonitor *monitor;
+
+    monitor = name_ownership_monitor_new(bus_type, funcs, data);
+    pending_name_ownership_monitors = g_slist_prepend(pending_name_ownership_monitors, monitor);
+
+    _gjs_dbus_ensure_connect_idle(bus_type);
+
+    return monitor->id;
+}
+
+static void
+release_name_internal (DBusBusType                  bus_type,
+                       const GjsDBusNameOwnerFuncs *funcs,
+                       void                        *data,
+                       unsigned int                 id)
+{
+    GjsDBusInfo *info;
+    GSList *l;
+    GjsNameOwnershipMonitor *monitor;
+    DBusConnection *connection;
+
+    connection = _gjs_dbus_get_weak_ref(bus_type);
+    if (!connection)
+        return;
+
+    info = _gjs_dbus_ensure_info(connection);
+
+    /* Check first pending list */
+    for (l = pending_name_ownership_monitors; l; l = l->next) {
+        monitor = l->data;
+        /* If the id is valid an matches, we are done */
+        if (monitor->state == NAME_PRIMARY_OWNER &&
+            ((id != GJS_DBUS_NAME_OWNER_MONITOR_INVALID_ID && monitor->id == id) ||
+             (monitor->funcs == funcs &&
+              monitor->data == data))) {
+            dbus_bus_release_name(connection, monitor->funcs->name, NULL);
+            pending_name_ownership_monitors =
+                g_slist_remove(pending_name_ownership_monitors,
+                               monitor);
+            name_ownership_monitor_free(monitor);
+            /* If the monitor was in the pending list it
+             * can't be in the processed list
+             */
+            return;
+        }
+    }
+
+    for (l = info->name_ownership_monitors; l; l = l->next) {
+        monitor = l->data;
+        /* If the id is valid an matches, we are done */
+        if (monitor->state == NAME_PRIMARY_OWNER &&
+            ((id != GJS_DBUS_NAME_OWNER_MONITOR_INVALID_ID && monitor->id == id) ||
+             (monitor->funcs == funcs &&
+              monitor->data == data))) {
+            dbus_bus_release_name(connection, monitor->funcs->name, NULL);
+            info->name_ownership_monitors = g_slist_remove(info->name_ownership_monitors,
+                                                           monitor);
+            name_ownership_monitor_free(monitor);
+            break;
+        }
+    }
+}
+
+void
+gjs_dbus_release_name_by_id (DBusBusType  bus_type,
+                             unsigned int id)
+{
+    release_name_internal(bus_type, NULL, NULL, id);
+}
+
+void
+gjs_dbus_release_name (DBusBusType                  bus_type,
+                       const GjsDBusNameOwnerFuncs *funcs,
+                       void                        *data)
+{
+    release_name_internal(bus_type, funcs, data,
+                          GJS_DBUS_NAME_OWNER_MONITOR_INVALID_ID);
+}
+
+static void
+notify_name_owner_changed(DBusConnection *connection,
+                          const char     *name,
+                          const char     *new_owner)
+{
+    GjsDBusInfo *info;
+    GjsNameWatch *watch;
+    GSList *l, *watchers;
+    gchar *old_owner;
+
+    info = _gjs_dbus_ensure_info(connection);
+
+    if (*new_owner == '\0')
+        new_owner = NULL;
+
+    watch = g_hash_table_lookup(info->name_watches, name);
+
+    if (watch == NULL)
+        return;
+
+    if ((watch->current_owner == new_owner) ||
+        (watch->current_owner && new_owner &&
+         strcmp(watch->current_owner, new_owner) == 0)) {
+        /* No change */
+        return;
+    }
+
+    /* we copy the list before iterating, because the
+     * callbacks may modify it */
+    watchers = g_slist_copy(watch->watchers);
+    g_slist_foreach(watchers, (GFunc)name_watcher_ref, NULL);
+
+    /* copy the old owner in case the watch is removed in
+     * the callbacks */
+    old_owner = g_strdup(watch->current_owner);
+
+    /* vanish the old owner */
+    if (old_owner != NULL) {
+        for (l = watchers;
+             l != NULL;
+             l = l->next) {
+            GjsNameWatcher *watcher = l->data;
+
+            if (watcher->notify_idle != 0) {
+                /* Name owner changed before we notified
+                 * the watcher of the initial name. We will notify
+                 * him now of the old name, then that this name
+                 * vanished.
+                 *
+                 * This is better than not sending calling any
+                 * callback, it might for instance trigger destroying
+                 * signal watchers on the unique name.
+                 */
+                g_source_remove(watcher->notify_idle);
+                notify_watcher_name_appeared(watcher);
+            }
+
+            if (!watcher->destroyed) {
+                (* watcher->funcs->vanished) (connection,
+                                              name,
+                                              old_owner,
+                                              watcher->data);
+            }
+        }
+    }
+
+    /* lookup for the watch again, since it might have vanished
+     * if all watchers were removed in the watcher->vanished
+     * callbacks */
+    watch = g_hash_table_lookup(info->name_watches, name);
+
+    if (watch) {
+        g_free(watch->current_owner);
+        watch->current_owner = g_strdup(new_owner);
+    }
+
+    /* appear the new owner */
+    if (new_owner != NULL) {
+        for (l = watchers;
+             l != NULL;
+             l = l->next) {
+            GjsNameWatcher *watcher = l->data;
+
+            if (!watcher->destroyed) {
+                (* watcher->funcs->appeared) (connection,
+                                              name,
+                                              new_owner,
+                                              watcher->data);
+            }
+        }
+    }
+
+    /* now destroy our copy */
+    g_slist_foreach(watchers, (GFunc)name_watcher_unref, NULL);
+    g_slist_free(watchers);
+
+    g_free(old_owner);
+}
+
+static DBusHandlerResult
+name_watch_filter_message(DBusConnection *connection,
+                          DBusMessage    *message,
+                          void           *data)
+{
+    GjsDBusInfo *info;
+
+    info = _gjs_dbus_ensure_info(connection);
+
+    if (dbus_message_is_signal(message, DBUS_INTERFACE_DBUS, "NameOwnerChanged") &&
+        dbus_message_has_sender(message, DBUS_SERVICE_DBUS)) {
+        const char *name = NULL;
+        const char *old_owner = NULL;
+        const char *new_owner = NULL;
+        if (dbus_message_get_args(message, NULL,
+                                  DBUS_TYPE_STRING, &name,
+                                  DBUS_TYPE_STRING, &old_owner,
+                                  DBUS_TYPE_STRING, &new_owner,
+                                  DBUS_TYPE_INVALID)) {
+            gjs_debug(GJS_DEBUG_DBUS, "NameOwnerChanged %s:   %s -> %s",
+                    name, old_owner, new_owner);
+
+            notify_name_owner_changed(connection, name, new_owner);
+        } else {
+            gjs_debug(GJS_DEBUG_DBUS, "NameOwnerChanged has wrong arguments???");
+        }
+    } else if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) {
+
+        gjs_debug(GJS_DEBUG_DBUS, "Disconnected in %s", G_STRFUNC);
+
+        /* FIXME set all current owners to NULL, and move watches back to the pending
+         * list so they are found on reconnect.
+         */
+    }
+
+    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+
+void
+_gjs_dbus_set_matching_name_owner_changed(DBusConnection *connection,
+                                          const char     *bus_name,
+                                          gboolean        matched)
+{
+    char *s;
+
+    gjs_debug(GJS_DEBUG_DBUS, "%s NameOwnerChanged on name '%s'",
+              matched ? "Matching" : "No longer matching",
+              bus_name);
+
+    s = g_strdup_printf("type='signal',sender='"
+                        DBUS_SERVICE_DBUS
+                        "',interface='"
+                        DBUS_INTERFACE_DBUS
+                        "',member='"
+                        "NameOwnerChanged"
+                        "',arg0='%s'",
+                        bus_name);
+
+    if (matched)
+        dbus_bus_add_match(connection,
+                           s, NULL); /* asking for error would make this block */
+    else
+        dbus_bus_remove_match(connection, s, NULL);
+
+    g_free(s);
+}
+
+static void
+on_start_service_reply(GjsDBusProxy    *proxy,
+                       DBusMessage     *message,
+                       void            *data)
+{
+    gjs_debug(GJS_DEBUG_DBUS, "Got successful reply to service start");
+}
+
+static void
+on_start_service_error(GjsDBusProxy    *proxy,
+                       const char      *error_name,
+                       const char      *error_message,
+                       void            *data)
+{
+    gjs_debug(GJS_DEBUG_DBUS, "Got error starting service: %s: %s",
+            error_name, error_message);
+}
+
+void
+gjs_dbus_start_service(DBusConnection *connection,
+                       const char     *name)
+{
+    DBusMessage *message;
+    dbus_uint32_t flags;
+    GjsDBusInfo *info;
+
+    gjs_debug(GJS_DEBUG_DBUS, "Starting service '%s'",
+            name);
+
+    info = _gjs_dbus_ensure_info(connection);
+
+    message = gjs_dbus_proxy_new_method_call(info->driver_proxy,
+                                             "StartServiceByName");
+
+    flags = 0;
+    if (dbus_message_append_args(message,
+                                 DBUS_TYPE_STRING, &name,
+                                 DBUS_TYPE_UINT32, &flags,
+                                 DBUS_TYPE_INVALID)) {
+        gjs_dbus_proxy_send(info->driver_proxy,
+                            message,
+                            on_start_service_reply,
+                            on_start_service_error,
+                            NULL);
+    } else {
+        gjs_debug(GJS_DEBUG_DBUS, "No memory appending args to StartServiceByName");
+    }
+
+    dbus_message_unref(message);
+}
+
+typedef struct {
+    DBusConnection *connection;
+    char *name;
+    GjsDBusWatchNameFlags flags;
+} GetOwnerRequest;
+
+static GetOwnerRequest*
+get_owner_request_new(DBusConnection       *connection,
+                      const char           *name,
+                      GjsDBusWatchNameFlags flags)
+{
+    GetOwnerRequest *gor;
+
+    gor = g_slice_new0(GetOwnerRequest);
+    gor->connection = connection;
+    gor->name = g_strdup(name);
+    gor->flags = flags;
+    dbus_connection_ref(connection);
+
+    return gor;
+}
+
+static void
+get_owner_request_free(GetOwnerRequest *gor)
+{
+    dbus_connection_unref(gor->connection);
+    g_free(gor->name);
+    g_slice_free(GetOwnerRequest, gor);
+}
+
+static void
+on_get_owner_reply(DBusPendingCall *pending,
+                   void            *user_data)
+{
+    DBusMessage *reply;
+    GetOwnerRequest *gor;
+
+    gor = user_data;
+
+    reply = dbus_pending_call_steal_reply(pending);
+    if (reply == NULL) {
+        g_warning("NULL reply in on_get_owner_reply?");
+        return;
+    }
+
+    if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN) {
+        const char *current_owner = NULL;
+
+        if (!dbus_message_get_args(reply, NULL,
+                                   DBUS_TYPE_STRING, &current_owner,
+                                   DBUS_TYPE_INVALID)) {
+            gjs_debug(GJS_DEBUG_DBUS, "GetNameOwner has wrong args '%s'",
+                    dbus_message_get_signature(reply));
+        } else {
+            gjs_debug(GJS_DEBUG_DBUS, "Got owner '%s' for name '%s'",
+                    current_owner, gor->name);
+            if (current_owner != NULL) {
+                notify_name_owner_changed(gor->connection,
+                                          gor->name,
+                                          current_owner);
+            }
+        }
+    } else if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
+        if (g_str_equal(dbus_message_get_error_name(reply),
+                        DBUS_ERROR_NAME_HAS_NO_OWNER)) {
+            gjs_debug(GJS_DEBUG_DBUS, "'%s' was not running",
+                    gor->name);
+            if (gor->flags & GJS_DBUS_NAME_START_IF_NOT_FOUND) {
+                gjs_debug(GJS_DEBUG_DBUS, "  (starting it up)");
+                gjs_dbus_start_service(gor->connection, gor->name);
+            } else {
+                /* no owner for now, notify app */
+                notify_name_owner_changed(gor->connection,
+                                          gor->name,
+                                          "");
+            }
+        } else {
+            gjs_debug(GJS_DEBUG_DBUS, "Error getting owner of name '%s': %s",
+                    gor->name,
+                    dbus_message_get_error_name(reply));
+
+            /* Notify no owner for now, ensuring the app
+             * gets advised "appeared" or "vanished",
+             * one or the other.
+             */
+            notify_name_owner_changed(gor->connection,
+                                      gor->name,
+                                      "");
+        }
+    } else {
+        gjs_debug(GJS_DEBUG_DBUS, "Nonsensical reply type to GetNameOwner");
+    }
+
+    dbus_message_unref(reply);
+}
+
+static void
+request_name_owner(DBusConnection *connection,
+                   GjsDBusInfo    *info,
+                   GjsNameWatch   *watch)
+{
+    DBusMessage *message;
+    DBusPendingCall *call;
+
+    message = dbus_message_new_method_call(DBUS_SERVICE_DBUS,
+                                           DBUS_PATH_DBUS,
+                                           DBUS_INTERFACE_DBUS,
+                                           "GetNameOwner");
+    if (message == NULL)
+        g_error("no memory");
+
+    if (!dbus_message_append_args(message,
+                                  DBUS_TYPE_STRING, &watch->name,
+                                  DBUS_TYPE_INVALID))
+        g_error("no memory");
+
+    call = NULL;
+    dbus_connection_send_with_reply(connection, message, &call, -1);
+    if (call != NULL) {
+        GetOwnerRequest *gor;
+        GjsDBusWatchNameFlags flags;
+        GSList *l;
+
+        gjs_debug(GJS_DEBUG_DBUS, "Sent GetNameOwner for '%s'",
+                watch->name);
+
+        flags = 0;
+        for (l = watch->watchers;
+             l != NULL;
+             l = l->next) {
+            GjsNameWatcher *watcher = l->data;
+
+            if (watcher->flags & GJS_DBUS_NAME_START_IF_NOT_FOUND)
+                flags |= GJS_DBUS_NAME_START_IF_NOT_FOUND;
+        }
+
+        gor = get_owner_request_new(connection, watch->name, flags);
+
+        if (!dbus_pending_call_set_notify(call, on_get_owner_reply,
+                                          gor,
+                                          (DBusFreeFunction) get_owner_request_free))
+            g_error("no memory");
+
+        /* the connection will hold a ref to the pending call */
+        dbus_pending_call_unref(call);
+    } else {
+        gjs_debug(GJS_DEBUG_DBUS, "GetNameOwner for '%s' not sent, connection disconnected",
+                watch->name);
+    }
+}
+
+static gboolean
+notify_watcher_name_appeared(gpointer data)
+{
+    GjsNameWatcher *watcher;
+    DBusConnection *connection;
+
+    watcher = data;
+    watcher->notify_idle = 0;
+
+    connection = _gjs_dbus_get_weak_ref(watcher->bus_type);
+
+    if (!connection)
+        return FALSE;
+
+    (* watcher->funcs->appeared) (connection,
+                                  watcher->watch->name,
+                                  watcher->watch->current_owner,
+                                  watcher->data);
+    return FALSE;
+}
+
+static void
+create_watch_for_watcher(DBusConnection *connection,
+                         GjsDBusInfo    *info,
+                         const char     *name,
+                         GjsNameWatcher *watcher)
+{
+    GjsNameWatch *watch;
+
+    watch = g_hash_table_lookup(info->name_watches, name);
+    if (watch == NULL) {
+        watch = name_watch_new(name);
+
+        g_hash_table_replace(info->name_watches, watch->name, watch);
+
+        watch->watchers = g_slist_prepend(watch->watchers, watcher);
+
+        _gjs_dbus_set_matching_name_owner_changed(connection, watch->name, TRUE);
+
+        request_name_owner(connection, info, watch);
+    } else {
+        watch->watchers = g_slist_prepend(watch->watchers, watcher);
+    }
+    name_watcher_ref(watcher);
+
+    watcher->watch = watch;
+
+}
+
+static void
+process_pending_name_watchers(DBusConnection *connection,
+                              GjsDBusInfo    *info)
+{
+    GSList *still_pending;
+
+    still_pending = NULL;
+    while (pending_name_watchers != NULL) {
+        GjsPendingNameWatcher *pending;
+        GjsNameWatch *watch;
+
+        pending = pending_name_watchers->data;
+        pending_name_watchers = g_slist_remove(pending_name_watchers,
+                                               pending_name_watchers->data);
+
+        if (pending->bus_type != info->bus_type) {
+            still_pending = g_slist_prepend(still_pending, pending);
+            continue;
+        }
+
+        create_watch_for_watcher(connection,
+                                 info,
+                                 pending->name,
+                                 pending->watcher);
+
+        watch = pending->watcher->watch;
+
+        /* If we already know the owner, let the new watcher know */
+        if (watch->current_owner != NULL) {
+            (* pending->watcher->funcs->appeared) (connection,
+                                                   watch->name,
+                                                   watch->current_owner,
+                                                   pending->watcher->data);
+        }
+
+        g_free(pending->name);
+        name_watcher_unref(pending->watcher);
+        g_slice_free(GjsPendingNameWatcher, pending);
+    }
+
+    g_assert(pending_name_watchers == NULL);
+    pending_name_watchers = still_pending;
+}
+
+static void
+name_watch_remove_watcher(GjsNameWatch     *watch,
+                          GjsNameWatcher   *watcher)
+{
+    watch->watchers = g_slist_remove(watch->watchers,
+                                     watcher);
+
+    if (watcher->notify_idle) {
+        g_source_remove(watcher->notify_idle);
+        watcher->notify_idle = 0;
+    }
+
+    watcher->destroyed = TRUE;
+    name_watcher_unref(watcher);
+}
+
+void
+gjs_dbus_watch_name(DBusBusType                  bus_type,
+                    const char                  *name,
+                    GjsDBusWatchNameFlags        flags,
+                    const GjsDBusWatchNameFuncs *funcs,
+                    void                        *data)
+{
+    GjsNameWatcher *watcher;
+    DBusConnection *connection;
+
+    gjs_debug(GJS_DEBUG_DBUS, "Adding watch on name '%s'",
+            name);
+
+    watcher = name_watcher_new(flags, funcs, data, bus_type);
+
+    connection = _gjs_dbus_get_weak_ref(bus_type);
+
+    if (connection) {
+        GjsDBusInfo *info;
+
+        info = _gjs_dbus_ensure_info(connection);
+
+        create_watch_for_watcher(connection,
+                                 info,
+                                 name,
+                                 watcher);
+        /* The initial reference is now transferred to the watch */
+        name_watcher_unref(watcher);
+
+        /* If we already know the owner, notify the user in an idle */
+        if (watcher->watch->current_owner) {
+            watcher->notify_idle =
+                g_idle_add_full(G_PRIORITY_DEFAULT_IDLE,
+                                notify_watcher_name_appeared,
+                                watcher,
+                                (GDestroyNotify)name_watcher_unref);
+            name_watcher_ref(watcher);
+        }
+
+    } else {
+        GjsPendingNameWatcher *pending;
+
+        pending = g_slice_new0(GjsPendingNameWatcher);
+
+        pending->bus_type = bus_type;
+        pending->name = g_strdup(name);
+        pending->watcher = watcher;
+
+        pending_name_watchers = g_slist_prepend(pending_name_watchers, pending);
+
+        _gjs_dbus_ensure_connect_idle(pending->bus_type);
+    }
+}
+
+void
+gjs_dbus_unwatch_name(DBusBusType                  bus_type,
+                      const char                  *name,
+                      const GjsDBusWatchNameFuncs *funcs,
+                      void                        *data)
+{
+    DBusConnection *connection;
+    GjsDBusInfo *info;
+    GjsNameWatch *watch;
+    GSList *l;
+    GjsNameWatcher *watcher;
+
+    gjs_debug(GJS_DEBUG_DBUS, "Removing watch on name '%s'",
+            name);
+
+    connection = _gjs_dbus_get_weak_ref(bus_type);
+    if (connection == NULL) {
+        /* right now our state is entirely hosed if we disconnect
+         * (we don't move the watchers out of the connection data),
+         * so can't do much here without larger changes to the file
+         */
+        g_warning("Have not implemented disconnect handling");
+        return;
+    }
+
+    info = _gjs_dbus_ensure_info(connection);
+
+    /* could still be pending */
+    process_pending_name_watchers(connection, info);
+
+    watch = g_hash_table_lookup(info->name_watches, name);
+
+    if (watch == NULL) {
+        g_warning("attempt to unwatch name %s but nobody is watching that",
+                  name);
+        return;
+    }
+
+    watcher = NULL;
+    for (l = watch->watchers; l != NULL; l = l->next) {
+        watcher = l->data;
+
+        if (watcher->funcs == funcs &&
+            watcher->data == data)
+            break;
+    }
+
+    if (l == NULL) {
+        g_warning("Could not find a watch on %s matching %p %p",
+                  name, funcs, data);
+        return;
+    }
+    g_assert(l->data == watcher);
+
+    name_watch_remove_watcher(watch, watcher);
+
+    /* Clear out the watch if it's gone */
+    if (watch->watchers == NULL) {
+        g_hash_table_remove(info->name_watches, watch->name);
+
+        _gjs_dbus_set_matching_name_owner_changed(connection, watch->name, FALSE);
+
+        name_watch_free(watch);
+    }
+}
+
+const char*
+gjs_dbus_get_watched_name_owner(DBusBusType   bus_type,
+                                const char   *name)
+{
+    DBusConnection *connection;
+    GjsNameWatch *watch;
+    GjsDBusInfo *info;
+
+    connection = _gjs_dbus_get_weak_ref(bus_type);
+    if (connection == NULL) {
+        return NULL;
+    }
+
+    info = _gjs_dbus_ensure_info(connection);
+
+    /* could still be pending */
+    process_pending_name_watchers(connection, info);
+
+    watch = g_hash_table_lookup(info->name_watches, name);
+    if (watch == NULL) {
+        g_warning("Tried to get owner of '%s' but there is no watch on it",
+                  name);
+        return NULL;
+    }
+
+    return watch->current_owner;
+}
+
+void
+gjs_dbus_register_json(DBusConnection          *connection,
+                       const char              *iface_name,
+                       const GjsDBusJsonMethod *methods,
+                       int                      n_methods)
+{
+    GjsDBusInfo *info;
+    GjsJsonIface *iface;
+
+    info = _gjs_dbus_ensure_info(connection);
+
+    iface = json_iface_new(iface_name, methods, n_methods);
+
+    g_hash_table_replace(info->json_ifaces, iface->name, iface);
+}
+
+void
+gjs_dbus_unregister_json(DBusConnection *connection,
+                         const char     *iface_name)
+{
+    GjsDBusInfo *info;
+
+    info = _gjs_dbus_ensure_info(connection);
+
+    g_hash_table_remove(info->json_ifaces, iface_name);
+}
+
+typedef struct {
+    DBusConnection *connection;
+    GObject *gobj;
+    char *iface_name;
+} GjsDBusGObject;
+
+static void
+gobj_path_unregistered(DBusConnection  *connection,
+                       void            *user_data)
+{
+    GjsDBusGObject *g;
+
+    g = user_data;
+
+    if (g->gobj) {
+        g_object_remove_weak_pointer(g->gobj, (void**) &g->gobj);
+        g->gobj = NULL;
+    }
+
+    g_free(g->iface_name);
+    g_slice_free(GjsDBusGObject, g);
+}
+
+static DBusHandlerResult
+gobj_path_message(DBusConnection  *connection,
+                  DBusMessage     *message,
+                  void            *user_data)
+{
+    GjsDBusGObject *g;
+    GjsDBusInfo *info;
+    GjsJsonIface *iface;
+    const char *message_iface;
+    const char *message_method;
+    DBusError derror;
+    int i;
+    const GjsDBusJsonMethod *method;
+    DBusMessageIter arg_iter, dict_iter;
+
+    info = _gjs_dbus_ensure_info(connection);
+    g = user_data;
+
+    gjs_debug(GJS_DEBUG_DBUS, "Received message to iface %s gobj %p",
+            g->iface_name, g->gobj);
+
+    if (g->gobj == NULL) {
+        /* GObject was destroyed */
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    }
+
+    if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_METHOD_CALL)
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    dbus_error_init(&derror);
+
+    message_iface = dbus_message_get_interface(message);
+
+    /* FIXME implement Introspectable() just to enable dbus debugger */
+
+    if (message_iface != NULL &&
+        strcmp(message_iface, g->iface_name) != 0) {
+
+        dbus_set_error(&derror, DBUS_ERROR_UNKNOWN_METHOD,
+                       "Interface '%s' not implemented by this object, did you mean '%s'?",
+                       message_iface, g->iface_name);
+
+        goto out;
+    }
+
+    iface = g_hash_table_lookup(info->json_ifaces,
+                                g->iface_name);
+    if (iface == NULL) {
+        g_warning("Object registered with iface %s but that iface is not registered",
+                  g->iface_name);
+        dbus_set_error(&derror, DBUS_ERROR_UNKNOWN_METHOD,
+                       "Bug - '%s' is not registered",
+                       g->iface_name);
+        goto out;
+    }
+
+    method = NULL;
+    message_method = dbus_message_get_member(message);
+    for (i = 0; i < iface->n_methods; ++i) {
+        if (strcmp(message_method, iface->methods[i].name) == 0) {
+            method = &iface->methods[i];
+            break;
+        }
+    }
+
+    if (method == NULL) {
+        dbus_set_error(&derror, DBUS_ERROR_UNKNOWN_METHOD,
+                       "Interface '%s' has no method '%s'",
+                       g->iface_name, message_method);
+        goto out;
+    }
+
+    if (!dbus_message_has_signature(message, "a{sv}")) {
+        dbus_set_error(&derror, DBUS_ERROR_INVALID_ARGS,
+                       "Method %s.%s should have 1 argument which is a dictionary",
+                       g->iface_name, message_method);
+        goto out;
+    }
+
+    dbus_message_iter_init(message, &arg_iter);
+    dbus_message_iter_recurse(&arg_iter, &dict_iter);
+
+    if (method->sync_func != NULL) {
+        DBusMessage *reply;
+        DBusMessageIter out_arg_iter, out_dict_iter;
+
+        reply = dbus_message_new_method_return(message);
+        if (reply == NULL) {
+            dbus_set_error(&derror, DBUS_ERROR_NO_MEMORY,
+                           "No memory");
+            goto out;
+        }
+
+        dbus_message_iter_init_append(reply, &out_arg_iter);
+        dbus_message_iter_open_container(&out_arg_iter,
+                                         DBUS_TYPE_ARRAY, "{sv}",
+                                         &out_dict_iter);
+
+        g_object_ref(g->gobj);
+        (* method->sync_func) (connection, message,
+                               &dict_iter, &out_dict_iter,
+                               g->gobj,
+                               &derror);
+        g_object_unref(g->gobj);
+
+        dbus_message_iter_close_container(&out_arg_iter, &out_dict_iter);
+
+        if (!dbus_error_is_set(&derror)) {
+            dbus_connection_send(connection, reply, NULL);
+        }
+        dbus_message_unref(reply);
+
+    } else if (method->async_func != NULL) {
+        g_object_ref(g->gobj);
+        (* method->async_func) (connection, message,
+                                &dict_iter,
+                                g->gobj);
+        g_object_unref(g->gobj);
+    } else {
+        g_warning("Method %s does not have any implementation", method->name);
+    }
+
+ out:
+    if (dbus_error_is_set(&derror)) {
+        DBusMessage *reply;
+
+        reply = dbus_message_new_error(message,
+                                       derror.name,
+                                       derror.message);
+        dbus_error_free(&derror);
+
+        if (reply != NULL) {
+            dbus_connection_send(connection, reply, NULL);
+
+            dbus_message_unref(reply);
+        } else {
+            /* use g_printerr not g_warning since this is NOT a "can
+             * never happen" just a "probably will never happen"
+             */
+            g_printerr("Could not send OOM error\n");
+        }
+    }
+
+    return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusObjectPathVTable gobj_vtable = {
+    gobj_path_unregistered,
+    gobj_path_message,
+    NULL,
+};
+
+/* Note that because of how this works, each object can be registered
+ * at multiple paths but only once per path. Which is sort of bizarre,
+ * but we'll fix it when we need it.
+ */
+void
+gjs_dbus_register_g_object(DBusConnection *connection,
+                           const char     *path,
+                           GObject        *gobj,
+                           const char     *iface_name)
+{
+    GjsDBusGObject *g;
+
+    g = g_slice_new0(GjsDBusGObject);
+    g->iface_name = g_strdup(iface_name);
+    g->gobj = gobj;
+
+    if (!dbus_connection_register_object_path(connection, path,
+                                              &gobj_vtable, g)) {
+        g_warning("Failed to register object path %s", path);
+    }
+
+    g_object_add_weak_pointer(g->gobj, (void**) &g->gobj);
+}
+
+void
+gjs_dbus_unregister_g_object (DBusConnection *connection,
+                              const char     *path)
+{
+    dbus_connection_unregister_object_path(connection, path);
+}
+
+static void
+open_json_entry(DBusMessageIter *dict_iter,
+                const char      *key,
+                const char      *signature,
+                DBusMessageIter *entry_iter,
+                DBusMessageIter *variant_iter)
+{
+    dbus_message_iter_open_container(dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, entry_iter);
+
+    dbus_message_iter_append_basic(entry_iter, DBUS_TYPE_STRING, &key);
+
+    dbus_message_iter_open_container(entry_iter, DBUS_TYPE_VARIANT, signature, variant_iter);
+}
+
+static void
+close_json_entry(DBusMessageIter *dict_iter,
+                 DBusMessageIter *entry_iter,
+                 DBusMessageIter *variant_iter)
+{
+    dbus_message_iter_close_container(entry_iter, variant_iter);
+
+    dbus_message_iter_close_container(dict_iter, entry_iter);
+}
+
+static void
+open_json_entry_array(DBusMessageIter *dict_iter,
+                      const char      *key,
+                      int              array_element_type,
+                      DBusMessageIter *entry_iter,
+                      DBusMessageIter *variant_iter,
+                      DBusMessageIter *array_iter)
+{
+    char buf[3];
+    buf[0] = 'a';
+    buf[1] = array_element_type;
+    buf[2] = '\0';
+
+    open_json_entry(dict_iter, key, buf, entry_iter, variant_iter);
+
+    dbus_message_iter_open_container(variant_iter, DBUS_TYPE_ARRAY, &buf[1], array_iter);
+}
+
+static void
+close_json_entry_array(DBusMessageIter *dict_iter,
+                       DBusMessageIter *entry_iter,
+                       DBusMessageIter *variant_iter,
+                       DBusMessageIter *array_iter)
+{
+    dbus_message_iter_close_container(variant_iter, array_iter);
+
+    close_json_entry(dict_iter, entry_iter, variant_iter);
+}
+
+void
+gjs_dbus_append_json_entry (DBusMessageIter *dict_iter,
+                            const char      *key,
+                            int              dbus_type,
+                            void            *basic_value_p)
+{
+    DBusMessageIter entry_iter, variant_iter;
+    char buf[2];
+
+    buf[0] = dbus_type;
+    buf[1] = '\0';
+
+    open_json_entry(dict_iter, key, buf, &entry_iter, &variant_iter);
+
+    dbus_message_iter_append_basic(&variant_iter, dbus_type, basic_value_p);
+
+    close_json_entry(dict_iter, &entry_iter, &variant_iter);
+}
+
+void
+gjs_dbus_append_json_entry_STRING (DBusMessageIter *dict_iter,
+                                   const char      *key,
+                                   const char      *value)
+{
+    gjs_dbus_append_json_entry(dict_iter, key, DBUS_TYPE_STRING, &value);
+}
+
+void
+gjs_dbus_append_json_entry_INT32 (DBusMessageIter *dict_iter,
+                                  const char      *key,
+                                  dbus_int32_t     value)
+{
+    gjs_dbus_append_json_entry(dict_iter, key, DBUS_TYPE_INT32, &value);
+}
+
+void
+gjs_dbus_append_json_entry_DOUBLE (DBusMessageIter *dict_iter,
+                                   const char      *key,
+                                   double           value)
+{
+    gjs_dbus_append_json_entry(dict_iter, key, DBUS_TYPE_DOUBLE, &value);
+}
+
+void
+gjs_dbus_append_json_entry_BOOLEAN (DBusMessageIter *dict_iter,
+                                  const char      *key,
+                                  dbus_bool_t      value)
+{
+    gjs_dbus_append_json_entry(dict_iter, key, DBUS_TYPE_BOOLEAN, &value);
+}
+
+/* when coming from a dynamic language, we don't know what type of array '[]' is supposed to be */
+void
+gjs_dbus_append_json_entry_EMPTY_ARRAY (DBusMessageIter  *dict_iter,
+                                        const char       *key)
+{
+    DBusMessageIter entry_iter, variant_iter, array_iter;
+
+    /* so just say VARIANT even though there won't be any elements in the array */
+    open_json_entry_array(dict_iter, key, DBUS_TYPE_VARIANT, &entry_iter, &variant_iter, &array_iter);
+
+    close_json_entry_array(dict_iter, &entry_iter, &variant_iter, &array_iter);
+}
+
+void
+gjs_dbus_append_json_entry_STRING_ARRAY (DBusMessageIter  *dict_iter,
+                                         const char       *key,
+                                         const char      **value)
+{
+    DBusMessageIter entry_iter, variant_iter, array_iter;
+    int i;
+
+    open_json_entry_array(dict_iter, key, DBUS_TYPE_STRING, &entry_iter, &variant_iter, &array_iter);
+
+    for (i = 0; value[i] != NULL; ++i) {
+        dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, &value[i]);
+    }
+
+    close_json_entry_array(dict_iter, &entry_iter, &variant_iter, &array_iter);
+}
+
+gboolean
+gjs_dbus_message_iter_get_gsize(DBusMessageIter  *iter,
+                                gsize            *value_p)
+{
+    switch (dbus_message_iter_get_arg_type(iter)) {
+    case DBUS_TYPE_INT32:
+        {
+            dbus_int32_t v;
+            dbus_message_iter_get_basic(iter, &v);
+            if (v < 0)
+                return FALSE;
+            *value_p = v;
+        }
+        break;
+    case DBUS_TYPE_UINT32:
+        {
+            dbus_uint32_t v;
+            dbus_message_iter_get_basic(iter, &v);
+            *value_p = v;
+        }
+        break;
+    case DBUS_TYPE_INT64:
+        {
+            dbus_int64_t v;
+            dbus_message_iter_get_basic(iter, &v);
+            if (v < 0)
+                return FALSE;
+            if (((guint64)v) > G_MAXSIZE)
+                return FALSE;
+            *value_p = v;
+        }
+        break;
+    case DBUS_TYPE_UINT64:
+        {
+            dbus_uint64_t v;
+            dbus_message_iter_get_basic(iter, &v);
+            if (v > G_MAXSIZE)
+                return FALSE;
+            *value_p = v;
+        }
+        break;
+    default:
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+gboolean
+gjs_dbus_message_iter_get_gssize(DBusMessageIter  *iter,
+                                 gssize           *value_p)
+{
+    switch (dbus_message_iter_get_arg_type(iter)) {
+    case DBUS_TYPE_INT32:
+        {
+            dbus_int32_t v;
+            dbus_message_iter_get_basic(iter, &v);
+            *value_p = v;
+        }
+        break;
+    case DBUS_TYPE_UINT32:
+        {
+            dbus_uint32_t v;
+            dbus_message_iter_get_basic(iter, &v);
+            if (v > (guint32) G_MAXSSIZE)
+                return FALSE;
+            *value_p = v;
+        }
+        break;
+    case DBUS_TYPE_INT64:
+        {
+            dbus_int64_t v;
+            dbus_message_iter_get_basic(iter, &v);
+            if (v > (gint64) G_MAXSSIZE)
+                return FALSE;
+            if (v < (gint64) G_MINSSIZE)
+                return FALSE;
+            *value_p = v;
+        }
+        break;
+    case DBUS_TYPE_UINT64:
+        {
+            dbus_uint64_t v;
+            dbus_message_iter_get_basic(iter, &v);
+            if (v > (guint64) G_MAXSSIZE)
+                return FALSE;
+            *value_p = v;
+        }
+        break;
+    default:
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+#if GJS_BUILD_TESTS
+
+#include "dbus-proxy.h"
+#include "dbus-input-stream.h"
+#include "dbus-output-stream.h"
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+
+static pid_t test_service_pid = 0;
+static GjsDBusProxy *test_service_proxy = NULL;
+
+static pid_t test_io_pid = 0;
+static GjsDBusProxy *test_io_proxy = NULL;
+
+static GMainLoop *client_loop = NULL;
+
+static int n_running_children = 0;
+
+static GjsDBusInputStream  *input_from_io_service;
+static GjsDBusOutputStream *output_to_io_service;
+
+static const char stream_data_to_io_service[] = "This is sent from the main test process to the IO service.";
+static const char stream_data_from_io_service[] = "This is sent from the IO service to the main test process. The quick brown fox, etc.";
+
+static void do_test_service_child (void);
+static void do_test_io_child      (void);
+
+/* quit when all children are gone */
+static void
+another_child_down(void)
+{
+    g_assert(n_running_children > 0);
+    n_running_children -= 1;
+
+    if (n_running_children == 0) {
+        g_main_loop_quit(client_loop);
+    }
+}
+
+static const char*
+extract_string_arg(DBusMessageIter *in_iter,
+                   const char      *prop_name,
+                   DBusError       *error)
+{
+    const char *s;
+
+    s = NULL;
+    while (dbus_message_iter_get_arg_type(in_iter) == DBUS_TYPE_DICT_ENTRY) {
+        DBusMessageIter entry_iter, variant_iter;
+        const char *key;
+
+        dbus_message_iter_recurse(in_iter, &entry_iter);
+
+        dbus_message_iter_get_basic(&entry_iter, &key);
+
+        if (strcmp(key, prop_name) == 0) {
+            dbus_message_iter_next(&entry_iter);
+
+            dbus_message_iter_recurse(&entry_iter, &variant_iter);
+            if (dbus_message_iter_get_arg_type(&variant_iter) != DBUS_TYPE_STRING) {
+                dbus_set_error(error, DBUS_ERROR_INVALID_ARGS,
+                               "Value of '%s' prop should be a string",
+                               prop_name);
+                return NULL;
+            }
+
+            dbus_message_iter_get_basic(&variant_iter, &s);
+
+            return s;
+        }
+    }
+
+    dbus_set_error(error, DBUS_ERROR_INVALID_ARGS,
+                   "No '%s' prop provided", prop_name);
+    return NULL;
+}
+
+static void
+fork_child_test_service(void)
+{
+    pid_t child_pid;
+
+    /* it would break to fork after we already connected */
+    g_assert(session_bus_weak_ref == NULL);
+    g_assert(system_bus_weak_ref == NULL);
+    g_assert(test_service_pid == 0);
+
+    child_pid = fork();
+
+    if (child_pid == -1) {
+        g_error("Failed to fork dbus service");
+    } else if (child_pid > 0) {
+        /* We are the parent */
+        test_service_pid = child_pid;
+        n_running_children += 1;
+
+        return;
+    }
+
+    /* we are the child, set up a service for main test process to talk to */
+
+    do_test_service_child();
+}
+
+/* This test function doesn't really test anything, just sets up
+ * for the following one
+ */
+static void
+fork_child_test_io(void)
+{
+    pid_t child_pid;
+
+    /* it would break to fork after we already connected */
+    g_assert(session_bus_weak_ref == NULL);
+    g_assert(system_bus_weak_ref == NULL);
+    g_assert(test_io_pid == 0);
+
+    child_pid = fork();
+
+    if (child_pid == -1) {
+        g_error("Failed to fork dbus service");
+    } else if (child_pid > 0) {
+        /* We are the parent */
+        test_io_pid = child_pid;
+        n_running_children += 1;
+
+        return;
+    }
+
+    /* we are the child, set up a service for main test process to talk to */
+
+    do_test_io_child();
+}
+
+static void
+on_expected_fnf_error_reply_kill_child(GjsDBusProxy    *proxy,
+                                       const char      *error_name,
+                                       const char      *error_message,
+                                       void            *data)
+{
+    gjs_debug(GJS_DEBUG_DBUS, "got expected error reply to alwaysErrorSync, killing child");
+
+    /* We were expecting an error, good. */
+    if (strcmp(error_name, DBUS_ERROR_FILE_NOT_FOUND) != 0) {
+        g_error("Got error we did not expect %s: %s",
+                error_name, error_message);
+    }
+
+    if (kill(test_service_pid, SIGTERM) < 0) {
+        g_error("Test service was no longer around... it must have failed somehow (%s)",
+                strerror(errno));
+    }
+
+    /* We will quit main loop when we see the child go away */
+}
+
+static void
+on_unexpected_error_reply(GjsDBusProxy    *proxy,
+                          const char      *error_name,
+                          const char      *error_message,
+                          void            *data)
+{
+    const char *context_text = data;
+
+    g_error("Got error %s: '%s' context was: %s",
+            error_name, error_message, context_text);
+}
+
+static void
+on_get_always_error_reply(GjsDBusProxy    *proxy,
+                          DBusMessage     *message,
+                          DBusMessageIter *return_value_iter,
+                          void            *data)
+{
+    g_error("alwaysError json method supposed to return an error always, not a valid reply");
+}
+
+static void
+on_get_some_stuff_reply(GjsDBusProxy    *proxy,
+                        DBusMessage     *message,
+                        DBusMessageIter *return_value_iter,
+                        void            *data)
+{
+    gjs_debug(GJS_DEBUG_DBUS, "reply received to getSomeStuffSync");
+
+    /* FIXME look at the return value to see if it's what
+     * the test service sends
+     */
+
+    gjs_dbus_proxy_call_json_async(test_service_proxy,
+                                   "alwaysErrorSync",
+                                   on_get_always_error_reply,
+                                   on_expected_fnf_error_reply_kill_child,
+                                   NULL,
+                                   NULL);
+}
+
+static void
+on_test_service_appeared(DBusConnection *connection,
+                         const char     *name,
+                         const char     *new_owner_unique_name,
+                         void           *data)
+{
+    dbus_int32_t v_INT32;
+
+    gjs_debug(GJS_DEBUG_DBUS, "%s appeared",
+            name);
+
+    test_service_proxy =
+        gjs_dbus_proxy_new(connection, new_owner_unique_name,
+                           "/com/litl/test/object42",
+                           "com.litl.TestIface");
+    v_INT32 = 42;
+    gjs_dbus_proxy_call_json_async(test_service_proxy,
+                                   "getSomeStuffSync",
+                                   on_get_some_stuff_reply,
+                                   on_unexpected_error_reply,
+                                   "getSomeStuffSync call from on_test_service_appeared",
+                                   "yourNameIs", DBUS_TYPE_STRING, &name,
+                                   "yourUniqueNameIs", DBUS_TYPE_STRING, &new_owner_unique_name,
+                                   "anIntegerIs", DBUS_TYPE_INT32, &v_INT32,
+                                   NULL);
+}
+
+static void
+on_test_service_vanished(DBusConnection *connection,
+                         const char     *name,
+                         const char     *old_owner_unique_name,
+                         void           *data)
+{
+    gjs_debug(GJS_DEBUG_DBUS, "%s vanished", name);
+
+    another_child_down();
+}
+
+static GjsDBusWatchNameFuncs watch_test_service_funcs = {
+    on_test_service_appeared,
+    on_test_service_vanished
+};
+
+static void
+on_confirm_streams_reply(GjsDBusProxy    *proxy,
+                         DBusMessage     *message,
+                         DBusMessageIter *return_value_iter,
+                         void            *data)
+{
+    const char *received;
+
+    received = extract_string_arg(return_value_iter,
+                                  "received",
+                                  NULL);
+    g_assert(received != NULL);
+
+    if (strcmp(received, stream_data_to_io_service) != 0) {
+        g_error("We sent the child process '%s' but it says it got '%s'",
+                stream_data_to_io_service,
+                received);
+    }
+
+    gjs_debug(GJS_DEBUG_DBUS, "com.litl.TestIO says it got: '%s'", received);
+
+    /* We've exchanged all our streams - time to kill the TestIO
+     * child process
+     */
+    gjs_debug(GJS_DEBUG_DBUS, "Sending TERM to TestIO child");
+    if (kill(test_io_pid, SIGTERM) < 0) {
+        g_error("Test IO service was no longer around... it must have failed somehow (%s)",
+                strerror(errno));
+    }
+}
+
+static void
+on_setup_streams_reply(GjsDBusProxy    *proxy,
+                       DBusMessage     *message,
+                       DBusMessageIter *return_value_iter,
+                       void            *data)
+{
+    const char *stream_path;
+    gsize total;
+    gssize result;
+    gsize read_size;
+    GError *error;
+    GString *str;
+    char buf[10];
+
+    gjs_debug(GJS_DEBUG_DBUS, "Got reply to setupStreams");
+
+    stream_path = extract_string_arg(return_value_iter,
+                                     "stream",
+                                     NULL);
+    g_assert(stream_path != NULL);
+
+    output_to_io_service =
+        gjs_dbus_output_stream_new(gjs_dbus_proxy_get_connection(proxy),
+                                   dbus_message_get_sender(message),
+                                   stream_path);
+
+    g_assert(input_from_io_service && output_to_io_service);
+
+    /* Write to the output stream */
+
+    total = strlen(stream_data_to_io_service);
+
+    error = NULL;
+    result = g_output_stream_write(G_OUTPUT_STREAM(output_to_io_service),
+                                   stream_data_to_io_service,
+                                   10,
+                                   NULL,
+                                   &error);
+    if (result < 0) {
+        g_error("Error writing to output stream: %s", error->message);
+        g_error_free(error);
+    }
+
+    if (result != 10) {
+        g_error("Wrote %d instead of 10 bytes", (int) result);
+    }
+
+    if (!g_output_stream_write_all(G_OUTPUT_STREAM(output_to_io_service),
+                                   stream_data_to_io_service + 10,
+                                   total - 10,
+                                   NULL, NULL, &error)) {
+        g_error("Error writing all to output stream: %s", error->message);
+        g_error_free(error);
+    }
+
+    /* flush should do nothing here, and is not needed, but
+     * just calling it to test it
+     */
+    if (!g_output_stream_flush(G_OUTPUT_STREAM(output_to_io_service), NULL, &error)) {
+        g_error("Error flushing output stream: %s", error->message);
+        g_error_free(error);
+    }
+
+    if (!g_output_stream_close(G_OUTPUT_STREAM(output_to_io_service), NULL, &error)) {
+        g_error("Error closing output stream: %s", error->message);
+        g_error_free(error);
+    }
+    g_object_unref(output_to_io_service);
+    output_to_io_service = NULL;
+
+    /* Now read from the input stream - in an inefficient way to be sure
+     * we test multiple, partial reads
+     */
+
+    read_size = 1;
+    str = g_string_new(NULL);
+
+    while (TRUE) {
+        /* test get_received() */
+        g_assert(gjs_dbus_input_stream_get_received(input_from_io_service) <= strlen(stream_data_from_io_service));
+
+        /* This is a blocking read... in production code, you would
+         * want to use the ready-to-read signal instead to avoid
+         * blocking when there is nothing to read.
+         */
+        result = g_input_stream_read(G_INPUT_STREAM(input_from_io_service),
+                                     buf,
+                                     read_size,
+                                     NULL, &error);
+        if (result < 0) {
+            g_error("Error reading %d bytes from input stream: %s",
+                    (int) read_size, error->message);
+            g_error_free(error);
+        }
+
+        if (result == 0) {
+            /* EOF */
+            break;
+        }
+
+        g_string_append_len(str, buf, result);
+
+        if (read_size < sizeof(buf))
+            read_size += 1;
+    }
+
+    if (!g_input_stream_close(G_INPUT_STREAM(input_from_io_service), NULL, &error)) {
+        g_error("Error closing input stream: %s", error->message);
+        g_error_free(error);
+    }
+    g_object_unref(input_from_io_service);
+    input_from_io_service = NULL;
+
+    /* Now make the confirmStreams call
+     */
+    gjs_debug(GJS_DEBUG_DBUS, "Confirming to com.litl.TestIO we got: '%s'", str->str);
+
+    gjs_dbus_proxy_call_json_async(test_io_proxy,
+                                   "confirmStreamsData",
+                                   on_confirm_streams_reply,
+                                   on_unexpected_error_reply,
+                                   "confirmStreamsData call from on_setup_streams_reply",
+                                   "received", DBUS_TYPE_STRING, &str->str,
+                                   NULL);
+
+    g_string_free(str, TRUE);
+}
+
+static void
+on_test_io_appeared(DBusConnection *connection,
+                    const char     *name,
+                    const char     *new_owner_unique_name,
+                    void           *data)
+{
+    const char *stream_path;
+
+    gjs_debug(GJS_DEBUG_DBUS, "%s appeared",
+            name);
+
+    test_io_proxy =
+        gjs_dbus_proxy_new(connection, new_owner_unique_name,
+                           "/com/litl/test/object47",
+                           "com.litl.TestIO");
+
+    input_from_io_service =
+        g_object_new(GJS_TYPE_DBUS_INPUT_STREAM, NULL);
+    gjs_dbus_input_stream_attach(input_from_io_service, connection);
+
+    stream_path = gjs_dbus_input_stream_get_path(input_from_io_service);
+
+    gjs_dbus_proxy_call_json_async(test_io_proxy,
+                                   "setupStreams",
+                                   on_setup_streams_reply,
+                                   on_unexpected_error_reply,
+                                   "setupStreams call from on_test_io_appeared",
+                                   "stream", DBUS_TYPE_STRING, &stream_path,
+                                   NULL);
+}
+
+static void
+on_test_io_vanished(DBusConnection *connection,
+                    const char     *name,
+                    const char     *old_owner_unique_name,
+                    void           *data)
+{
+    gjs_debug(GJS_DEBUG_DBUS, "%s vanished", name);
+
+    another_child_down();
+}
+
+static GjsDBusWatchNameFuncs watch_test_io_funcs = {
+    on_test_io_appeared,
+    on_test_io_vanished
+};
+
+void
+bigtest_test_func_util_dbus_client(void)
+{
+    pid_t result;
+    int status;
+
+    /* We have to fork() to avoid creating the DBusConnection*
+     * and thus preventing other dbus-using tests from forking
+     * children.  This dbus bug, when the fix makes it into Ubuntu,
+     * should solve the problem:
+     * https://bugs.freedesktop.org/show_bug.cgi?id=15570
+     *
+     * The symptom of that bug is failure to connect to the bus in
+     * dbus-signals.c tests. The symptom of opening a connection
+     * before forking children is the connection FD shared among
+     * multiple processes, i.e. huge badness.
+     */
+    if (!g_test_trap_fork(0, 0)) {
+        /* We are the parent */
+        g_test_trap_assert_passed();
+        return;
+    }
+
+    /* All this stuff runs in the forked child only */
+
+    fork_child_test_service();
+    fork_child_test_io();
+
+    g_type_init();
+
+    g_assert(test_service_pid != 0);
+    g_assert(test_io_pid != 0);
+
+    gjs_dbus_watch_name(DBUS_BUS_SESSION,
+                        "com.litl.TestService",
+                        0,
+                        &watch_test_service_funcs,
+                        NULL);
+
+    gjs_dbus_watch_name(DBUS_BUS_SESSION,
+                        "com.litl.TestIO",
+                        0,
+                        &watch_test_io_funcs,
+                        NULL);
+
+    client_loop = g_main_loop_new(NULL, FALSE);
+
+    g_main_loop_run(client_loop);
+
+    if (test_service_proxy != NULL)
+        g_object_unref(test_service_proxy);
+
+    if (test_io_proxy != NULL)
+        g_object_unref(test_io_proxy);
+
+    /* child was killed already, or should have been */
+
+    gjs_debug(GJS_DEBUG_DBUS, "waitpid() for first child");
+
+    result = waitpid(test_service_pid, &status, 0);
+    if (result < 0) {
+        g_error("Failed to waitpid() for forked child: %s", strerror(errno));
+    }
+
+    if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
+        g_error("Forked dbus service child exited with error code %d", WEXITSTATUS(status));
+    }
+
+    if (WIFSIGNALED(status) && WTERMSIG(status) != SIGTERM) {
+        g_error("Forked dbus service child exited on wrong signal number %d", WTERMSIG(status));
+    }
+
+    gjs_debug(GJS_DEBUG_DBUS, "waitpid() for second child");
+
+    result = waitpid(test_io_pid, &status, 0);
+    if (result < 0) {
+        g_error("Failed to waitpid() for forked child: %s", strerror(errno));
+    }
+
+    if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
+        g_error("Forked dbus service child exited with error code %d", WEXITSTATUS(status));
+    }
+
+    if (WIFSIGNALED(status) && WTERMSIG(status) != SIGTERM) {
+        g_error("Forked dbus service child exited on wrong signal number %d", WTERMSIG(status));
+    }
+
+    gjs_debug(GJS_DEBUG_DBUS, "dbus client test completed");
+
+    /* We want to kill dbus so the weak refs are NULL to start the
+     * next dbus-related test, which allows those tests
+     * to fork new child processes.
+     */
+    _gjs_dbus_dispose_info(_gjs_dbus_get_weak_ref(DBUS_BUS_SESSION));
+    dbus_shutdown();
+
+    gjs_debug(GJS_DEBUG_DBUS, "dbus shut down");
+
+    /* FIXME this is here only while we need g_test_trap_fork(),
+     * see comment above.
+     */
+    exit(0);
+}
+
+/*
+ * First child service we forked, tests general dbus API
+ */
+
+static gboolean currently_have_test_service = FALSE;
+static GObject *test_service_object = NULL;
+
+static void
+test_service_get_some_stuff_sync(DBusConnection  *connection,
+                                 DBusMessage     *message,
+                                 DBusMessageIter *in_iter,
+                                 DBusMessageIter *out_iter,
+                                 void            *data,
+                                 DBusError       *error)
+{
+    gjs_debug(GJS_DEBUG_DBUS, "com.litl.TestService got getSomeStuffSync");
+
+    g_assert(G_IS_OBJECT(data));
+
+    gjs_dbus_append_json_entry_BOOLEAN(out_iter,
+                                       "haveTestService",
+                                       currently_have_test_service);
+}
+
+static void
+test_service_always_error_sync(DBusConnection  *connection,
+                               DBusMessage     *message,
+                               DBusMessageIter *in_iter,
+                               DBusMessageIter *out_iter,
+                               void            *data,
+                               DBusError       *error)
+{
+    gjs_debug(GJS_DEBUG_DBUS, "com.litl.TestService got alwaysErrorSync");
+
+    g_assert(G_IS_OBJECT(data));
+
+    dbus_set_error(error, DBUS_ERROR_FILE_NOT_FOUND,
+                   "Did not find some kind of file! Help!");
+}
+
+static GjsDBusJsonMethod test_service_methods[] = {
+    { "getSomeStuffSync", test_service_get_some_stuff_sync, NULL },
+    { "alwaysErrorSync",  test_service_always_error_sync, NULL }
+};
+
+static void
+on_test_service_acquired(DBusConnection *connection,
+                         const char     *name,
+                         void           *data)
+{
+    g_assert(!currently_have_test_service);
+    currently_have_test_service = TRUE;
+
+    gjs_debug(GJS_DEBUG_DBUS, "com.litl.TestService acquired by child");
+
+    gjs_dbus_register_json(connection,
+                           "com.litl.TestIface",
+                           test_service_methods,
+                           G_N_ELEMENTS(test_service_methods));
+
+    test_service_object = g_object_new(G_TYPE_OBJECT, NULL);
+
+    gjs_dbus_register_g_object(connection,
+                               "/com/litl/test/object42",
+                               test_service_object,
+                               "com.litl.TestIface");
+}
+
+static void
+on_test_service_lost(DBusConnection *connection,
+                     const char     *name,
+                     void           *data)
+{
+    g_assert(currently_have_test_service);
+    currently_have_test_service = FALSE;
+
+    gjs_debug(GJS_DEBUG_DBUS, "com.litl.TestService lost by child");
+
+    gjs_dbus_unregister_g_object(connection,
+                                 "/com/litl/test/object42");
+
+    gjs_dbus_unregister_json(connection,
+                             "com.litl.TestIface");
+}
+
+static GjsDBusNameOwnerFuncs test_service_funcs = {
+    "com.litl.TestService",
+    GJS_DBUS_NAME_SINGLE_INSTANCE,
+    on_test_service_acquired,
+    on_test_service_lost
+};
+
+static void
+do_test_service_child(void)
+{
+    GMainLoop *loop;
+
+    g_type_init();
+
+    loop = g_main_loop_new(NULL, FALSE);
+
+    gjs_dbus_acquire_name(DBUS_BUS_SESSION,
+                          &test_service_funcs,
+                          NULL);
+
+    g_main_loop_run(loop);
+
+    /* Don't return to the test program main() */
+    exit(0);
+}
+
+/*
+ * Second child service we forked, tests IO streams
+ */
+
+static gboolean currently_have_test_io = FALSE;
+static GObject *test_io_object = NULL;
+
+static GjsDBusInputStream  *io_input_stream = NULL;
+static GjsDBusOutputStream *io_output_stream = NULL;
+
+static GString *input_buffer = NULL;
+
+static void
+test_io_confirm_streams_data(DBusConnection  *connection,
+                             DBusMessage     *message,
+                             DBusMessageIter *in_iter,
+                             DBusMessageIter *out_iter,
+                             void            *data,
+                             DBusError       *error)
+{
+    const char *received;
+
+    gjs_debug(GJS_DEBUG_DBUS, "com.litl.TestIO got confirmStreamsData");
+
+    g_assert(G_IS_OBJECT(data));
+
+    received = extract_string_arg(in_iter, "received", error);
+    if (received == NULL) {
+        g_assert(error == NULL || dbus_error_is_set(error));
+        return;
+    }
+
+    if (strcmp(received, stream_data_from_io_service) != 0) {
+        g_error("We sent the main process '%s' but it says it got '%s'",
+                stream_data_from_io_service,
+                received);
+        return;
+    }
+
+    /* We were reading from the main process in the main loop.
+     * As a hack, we'll block in the main loop here to test.
+     * In a real app, never block in the main loop; you would
+     * just plain block, e.g. in g_input_stream_read(), if
+     * you wanted to block. But don't block.
+     */
+    while (io_input_stream != NULL) {
+        g_main_iteration(TRUE);
+    }
+
+    gjs_dbus_append_json_entry_STRING(out_iter,
+                                      "received",
+                                      input_buffer->str);
+
+    g_string_free(input_buffer, TRUE);
+    input_buffer = NULL;
+}
+
+static void
+on_input_ready(GjsDBusInputStream *dbus_stream,
+               void               *data)
+{
+    GInputStream *stream;
+    char buf[3];
+    gssize result;
+    GError *error;
+
+    stream = G_INPUT_STREAM(dbus_stream);
+
+    g_assert(dbus_stream == io_input_stream);
+
+    /* test get_received() */
+    g_assert(gjs_dbus_input_stream_get_received(dbus_stream) <= strlen(stream_data_to_io_service));
+
+    /* Should not block, since we got the ready-to-read signal */
+    error = NULL;
+    result = g_input_stream_read(G_INPUT_STREAM(io_input_stream),
+                                 buf,
+                                 sizeof(buf),
+                                 NULL,
+                                 &error);
+    if (result < 0) {
+        g_error("Error reading bytes from input stream: %s",
+                error->message);
+        g_error_free(error);
+    }
+
+    if (result == 0) {
+        /* EOF */
+        if (!g_input_stream_close(G_INPUT_STREAM(io_input_stream), NULL, &error)) {
+            g_error("Error closing input stream in child: %s", error->message);
+            g_error_free(error);
+        }
+        g_object_unref(io_input_stream);
+        io_input_stream = NULL;
+
+        return;
+    }
+
+    g_string_append_len(input_buffer, buf, result);
+
+    /* We should automatically get another callback if there's more data or EOF
+     * was not yet reached.
+     */
+}
+
+static void
+test_io_setup_streams(DBusConnection  *connection,
+                      DBusMessage     *message,
+                      DBusMessageIter *in_iter,
+                      DBusMessageIter *out_iter,
+                      void            *data,
+                      DBusError       *error)
+{
+    const char *stream_path;
+    gsize total;
+    gsize remaining;
+    gssize result;
+    GError *gerror;
+
+    gjs_debug(GJS_DEBUG_DBUS, "com.litl.TestIO got setupStreams");
+
+    g_assert(G_IS_OBJECT(data));
+
+    stream_path = extract_string_arg(in_iter, "stream", error);
+
+    if (stream_path == NULL) {
+        g_assert(error == NULL || dbus_error_is_set(error));
+        return;
+    }
+
+    /* Create output stream to write to caller's path */
+    io_output_stream =
+        gjs_dbus_output_stream_new(connection,
+                                   dbus_message_get_sender(message),
+                                   stream_path);
+
+    /* Create input stream and return its path to caller */
+    io_input_stream =
+        g_object_new(GJS_TYPE_DBUS_INPUT_STREAM,
+                     NULL);
+    gjs_dbus_input_stream_attach(io_input_stream,
+                                 connection);
+    stream_path = gjs_dbus_input_stream_get_path(io_input_stream);
+
+    gjs_dbus_append_json_entry_STRING(out_iter,
+                                      "stream",
+                                      stream_path);
+
+    /* Set up callbacks to read input stream in an async way */
+    input_buffer = g_string_new(NULL);
+
+    g_signal_connect(io_input_stream,
+                     "ready-to-read",
+                     G_CALLBACK(on_input_ready),
+                     NULL);
+
+    /* Write to output stream */
+    gerror = NULL;
+    total = strlen(stream_data_from_io_service);
+    remaining = total;
+    while (remaining > 0) {
+        /* One byte at a time, fun torture test, totally silly in real
+         * code of course
+         */
+        result = g_output_stream_write(G_OUTPUT_STREAM(io_output_stream),
+                                       stream_data_from_io_service + (total - remaining),
+                                       1,
+                                       NULL,
+                                       &gerror);
+        if (result < 0) {
+            g_assert(gerror != NULL);
+            g_error("Error writing to output stream: %s", gerror->message);
+            g_error_free(gerror);
+        }
+
+        if (result != 1) {
+            g_error("Wrote %d instead of 1 bytes", (int) result);
+        }
+
+        remaining -= 1;
+    }
+
+    /* flush should do nothing here, and is not needed, but
+     * just calling it to test it
+     */
+    if (!g_output_stream_flush(G_OUTPUT_STREAM(io_output_stream), NULL, &gerror)) {
+        g_assert(gerror != NULL);
+        g_error("Error flushing output stream: %s", gerror->message);
+        g_error_free(gerror);
+    }
+
+    if (!g_output_stream_close(G_OUTPUT_STREAM(io_output_stream), NULL, &gerror)) {
+        g_assert(gerror != NULL);
+        g_error("Error closing output stream: %s", gerror->message);
+        g_error_free(gerror);
+    }
+    g_object_unref(io_output_stream);
+    io_output_stream = NULL;
+
+
+    /* Now return, and wait for our input stream data to come in from
+     * the main process
+     */
+}
+
+static GjsDBusJsonMethod test_io_methods[] = {
+    { "setupStreams", test_io_setup_streams, NULL },
+    { "confirmStreamsData", test_io_confirm_streams_data, NULL }
+};
+
+static void
+on_test_io_acquired(DBusConnection *connection,
+                    const char     *name,
+                    void           *data)
+{
+    g_assert(!currently_have_test_io);
+    currently_have_test_io = TRUE;
+
+    gjs_debug(GJS_DEBUG_DBUS, "com.litl.TestIO acquired by child");
+
+    gjs_dbus_register_json(connection,
+                           "com.litl.TestIO",
+                           test_io_methods,
+                           G_N_ELEMENTS(test_io_methods));
+
+    test_io_object = g_object_new(G_TYPE_OBJECT, NULL);
+
+    gjs_dbus_register_g_object(connection,
+                               "/com/litl/test/object47",
+                               test_io_object,
+                               "com.litl.TestIO");
+}
+
+static void
+on_test_io_lost(DBusConnection *connection,
+                const char     *name,
+                void           *data)
+{
+    g_assert(currently_have_test_io);
+    currently_have_test_io = FALSE;
+
+    gjs_debug(GJS_DEBUG_DBUS, "com.litl.TestIO lost by child");
+
+    gjs_dbus_unregister_g_object(connection,
+                                 "/com/litl/test/object47");
+
+    gjs_dbus_unregister_json(connection,
+                             "com.litl.TestIO");
+}
+
+static GjsDBusNameOwnerFuncs test_io_funcs = {
+    "com.litl.TestIO",
+    GJS_DBUS_NAME_SINGLE_INSTANCE,
+    on_test_io_acquired,
+    on_test_io_lost
+};
+
+static void
+do_test_io_child(void)
+{
+    GMainLoop *loop;
+
+    g_type_init();
+
+    loop = g_main_loop_new(NULL, FALSE);
+
+    gjs_dbus_acquire_name(DBUS_BUS_SESSION,
+                          &test_io_funcs,
+                          NULL);
+
+    g_main_loop_run(loop);
+
+    /* Don't return to the test program main() */
+    exit(0);
+}
+
+#endif /* GJS_BUILD_TESTS */
diff --git a/gjsdbus/dbus.h b/gjsdbus/dbus.h
new file mode 100644
index 0000000..b6415e9
--- /dev/null
+++ b/gjsdbus/dbus.h
@@ -0,0 +1,235 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* Copyright 2008 litl, LLC. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef __GJS_UTIL_DBUS_H__
+#define __GJS_UTIL_DBUS_H__
+
+#include <glib-object.h>
+#include <dbus/dbus.h>
+
+G_BEGIN_DECLS
+
+/* Convenience macro */
+
+#define GJS_DBUS_NAME_FROM_TYPE(type) ((type) == DBUS_BUS_SESSION ? "session" : "system")
+
+/* Error names */
+#define GJS_DBUS_ERROR_STREAM_RECEIVER_CLOSED "com.litl.Error.Stream.ReceiverClosed"
+
+/*
+ * Monitor whether we are connected / not-connected to the bus
+ */
+
+typedef void (* GjsDBusConnectionOpenedFunc)   (DBusConnection *connection,
+                                                void           *data);
+typedef void (* GjsDBusConnectionClosedFunc)   (DBusConnection *connection,
+                                                void           *data);
+
+typedef struct {
+    DBusBusType which_bus;
+    GjsDBusConnectionOpenedFunc opened;
+    GjsDBusConnectionClosedFunc closed;
+} GjsDBusConnectFuncs;
+
+void gjs_dbus_add_connect_funcs             (const GjsDBusConnectFuncs *funcs,
+                                             void                      *data);
+void gjs_dbus_remove_connect_funcs          (const GjsDBusConnectFuncs *funcs,
+                                             void                      *data);
+void gjs_dbus_add_connect_funcs_sync_notify (const GjsDBusConnectFuncs *funcs,
+                                             void                      *data);
+
+
+void gjs_dbus_add_bus_weakref    (DBusBusType      bus_type,
+                                  DBusConnection **connection_p);
+void gjs_dbus_remove_bus_weakref (DBusBusType      bus_type,
+                                  DBusConnection **connection_p);
+
+void gjs_dbus_try_connecting_now (DBusBusType which_bus);
+
+/*
+ * Own a bus name
+ *
+ */
+
+typedef enum {
+    GJS_DBUS_NAME_SINGLE_INSTANCE,
+    GJS_DBUS_NAME_MANY_INSTANCES
+} GjsDBusNameType;
+
+typedef void (* GjsDBusNameAcquiredFunc) (DBusConnection *connection,
+                                          const char     *name,
+                                          void           *data);
+typedef void (* GjsDBusNameLostFunc)     (DBusConnection *connection,
+                                          const char     *name,
+                                          void           *data);
+
+typedef struct {
+    const char *name;
+    GjsDBusNameType type;
+    GjsDBusNameAcquiredFunc acquired;
+    GjsDBusNameLostFunc lost;
+} GjsDBusNameOwnerFuncs;
+
+guint        gjs_dbus_acquire_name       (DBusBusType                  bus_type,
+                                          const GjsDBusNameOwnerFuncs *funcs,
+                                          void                        *data);
+void         gjs_dbus_release_name       (DBusBusType                  bus_type,
+                                          const GjsDBusNameOwnerFuncs *funcs,
+                                          void                        *data);
+void         gjs_dbus_release_name_by_id (DBusBusType                  bus_type,
+                                          guint                        id);
+
+/*
+ * Keep track of someone else's bus name
+ *
+ */
+
+typedef enum {
+    GJS_DBUS_NAME_START_IF_NOT_FOUND = 0x1
+} GjsDBusWatchNameFlags;
+
+typedef void (* GjsDBusNameAppearedFunc) (DBusConnection *connection,
+                                          const char     *name,
+                                          const char     *new_owner_unique_name,
+                                          void           *data);
+typedef void (* GjsDBusNameVanishedFunc) (DBusConnection *connection,
+                                          const char     *name,
+                                          const char     *old_owner_unique_name,
+                                          void           *data);
+
+typedef struct {
+    GjsDBusNameAppearedFunc appeared;
+    GjsDBusNameVanishedFunc vanished;
+} GjsDBusWatchNameFuncs;
+
+void        gjs_dbus_watch_name             (DBusBusType                  bus_type,
+                                             const char                  *name,
+                                             GjsDBusWatchNameFlags        flags,
+                                             const GjsDBusWatchNameFuncs *funcs,
+                                             void                        *data);
+void        gjs_dbus_unwatch_name           (DBusBusType                  bus_type,
+                                             const char                  *name,
+                                             const GjsDBusWatchNameFuncs *funcs,
+                                             void                        *data);
+const char* gjs_dbus_get_watched_name_owner (DBusBusType                  bus_type,
+                                             const char                  *name);
+
+
+typedef void (* GjsDBusSignalHandler) (DBusConnection *connection,
+                                       DBusMessage    *message,
+                                       void           *data);
+int gjs_dbus_watch_signal          (DBusBusType           bus_type,
+                                    const char           *sender,
+                                    const char           *path,
+                                    const char           *iface,
+                                    const char           *name,
+                                    GjsDBusSignalHandler  handler,
+                                    void                 *data,
+                                    GDestroyNotify        data_dnotify);
+void gjs_dbus_unwatch_signal       (DBusBusType           bus_type,
+                                    const char           *sender,
+                                    const char           *path,
+                                    const char           *iface,
+                                    const char           *name,
+                                    GjsDBusSignalHandler  handler,
+                                    void                 *data);
+void gjs_dbus_unwatch_signal_by_id (DBusBusType           bus_type,
+                                    int                   id);
+
+/* A "json method" is a D-Bus method with signature
+ *      DICT jsonMethodName(DICT)
+ * with the idea that it both takes and returns
+ * a JavaScript-style dictionary. This makes
+ * our JavaScript-to-dbus bindings really simple,
+ * and avoids a lot of futzing with dbus IDL.
+ *
+ * Of course it's completely annoying for someone
+ * using D-Bus in a "normal" way but the idea is just
+ * to use this to communicate within our own app
+ * that happens to consist of multiple processes
+ * and have bits written in JS.
+ */
+typedef void (* GjsDBusJsonSyncMethodFunc)  (DBusConnection  *connection,
+                                             DBusMessage     *message,
+                                             DBusMessageIter *in_iter,
+                                             DBusMessageIter *out_iter,
+                                             void            *data,
+                                             DBusError       *error);
+
+typedef void (* GjsDBusJsonAsyncMethodFunc) (DBusConnection  *connection,
+                                             DBusMessage     *message,
+                                             DBusMessageIter *in_iter,
+                                             void            *data);
+
+typedef struct {
+    const char *name;
+    /* one of these two but not both should be non-NULL */
+    GjsDBusJsonSyncMethodFunc sync_func;
+    GjsDBusJsonAsyncMethodFunc async_func;
+} GjsDBusJsonMethod;
+
+void gjs_dbus_register_json       (DBusConnection          *connection,
+                                   const char              *iface_name,
+                                   const GjsDBusJsonMethod *methods,
+                                   int                      n_methods);
+void gjs_dbus_unregister_json     (DBusConnection          *connection,
+                                   const char              *iface_name);
+void gjs_dbus_register_g_object   (DBusConnection          *connection,
+                                   const char              *path,
+                                   GObject                 *gobj,
+                                   const char              *iface_name);
+void gjs_dbus_unregister_g_object (DBusConnection          *connection,
+                                   const char              *path);
+
+void gjs_dbus_append_json_entry              (DBusMessageIter  *dict_iter,
+                                              const char       *key,
+                                              int               dbus_type,
+                                              void             *basic_value_p);
+void gjs_dbus_append_json_entry_STRING       (DBusMessageIter  *dict_iter,
+                                              const char       *key,
+                                              const char       *value);
+void gjs_dbus_append_json_entry_INT32        (DBusMessageIter  *dict_iter,
+                                              const char       *key,
+                                              dbus_int32_t      value);
+void gjs_dbus_append_json_entry_DOUBLE       (DBusMessageIter  *dict_iter,
+                                              const char       *key,
+                                              double            value);
+void gjs_dbus_append_json_entry_BOOLEAN      (DBusMessageIter  *dict_iter,
+                                              const char       *key,
+                                              dbus_bool_t       value);
+void gjs_dbus_append_json_entry_EMPTY_ARRAY  (DBusMessageIter  *dict_iter,
+                                              const char       *key);
+void gjs_dbus_append_json_entry_STRING_ARRAY (DBusMessageIter  *dict_iter,
+                                              const char       *key,
+                                              const char      **value);
+
+gboolean gjs_dbus_message_iter_get_gsize  (DBusMessageIter  *iter,
+                                           gsize            *value_p);
+gboolean gjs_dbus_message_iter_get_gssize (DBusMessageIter  *iter,
+                                           gssize           *value_p);
+
+void gjs_dbus_start_service(DBusConnection *connection,
+                            const char     *name);
+
+G_END_DECLS
+
+#endif  /* __GJS_UTIL_DBUS_H__ */
diff --git a/modules/dbus-exports.c b/modules/dbus-exports.c
new file mode 100644
index 0000000..2238d46
--- /dev/null
+++ b/modules/dbus-exports.c
@@ -0,0 +1,1858 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* Copyright 2008 litl, LLC. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <config.h>
+
+#include "dbus-exports.h"
+#include "dbus-values.h"
+
+#include "gjsdbus/dbus.h"
+
+#include <gjs/gjs.h>
+
+#include <util/log.h>
+
+#include <jsapi.h>
+
+#include <string.h>
+
+typedef struct {
+    char *name;
+    char *signature;
+    gboolean readable;
+    gboolean writable;
+} PropertyDetails;
+
+/*
+ * The dbus.exports object contains objects whose methods are exported
+ * over dbus. So e.g. if you create dbus.exports.foo.bar then
+ * dbus method calls to path /foo/bar would go to this object.
+ */
+
+typedef struct {
+    void *dummy;
+
+    /* Back-pointers to ourselves.
+     *
+     * The JSObject* may not be safe if a copying GC can move them
+     * around.  However, the alternatives are complicated, and
+     * SpiderMonkey currently uses mark-and-sweep, so I think this
+     * should be fine. We'll see I guess.
+     *
+     * Saving the context is also questionable: Objects can jump between
+     * contexts (they are only permanently bound to a runtime). However,
+     * we assume, AFAIK safely, that in our usage model the context where
+     * we created the exports object will always be the right one to
+     * invoke incoming calls. A context contains the global scope and an
+     * execution stack.
+     */
+    JSContext  *context;
+    JSObject   *object;
+
+    DBusBusType which_bus;
+    DBusConnection *connection_weak_ref;
+    gboolean filter_was_registered;
+} Exports;
+
+static struct JSClass gjs_js_exports_class;
+
+GJS_DEFINE_PRIV_FROM_JS(Exports, gjs_js_exports_class);
+
+static void              property_details_init  (PropertyDetails *details);
+static void              property_details_clear (PropertyDetails *details);
+
+static void              on_bus_opened        (DBusConnection *connection,
+                                               void           *data);
+static void              on_bus_closed        (DBusConnection *connection,
+                                               void           *data);
+static DBusHandlerResult on_message           (DBusConnection *connection,
+                                               DBusMessage    *message,
+                                               void           *user_data);
+
+static const GjsDBusConnectFuncs system_connect_funcs = {
+    DBUS_BUS_SYSTEM,
+    on_bus_opened,
+    on_bus_closed
+};
+
+static const GjsDBusConnectFuncs session_connect_funcs = {
+    DBUS_BUS_SESSION,
+    on_bus_opened,
+    on_bus_closed
+};
+
+static void
+on_bus_opened(DBusConnection *connection,
+              void           *data)
+{
+    Exports *priv = data;
+
+    g_assert(priv->connection_weak_ref == NULL);
+
+    priv->connection_weak_ref = connection;
+
+    gjs_debug(GJS_DEBUG_DBUS, "%s bus opened, exporting JS dbus methods", GJS_DBUS_NAME_FROM_TYPE(priv->which_bus));
+
+    if (priv->filter_was_registered)
+        return;
+
+    if (!dbus_connection_add_filter(connection,
+                                    on_message, priv,
+                                    NULL)) {
+        gjs_debug(GJS_DEBUG_DBUS, "Failed to add message filter");
+        return;
+    }
+
+    priv->filter_was_registered = TRUE;
+}
+
+static void
+on_bus_closed(DBusConnection *connection,
+              void           *data)
+{
+    Exports *priv = data;
+
+    g_assert(priv->connection_weak_ref != NULL);
+
+    priv->connection_weak_ref = NULL;
+
+    gjs_debug(GJS_DEBUG_DBUS, "%s bus closed, unexporting JS dbus methods", GJS_DBUS_NAME_FROM_TYPE(priv->which_bus));
+
+    if (priv->filter_was_registered) {
+        dbus_connection_remove_filter(connection,
+                                      on_message, priv);
+        priv->filter_was_registered = FALSE;
+    }
+}
+
+#define dbus_reply_from_exception(context, message, reply_p)            \
+    (dbus_reply_from_exception_and_sender((context),                    \
+                                          dbus_message_get_sender(message), \
+                                          dbus_message_get_serial(message), \
+                                          (reply_p)))
+static JSBool
+dbus_reply_from_exception_and_sender(JSContext    *context,
+                                     const char   *sender,
+                                     dbus_uint32_t serial,
+                                     DBusMessage **reply_p)
+{
+    char *s;
+    jsval exc;
+    const char *name = NULL;
+    jsval nameval;
+
+    *reply_p = NULL;
+
+    if (!JS_GetPendingException(context, &exc))
+        return JS_FALSE;
+
+    if (JSVAL_IS_OBJECT(exc) &&
+        gjs_object_get_property(context, JSVAL_TO_OBJECT(exc),
+                                "dbusErrorName", &nameval))
+        name = gjs_string_get_ascii_checked(context, nameval);
+
+    if (!gjs_log_exception(context, &s))
+        return JS_FALSE;
+
+    gjs_debug(GJS_DEBUG_DBUS,
+              "JS exception we will send as dbus reply to %s: %s",
+              sender,
+              s);
+
+    *reply_p = dbus_message_new(DBUS_MESSAGE_TYPE_ERROR);
+    dbus_message_set_destination(*reply_p, sender);
+    dbus_message_set_reply_serial(*reply_p, serial);
+    dbus_message_set_no_reply(*reply_p, TRUE);
+    dbus_message_set_error_name(*reply_p, name ? name : DBUS_ERROR_FAILED);
+    if (s != NULL) {
+        DBusMessageIter iter;
+
+        dbus_message_iter_init_append(*reply_p, &iter);
+
+        if (!dbus_message_iter_append_basic(&iter,
+                                            DBUS_TYPE_STRING,
+                                            &s)) {
+            dbus_message_unref(*reply_p);
+            g_free(s);
+            return JS_FALSE;
+        }
+        g_free(s);
+    }
+
+    return JS_TRUE;
+}
+
+static JSBool
+signature_from_method(JSContext   *context,
+                      JSObject    *method_obj,
+                      const char **signature)
+{
+    jsval signature_value;
+
+    if (gjs_object_get_property(context,
+                                method_obj, "outSignature",
+                                &signature_value)) {
+        *signature = gjs_string_get_ascii_checked(context,
+                                                  signature_value);
+        if (*signature == NULL) {
+            return JS_FALSE;
+        }
+    } else {
+        /* We default to a{sv} */
+        *signature = "a{sv}";
+    }
+
+    return JS_TRUE;
+}
+
+static gboolean
+signature_has_one_element(const char *signature)
+{
+    DBusSignatureIter iter;
+
+    if (!signature)
+        return FALSE;
+
+    dbus_signature_iter_init(&iter, signature);
+
+    return !dbus_signature_iter_next(&iter);
+}
+
+static DBusMessage *
+build_reply_from_jsval(JSContext     *context,
+                       const char    *signature,
+                       const char    *sender,
+                       dbus_uint32_t  serial,
+                       jsval          rval)
+{
+    DBusMessage *reply;
+    DBusMessageIter arg_iter;
+    DBusSignatureIter sig_iter;
+    JSBool marshalled = JS_FALSE;
+
+    reply = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_RETURN);
+    dbus_message_set_destination(reply, sender);
+    dbus_message_set_reply_serial(reply, serial);
+    dbus_message_set_no_reply(reply, TRUE);
+
+    dbus_message_iter_init_append(reply, &arg_iter);
+
+    if (rval == JSVAL_VOID || g_str_equal(signature, "")) {
+        /* We don't want to send anything in these cases so skip the
+         * marshalling altogether.
+         */
+        return reply;
+    }
+
+    dbus_signature_iter_init(&sig_iter, signature);
+
+    if (signature_has_one_element(signature)) {
+        marshalled = gjs_js_one_value_to_dbus(context, rval, &arg_iter, &sig_iter);
+    } else {
+        if (!JS_IsArrayObject(context, JSVAL_TO_OBJECT(rval))) {
+            gjs_debug(GJS_DEBUG_DBUS,
+                      "Signature has multiple items but return value is not an array");
+            return reply;
+        }
+        marshalled = gjs_js_values_to_dbus(context, 0, rval, &arg_iter, &sig_iter);
+    }
+
+    if (!marshalled) {
+        /* replace our planned reply with an error */
+        dbus_message_unref(reply);
+        if (!dbus_reply_from_exception_and_sender(context, sender, serial, &reply))
+            gjs_debug(GJS_DEBUG_DBUS,
+                      "conversion of dbus return value failed but no exception was set?");
+    }
+
+    return reply;
+}
+
+static DBusMessage*
+invoke_js_from_dbus(JSContext   *context,
+                    DBusMessage *method_call,
+                    JSObject    *this_obj,
+                    JSObject    *method_obj)
+{
+    DBusMessage *reply;
+    int argc;
+    jsval *argv;
+    jsval rval;
+    DBusMessageIter arg_iter;
+    GjsRootedArray *values;
+    const char *signature;
+
+    if (JS_IsExceptionPending(context)) {
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "Exception was pending before invoking JS method??? Not expected");
+        gjs_log_exception(context, NULL);
+    }
+
+    reply = NULL;
+
+    dbus_message_iter_init(method_call, &arg_iter);
+
+    if (!gjs_js_values_from_dbus(context, &arg_iter, &values)) {
+        if (!dbus_reply_from_exception(context, method_call, &reply))
+            gjs_debug(GJS_DEBUG_DBUS,
+                      "conversion of dbus method arg failed but no exception was set?");
+        return reply;
+    }
+
+    argc = gjs_rooted_array_get_length(context, values);
+    argv = gjs_rooted_array_get_data(context, values);
+
+    gjs_js_add_dbus_props(context, method_call, argv[0]);
+
+    rval = JSVAL_VOID;
+    JS_AddRoot(context, &rval);
+
+    if (!gjs_call_function_value(context,
+                                 this_obj,
+                                 OBJECT_TO_JSVAL(method_obj),
+                                 argc,
+                                 argv,
+                                 &rval)) {
+        /* Exception thrown... */
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "dbus method invocation failed");
+
+        JS_RemoveRoot(context, &rval);
+
+        if (!dbus_reply_from_exception(context, method_call, &reply))
+            gjs_debug(GJS_DEBUG_DBUS,
+                      "dbus method invocation failed but no exception was set?");
+
+        goto out;
+    }
+
+    if (dbus_reply_from_exception(context, method_call, &reply)) {
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "Closure invocation succeeded but an exception was set?");
+        goto out;
+    }
+
+    if (!signature_from_method(context,
+                               method_obj,
+                               &signature)) {
+        if (!dbus_reply_from_exception(context, method_call, &reply))
+            gjs_debug(GJS_DEBUG_DBUS,
+                      "dbus method invocation failed but no exception was set?");
+
+        goto out;
+    }
+
+    reply = build_reply_from_jsval(context,
+                                   signature,
+                                   dbus_message_get_sender(method_call),
+                                   dbus_message_get_serial(method_call),
+                                   rval);
+
+ out:
+    gjs_rooted_array_free(context, values, TRUE);
+    JS_RemoveRoot(context, &rval);
+
+    if (reply)
+        gjs_debug(GJS_DEBUG_DBUS, "Sending %s reply to dbus method %s",
+                  dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN ?
+                  "normal" : "error",
+                  dbus_message_get_member(method_call));
+    else
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "Failed to create reply to dbus method %s",
+                  dbus_message_get_member(method_call));
+
+    return reply;
+}
+
+static JSBool
+async_call_callback(JSContext *context,
+                    JSObject  *obj,
+                    uintN      argc,
+                    jsval     *argv,
+                    jsval     *retval)
+{
+    DBusConnection *connection;
+    DBusBusType which_bus;
+    DBusMessage *reply;
+    JSObject *callback_object;
+    const char *sender;
+    dbus_uint32_t serial;
+    jsval prop_value;
+    const char *signature;
+    gboolean thrown;
+
+    *retval = JSVAL_VOID;
+    callback_object = JSVAL_TO_OBJECT(JS_ARGV_CALLEE(argv));
+    reply = NULL;
+    thrown = FALSE;
+
+    if (!gjs_object_require_property(context,
+                                     callback_object,
+                                     "DBus async call callback",
+                                     "_dbusSender",
+                                     &prop_value)) {
+        /* we are a little screwed because we can't send the
+         * error back. This should never happen though */
+        gjs_log_and_keep_exception(context, NULL);
+        return JS_FALSE;
+    }
+    sender = gjs_string_get_ascii_checked(context, prop_value);
+    if (!sender)
+        return JS_FALSE;
+
+    if (!gjs_object_require_property(context,
+                                     callback_object,
+                                     "DBus async call callback",
+                                     "_dbusSerial",
+                                     &prop_value)) {
+        gjs_log_and_keep_exception(context, NULL);
+        return JS_FALSE;
+    }
+    if (!JS_ValueToECMAUint32(context, prop_value, &serial))
+        return JS_FALSE;
+
+    if (!gjs_object_require_property(context,
+                                     callback_object,
+                                     "DBus async call callback",
+                                     "_dbusBusType",
+                                     &prop_value)) {
+        gjs_log_and_keep_exception(context, NULL);
+        return JS_FALSE;
+    }
+    which_bus = JSVAL_TO_INT(prop_value);
+
+    /* From now we have enough information to
+     * send the exception back to the callee so we'll do so
+     */
+    if (!gjs_object_require_property(context,
+                                     callback_object,
+                                     "DBus async call callback",
+                                     "_dbusOutSignature",
+                                     &prop_value)) {
+        thrown = TRUE;
+        goto out;
+    }
+    signature = gjs_string_get_ascii_checked(context, prop_value);
+    if (!signature)
+        return JS_FALSE;
+
+    if (argc != 1) {
+        gjs_throw(context, "The callback to async DBus calls takes one argument, "
+                  "the return value or array of return values");
+        thrown = TRUE;
+        goto out;
+    }
+
+    reply = build_reply_from_jsval(context,
+                                   signature,
+                                   sender,
+                                   serial,
+                                   argv[0]);
+
+ out:
+    if (!reply && thrown) {
+        if (!dbus_reply_from_exception_and_sender(context, sender, serial, &reply))
+            gjs_debug(GJS_DEBUG_DBUS,
+                      "dbus method invocation failed but no exception was set?");
+    }
+
+    if (reply) {
+        gjs_dbus_add_bus_weakref(which_bus, &connection);
+        if (!connection) {
+            gjs_throw(context, "We were disconnected from the bus before the callback "
+                      "to some async remote call was called");
+            dbus_message_unref(reply);
+            gjs_dbus_remove_bus_weakref(which_bus, &connection);
+            return JS_FALSE;
+        }
+        dbus_connection_send(connection, reply, NULL);
+        gjs_dbus_remove_bus_weakref(which_bus, &connection);
+        dbus_message_unref(reply);
+    }
+
+    return (thrown == FALSE);
+}
+
+/* returns an error message or NULL */
+static DBusMessage *
+invoke_js_async_from_dbus(JSContext   *context,
+                          DBusBusType  bus_type,
+                          DBusMessage *method_call,
+                          JSObject    *this_obj,
+                          JSObject    *method_obj)
+{
+    DBusMessage *reply;
+    int argc;
+    jsval *argv;
+    DBusMessageIter arg_iter;
+    GjsRootedArray *values;
+    JSFunction *callback;
+    JSObject *callback_object;
+    JSString *sender_string;
+    jsval serial_value;
+    gboolean thrown;
+    jsval ignored;
+    const char *signature;
+    JSString *signature_string;
+
+    reply = NULL;
+    thrown = FALSE;
+    argc = 0;
+    argv = NULL;
+
+    if (JS_IsExceptionPending(context)) {
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "Exception was pending before invoking JS method??? Not expected");
+        gjs_log_exception(context, NULL);
+    }
+
+    dbus_message_iter_init(method_call, &arg_iter);
+
+    if (!gjs_js_values_from_dbus(context, &arg_iter, &values)) {
+        if (!dbus_reply_from_exception(context, method_call, &reply))
+            gjs_debug(GJS_DEBUG_DBUS,
+                      "conversion of dbus method arg failed but no exception was set?");
+        return reply;
+    }
+
+    /* we will add an argument, the callback */
+    callback = JS_NewFunction(context,
+                              async_call_callback,
+                              1, 0,
+                              NULL,
+                              "" /* anonymous */);
+
+    if (!callback) {
+        thrown = TRUE;
+        goto out;
+    }
+
+    callback_object = JS_GetFunctionObject(callback);
+    g_assert(callback_object);
+
+    gjs_rooted_array_append(context, values, OBJECT_TO_JSVAL(callback_object));
+
+    /* We attach the DBus sender and serial as properties to
+     * callback, so we don't need to bother with memory managing them
+     * if the callback is never called and just discarded.*/
+    sender_string = JS_NewStringCopyZ(context, dbus_message_get_sender(method_call));
+    if (!sender_string) {
+        thrown = TRUE;
+        goto out;
+    }
+
+    if (!JS_DefineProperty(context,
+                           callback_object,
+                           "_dbusSender",
+                           STRING_TO_JSVAL(sender_string),
+                           NULL, NULL,
+                           0)) {
+        thrown = TRUE;
+        goto out;
+    }
+
+    if (!JS_NewNumberValue(context,
+                           (double)dbus_message_get_serial(method_call),
+                           &serial_value)) {
+        thrown = TRUE;
+        goto out;
+    }
+
+    if (!JS_DefineProperty(context,
+                           callback_object,
+                           "_dbusSerial",
+                           serial_value,
+                           NULL, NULL,
+                           0)) {
+        thrown = TRUE;
+        goto out;
+    }
+
+    if (!JS_DefineProperty(context,
+                           callback_object,
+                           "_dbusBusType",
+                           INT_TO_JSVAL(bus_type),
+                           NULL, NULL,
+                           0)) {
+        thrown = TRUE;
+        goto out;
+    }
+
+    if (!signature_from_method(context,
+                               method_obj,
+                               &signature)) {
+        thrown = TRUE;
+        goto out;
+    }
+
+    signature_string = JS_NewStringCopyZ(context, signature);
+    if (!signature_string) {
+        thrown = TRUE;
+        goto out;
+    }
+
+    if (!JS_DefineProperty(context,
+                           callback_object,
+                           "_dbusOutSignature",
+                           STRING_TO_JSVAL(signature_string),
+                           NULL, NULL,
+                           0)) {
+        thrown = TRUE;
+        goto out;
+    }
+
+    argc = gjs_rooted_array_get_length(context, values);
+    argv = gjs_rooted_array_get_data(context, values);
+
+    if (!gjs_call_function_value(context,
+                                 this_obj,
+                                 OBJECT_TO_JSVAL(method_obj),
+                                 argc,
+                                 argv,
+                                 &ignored)) {
+        thrown = TRUE;
+        goto out;
+    }
+
+ out:
+    if (thrown) {
+        if (!dbus_reply_from_exception(context, method_call, &reply))
+            gjs_debug(GJS_DEBUG_DBUS,
+                      "conversion of dbus method arg failed but no exception was set?");
+    }
+
+    if (argv) {
+        gjs_unroot_value_locations(context, argv, argc);
+    }
+
+    return reply;
+}
+
+static JSObject*
+find_js_property_by_path(JSContext  *context,
+                         JSObject   *root_obj,
+                         const char *path,
+                         JSObject **dir_obj_p)
+{
+    char **elements;
+    int i;
+    JSObject *obj;
+    jsval value;
+
+    elements = g_strsplit(path, "/", -1);
+    obj = root_obj;
+
+    /* g_strsplit returns empty string for the first
+     * '/' and if you split just "/" it returns two
+     * empty strings. We just skip all empty strings,
+     * and start with element[1] since the first is
+     * always an empty string.
+     */
+    for (i = 1; elements[i] != NULL; ++i) {
+
+        if (*(elements[i]) == '\0')
+            continue;
+
+        gjs_object_get_property(context, obj, elements[i], &value);
+
+        if (value == JSVAL_VOID ||
+            JSVAL_IS_NULL(value) ||
+            !JSVAL_IS_OBJECT(value)) {
+            obj = NULL;
+            break;
+        }
+
+        obj = JSVAL_TO_OBJECT(value);
+    }
+
+    g_strfreev(elements);
+
+    if (obj != NULL) {
+        // this is the directory object; see if there's an actual
+        // implementation object hanging off it.
+        if (dir_obj_p)
+            *dir_obj_p = obj;
+
+        gjs_object_get_property(context, obj, "-impl-", &value);
+
+        if (value == JSVAL_VOID ||
+            JSVAL_IS_NULL(value) ||
+            !JSVAL_IS_OBJECT(value)) {
+            obj = NULL;
+        } else {
+            obj = JSVAL_TO_OBJECT(value);
+        }
+    }
+
+    return obj;
+}
+
+static gboolean
+find_method(JSContext  *context,
+            JSObject   *obj,
+            const char *method_name,
+            jsval      *method_value)
+{
+    gjs_object_get_property(context,
+                            obj,
+                            method_name,
+                            method_value);
+
+    if (*method_value == JSVAL_VOID ||
+        JSVAL_IS_NULL(*method_value) ||
+        !JSVAL_IS_OBJECT(*method_value)) {
+        return JS_FALSE;
+    }
+
+    return JS_TRUE;
+}
+
+/* FALSE on exception only; if no array, sets its val to void */
+static gboolean
+find_properties_array(JSContext       *context,
+                      JSObject        *obj,
+                      const char      *iface,
+                      jsval           *array_p,
+                      unsigned int    *array_length_p)
+{
+    /* We are looking for obj._dbusInterfaces[iface].properties */
+
+    jsval ifaces_val;
+    jsval iface_val;
+
+    *array_p = JSVAL_VOID;
+    *array_length_p = 0;
+
+    ifaces_val = JSVAL_VOID;
+    if (!gjs_object_get_property(context,
+                                 obj,
+                                 "_dbusInterfaces",
+                                 &ifaces_val)) {
+        /* NOT an exception ... object simply has no properties */
+        return JS_TRUE;
+    }
+
+    iface_val = JSVAL_VOID;
+    if (!gjs_object_get_property(context,
+                                 JSVAL_TO_OBJECT(ifaces_val),
+                                 iface,
+                                 &iface_val)) {
+        /* NOT an exception ... object simply lacks the interface */
+    }
+
+    /* http://bugzilla.gnome.org/show_bug.cgi?id=569933
+     * libnm is screwed up and passes wrong interface.
+     * Fortunately the properties interface does not
+     * have any properties so there's no case where
+     * we actually want to use it.
+     */
+    if (iface_val == JSVAL_VOID &&
+        strcmp(iface, DBUS_INTERFACE_PROPERTIES) == 0) {
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "Changing interface to work around GNOME bug 569933");
+
+        if (!gjs_object_get_property(context,
+                                     JSVAL_TO_OBJECT(ifaces_val),
+                                     "org.freedesktop.NetworkManager",
+                                     &iface_val)) {
+            /* NOT an exception ... object simply lacks the interface */
+        }
+    }
+
+    if (iface_val == JSVAL_VOID) {
+        /* NOT an exception ... object simply lacks the interface */
+        return JS_TRUE;
+    }
+
+    if (!gjs_object_get_property(context,
+                                 JSVAL_TO_OBJECT(iface_val),
+                                 "properties",
+                                 array_p)) {
+        /* NOT an exception ... interface simply has no properties */
+        return JS_TRUE;
+    }
+
+    if (!JS_GetArrayLength(context, JSVAL_TO_OBJECT(*array_p),
+                           array_length_p)) {
+        gjs_throw(context, "Error retrieving length property of properties array");
+        return JS_FALSE;
+    }
+
+    return JS_TRUE;
+}
+
+static void
+property_details_init(PropertyDetails *details)
+{
+    details->name = NULL;
+    details->signature = NULL;
+    details->readable = FALSE;
+    details->writable = FALSE;
+}
+
+static void
+property_details_clear(PropertyDetails *details)
+{
+    g_free(details->name);
+    g_free(details->signature);
+    property_details_init(details);
+}
+
+/* FALSE on exception; throws if details are bad */
+static gboolean
+unpack_property_details(JSContext       *context,
+                        JSObject        *prop_description,
+                        PropertyDetails *details)
+{
+    jsval name_val;
+    jsval signature_val;
+    jsval access_val;
+    const char *name;
+    const char *signature;
+    const char *access;
+
+    if (!gjs_object_get_property(context,
+                                 prop_description,
+                                 "name",
+                                 &name_val)) {
+        gjs_throw(context,
+                  "Property has no name");
+        return JS_FALSE;
+    }
+
+    name = gjs_string_get_ascii_checked(context,
+                                        name_val);
+    if (name == NULL) {
+        return JS_FALSE;
+    }
+
+    if (!gjs_object_get_property(context,
+                                 prop_description,
+                                 "signature",
+                                 &signature_val)) {
+        gjs_throw(context,
+                  "Property %s has no signature",
+                  name);
+        return JS_FALSE;
+    }
+
+    signature = gjs_string_get_ascii_checked(context,
+                                             signature_val);
+    if (signature == NULL) {
+        return JS_FALSE;
+    }
+
+    if (!gjs_object_get_property(context,
+                                 prop_description,
+                                 "access",
+                                 &access_val)) {
+        gjs_throw(context,
+                  "Property %s has no access",
+                  name);
+        return JS_FALSE;
+    }
+
+    access = gjs_string_get_ascii_checked(context,
+                                          access_val);
+    if (access == NULL) {
+        return JS_FALSE;
+    }
+
+    g_assert(name && signature && access);
+
+    if (strcmp(access, "readwrite") == 0) {
+        details->readable = TRUE;
+        details->writable = TRUE;
+    } else if (strcmp(access, "read") == 0) {
+        details->readable = TRUE;
+    } else if (strcmp(access, "write") == 0) {
+        details->writable = TRUE;
+    } else {
+        gjs_throw(context, "Unknown access on property, should be readwrite read or write");
+        return JS_FALSE;
+    }
+
+    details->name = g_strdup(name);
+    details->signature = g_strdup(signature);
+
+    return JS_TRUE;
+}
+
+/* FALSE on exception, NULL property name in details if no such
+ * property. Exceptions here are only for malformed introspection data
+ * or out of memory or something, not a missing property.
+ */
+static gboolean
+find_property_details(JSContext       *context,
+                      JSObject        *obj,
+                      const char      *iface,
+                      const char      *prop_name,
+                      PropertyDetails *details)
+{
+    /* We are looking for obj._dbusInterfaces[iface].properties array
+     * member where property.name == prop_name
+     */
+    jsval properties_array_val;
+    unsigned int length, i;
+
+    g_assert(details->name == NULL);
+
+    properties_array_val = JSVAL_VOID;
+    if (!find_properties_array(context, obj,
+                               iface, &properties_array_val,
+                               &length)) {
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "No properties found on interface %s",
+                  iface);
+        return JS_FALSE;
+    }
+
+    if (properties_array_val == JSVAL_VOID) {
+        /* NOT an exception ... interface simply has no properties */
+        return JS_TRUE;
+    }
+
+    for (i = 0; i < length; ++i) {
+        jsval property_val;
+
+        property_val = JSVAL_VOID;
+        if (!JS_GetElement(context, JSVAL_TO_OBJECT(properties_array_val),
+                           i, &property_val) ||
+            property_val == JSVAL_VOID) {
+            gjs_throw(context,
+                      "Error accessing element %d of properties array",
+                      i);
+            return JS_FALSE;
+        }
+
+        if (!unpack_property_details(context,
+                                     JSVAL_TO_OBJECT(property_val),
+                                     details)) {
+            return JS_FALSE;
+        }
+
+        if (strcmp(prop_name, details->name) != 0) {
+            property_details_clear(details);
+            continue;
+        }
+
+        return JS_TRUE;
+    }
+
+    /* Property was not found on the object, not an exception */
+    return JS_TRUE;
+}
+
+static DBusMessage*
+handle_get_property(JSContext      *context,
+                    JSObject       *obj,
+                    DBusMessage    *message,
+                    DBusError      *derror)
+{
+    const char *iface = NULL;
+    const char *prop_name = NULL;
+    PropertyDetails details;
+    jsval value;
+    DBusMessageIter iter;
+    DBusMessageIter variant_iter;
+    DBusSignatureIter sig_iter;
+    DBusMessage *reply;
+
+    reply = NULL;
+
+    if (!dbus_message_get_args(message,
+                               derror,
+                               DBUS_TYPE_STRING, &iface,
+                               DBUS_TYPE_STRING, &prop_name,
+                               DBUS_TYPE_INVALID)) {
+        return NULL;
+    }
+
+    property_details_init(&details);
+    if (!find_property_details(context, obj, iface, prop_name,
+                               &details)) {
+
+        /* Should mean an exception was set */
+
+        if (dbus_reply_from_exception(context, message,
+                                      &reply)) {
+            return reply;
+        } else {
+            dbus_set_error(derror,
+                           DBUS_ERROR_INVALID_ARGS,
+                           "Getting property %s.%s an exception should have been set",
+                           iface, prop_name);
+            return NULL;
+        }
+    }
+
+    /* http://bugzilla.gnome.org/show_bug.cgi?id=570031
+     * org.freedesktop.NetworkManager used rather than
+     * org.freedesktop.NetworkManager.Connection.Active
+     * on property Devices
+     */
+    if (details.name == NULL &&
+        strcmp(prop_name, "Devices") == 0 &&
+        strcmp(iface, "org.freedesktop.NetworkManager") == 0) {
+        if (!find_property_details(context, obj,
+                                   "org.freedesktop.NetworkManager.Connection.Active",
+                                   prop_name,
+                                   &details)) {
+            /* Should mean an exception was set */
+
+            if (dbus_reply_from_exception(context, message,
+                                          &reply)) {
+                return reply;
+            } else {
+                dbus_set_error(derror,
+                               DBUS_ERROR_INVALID_ARGS,
+                               "Getting property %s.%s an exception should have been set",
+                               iface, prop_name);
+                return NULL;
+            }
+        }
+    }
+
+    if (details.name == NULL) {
+        dbus_set_error(derror,
+                       DBUS_ERROR_INVALID_ARGS,
+                       "No such property %s.%s",
+                       iface, prop_name);
+        return NULL;
+    }
+
+    g_assert(details.name != NULL);
+    g_assert(details.signature != NULL);
+
+    if (!details.readable) {
+
+        property_details_clear(&details);
+
+        dbus_set_error(derror,
+                       DBUS_ERROR_INVALID_ARGS,
+                       "Property %s.%s not readable",
+                       iface, prop_name);
+        return NULL;
+    }
+
+    value = JSVAL_VOID;
+    JS_AddRoot(context, &value);
+    if (!gjs_object_require_property(context, obj,
+                                     "DBus GetProperty callee",
+                                     prop_name, &value)) {
+
+        JS_RemoveRoot(context, &value);
+        property_details_clear(&details);
+
+        dbus_reply_from_exception(context, message,
+                                  &reply);
+        g_assert(reply != NULL);
+        return reply;
+    }
+
+    reply = dbus_message_new_method_return(message);
+    g_assert(reply != NULL); /* assert not oom */
+
+    dbus_message_iter_init_append(reply, &iter);
+
+    dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT,
+                                     details.signature,
+                                     &variant_iter);
+
+    dbus_signature_iter_init(&sig_iter, details.signature);
+    if (!gjs_js_one_value_to_dbus(context, value,
+                                  &variant_iter, &sig_iter)) {
+
+        property_details_clear(&details);
+        JS_RemoveRoot(context, &value);
+
+        dbus_message_unref(reply);
+        reply = NULL;
+        dbus_reply_from_exception(context, message,
+                                  &reply);
+
+        return reply;
+    }
+
+    dbus_message_iter_close_container(&iter, &variant_iter);
+
+    JS_RemoveRoot(context, &value);
+
+    property_details_clear(&details);
+
+    return reply;
+}
+
+static DBusMessage*
+handle_get_all_properties(JSContext      *context,
+                          JSObject       *obj,
+                          DBusMessage    *message,
+                          DBusError      *derror)
+{
+    const char *iface;
+    jsval properties_array_val;
+    unsigned int length, i;
+    DBusMessage *reply;
+    DBusMessageIter iter;
+    DBusMessageIter dict_iter;
+
+    reply = NULL;
+    iface = NULL;
+
+    if (!dbus_message_get_args(message,
+                               derror,
+                               DBUS_TYPE_STRING, &iface,
+                               DBUS_TYPE_INVALID)) {
+        return NULL;
+    }
+
+    properties_array_val = JSVAL_VOID;
+    if (!find_properties_array(context, obj,
+                               iface, &properties_array_val,
+                               &length)) {
+        goto js_exception;
+    }
+
+    /* Open return dictionary */
+    reply = dbus_message_new_method_return(message);
+    dbus_message_iter_init_append(reply, &iter);
+    dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+                                     "{sv}", &dict_iter);
+
+    if (properties_array_val != JSVAL_VOID) {
+        for (i = 0; i < length; ++i) {
+            jsval property_val;
+            PropertyDetails details;
+            DBusMessageIter entry_iter;
+            DBusSignatureIter sig_iter;
+            jsval value;
+
+            property_val = JSVAL_VOID;
+            if (!JS_GetElement(context, JSVAL_TO_OBJECT(properties_array_val),
+                               i, &property_val) ||
+                property_val == JSVAL_VOID) {
+                gjs_throw(context,
+                          "Error accessing element %d of properties array",
+                          i);
+                goto js_exception;
+            }
+
+            property_details_init(&details);
+            if (!unpack_property_details(context,
+                                         JSVAL_TO_OBJECT(property_val),
+                                         &details)) {
+                goto js_exception;
+            }
+
+            g_assert(details.name != NULL);
+            g_assert(details.signature != NULL);
+
+            if (!details.readable) {
+                property_details_clear(&details);
+
+                continue;
+            }
+
+            value = JSVAL_VOID;
+            JS_AddRoot(context, &value);
+            if (!gjs_object_require_property(context, obj,
+                                             "DBus GetAllProperties callee",
+                                             details.name, &value)) {
+
+                property_details_clear(&details);
+                JS_RemoveRoot(context, &value);
+
+                goto js_exception;
+            }
+
+            dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY,
+                                             NULL, &entry_iter);
+
+            dbus_message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING,
+                                           &details.name);
+
+            property_details_clear(&details);
+
+            dbus_signature_iter_init(&sig_iter, "v");
+            if (!gjs_js_one_value_to_dbus(context, value, &entry_iter,
+                                          &sig_iter)) {
+                JS_RemoveRoot(context, &value);
+                goto js_exception;
+            }
+
+            JS_RemoveRoot(context, &value);
+
+            dbus_message_iter_close_container(&dict_iter, &entry_iter);
+        }
+    }
+
+    /* close return dictionary */
+    dbus_message_iter_close_container(&iter, &dict_iter);
+
+    return reply;
+
+ js_exception:
+
+    if (reply)
+        dbus_message_unref(reply);
+
+    dbus_reply_from_exception(context, message, &reply);
+
+    g_assert(reply != NULL);
+
+    return reply;
+}
+
+static DBusMessage*
+handle_set_property(JSContext      *context,
+                    JSObject       *obj,
+                    DBusMessage    *message,
+                    DBusError      *derror)
+{
+    const char *iface = NULL;
+    const char *prop_name = NULL;
+    DBusMessageIter iter;
+    PropertyDetails details;
+    jsval value;
+    DBusMessage *reply;
+
+    reply = NULL;
+
+    if (!dbus_message_has_signature(message,
+                                    "ssv")) {
+        dbus_set_error(derror,
+                       DBUS_ERROR_INVALID_ARGS,
+                       DBUS_INTERFACE_PROPERTIES ".Set signature is not '%s'",
+                       dbus_message_get_signature(message));
+        return NULL;
+    }
+
+    dbus_message_iter_init(message, &iter);
+
+    dbus_message_iter_get_basic(&iter, &iface);
+    dbus_message_iter_next(&iter);
+
+    dbus_message_iter_get_basic(&iter, &prop_name);
+    dbus_message_iter_next(&iter);
+
+    property_details_init(&details);
+    if (!find_property_details(context, obj, iface, prop_name,
+                               &details)) {
+
+        /* Should mean exception set */
+
+        if (dbus_reply_from_exception(context, message,
+                                      &reply)) {
+            return reply;
+        } else {
+            dbus_set_error(derror,
+                           DBUS_ERROR_INVALID_ARGS,
+                           "Getting property %s.%s an exception should have been set",
+                           iface, prop_name);
+            return NULL;
+        }
+    }
+
+    if (details.name == NULL) {
+        dbus_set_error(derror,
+                       DBUS_ERROR_INVALID_ARGS,
+                       "No such property %s.%s",
+                       iface, prop_name);
+        return NULL;
+    }
+
+    g_assert(details.name != NULL);
+    g_assert(details.signature != NULL);
+
+    /* FIXME At the moment we don't use the signature when setting,
+     * though really to be fully paranoid we probably ought to.
+     */
+
+    if (!details.writable) {
+        property_details_clear(&details);
+
+        dbus_set_error(derror,
+                       DBUS_ERROR_INVALID_ARGS,
+                       "Property %s.%s not writable",
+                       iface, prop_name);
+        return NULL;
+    }
+
+    property_details_clear(&details);
+
+    value = JSVAL_VOID;
+    JS_AddRoot(context, &value);
+    gjs_js_one_value_from_dbus(context, &iter, &value);
+
+    if (dbus_reply_from_exception(context, message, &reply)) {
+        JS_RemoveRoot(context, &value);
+        return reply;
+    }
+
+    /* this throws on oom or if prop is read-only for example */
+    JS_SetProperty(context, obj, prop_name, &value);
+
+    JS_RemoveRoot(context, &value);
+
+    if (!dbus_reply_from_exception(context, message, &reply)) {
+        g_assert(reply == NULL);
+        reply = dbus_message_new_method_return(message);
+    }
+
+    g_assert(reply != NULL);
+    return reply;
+}
+
+static DBusHandlerResult
+handle_properties(JSContext      *context,
+                  DBusConnection *connection,
+                  JSObject       *obj,
+                  DBusMessage    *message,
+                  const char     *method_name)
+{
+    DBusError derror;
+    DBusMessage *reply;
+
+    reply = NULL;
+    dbus_error_init(&derror);
+
+    if (strcmp(method_name, "Get") == 0) {
+        reply = handle_get_property(context, obj, message, &derror);
+    } else if (strcmp(method_name, "Set") == 0) {
+        reply = handle_set_property(context, obj, message, &derror);
+    } else if (strcmp(method_name, "GetAll") == 0) {
+        reply = handle_get_all_properties(context, obj, message, &derror);
+    } else {
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    }
+
+    if (dbus_error_is_set(&derror)) {
+        g_assert(reply == NULL);
+        reply = dbus_message_new_error(message,
+                                       derror.name,
+                                       derror.message);
+    }
+    g_assert(reply != NULL); /* note: fails on OOM */
+
+    dbus_connection_send(connection, reply, NULL);
+    dbus_message_unref(reply);
+
+    return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult
+handle_introspect(JSContext      *context,
+                  DBusConnection *connection,
+                  JSObject       *dir_obj,
+                  JSObject       *obj,
+                  DBusMessage    *message)
+{
+    DBusMessage *reply;
+    char **children;
+    char *interfaceXML;
+    GString *doc;
+    int i;
+    JSObject *props_iter;
+    jsid prop_id;
+
+    reply = NULL;
+
+    if (!dbus_connection_list_registered (connection,
+                                          dbus_message_get_path (message),
+                                          &children)) {
+        g_error("No memory");
+    }
+
+    doc = g_string_new(NULL);
+
+    g_string_append(doc, DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE);
+    g_string_append(doc, "<node>\n");
+
+    for (i = 0; children[i] != NULL; ++i) {
+        g_string_append_printf(doc, "  <node name=\"%s\"/>\n",
+                               children[i]);
+    }
+
+    props_iter = JS_NewPropertyIterator(context, dir_obj);
+
+    prop_id = JSVAL_VOID;
+    if (!JS_NextProperty(context, props_iter, &prop_id)) {
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "Failed to get next property iterating dbus directory");
+        goto out;
+    }
+
+    while (prop_id != JSVAL_VOID) {
+        jsval keyval;
+
+        if (!JS_IdToValue(context, prop_id, &keyval)) {
+            gjs_debug(GJS_DEBUG_DBUS,
+                      "Failed to convert dbus object id to value");
+            goto out;
+        }
+
+        /* just ignore non-string keys */
+        if (JSVAL_IS_STRING(keyval)) {
+            const char *key;
+            jsval valueval;
+
+            key = gjs_string_get_ascii(keyval);
+
+            if (!gjs_object_require_property(context, dir_obj,
+                                             "dbus directory",
+                                             key, &valueval)) {
+                gjs_debug(GJS_DEBUG_DBUS,
+                          "Somehow failed to get property of dbus object");
+                goto out;
+            }
+
+            /* ignore non-object values and the '-impl-' node. */
+            if (JSVAL_IS_OBJECT(valueval) && strcmp(key, "-impl-") != 0) {
+                g_string_append_printf(doc, "  <node name=\"%s\"/>\n",
+                                       key);
+            }
+        }
+
+        prop_id = JSVAL_VOID;
+        if (!JS_NextProperty(context, props_iter, &prop_id)) {
+            gjs_debug(GJS_DEBUG_DBUS,
+                      "Failed to get next property iterating dbus object");
+            goto out;
+        }
+    }
+
+    // add interface description for this node
+    if (obj != NULL) {
+        jsval valueval;
+
+        if (!JS_CallFunctionName(context, obj, "getDBusInterfaceXML", 0, NULL,
+                                 &valueval)) {
+            gjs_log_exception(context, NULL);
+        } else if (!gjs_string_to_utf8(context, valueval, &interfaceXML)) {
+            gjs_debug(GJS_DEBUG_DBUS,
+                      "Couldn't stringify getDBusInterfaceXML() retval");
+            JS_ClearPendingException(context);
+        } else {
+            g_string_append(doc, interfaceXML);
+            g_free(interfaceXML);
+        }
+
+    }
+
+    g_string_append_printf(doc, "</node>\n");
+
+    reply = dbus_message_new_method_return(message);
+    if (reply == NULL)
+        g_error("No memory");
+
+    dbus_message_append_args(reply,
+                             DBUS_TYPE_STRING, &doc->str,
+                             DBUS_TYPE_INVALID);
+
+    dbus_connection_send(connection, reply, NULL);
+
+ out:
+    if (reply != NULL)
+        dbus_message_unref(reply);
+    else
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "Error introspecting dbus exports object; shouldn't happen, apparently it did, figure it out...");
+
+    g_string_free(doc, TRUE);
+    dbus_free_string_array(children);
+
+    return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult
+on_message(DBusConnection *connection,
+           DBusMessage    *message,
+           void           *user_data)
+{
+    const char *path;
+    DBusHandlerResult result;
+    JSObject *obj, *dir_obj = NULL;
+    const char *method_name;
+    char *async_method_name;
+    jsval method_value;
+    DBusMessage *reply;
+    Exports *priv;
+
+    priv = user_data;
+    async_method_name = NULL;
+    reply = NULL;
+
+    if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_METHOD_CALL)
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    JS_BeginRequest(priv->context);
+    method_value = JSVAL_VOID;
+    JS_AddRoot(priv->context, &method_value);
+
+    result = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    path = dbus_message_get_path(message);
+
+    obj = find_js_property_by_path(priv->context,
+                                   priv->object,
+                                   path, &dir_obj);
+
+    method_name = dbus_message_get_member(message);
+
+    /* we implement most of Introspect for all exported objects, although
+     * they can provide their own interface descriptions if they like.
+     * (Note that obj may == NULL here, which just means this is a "bare"
+     * directory node, with no implementation attached.
+     */
+    if (dbus_message_is_method_call (message,
+                                     DBUS_INTERFACE_INTROSPECTABLE,
+                                     "Introspect")) {
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "Default-introspecting JS obj at dbus path %s",
+                  path);
+
+        result = handle_introspect(priv->context,
+                                   connection,
+                                   dir_obj, obj,
+                                   message);
+        goto out;
+    }
+
+    if (obj == NULL) {
+        /* No JS object at the path, no need to be noisy. If there's another
+         * handler it can handle the message, otherwise the caller should
+         * receive (and log) NoSuchMethod error. */
+        goto out;
+    }
+
+    if (dbus_message_has_interface(message,
+                                   DBUS_INTERFACE_PROPERTIES)) {
+        const char *iface;
+        iface = NULL;
+        dbus_message_get_args(message, NULL,
+                              DBUS_TYPE_STRING, &iface,
+                              DBUS_TYPE_INVALID);
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "Properties request %s on %s",
+                  method_name,
+                  iface ? iface : "MISSING INTERFACE");
+        result = handle_properties(priv->context, connection,
+                                   obj, message, method_name);
+        goto out;
+    }
+
+    async_method_name = g_strdup_printf("%sAsync", method_name);
+
+    /* try first if an async version exists */
+    if (find_method(priv->context,
+                    obj,
+                    async_method_name,
+                    &method_value)) {
+
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "Invoking async method %s on JS obj at dbus path %s",
+                  async_method_name, path);
+
+        reply = invoke_js_async_from_dbus(priv->context,
+                                          priv->which_bus,
+                                          message,
+                                          obj,
+                                          JSVAL_TO_OBJECT(method_value));
+
+        result = DBUS_HANDLER_RESULT_HANDLED;
+
+        /* otherwise try the sync version */
+    } else if (find_method(priv->context,
+                           obj,
+                           method_name,
+                           &method_value)) {
+
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "Invoking method %s on JS obj at dbus path %s",
+                  method_name, path);
+
+        reply = invoke_js_from_dbus(priv->context,
+                                    message,
+                                    obj,
+                                    JSVAL_TO_OBJECT(method_value));
+
+        result = DBUS_HANDLER_RESULT_HANDLED;
+
+        /* otherwise do nothing, method not found */
+    } else {
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "There is a JS object at %s but it has no method %s",
+                  path, method_name);
+    }
+
+    if (reply != NULL) {
+        dbus_connection_send(connection, reply, NULL);
+        dbus_message_unref(reply);
+    }
+
+ out:
+    if (async_method_name)
+        g_free(async_method_name);
+    JS_RemoveRoot(priv->context, &method_value);
+    JS_EndRequest(priv->context);
+    return result;
+}
+
+/*
+ * Like JSResolveOp, but flags provide contextual information as follows:
+ *
+ *  JSRESOLVE_QUALIFIED   a qualified property id: obj.id or obj[id], not id
+ *  JSRESOLVE_ASSIGNING   obj[id] is on the left-hand side of an assignment
+ *  JSRESOLVE_DETECTING   'if (o.p)...' or similar detection opcode sequence
+ *  JSRESOLVE_DECLARING   var, const, or exports prolog declaration opcode
+ *  JSRESOLVE_CLASSNAME   class name used when constructing
+ *
+ * The *objp out parameter, on success, should be null to indicate that id
+ * was not resolved; and non-null, referring to obj or one of its prototypes,
+ * if id was resolved.
+ */
+static JSBool
+exports_new_resolve(JSContext *context,
+                    JSObject  *obj,
+                    jsval      id,
+                    uintN      flags,
+                    JSObject **objp)
+{
+    Exports *priv;
+    const char *name;
+
+    *objp = NULL;
+
+    if (!gjs_get_string_id(id, &name))
+        return JS_TRUE; /* not resolved, but no error */
+
+    priv = priv_from_js(context, obj);
+    gjs_debug_jsprop(GJS_DEBUG_DBUS, "Resolve prop '%s' hook obj %p priv %p", name, obj, priv);
+
+    if (priv == NULL)
+        return JS_TRUE; /* we are the prototype, or have the wrong class */
+
+    return JS_TRUE;
+}
+
+/* If we set JSCLASS_CONSTRUCT_PROTOTYPE flag, then this is called on
+ * the prototype in addition to on each instance. When called on the
+ * prototype, "obj" is the prototype, and "retval" is the prototype
+ * also, but can be replaced with another object to use instead as the
+ * prototype. If we don't set JSCLASS_CONSTRUCT_PROTOTYPE we can
+ * identify the prototype as an object of our class with NULL private
+ * data.
+ */
+static JSBool
+exports_constructor(JSContext *context,
+                    JSObject  *obj,
+                    uintN      argc,
+                    jsval     *argv,
+                    jsval     *retval)
+{
+    Exports *priv;
+
+    priv = g_slice_new0(Exports);
+
+    GJS_INC_COUNTER(dbus_exports);
+
+    g_assert(priv_from_js(context, obj) == NULL);
+    JS_SetPrivate(context, obj, priv);
+
+    gjs_debug_lifecycle(GJS_DEBUG_DBUS,
+                        "exports constructor, obj %p priv %p", obj, priv);
+
+    priv->context = context;
+    priv->object = obj;
+
+    return JS_TRUE;
+}
+
+static JSBool
+add_connect_funcs(JSContext  *context,
+                  JSObject   *obj,
+                  DBusBusType which_bus)
+{
+    Exports *priv;
+    GjsDBusConnectFuncs const *connect_funcs;
+
+    priv = priv_from_js(context, obj);
+    if (priv == NULL)
+        return JS_FALSE;
+
+    if (which_bus == DBUS_BUS_SESSION) {
+        connect_funcs = &session_connect_funcs;
+    } else if (which_bus == DBUS_BUS_SYSTEM) {
+        connect_funcs = &system_connect_funcs;
+    } else
+        g_assert_not_reached();
+
+    priv->which_bus = which_bus;
+    gjs_dbus_add_connect_funcs_sync_notify(connect_funcs, priv);
+
+    return JS_TRUE;
+}
+
+static void
+exports_finalize(JSContext *context,
+                 JSObject  *obj)
+{
+    Exports *priv;
+    GjsDBusConnectFuncs const *connect_funcs;
+
+    priv = priv_from_js(context, obj);
+    gjs_debug_lifecycle(GJS_DEBUG_DBUS,
+                        "finalize, obj %p priv %p", obj, priv);
+    if (priv == NULL)
+        return; /* we are the prototype, not a real instance, so constructor never called */
+
+    if (priv->which_bus == DBUS_BUS_SESSION) {
+        connect_funcs = &session_connect_funcs;
+    } else if (priv->which_bus == DBUS_BUS_SYSTEM) {
+        connect_funcs = &system_connect_funcs;
+    } else
+        g_assert_not_reached();
+
+    gjs_dbus_remove_connect_funcs(connect_funcs, priv);
+
+    if (priv->connection_weak_ref != NULL) {
+        on_bus_closed(priv->connection_weak_ref, priv);
+    }
+
+    GJS_DEC_COUNTER(dbus_exports);
+    g_slice_free(Exports, priv);
+}
+
+/* The bizarre thing about this vtable is that it applies to both
+ * instances of the object, and to the prototype that instances of the
+ * class have.
+ *
+ * Also, there's a constructor field in here, but as far as I can
+ * tell, it would only be used if no constructor were provided to
+ * JS_InitClass. The constructor from JS_InitClass is not applied to
+ * the prototype unless JSCLASS_CONSTRUCT_PROTOTYPE is in flags.
+ */
+static struct JSClass gjs_js_exports_class = {
+    "DBusExports", /* means "new DBusExports()" works */
+    JSCLASS_HAS_PRIVATE |
+    JSCLASS_NEW_RESOLVE |
+    JSCLASS_NEW_RESOLVE_GETS_START,
+    JS_PropertyStub,
+    JS_PropertyStub,
+    JS_PropertyStub,
+    JS_PropertyStub,
+    JS_EnumerateStub,
+    (JSResolveOp) exports_new_resolve, /* needs cast since it's the new resolve signature */
+    JS_ConvertStub,
+    exports_finalize,
+    NULL,
+    NULL,
+    NULL,
+    NULL, NULL, NULL, NULL, NULL
+};
+
+static JSPropertySpec gjs_js_exports_proto_props[] = {
+    { NULL }
+};
+
+static JSFunctionSpec gjs_js_exports_proto_funcs[] = {
+    { NULL }
+};
+
+static JSObject*
+exports_new(JSContext  *context,
+            DBusBusType which_bus)
+{
+    JSObject *exports;
+    JSObject *global;
+
+    /* put constructor for DBusExports() in the global namespace */
+    global = JS_GetGlobalObject(context);
+
+    if (!gjs_object_has_property(context, global, gjs_js_exports_class.name)) {
+        JSObject *prototype;
+
+        prototype = JS_InitClass(context, global,
+                                 /* parent prototype JSObject* for
+                                  * prototype; NULL for
+                                  * Object.prototype
+                                  */
+                                 NULL,
+                                 &gjs_js_exports_class,
+                                 /* constructor for instances (NULL for
+                                  * none - just name the prototype like
+                                  * Math - rarely correct)
+                                  */
+                                 exports_constructor,
+                                 /* number of constructor args */
+                                 0,
+                                 /* props of prototype */
+                                 &gjs_js_exports_proto_props[0],
+                                 /* funcs of prototype */
+                                 &gjs_js_exports_proto_funcs[0],
+                                 /* props of constructor, MyConstructor.myprop */
+                                 NULL,
+                                 /* funcs of constructor, MyConstructor.myfunc() */
+                                 NULL);
+        if (prototype == NULL)
+            return JS_FALSE;
+
+        g_assert(gjs_object_has_property(context, global, gjs_js_exports_class.name));
+
+        gjs_debug(GJS_DEBUG_DBUS, "Initialized class %s prototype %p",
+                  gjs_js_exports_class.name, prototype);
+    }
+
+    exports = JS_ConstructObject(context, &gjs_js_exports_class, NULL, NULL);
+    /* may be NULL */
+
+    return exports;
+}
+
+JSBool
+gjs_js_define_dbus_exports(JSContext      *context,
+                           JSObject       *in_object,
+                           DBusBusType     which_bus)
+{
+    JSObject *exports;
+    JSContext *load_context;
+
+    load_context = gjs_runtime_get_load_context(JS_GetRuntime(context));
+
+    exports = exports_new(load_context, which_bus);
+    if (exports == NULL) {
+        gjs_move_exception(load_context, context);
+        return JS_FALSE;
+    }
+
+    if (!add_connect_funcs(context, exports, which_bus))
+        return JS_FALSE;
+
+    if (!JS_DefineProperty(context, in_object,
+                           "exports",
+                           OBJECT_TO_JSVAL(exports),
+                           NULL, NULL,
+                           GJS_MODULE_PROP_FLAGS))
+        return JS_FALSE;
+
+    return JS_TRUE;
+}
diff --git a/modules/dbus-exports.h b/modules/dbus-exports.h
new file mode 100644
index 0000000..1ee2040
--- /dev/null
+++ b/modules/dbus-exports.h
@@ -0,0 +1,37 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* Copyright 2008 litl, LLC. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#ifndef __GJS_JS_DBUS_EXPORTS_H__
+#define __GJS_JS_DBUS_EXPORTS_H__
+
+#include <glib.h>
+#include <jsapi.h>
+#include <dbus/dbus.h>
+
+G_BEGIN_DECLS
+
+JSBool gjs_js_define_dbus_exports (JSContext      *context,
+                                   JSObject       *in_object,
+                                   DBusBusType     which_bus);
+
+G_END_DECLS
+
+#endif  /* __GJS_JS_DBUS_EXPORTS_H__ */
diff --git a/modules/dbus-values.c b/modules/dbus-values.c
new file mode 100644
index 0000000..641115d
--- /dev/null
+++ b/modules/dbus-values.c
@@ -0,0 +1,973 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* Copyright 2008 litl, LLC. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <config.h>
+
+#include "dbus-values.h"
+
+#include <gjs/gjs.h>
+
+#include <gjsdbus/dbus.h>
+#include <util/log.h>
+
+#include <string.h>
+
+JSBool
+gjs_js_one_value_from_dbus(JSContext       *context,
+                           DBusMessageIter *iter,
+                           jsval           *value_p)
+{
+    int arg_type;
+
+    *value_p = JSVAL_VOID;
+
+    arg_type = dbus_message_iter_get_arg_type(iter);
+
+    gjs_debug(GJS_DEBUG_DBUS_MARSHAL,
+              "Converting dbus type '%c' to jsval",
+              arg_type != DBUS_TYPE_INVALID ? arg_type : '0');
+
+    switch (arg_type) {
+    case DBUS_TYPE_STRUCT:
+        {
+            JSObject *obj;
+            DBusMessageIter struct_iter;
+            int index;
+
+            obj = JS_NewArrayObject(context, 0, JSVAL_NULL);
+            if (obj == NULL)
+                return JS_FALSE;
+
+            dbus_message_iter_recurse(iter, &struct_iter);
+            index = 0;
+            while (dbus_message_iter_get_arg_type(&struct_iter) != DBUS_TYPE_INVALID) {
+                jsval prop_value;
+
+                prop_value = JSVAL_VOID;
+                JS_AddRoot(context, &prop_value);
+                if (!gjs_js_one_value_from_dbus(context, &struct_iter, &prop_value)) {
+                    JS_RemoveRoot(context, &prop_value);
+                    return JS_FALSE;
+                }
+
+                if (!JS_DefineElement(context, obj,
+                                      index, prop_value,
+                                      NULL, NULL, JSPROP_ENUMERATE)) {
+                    JS_RemoveRoot(context, &prop_value);
+                    return JS_FALSE;
+                }
+
+                JS_RemoveRoot(context, &prop_value);
+                dbus_message_iter_next(&struct_iter);
+                index ++;
+            }
+            *value_p = OBJECT_TO_JSVAL(obj);
+        }
+        break;
+    case DBUS_TYPE_ARRAY:
+        {
+            int elem_type = dbus_message_iter_get_element_type(iter);
+
+            if (elem_type == DBUS_TYPE_DICT_ENTRY) {
+                /* Create a dictionary object */
+                JSObject *obj;
+                DBusMessageIter array_iter;
+
+                obj = JS_ConstructObject(context, NULL, NULL, NULL);
+                if (obj == NULL)
+                    return JS_FALSE;
+
+                JS_AddRoot(context, &obj);
+                dbus_message_iter_recurse(iter, &array_iter);
+                while (dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_INVALID) {
+                    DBusMessageIter entry_iter;
+                    const char *key;
+                    jsval entry_value;
+
+                    dbus_message_iter_recurse(&array_iter, &entry_iter);
+
+                    if (dbus_message_iter_get_arg_type(&entry_iter) != DBUS_TYPE_STRING) {
+                        gjs_throw(context, "Dictionary keys are not strings, can't convert to JavaScript");
+                        JS_RemoveRoot(context, &obj);
+                        return JS_FALSE;
+                    }
+
+                    dbus_message_iter_get_basic(&entry_iter, &key);
+
+                    dbus_message_iter_next(&entry_iter);
+
+                    gjs_debug(GJS_DEBUG_DBUS_MARSHAL,
+                              "Defining dict entry %s in jsval dict", key);
+
+                    entry_value = JSVAL_VOID;
+                    JS_AddRoot(context, &entry_value);
+                    if (!gjs_js_one_value_from_dbus(context, &entry_iter, &entry_value)) {
+                        JS_RemoveRoot(context, &entry_value);
+                        JS_RemoveRoot(context, &obj);
+                        return JS_FALSE;
+                    }
+
+                    if (!JS_DefineProperty(context, obj,
+                                           key, entry_value,
+                                           NULL, NULL, JSPROP_ENUMERATE)) {
+                        JS_RemoveRoot(context, &entry_value);
+                        JS_RemoveRoot(context, &obj);
+                        return JS_FALSE;
+                    }
+
+                    JS_RemoveRoot(context, &entry_value);
+                    dbus_message_iter_next(&array_iter);
+                }
+
+                *value_p = OBJECT_TO_JSVAL(obj);
+                JS_RemoveRoot(context, &obj);
+            } else if (elem_type == DBUS_TYPE_BYTE) {
+                /* byte arrays go to a string */
+                const char *v_BYTES;
+                int n_bytes;
+                DBusMessageIter array_iter;
+
+                dbus_message_iter_recurse(iter, &array_iter);
+                dbus_message_iter_get_fixed_array(&array_iter,
+                                                  &v_BYTES, &n_bytes);
+
+                if (!gjs_string_from_binary_data(context, v_BYTES, n_bytes, value_p))
+                    return JS_FALSE;
+            } else {
+                JSObject *obj;
+                DBusMessageIter array_iter;
+                int index;
+
+                obj = JS_NewArrayObject(context, 0, JSVAL_NULL);
+                if (obj == NULL)
+                    return JS_FALSE;
+
+                JS_AddRoot(context, &obj);
+                dbus_message_iter_recurse(iter, &array_iter);
+                index = 0;
+                while (dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_INVALID) {
+                    jsval prop_value;
+
+                    prop_value = JSVAL_VOID;
+                    JS_AddRoot(context, &prop_value);
+                    if (!gjs_js_one_value_from_dbus(context, &array_iter, &prop_value)) {
+                        JS_RemoveRoot(context, &prop_value);
+                        JS_RemoveRoot(context, &obj);
+                        return JS_FALSE;
+                    }
+
+                    if (!JS_DefineElement(context, obj,
+                                          index, prop_value,
+                                          NULL, NULL, JSPROP_ENUMERATE)) {
+                        JS_RemoveRoot(context, &prop_value);
+                        JS_RemoveRoot(context, &obj);
+                        return JS_FALSE;
+                    }
+
+                    JS_RemoveRoot(context, &prop_value);
+                    dbus_message_iter_next(&array_iter);
+                    index ++;
+                }
+                *value_p = OBJECT_TO_JSVAL(obj);
+                JS_RemoveRoot(context, &obj);
+            }
+        }
+        break;
+    case DBUS_TYPE_BOOLEAN:
+        {
+            dbus_bool_t v_BOOLEAN;
+            dbus_message_iter_get_basic(iter, &v_BOOLEAN);
+            *value_p = BOOLEAN_TO_JSVAL(v_BOOLEAN);
+        }
+        break;
+    case DBUS_TYPE_BYTE:
+        {
+            unsigned char v_BYTE;
+            dbus_message_iter_get_basic(iter, &v_BYTE);
+            if (!JS_NewNumberValue(context, v_BYTE, value_p))
+                return JS_FALSE;
+        }
+        break;
+    case DBUS_TYPE_INT32:
+        {
+            dbus_int32_t v_INT32;
+            dbus_message_iter_get_basic(iter, &v_INT32);
+            if (!JS_NewNumberValue(context, v_INT32, value_p))
+                return JS_FALSE;
+        }
+        break;
+    case DBUS_TYPE_UINT32:
+        {
+            dbus_uint32_t v_UINT32;
+            dbus_message_iter_get_basic(iter, &v_UINT32);
+            if (!JS_NewNumberValue(context, v_UINT32, value_p))
+                return JS_FALSE;
+        }
+        break;
+    case DBUS_TYPE_INT64:
+        {
+            dbus_int64_t v_INT64;
+            dbus_message_iter_get_basic(iter, &v_INT64);
+            if (!JS_NewNumberValue(context, v_INT64, value_p))
+                return JS_FALSE;
+        }
+        break;
+    case DBUS_TYPE_UINT64:
+        {
+            dbus_uint64_t v_UINT64;
+            dbus_message_iter_get_basic(iter, &v_UINT64);
+            if (!JS_NewNumberValue(context, v_UINT64, value_p))
+                return JS_FALSE;
+        }
+        break;
+    case DBUS_TYPE_DOUBLE:
+        {
+            double v_DOUBLE;
+            dbus_message_iter_get_basic(iter, &v_DOUBLE);
+            if (!JS_NewDoubleValue(context, v_DOUBLE, value_p))
+                return JS_FALSE;
+        }
+        break;
+    case DBUS_TYPE_OBJECT_PATH:
+    case DBUS_TYPE_STRING:
+        {
+            const char *v_STRING;
+
+            dbus_message_iter_get_basic(iter, &v_STRING);
+
+            if (!gjs_string_from_utf8(context, v_STRING, -1, value_p))
+                return JS_FALSE;
+
+        }
+        break;
+
+    case DBUS_TYPE_VARIANT:
+        {
+            DBusMessageIter variant_iter;
+
+            dbus_message_iter_recurse(iter, &variant_iter);
+
+            return gjs_js_one_value_from_dbus(context, &variant_iter, value_p);
+        }
+        break;
+
+    case DBUS_TYPE_INVALID:
+        *value_p = JSVAL_VOID;
+        break;
+
+    default:
+        gjs_debug(GJS_DEBUG_DBUS, "Don't know how to convert dbus type %c to JavaScript",
+                  arg_type);
+        gjs_throw(context, "Don't know how to convert dbus type %c to JavaScript",
+                     arg_type);
+        return JS_FALSE;
+    }
+
+    return JS_TRUE;
+}
+
+JSBool
+gjs_js_values_from_dbus(JSContext          *context,
+                        DBusMessageIter    *iter,
+                        GjsRootedArray    **array_p)
+{
+    GjsRootedArray *array;
+    jsval value;
+
+    value = JSVAL_VOID;
+    JS_AddRoot(context, &value);
+
+    *array_p = NULL;
+
+    array = gjs_rooted_array_new();
+
+    do {
+        if (!gjs_js_one_value_from_dbus(context, iter, &value)) {
+            gjs_rooted_array_free(context, array, TRUE);
+            JS_RemoveRoot(context, &value);
+            return JS_FALSE; /* error message already set */
+        }
+
+        gjs_rooted_array_append(context, array, value);
+    } while (dbus_message_iter_next(iter));
+
+    *array_p = array;
+
+    JS_RemoveRoot(context, &value);
+
+    return JS_TRUE;
+}
+
+static void
+append_basic_maybe_in_variant(DBusMessageIter *iter,
+                              int              dbus_type,
+                              void            *value,
+                              gboolean         wrap_in_variant)
+{
+    if (wrap_in_variant) {
+        char buf[2];
+        DBusMessageIter variant_iter;
+
+        buf[0] = dbus_type;
+        buf[1] = '\0';
+
+        dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, buf, &variant_iter);
+        dbus_message_iter_append_basic(&variant_iter, dbus_type, value);
+        dbus_message_iter_close_container(iter, &variant_iter);
+    } else {
+        dbus_message_iter_append_basic(iter, dbus_type, value);
+    }
+}
+
+static void
+append_byte_array_maybe_in_variant(DBusMessageIter *iter,
+                                   const char      *data,
+                                   gsize            len,
+                                   gboolean         wrap_in_variant)
+{
+    DBusMessageIter array_iter;
+    DBusMessageIter variant_iter;
+
+    if (wrap_in_variant) {
+        dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "ay",
+                                         &variant_iter);
+    }
+
+    dbus_message_iter_open_container(wrap_in_variant ? &variant_iter : iter,
+                                     DBUS_TYPE_ARRAY, "y", &array_iter);
+
+    dbus_message_iter_append_fixed_array(&array_iter, DBUS_TYPE_BYTE,
+                                         &data, len);
+
+    dbus_message_iter_close_container(wrap_in_variant ? &variant_iter : iter,
+                                      &array_iter);
+
+    if (wrap_in_variant) {
+        dbus_message_iter_close_container(iter, &variant_iter);
+    }
+}
+
+static JSBool
+append_string(JSContext       *context,
+              DBusMessageIter *iter,
+              const char      *forced_signature,
+              const char      *s,
+              gsize            len)
+{
+    int forced_type;
+
+    if (forced_signature == NULL ||
+        *forced_signature == DBUS_TYPE_INVALID)
+        forced_type = DBUS_TYPE_STRING;
+    else
+        forced_type = *forced_signature;
+
+    switch (forced_type) {
+    case DBUS_TYPE_STRING:
+    case DBUS_TYPE_OBJECT_PATH:
+    case DBUS_TYPE_SIGNATURE:
+        append_basic_maybe_in_variant(iter, forced_type, &s, FALSE);
+        break;
+    case DBUS_TYPE_VARIANT:
+        append_basic_maybe_in_variant(iter, DBUS_TYPE_STRING, &s, TRUE);
+        break;
+    case DBUS_TYPE_ARRAY:
+        g_assert(forced_signature != NULL);
+        g_assert(forced_signature[0] == DBUS_TYPE_ARRAY);
+        if (forced_signature[1] == DBUS_TYPE_BYTE) {
+            append_byte_array_maybe_in_variant(iter,
+                                               s, len,
+                                               FALSE);
+        } else {
+            gjs_throw(context,
+                      "JavaScript string can't be converted to dbus array with elements of type '%c'",
+                      forced_signature[1]);
+            return JS_FALSE;
+        }
+        break;
+    default:
+        gjs_throw(context,
+                  "JavaScript string can't be converted to dbus type '%c'",
+                  forced_type);
+        return JS_FALSE;
+    }
+
+    return JS_TRUE;
+}
+
+static JSBool
+append_int32(JSContext       *context,
+             DBusMessageIter *iter,
+             int              forced_type,
+             dbus_int32_t     v_INT32)
+{
+    if (forced_type == DBUS_TYPE_INVALID)
+        forced_type = DBUS_TYPE_INT32;
+
+    switch (forced_type) {
+    case DBUS_TYPE_INT32:
+        append_basic_maybe_in_variant(iter, forced_type, &v_INT32, FALSE);
+        break;
+    case DBUS_TYPE_VARIANT:
+        append_basic_maybe_in_variant(iter, DBUS_TYPE_INT32, &v_INT32, TRUE);
+        break;
+    case DBUS_TYPE_UINT32:
+        {
+            dbus_uint32_t v_UINT32 = v_INT32;
+            append_basic_maybe_in_variant(iter, forced_type, &v_UINT32, FALSE);
+        }
+        break;
+    case DBUS_TYPE_DOUBLE:
+        {
+            double v_DOUBLE = v_INT32;
+            append_basic_maybe_in_variant(iter, forced_type, &v_DOUBLE, FALSE);
+        }
+        break;
+    case DBUS_TYPE_BYTE:
+        {
+            unsigned char v_BYTE = v_INT32;
+            append_basic_maybe_in_variant(iter, forced_type, &v_BYTE, FALSE);
+        }
+        break;
+    default:
+        gjs_throw(context,
+                  "JavaScript Integer can't be converted to dbus type '%c'",
+                  forced_type);
+        return JS_FALSE;
+    }
+
+    return JS_TRUE;
+}
+
+static JSBool
+append_double(JSContext       *context,
+              DBusMessageIter *iter,
+              int              forced_type,
+              double           v_DOUBLE)
+{
+    if (forced_type == DBUS_TYPE_INVALID)
+        forced_type = DBUS_TYPE_DOUBLE;
+
+    switch (forced_type) {
+    case DBUS_TYPE_DOUBLE:
+        append_basic_maybe_in_variant(iter, forced_type, &v_DOUBLE, FALSE);
+        break;
+    case DBUS_TYPE_VARIANT:
+        append_basic_maybe_in_variant(iter, DBUS_TYPE_DOUBLE, &v_DOUBLE, TRUE);
+        break;
+    case DBUS_TYPE_INT32:
+        {
+            dbus_int32_t v_INT32 = v_DOUBLE;
+            append_basic_maybe_in_variant(iter, forced_type, &v_INT32, FALSE);
+        }
+        break;
+    case DBUS_TYPE_UINT32:
+        {
+            dbus_uint32_t v_UINT32 = v_DOUBLE;
+            append_basic_maybe_in_variant(iter, forced_type, &v_UINT32, FALSE);
+        }
+        break;
+    default:
+        gjs_throw(context,
+                  "JavaScript Number can't be converted to dbus type '%c'",
+                  forced_type);
+        return JS_FALSE;
+    }
+
+    return JS_TRUE;
+}
+
+static JSBool
+append_boolean(JSContext       *context,
+               DBusMessageIter *iter,
+               int              forced_type,
+               dbus_bool_t      v_BOOLEAN)
+{
+    if (forced_type == DBUS_TYPE_INVALID)
+        forced_type = DBUS_TYPE_BOOLEAN;
+
+    switch (forced_type) {
+    case DBUS_TYPE_BOOLEAN:
+        append_basic_maybe_in_variant(iter, forced_type, &v_BOOLEAN, FALSE);
+        break;
+    case DBUS_TYPE_VARIANT:
+        append_basic_maybe_in_variant(iter, DBUS_TYPE_BOOLEAN, &v_BOOLEAN, TRUE);
+        break;
+    default:
+        gjs_throw(context,
+                  "JavaScript Boolean can't be converted to dbus type '%c'",
+                  forced_type);
+        return JS_FALSE;
+    }
+
+    return JS_TRUE;
+}
+
+static JSBool
+append_array(JSContext         *context,
+             DBusMessageIter   *iter,
+             DBusSignatureIter *sig_iter,
+             JSObject          *array,
+             int                length)
+{
+    DBusSignatureIter element_sig_iter;
+    int forced_type;
+    jsval element;
+    DBusMessageIter array_iter;
+    DBusMessageIter variant_iter;
+    int i;
+    char *sig;
+
+    forced_type = dbus_signature_iter_get_current_type(sig_iter);
+
+    if (forced_type == DBUS_TYPE_VARIANT) {
+        DBusSignatureIter variant_sig_iter;
+
+        dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT,
+                                         "av",
+                                         &variant_iter);
+        dbus_signature_iter_init(&variant_sig_iter, "av");
+        if (!append_array(context, &variant_iter,
+                          &variant_sig_iter,
+                          array, length))
+            return JS_FALSE;
+        dbus_message_iter_close_container(iter, &variant_iter);
+
+        return JS_TRUE;
+    } else if (forced_type != DBUS_TYPE_ARRAY) {
+        gjs_throw(context,
+                  "JavaScript Array can't be converted to dbus type %c",
+                  forced_type);
+        return JS_FALSE;
+    }
+
+    g_assert(dbus_signature_iter_get_current_type(sig_iter) == DBUS_TYPE_ARRAY);
+    dbus_signature_iter_recurse(sig_iter, &element_sig_iter);
+
+    sig = dbus_signature_iter_get_signature(&element_sig_iter);
+    dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, sig, &array_iter);
+    dbus_free(sig);
+
+    for (i = 0; i < length; i++) {
+        element = JSVAL_VOID;
+
+        if (!JS_GetElement(context, array, i, &element)) {
+            gjs_throw(context, "Failed to get element in JS Array");
+            return JS_FALSE;
+        }
+
+        gjs_debug(GJS_DEBUG_DBUS_MARSHAL,
+                  " Adding array element %u", i);
+
+        if (!gjs_js_one_value_to_dbus(context, element, &array_iter,
+                                      &element_sig_iter))
+            return JS_FALSE;
+    }
+
+    dbus_message_iter_close_container(iter, &array_iter);
+
+    return JS_TRUE;
+}
+
+static JSBool
+append_dict(JSContext         *context,
+            DBusMessageIter   *iter,
+            DBusSignatureIter *sig_iter,
+            JSObject          *props)
+{
+    DBusSignatureIter element_sig_iter;
+    int forced_type;
+    DBusMessageIter variant_iter;
+    JSObject *props_iter;
+    jsid prop_id;
+    DBusMessageIter dict_iter;
+    DBusSignatureIter dict_value_sig_iter;
+    char *sig;
+    jsval prop_signatures;
+
+    forced_type = dbus_signature_iter_get_current_type(sig_iter);
+
+    if (forced_type == DBUS_TYPE_VARIANT) {
+        DBusSignatureIter variant_sig_iter;
+
+        dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT,
+                                         "a{sv}",
+                                         &variant_iter);
+        dbus_signature_iter_init(&variant_sig_iter, "a{sv}");
+        if (!append_dict(context, &variant_iter,
+                         &variant_sig_iter,
+                         props))
+            return JS_FALSE;
+        dbus_message_iter_close_container(iter, &variant_iter);
+
+        return JS_TRUE;
+    } else if (forced_type != DBUS_TYPE_ARRAY) {
+        gjs_throw(context,
+                  "JavaScript Object can't be converted to dbus type %c",
+                  forced_type);
+        return JS_FALSE;
+    }
+
+    g_assert(dbus_signature_iter_get_current_type(sig_iter) == DBUS_TYPE_ARRAY);
+    dbus_signature_iter_recurse(sig_iter, &element_sig_iter);
+
+    if (dbus_signature_iter_get_current_type(&element_sig_iter) != DBUS_TYPE_DICT_ENTRY) {
+        gjs_throw(context,
+                  "Objects must be marshaled as array of dict entry not of %c",
+                  dbus_signature_iter_get_current_type(&element_sig_iter));
+        return JS_FALSE;
+    }
+
+    /* dbus itself enforces that dict keys are strings */
+
+    g_assert(dbus_signature_iter_get_current_type(&element_sig_iter) ==
+             DBUS_TYPE_DICT_ENTRY);
+    dbus_signature_iter_recurse(&element_sig_iter, &dict_value_sig_iter);
+    /* check it points to key type first */
+    g_assert(dbus_signature_iter_get_current_type(&dict_value_sig_iter) ==
+             DBUS_TYPE_STRING);
+    /* move to value type */
+    dbus_signature_iter_next(&dict_value_sig_iter);
+
+    sig = dbus_signature_iter_get_signature(&element_sig_iter);
+    dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, sig, &dict_iter);
+    dbus_free(sig);
+
+    /* If a dictionary contains another dictionary at key
+     * _dbus_signatures, the sub-dictionary can provide the signature
+     * of each value in the outer dictionary. This allows forcing
+     * integers to unsigned or whatever.
+     *
+     * _dbus_signatures has a weird name to avoid conflicting with
+     * real properties. Matches _dbus_sender which is used elsewhere.
+     *
+     * We don't bother rooting the signature object or the stuff in it
+     * because we assume the outer dictionary is rooted so the stuff
+     * in it is also.
+     */
+    prop_signatures = JSVAL_VOID;
+    gjs_object_get_property(context, props,
+                            "_dbus_signatures",
+                            &prop_signatures);
+
+    if (prop_signatures != JSVAL_VOID &&
+        !JSVAL_IS_OBJECT(prop_signatures)) {
+        gjs_throw(context,
+                  "_dbus_signatures prop must be an object");
+        return JS_FALSE;
+    }
+
+    if (prop_signatures != JSVAL_VOID &&
+        dbus_signature_iter_get_current_type(&dict_value_sig_iter) !=
+        DBUS_TYPE_VARIANT) {
+        gjs_throw(context,
+                  "Specifying _dbus_signatures for a dictionary with non-variant values is useless");
+        return JS_FALSE;
+    }
+
+    props_iter = JS_NewPropertyIterator(context, props);
+    if (props_iter == NULL) {
+        gjs_throw(context, "Failed to create property iterator for object props");
+        return JS_FALSE;
+    }
+
+    prop_id = JSVAL_VOID;
+    if (!JS_NextProperty(context, props_iter, &prop_id))
+        return JS_FALSE;
+
+    while (prop_id != JSVAL_VOID) {
+        jsval nameval;
+        char *name;
+        jsval propval;
+        DBusMessageIter entry_iter;
+        const char *value_signature;
+
+        if (!JS_IdToValue(context, prop_id, &nameval))
+            return JS_FALSE;
+
+        if (!gjs_string_to_utf8(context, nameval, &name))
+            return JS_FALSE;
+
+        if (strcmp(name, "_dbus_signatures") == 0) {
+            /* skip the magic "_dbus_signatures" field */
+            goto next;
+        }
+
+        /* see if this prop has a forced signature */
+        value_signature = NULL;
+        if (prop_signatures != JSVAL_VOID) {
+            jsval signature_value;
+            signature_value = JSVAL_VOID;
+            gjs_object_get_property(context,
+                                    JSVAL_TO_OBJECT(prop_signatures),
+                                    name, &signature_value);
+            if (signature_value != JSVAL_VOID) {
+                value_signature = gjs_string_get_ascii_checked(context,
+                                                               signature_value);
+                if (value_signature == NULL) {
+                    return JS_FALSE;
+                }
+            }
+        }
+
+        if (!gjs_object_require_property(context, props, "DBus append_dict", name, &propval))
+            return JS_FALSE;
+
+        gjs_debug(GJS_DEBUG_DBUS_MARSHAL,
+                  " Adding property %s",
+                  name);
+
+        /* gjs_js_one_value_to_dbus() would check this also, but would not
+         * print the property name, which is often useful
+         */
+        if (JSVAL_IS_NULL(propval)) {
+            gjs_throw(context, "Property '%s' has a null value, can't send over dbus",
+                      name);
+            return JS_FALSE;
+        }
+
+        dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY,
+                                         NULL, &entry_iter);
+
+        dbus_message_iter_append_basic(&entry_iter, DBUS_TYPE_STRING, &name);
+        g_free(name);
+
+        if (value_signature != NULL) {
+            DBusSignatureIter forced_signature_iter;
+            DBusMessageIter variant_iter;
+
+            g_assert(dbus_signature_iter_get_current_type(&dict_value_sig_iter) ==
+                     DBUS_TYPE_VARIANT);
+
+            dbus_message_iter_open_container(&entry_iter,
+                                             DBUS_TYPE_VARIANT,
+                                             value_signature,
+                                             &variant_iter);
+
+            dbus_signature_iter_init(&forced_signature_iter, value_signature);
+
+            if (!gjs_js_one_value_to_dbus(context, propval, &variant_iter,
+                                          &forced_signature_iter))
+                return JS_FALSE;
+
+            dbus_message_iter_close_container(&entry_iter, &variant_iter);
+        } else {
+            if (!gjs_js_one_value_to_dbus(context, propval, &entry_iter,
+                                          &dict_value_sig_iter))
+                return JS_FALSE;
+        }
+
+        dbus_message_iter_close_container(&dict_iter, &entry_iter);
+
+    next:
+        prop_id = JSVAL_VOID;
+        if (!JS_NextProperty(context, props_iter, &prop_id))
+            return JS_FALSE;
+    }
+
+    dbus_message_iter_close_container(iter, &dict_iter);
+
+    return JS_TRUE;
+}
+
+JSBool
+gjs_js_one_value_to_dbus(JSContext         *context,
+                         jsval              value,
+                         DBusMessageIter   *iter,
+                         DBusSignatureIter *sig_iter)
+{
+    int forced_type;
+
+    forced_type = dbus_signature_iter_get_current_type(sig_iter);
+
+    gjs_debug(GJS_DEBUG_DBUS_MARSHAL,
+              "Converting dbus type '%c' from jsval",
+              forced_type != DBUS_TYPE_INVALID ? forced_type : '0');
+
+    /* Don't write anything on the bus if the signature is empty */
+    if (forced_type == DBUS_TYPE_INVALID)
+        return JS_TRUE;
+
+    if (JSVAL_IS_NULL(value)) {
+        gjs_debug(GJS_DEBUG_DBUS, "Can't send null values over dbus");
+        gjs_throw(context, "Can't send null values over dbus");
+        return JS_FALSE;
+    } else if (JSVAL_IS_STRING(value)) {
+        char *data;
+        gsize len;
+        char buf[3] = { '\0', '\0', '\0' };
+        if (forced_type == DBUS_TYPE_ARRAY) {
+            buf[0] = DBUS_TYPE_ARRAY;
+            buf[1] = dbus_signature_iter_get_element_type(sig_iter);
+        } else {
+            buf[0] = forced_type;
+        }
+
+        data = NULL;
+        len = 0;
+        if (buf[1] == DBUS_TYPE_BYTE) {
+            if (!gjs_string_get_binary_data(context, value,
+                                            &data, &len))
+                return JS_FALSE;
+        } else {
+            if (!gjs_string_to_utf8(context, value, &data))
+                return JS_FALSE;
+            len = strlen(data);
+        }
+
+        if (!append_string(context,
+                           iter, buf,
+                           data, len)) {
+            g_free(data);
+            return JS_FALSE;
+        }
+
+        g_free(data);
+    } else if (JSVAL_IS_INT(value)) {
+        dbus_int32_t v_INT32;
+        if (!JS_ValueToInt32(context, value, &v_INT32))
+            return JS_FALSE;
+
+        if (!append_int32(context,
+                          iter, forced_type,
+                          v_INT32))
+            return JS_FALSE;
+    } else if (JSVAL_IS_DOUBLE(value)) {
+        double v_DOUBLE;
+        if (!JS_ValueToNumber(context, value, &v_DOUBLE))
+            return JS_FALSE;
+
+        if (!append_double(context,
+                           iter, forced_type,
+                           v_DOUBLE))
+            return JS_FALSE;
+    } else if (JSVAL_IS_BOOLEAN(value)) {
+        JSBool v_JS_BOOLEAN;
+        dbus_bool_t v_BOOLEAN;
+        if (!JS_ValueToBoolean(context, value, &v_JS_BOOLEAN))
+            return JS_FALSE;
+        v_BOOLEAN = v_JS_BOOLEAN != JS_FALSE;
+
+        if (!append_boolean(context,
+                            iter, forced_type,
+                            v_BOOLEAN))
+            return JS_FALSE;
+    } else if (JSVAL_IS_OBJECT(value)) {
+        JSObject *obj;
+        jsval lengthval;
+
+        obj = JSVAL_TO_OBJECT(value);
+
+        /* see if there's a length property */
+        gjs_object_get_property(context, obj, "length", &lengthval);
+
+        if (JSVAL_IS_INT(lengthval)) {
+            guint length;
+
+            length = JSVAL_TO_INT(lengthval);
+
+            gjs_debug(GJS_DEBUG_DBUS_MARSHAL,
+                              "Looks like an array length %u", length);
+            if (!append_array(context, iter, sig_iter, obj, length))
+                return JS_FALSE;
+        } else {
+            gjs_debug(GJS_DEBUG_DBUS_MARSHAL,
+                      "Looks like a dictionary");
+            if (!append_dict(context, iter, sig_iter, obj))
+                return JS_FALSE;
+        }
+    } else if (value == JSVAL_VOID) {
+        gjs_debug(GJS_DEBUG_DBUS, "Can't send void (undefined) values over dbus");
+        gjs_throw(context, "Can't send void (undefined) values over dbus");
+        return JS_FALSE;
+    } else {
+        gjs_debug(GJS_DEBUG_DBUS, "Don't know how to convert this jsval to dbus type");
+        gjs_throw(context, "Don't know how to convert this jsval to dbus type");
+        return JS_FALSE;
+    }
+
+    return JS_TRUE;
+}
+
+JSBool
+gjs_js_values_to_dbus(JSContext         *context,
+                      int                index,
+                      jsval              values,
+                      DBusMessageIter   *iter,
+                      DBusSignatureIter *sig_iter)
+{
+    jsval value;
+    jsuint length;
+
+    if (!JS_GetArrayLength(context, JSVAL_TO_OBJECT(values), &length)) {
+        gjs_throw(context, "Error retrieving length property of args array");
+        return JS_FALSE;
+    }
+
+    if (index > (int)length) {
+        gjs_throw(context, "Index %d is bigger than array length %d", index, length);
+        return JS_FALSE;
+    }
+
+    if (index == (int)length)
+        return JS_TRUE;
+
+    if (!JS_GetElement(context, JSVAL_TO_OBJECT(values),
+                       index, &value)) {
+        gjs_throw(context, "Error accessing element %d of args array", index);
+        return JS_FALSE;
+    }
+
+    if (!gjs_js_one_value_to_dbus(context, value, iter, sig_iter)) {
+        gjs_throw(context, "Error marshalling js value to dbus");
+        return JS_FALSE;
+    }
+
+    if (dbus_signature_iter_next(sig_iter)) {
+        return gjs_js_values_to_dbus(context, index + 1, values, iter, sig_iter);
+    }
+
+    return JS_TRUE;
+}
+
+/* If jsval is an object, add properties from the DBusMessage such as the
+ * sender. If jsval is not an object, do nothing.
+ */
+JSBool
+gjs_js_add_dbus_props(JSContext       *context,
+                      DBusMessage     *message,
+                      jsval            value)
+{
+    const char *sender;
+
+    if (!JSVAL_IS_OBJECT(value))
+        return JS_TRUE;
+
+    sender = dbus_message_get_sender(message);
+
+    if (!JS_DefineProperty(context, JSVAL_TO_OBJECT(value),
+                           "_dbus_sender",
+                           STRING_TO_JSVAL(JS_NewStringCopyZ(context, sender)),
+                           NULL, NULL, JSPROP_ENUMERATE))
+        return JS_FALSE;
+
+    return JS_TRUE;
+}
diff --git a/modules/dbus-values.h b/modules/dbus-values.h
new file mode 100644
index 0000000..fcd28a5
--- /dev/null
+++ b/modules/dbus-values.h
@@ -0,0 +1,53 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* Copyright 2008 litl, LLC. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#ifndef __BIG_JS_DBUS_VALUES_H__
+#define __BIG_JS_DBUS_VALUES_H__
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gjs/gjs.h>
+
+G_BEGIN_DECLS
+
+JSBool gjs_js_values_from_dbus    (JSContext          *context,
+                                   DBusMessageIter    *iter,
+                                   GjsRootedArray    **array_p);
+JSBool gjs_js_one_value_from_dbus (JSContext          *context,
+                                   DBusMessageIter    *iter,
+                                   jsval              *value_p);
+JSBool gjs_js_values_to_dbus      (JSContext          *context,
+                                   int                 index,
+                                   jsval               values,
+                                   DBusMessageIter    *iter,
+                                   DBusSignatureIter  *sig_iter);
+JSBool gjs_js_one_value_to_dbus   (JSContext          *context,
+                                   jsval               value,
+                                   DBusMessageIter    *iter,
+                                   DBusSignatureIter  *sig_iter);
+JSBool gjs_js_add_dbus_props      (JSContext          *context,
+                                   DBusMessage        *message,
+                                   jsval               value);
+
+
+G_END_DECLS
+
+#endif  /* __BIG_JS_DBUS_VALUES_H__ */
diff --git a/modules/dbus.c b/modules/dbus.c
new file mode 100644
index 0000000..27a32c0
--- /dev/null
+++ b/modules/dbus.c
@@ -0,0 +1,1684 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* Copyright 2008 litl, LLC. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#include <config.h>
+
+#include "dbus.h"
+#include "dbus-exports.h"
+#include "dbus-values.h"
+
+#include "../gjs/gjs.h"
+#include "../gi/closure.h"
+
+#include <util/log.h>
+#include <gjsdbus/dbus.h>
+
+static gboolean session_bus_weakref_added = FALSE;
+static DBusConnection *session_bus = NULL;
+static gboolean system_bus_weakref_added = FALSE;
+static DBusConnection *system_bus = NULL;
+
+#define DBUS_CONNECTION_FROM_TYPE(type) ((type) == DBUS_BUS_SESSION ? session_bus : system_bus)
+
+static JSBool
+get_bus_type_from_object(JSContext   *context,
+                         JSObject    *object,
+                         DBusBusType *bus_type)
+{
+    jsval value;
+
+    if (!gjs_object_get_property(context,
+                                 object,
+                                 "_dbusBusType",
+                                 &value)) {
+        gjs_throw(context, "Object has no _dbusBusType property, not a bus object?");
+        return JS_FALSE;
+    }
+
+    *bus_type =  (DBusBusType)JSVAL_TO_INT(value);
+    return JS_TRUE;
+}
+
+static JSBool
+bus_check(JSContext *context, DBusBusType bus_type)
+{
+    gboolean bus_weakref_added;
+    DBusConnection **bus_connection;
+
+    bus_weakref_added = bus_type == DBUS_BUS_SESSION ? session_bus_weakref_added :
+        system_bus_weakref_added;
+
+    bus_connection = bus_type == DBUS_BUS_SESSION ? &session_bus : &system_bus;
+
+    /* This is all done lazily only if a dbus-related method is actually invoked */
+
+    if (!bus_weakref_added) {
+        gjs_dbus_add_bus_weakref(bus_type, bus_connection);
+    }
+
+    if (*bus_connection == NULL)
+        gjs_dbus_try_connecting_now(bus_type); /* force a synchronous connection attempt */
+
+    /* Throw exception if connection attempt failed */
+    if (*bus_connection == NULL) {
+        const char *bus_type_name = bus_type == DBUS_BUS_SESSION ? "session" : "system";
+        gjs_debug(GJS_DEBUG_DBUS, "Failed to connect to %s bus", bus_type_name);
+        gjs_throw(context, "Not connected to %s message bus", bus_type_name);
+        return JS_FALSE;
+    }
+
+    return JS_TRUE;
+}
+
+static DBusMessage*
+prepare_call(JSContext   *context,
+             JSObject    *obj,
+             uintN        argc,
+             jsval       *argv,
+             DBusBusType  bus_type)
+{
+    DBusMessage *message;
+    const char *bus_name;
+    const char *path;
+    const char *interface;
+    const char *method;
+    gboolean    auto_start;
+    const char *out_signature;
+    const char *in_signature;
+    DBusMessageIter arg_iter;
+    DBusSignatureIter sig_iter;
+
+    if (!bus_check(context, bus_type))
+        return NULL;
+
+    bus_name = gjs_string_get_ascii_checked(context, argv[0]);
+    if (bus_name == NULL)
+        return NULL;
+
+    path = gjs_string_get_ascii_checked(context, argv[1]);
+    if (path == NULL)
+        return NULL;
+
+    if (JSVAL_IS_NULL(argv[2])) {
+        interface = NULL;
+    } else {
+        interface = gjs_string_get_ascii_checked(context, argv[2]);
+        if (interface == NULL)
+            return NULL; /* exception was set */
+    }
+
+    method = gjs_string_get_ascii_checked(context, argv[3]);
+    if (method == NULL)
+        return NULL;
+
+    out_signature = gjs_string_get_ascii_checked(context, argv[4]);
+    if (out_signature == NULL)
+        return NULL;
+
+    in_signature = gjs_string_get_ascii_checked(context, argv[5]);
+    if (in_signature == NULL)
+        return NULL;
+
+    g_assert(bus_name && path && method && in_signature && out_signature);
+
+    if (!JSVAL_IS_BOOLEAN(argv[6])) {
+        gjs_throw(context, "arg 7 must be boolean");
+        return NULL;
+    }
+    auto_start = JSVAL_TO_BOOLEAN(argv[6]);
+
+    /* FIXME should validate the bus_name, path, interface, method really, but
+     * we should just not write buggy JS ;-)
+     */
+
+    message = dbus_message_new_method_call(bus_name,
+                                           path,
+                                           interface,
+                                           method);
+    if (message == NULL) {
+        gjs_throw(context, "Out of memory (or invalid args to dbus_message_new_method_call)");
+        return NULL;
+    }
+
+    dbus_message_set_auto_start(message, auto_start);
+
+    dbus_message_iter_init_append(message, &arg_iter);
+
+    if (in_signature)
+        dbus_signature_iter_init(&sig_iter, in_signature);
+    else
+        dbus_signature_iter_init(&sig_iter, "a{sv}");
+
+    if (!gjs_js_values_to_dbus(context, 0, argv[8], &arg_iter, &sig_iter)) {
+        gjs_debug(GJS_DEBUG_DBUS, "Failed to marshal call from JS to dbus");
+        dbus_message_unref(message);
+        return NULL;
+    }
+
+    return message;
+}
+
+static JSBool
+complete_call(JSContext   *context,
+              jsval       *retval,
+              DBusMessage *reply,
+              DBusError   *derror)
+{
+    DBusMessageIter arg_iter;
+    GjsRootedArray *ret_values;
+    int array_length;
+
+    if (dbus_error_is_set(derror)) {
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "Error sending call: %s: %s",
+                  derror->name, derror->message);
+        gjs_throw(context,
+                  "DBus error: %s: %s",
+                  derror->name,
+                  derror->message);
+        dbus_error_free(derror);
+        return JS_FALSE;
+    }
+
+    if (reply == NULL) {
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "No reply received to call");
+        return JS_FALSE;
+    }
+
+    if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
+        dbus_set_error_from_message(derror, reply);
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "Error set by call: %s: %s",
+                  derror->name, derror->message);
+
+        gjs_throw(context,
+                     "DBus error: %s: %s",
+                     derror->name,
+                     derror->message);
+        dbus_error_free(derror);
+
+        return JS_FALSE;
+    }
+
+    dbus_message_iter_init(reply, &arg_iter);
+    if (!gjs_js_values_from_dbus(context, &arg_iter, &ret_values)) {
+        gjs_debug(GJS_DEBUG_DBUS, "Failed to marshal dbus call reply back to JS");
+        return JS_FALSE;
+    }
+
+    g_assert(ret_values != NULL);
+
+    array_length = gjs_rooted_array_get_length(context, ret_values);
+    if (array_length == 1) {
+        /* If the array only has one element return that element alone */
+        *retval = gjs_rooted_array_get(context,
+                                          ret_values,
+                                          0);
+    } else {
+        /* Otherwise return an array with all the return values. The
+         * funny assignment is to avoid creating a temporary JSObject
+         * we'd need to root
+         */
+        *retval = OBJECT_TO_JSVAL(JS_NewArrayObject(context, array_length,
+                                                    gjs_rooted_array_get_data(context, ret_values)));
+    }
+
+    /* We require the caller to have rooted retval or to have
+     * called JS_EnterLocalRootScope()
+     */
+    gjs_rooted_array_free(context, ret_values, TRUE);
+
+    gjs_js_add_dbus_props(context, reply, *retval);
+
+    return JS_TRUE;
+}
+
+static void
+pending_notify(DBusPendingCall *pending,
+               void            *user_data)
+{
+    GClosure *closure;
+    JSContext *context;
+    jsval argv[2];
+    jsval discard;
+    DBusMessage *reply;
+    DBusError derror;
+
+    closure = user_data;
+
+    context = gjs_closure_get_context(closure);
+
+    gjs_debug(GJS_DEBUG_DBUS,
+              "Notified of reply to async call closure %p context %p",
+              closure, context);
+
+    if (context == NULL) {
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "Closure destroyed before we could complete pending call");
+        return;
+    }
+
+    JS_BeginRequest(context);
+
+    /* reply may be NULL if none received? I think it may never be if
+     * we've already been notified, but be safe here.
+     */
+    reply = dbus_pending_call_steal_reply(pending);
+
+    dbus_error_init(&derror);
+    /* argv[0] will be the return value if any, argv[1] we fill with exception if any */
+    gjs_set_values(context, argv, 2, JSVAL_NULL);
+    gjs_root_value_locations(context, argv, 2);
+    complete_call(context, &argv[0], reply, &derror);
+    g_assert(!dbus_error_is_set(&derror)); /* not supposed to be left set by complete_call() */
+
+    if (reply)
+        dbus_message_unref(reply);
+
+    /* If completing the call failed, we still call the callback, but with null for the reply
+     * and an extra arg that is the exception. Kind of odd maybe.
+     */
+    if (JS_IsExceptionPending(context)) {
+        JS_GetPendingException(context, &argv[1]);
+        JS_ClearPendingException(context);
+    }
+
+    gjs_closure_invoke(closure, 2, &argv[0], &discard);
+
+    gjs_unroot_value_locations(context, argv, 2);
+
+    JS_EndRequest(context);
+}
+
+static void
+pending_free_closure(void *data)
+{
+    GClosure *closure;
+
+    closure = data;
+
+    g_closure_invalidate(data);
+    g_closure_unref(data);
+}
+
+/* Args are bus_name, object_path, iface, method, out signature, in signature, args, and callback to get returned value */
+static JSBool
+gjs_js_dbus_call_async(JSContext  *context,
+                       JSObject   *obj,
+                       uintN       argc,
+                       jsval      *argv,
+                       jsval      *retval)
+{
+    GClosure *closure;
+    DBusMessage *message;
+    DBusPendingCall *pending;
+    DBusConnection  *bus_connection;
+    DBusBusType bus_type;
+    int timeout;
+
+    if (argc < 10) {
+        gjs_throw(context, "Not enough args, need bus name, object path, interface, method, out signature, in signature, autostart flag, timeout limit, args, and callback");
+        return JS_FALSE;
+    }
+
+    /* No way to tell if the object is in fact a callable function, other than trying to
+     * call it later on.
+     */
+    if (!JSVAL_IS_OBJECT(argv[9])) {
+        gjs_throw(context, "arg 10 must be a callback to invoke when call completes");
+        return JS_FALSE;
+    }
+
+    if (!JSVAL_IS_INT(argv[7])) {
+        gjs_throw(context, "arg 8 must be int");
+        return JS_FALSE;
+    }
+    timeout = JSVAL_TO_INT(argv[7]);
+
+    if (!get_bus_type_from_object(context, obj, &bus_type))
+        return JS_FALSE;
+
+    message = prepare_call(context, obj, argc, argv, bus_type);
+
+    if (message == NULL)
+        return JS_FALSE;
+
+    bus_connection = DBUS_CONNECTION_FROM_TYPE(bus_type);
+
+    pending = NULL;
+    if (!dbus_connection_send_with_reply(bus_connection, message, &pending, timeout) ||
+        pending == NULL) {
+        gjs_debug(GJS_DEBUG_DBUS, "Failed to send async dbus message");
+        gjs_throw(context, "Failed to send dbus message");
+        dbus_message_unref(message);
+        return JS_FALSE;
+    }
+
+    g_assert(pending != NULL);
+
+    dbus_message_unref(message);
+
+    /* We cheat a bit here and use a closure to store a JavaScript function
+     * and deal with the GC root and other issues, even though we
+     * won't ever marshal via GValue
+     */
+    closure = gjs_closure_new(context, JSVAL_TO_OBJECT(argv[9]), "async call");
+    if (closure == NULL) {
+        dbus_pending_call_unref(pending);
+        return JS_FALSE;
+    }
+
+    g_closure_ref(closure);
+    g_closure_sink(closure);
+    dbus_pending_call_set_notify(pending, pending_notify, closure,
+                                 pending_free_closure);
+
+    dbus_pending_call_unref(pending); /* DBusConnection should still hold a ref until it's completed */
+
+    return JS_TRUE;
+}
+
+static JSBool
+fill_with_null_or_string(JSContext *context, const char **string_p, jsval value)
+{
+    if (JSVAL_IS_NULL(value))
+        *string_p = NULL;
+    else {
+        *string_p = gjs_string_get_ascii_checked(context, value);
+        if (!*string_p)
+            return JS_FALSE;
+    }
+
+    return JS_TRUE;
+}
+
+typedef struct {
+    int refcount;
+    DBusBusType bus_type;
+    int connection_id;
+    GClosure *closure;
+} SignalHandler;
+/* allow removal by passing in the callable
+ * FIXME don't think we ever end up using this,
+ * could get rid of it, it predates having an ID
+ * to remove by
+ */
+static GHashTable *signal_handlers_by_callable = NULL;
+
+static void signal_on_closure_invalidated (void          *data,
+                                           GClosure      *closure);
+static void signal_handler_ref            (SignalHandler *handler);
+static void signal_handler_unref          (SignalHandler *handler);
+
+
+static SignalHandler*
+signal_handler_new(JSContext *context,
+                   jsval      callable)
+{
+    SignalHandler *handler;
+
+    if (signal_handlers_by_callable &&
+        g_hash_table_lookup(signal_handlers_by_callable,
+                            JSVAL_TO_OBJECT(callable)) != NULL) {
+        /* To fix this, get rid of signal_handlers_by_callable
+         * and just require removal by id. Not sure we ever use
+         * removal by callable anyway.
+         */
+        gjs_throw(context,
+                  "For now, same callback cannot be the handler for two dbus signal connections");
+        return NULL;
+    }
+
+    handler = g_slice_new0(SignalHandler);
+    handler->refcount = 1;
+
+    /* We cheat a bit here and use a closure to store a JavaScript function
+     * and deal with the GC root and other issues, even though we
+     * won't ever marshal via GValue
+     */
+    handler->closure = gjs_closure_new(context,
+                                       JSVAL_TO_OBJECT(callable),
+                                       "signal watch");
+    if (handler->closure == NULL) {
+        g_free(handler);
+        return NULL;
+    }
+
+    g_closure_ref(handler->closure);
+    g_closure_sink(handler->closure);
+
+    g_closure_add_invalidate_notifier(handler->closure, handler,
+                                      signal_on_closure_invalidated);
+
+    if (!signal_handlers_by_callable) {
+        signal_handlers_by_callable =
+            g_hash_table_new_full(g_direct_hash,
+                                  g_direct_equal,
+                                  NULL,
+                                  NULL);
+    }
+
+    /* We keep a weak reference on the closure in a table indexed
+     * by the object, so we can retrieve it when removing the signal
+     * watch. The signal_handlers_by_callable owns one ref to the SignalHandler.
+     */
+    signal_handler_ref(handler);
+    g_hash_table_replace(signal_handlers_by_callable,
+                         JSVAL_TO_OBJECT(callable),
+                         handler);
+
+    return handler;
+}
+
+static void
+signal_handler_ref(SignalHandler *handler)
+{
+    g_assert(handler->refcount > 0);
+    handler->refcount += 1;
+}
+
+static void
+signal_handler_dispose(SignalHandler *handler)
+{
+    g_assert(handler->refcount > 0);
+
+    signal_handler_ref(handler);
+
+    if (handler->closure) {
+        /* invalidating closure could dispose
+         * re-entrantly, so set handler->closure
+         * NULL before we invalidate
+         */
+        GClosure *closure = handler->closure;
+        handler->closure = NULL;
+
+        g_hash_table_remove(signal_handlers_by_callable,
+                            gjs_closure_get_callable(closure));
+        if (g_hash_table_size(signal_handlers_by_callable) == 0) {
+            g_hash_table_destroy(signal_handlers_by_callable);
+            signal_handlers_by_callable = NULL;
+        }
+        /* the hash table owned 1 ref */
+        signal_handler_unref(handler);
+
+        g_closure_invalidate(closure);
+        g_closure_unref(closure);
+    }
+
+    /* remove signal if it hasn't been */
+    if (handler->connection_id != 0) {
+        int id = handler->connection_id;
+        handler->connection_id = 0;
+
+        /* this should clear another ref off the
+         * handler by calling signal_on_watch_removed
+         */
+        gjs_dbus_unwatch_signal_by_id(handler->bus_type,
+                                      id);
+    }
+
+    signal_handler_unref(handler);
+}
+
+static void
+signal_handler_unref(SignalHandler *handler)
+{
+    g_assert(handler->refcount > 0);
+
+    if (handler->refcount == 1) {
+        signal_handler_dispose(handler);
+    }
+
+    handler->refcount -= 1;
+    if (handler->refcount == 0) {
+        g_assert(handler->closure == NULL);
+        g_assert(handler->connection_id == 0);
+        g_slice_free(SignalHandler, handler);
+    }
+}
+
+static void
+signal_on_watch_removed(void *data)
+{
+    SignalHandler *handler = data;
+
+    handler->connection_id = 0; /* don't re-remove it */
+
+    /* The watch owns a ref; removing it
+     * also forces dispose, which invalidates
+     * the closure if that hasn't been done.
+     */
+    signal_handler_dispose(handler);
+    signal_handler_unref(handler);
+}
+
+static void
+signal_on_closure_invalidated(void     *data,
+                              GClosure *closure)
+{
+    SignalHandler *handler;
+
+    handler = data;
+
+    /* this removes the watch if it has not been */
+    signal_handler_dispose(handler);
+}
+
+static void
+signal_handler_callback(DBusConnection *connection,
+                        DBusMessage    *message,
+                        void           *data)
+{
+    JSContext *context;
+    SignalHandler *handler;
+    jsval ret_val;
+    DBusMessageIter arg_iter;
+    GjsRootedArray *arguments;
+
+    gjs_debug(GJS_DEBUG_DBUS,
+              "Signal handler called");
+
+    handler = data;
+
+    if (handler->closure == NULL) {
+        gjs_debug(GJS_DEBUG_DBUS, "dbus signal handler invalidated, ignoring");
+        return;
+    }
+
+    context = gjs_closure_get_context(handler->closure);
+
+    if (!context) {
+        /* The runtime is gone */
+        return;
+    }
+
+    dbus_message_iter_init(message, &arg_iter);
+    if (!gjs_js_values_from_dbus(context, &arg_iter, &arguments)) {
+        gjs_debug(GJS_DEBUG_DBUS, "Failed to marshal dbus signal to JS");
+        return;
+    }
+
+    signal_handler_ref(handler); /* for safety, in case handler removes itself */
+
+    g_assert(arguments != NULL);
+
+    ret_val = JSVAL_VOID;
+
+    gjs_debug(GJS_DEBUG_DBUS,
+              "Invoking closure on signal received, %d args",
+              gjs_rooted_array_get_length(context, arguments));
+    gjs_closure_invoke(handler->closure,
+                       gjs_rooted_array_get_length(context, arguments),
+                       gjs_rooted_array_get_data(context, arguments),
+                       &ret_val);
+
+    gjs_rooted_array_free(context, arguments, TRUE);
+
+    signal_handler_unref(handler); /* for safety */
+}
+
+/* Args are bus_name, object_path, iface, signal, and callback */
+static JSBool
+gjs_js_dbus_watch_signal(JSContext  *context,
+                         JSObject   *obj,
+                         uintN       argc,
+                         jsval      *argv,
+                         jsval      *retval)
+{
+    const char *bus_name;
+    const char *object_path;
+    const char *iface;
+    const char *signal;
+    SignalHandler *handler;
+    int id;
+    DBusBusType bus_type;
+
+    if (argc < 5) {
+        gjs_throw(context, "Not enough args, need bus name, object path, interface, signal and callback");
+        return JS_FALSE;
+    }
+
+    /* No way to tell if the object is in fact a callable function, other than trying to
+     * call it later on.
+     */
+    if (!JSVAL_IS_OBJECT(argv[4])) {
+        gjs_throw(context, "arg 5 must be a callback to invoke when call completes");
+        return JS_FALSE;
+    }
+
+    if (!fill_with_null_or_string(context, &bus_name, argv[0]))
+        return JS_FALSE;
+    if (!fill_with_null_or_string(context, &object_path, argv[1]))
+        return JS_FALSE;
+    if (!fill_with_null_or_string(context, &iface, argv[2]))
+        return JS_FALSE;
+    if (!fill_with_null_or_string(context, &signal, argv[3]))
+        return JS_FALSE;
+
+    if (!get_bus_type_from_object(context, obj, &bus_type))
+        return JS_FALSE;
+
+    handler = signal_handler_new(context, argv[4]);
+    if (handler == NULL)
+        return JS_FALSE;
+
+    id = gjs_dbus_watch_signal(bus_type,
+                               bus_name,
+                               object_path,
+                               iface,
+                               signal,
+                               signal_handler_callback,
+                               handler,
+                               signal_on_watch_removed);
+    handler->bus_type = bus_type;
+    handler->connection_id = id;
+
+    /* signal_on_watch_removed() takes ownership of our
+     * ref to the SignalHandler
+     */
+
+    *retval = INT_TO_JSVAL(id);
+
+    return JS_TRUE;
+}
+
+/* Args are handler id */
+static JSBool
+gjs_js_dbus_unwatch_signal_by_id(JSContext  *context,
+                                 JSObject   *obj,
+                                 uintN       argc,
+                                 jsval      *argv,
+                                 jsval      *retval)
+{
+    int id;
+    DBusBusType bus_type;
+
+    if (argc < 1) {
+        gjs_throw(context, "Not enough args, need handler id");
+        return JS_FALSE;
+    }
+
+    if (!get_bus_type_from_object(context, obj, &bus_type))
+        return JS_FALSE;
+
+    id = JSVAL_TO_INT(argv[0]);
+
+    gjs_dbus_unwatch_signal_by_id(bus_type,
+                                  id);
+    return JS_TRUE;
+}
+
+/* Args are bus_name, object_path, iface, signal, and callback */
+static JSBool
+gjs_js_dbus_unwatch_signal(JSContext  *context,
+                           JSObject   *obj,
+                           uintN       argc,
+                           jsval      *argv,
+                           jsval      *retval)
+{
+    const char *bus_name;
+    const char *object_path;
+    const char *iface;
+    const char *signal;
+    SignalHandler *handler;
+    DBusBusType bus_type;
+
+    if (argc < 5) {
+        gjs_throw(context, "Not enough args, need bus name, object path, interface, signal and callback");
+        return JS_FALSE;
+    }
+
+    if (!get_bus_type_from_object(context, obj, &bus_type))
+        return JS_FALSE;
+
+    /* No way to tell if the object is in fact a callable function, other than trying to
+     * call it later on.
+     */
+    if (!JSVAL_IS_OBJECT(argv[4])) {
+        gjs_throw(context, "arg 5 must be a callback to invoke when call completes");
+        return JS_FALSE;
+    }
+
+    if (!fill_with_null_or_string(context, &bus_name, argv[0]))
+        return JS_FALSE;
+    if (!fill_with_null_or_string(context, &object_path, argv[1]))
+        return JS_FALSE;
+    if (!fill_with_null_or_string(context, &iface, argv[2]))
+        return JS_FALSE;
+    if (!fill_with_null_or_string(context, &signal, argv[3]))
+        return JS_FALSE;
+
+    /* we don't complain if the signal seems to have been already removed
+     * or to never have been watched, to match g_signal_handler_disconnect
+     */
+    if (!signal_handlers_by_callable)
+        return JS_TRUE;
+
+    handler = g_hash_table_lookup(signal_handlers_by_callable, JSVAL_TO_OBJECT(argv[4]));
+
+    if (!handler)
+        return JS_TRUE;
+
+    /* This should dispose the handler which should in turn
+     * remove it from the handler table
+     */
+    gjs_dbus_unwatch_signal(bus_type,
+                            bus_name,
+                            object_path,
+                            iface,
+                            signal,
+                            signal_handler_callback,
+                            handler);
+
+    g_assert(g_hash_table_lookup(signal_handlers_by_callable,
+                                 JSVAL_TO_OBJECT(argv[4])) == NULL);
+
+    return JS_TRUE;
+}
+
+/* Args are object_path, iface, signal, arguments signature, arguments */
+static JSBool
+gjs_js_dbus_emit_signal(JSContext  *context,
+                        JSObject   *obj,
+                        uintN       argc,
+                        jsval      *argv,
+                        jsval      *retval)
+{
+    DBusConnection *bus_connection;
+    DBusMessage *message;
+    DBusMessageIter arg_iter;
+    DBusSignatureIter sig_iter;
+    const char *object_path;
+    const char *iface;
+    const char *signal;
+    const char *in_signature;
+    DBusBusType bus_type;
+
+    if (argc < 4) {
+        gjs_throw(context, "Not enough args, need object path, interface and signal and the arguments");
+        return JS_FALSE;
+    }
+
+    if (!JSVAL_IS_OBJECT(argv[4])) {
+        gjs_throw(context, "5th argument should be an array of arguments");
+        return JS_FALSE;
+    }
+
+    if (!get_bus_type_from_object(context, obj, &bus_type))
+        return JS_FALSE;
+
+    object_path = gjs_string_get_ascii_checked(context, argv[0]);
+    if (!object_path)
+        return JS_FALSE;
+    iface = gjs_string_get_ascii_checked(context, argv[1]);
+    if (!iface)
+        return JS_FALSE;
+    signal = gjs_string_get_ascii_checked(context, argv[2]);
+    if (!signal)
+        return JS_FALSE;
+    in_signature = gjs_string_get_ascii_checked(context, argv[3]);
+    if (!in_signature)
+        return JS_FALSE;
+
+    if (!bus_check(context, bus_type))
+        return JS_FALSE;
+
+    gjs_debug(GJS_DEBUG_DBUS,
+              "Emitting signal %s %s %s",
+              object_path,
+              iface,
+              signal);
+
+    bus_connection = DBUS_CONNECTION_FROM_TYPE(bus_type);
+
+    message = dbus_message_new_signal(object_path,
+                                      iface,
+                                      signal);
+
+    dbus_message_iter_init_append(message, &arg_iter);
+
+    dbus_signature_iter_init(&sig_iter, in_signature);
+
+    if (!gjs_js_values_to_dbus(context, 0, argv[4], &arg_iter, &sig_iter)) {
+        dbus_message_unref(message);
+        return JS_FALSE;
+    }
+
+    dbus_connection_send(bus_connection, message, NULL);
+
+    dbus_message_unref(message);
+
+    return JS_TRUE;
+}
+
+/* Args are bus_name, object_path, iface, method, out signature, in signature, args */
+static JSBool
+gjs_js_dbus_call(JSContext  *context,
+                 JSObject   *obj,
+                 uintN       argc,
+                 jsval      *argv,
+                 jsval      *retval)
+{
+    DBusMessage *message;
+    DBusError derror;
+    DBusMessage *reply;
+    JSBool result;
+    DBusConnection *bus_connection;
+    DBusBusType bus_type;
+
+    if (argc < 8) {
+        gjs_throw(context, "Not enough args, need bus name, object path, interface, method, out signature, in signature, autostart flag, and args");
+        return JS_FALSE;
+    }
+
+    if (!get_bus_type_from_object(context, obj, &bus_type))
+        return JS_FALSE;
+
+    message = prepare_call(context, obj, argc, argv, bus_type);
+
+    bus_connection = DBUS_CONNECTION_FROM_TYPE(bus_type);
+
+    /* send_with_reply_and_block() returns NULL if error was set. */
+    dbus_error_init(&derror);
+    reply = dbus_connection_send_with_reply_and_block(bus_connection, message, -1, &derror);
+
+    dbus_message_unref(message);
+
+    /* this consumes the DBusError leaving it freed */
+    /* The retval is (we hope) rooted by jsapi when it invokes the
+     * native function
+     */
+    result = complete_call(context, retval, reply, &derror);
+
+    if (reply)
+        dbus_message_unref(reply);
+
+    return result;
+}
+
+typedef struct {
+    GjsDBusNameOwnerFuncs funcs;
+    GClosure *acquired_closure;
+    GClosure *lost_closure;
+    DBusBusType bus_type;
+} GjsJSDBusNameOwner;
+
+static void
+on_name_acquired(DBusConnection *connection,
+                 const char     *name,
+                 void           *data)
+{
+    int argc;
+    jsval argv[1];
+    jsval rval;
+    JSContext *context;
+    GjsJSDBusNameOwner *owner;
+
+    owner = data;
+
+    context = gjs_closure_get_context(owner->acquired_closure);
+    if (context == NULL) {
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "Closure destroyed before we could notify name acquired");
+        return;
+    }
+
+    argc = 1;
+
+    argv[0] = STRING_TO_JSVAL(JS_NewStringCopyZ(context, name));
+    JS_AddRoot(context, &argv[0]);
+
+    rval = JSVAL_VOID;
+    JS_AddRoot(context, &rval);
+
+    gjs_closure_invoke(owner->acquired_closure,
+                       argc, argv, &rval);
+
+    JS_RemoveRoot(context, &argv[0]);
+    JS_RemoveRoot(context, &rval);
+}
+
+static void
+on_name_lost(DBusConnection *connection,
+             const char     *name,
+             void           *data)
+{
+    int argc;
+    jsval argv[1];
+    jsval rval;
+    JSContext *context;
+    GjsJSDBusNameOwner *owner;
+
+    owner = data;
+
+    context = gjs_closure_get_context(owner->lost_closure);
+    if (context == NULL) {
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "Closure destroyed before we could notify name lost");
+        return;
+    }
+
+    argc = 1;
+
+    argv[0] = STRING_TO_JSVAL(JS_NewStringCopyZ(context, name));
+    JS_AddRoot(context, &argv[0]);
+
+    rval = JSVAL_VOID;
+    JS_AddRoot(context, &rval);
+
+    gjs_closure_invoke(owner->lost_closure,
+                          argc, argv, &rval);
+
+    JS_RemoveRoot(context, &argv[0]);
+    JS_RemoveRoot(context, &rval);
+}
+
+static void
+owner_closure_invalidated(gpointer  data,
+                          GClosure *closure)
+{
+    GjsJSDBusNameOwner *owner;
+
+    owner = (GjsJSDBusNameOwner*)data;
+
+    if (owner) {
+        gjs_dbus_release_name(owner->bus_type,
+                              &owner->funcs,
+                              owner);
+
+        g_closure_unref(owner->acquired_closure);
+        g_closure_unref(owner->lost_closure);
+
+        g_slice_free(GjsJSDBusNameOwner, owner);
+    }
+
+}
+
+/* Args are bus_name, name type, acquired_func, lost_func */
+static JSBool
+gjs_js_dbus_acquire_name(JSContext  *context,
+                         JSObject   *obj,
+                         uintN       argc,
+                         jsval      *argv,
+                         jsval      *retval)
+{
+    const char *bus_name;
+    JSObject *acquire_func;
+    JSObject *lost_func;
+    GjsJSDBusNameOwner *owner;
+    DBusBusType bus_type;
+    GjsDBusNameType name_type;
+    unsigned int id;
+
+    if (argc < 4) {
+        gjs_throw(context, "Not enough args, need bus name, name type, acquired_func, lost_func");
+        return JS_FALSE;
+    }
+
+    if (!get_bus_type_from_object(context, obj, &bus_type))
+        return JS_FALSE;
+
+    bus_name = gjs_string_get_ascii_checked(context, argv[0]);
+    if (bus_name == NULL)
+        return JS_FALSE;
+
+    if (!JSVAL_IS_INT(argv[1])) {
+        gjs_throw(context, "Second arg is an integer representing the name type (single or multiple instances)\n"
+                     "Use the constants DBus.SINGLE_INSTANCE and DBus.MANY_INSTANCES, defined in the DBus module");
+        return JS_FALSE;
+    }
+
+    name_type = (GjsDBusNameType)JSVAL_TO_INT(argv[1]);
+
+    if (!JSVAL_IS_OBJECT(argv[2])) {
+        gjs_throw(context, "Third arg is a callback to invoke on acquiring the name");
+        return JS_FALSE;
+    }
+
+    acquire_func = JSVAL_TO_OBJECT(argv[2]);
+
+    if (!JSVAL_IS_OBJECT(argv[3])) {
+        gjs_throw(context, "Fourth arg is a callback to invoke on losing the name");
+        return JS_FALSE;
+    }
+
+    lost_func = JSVAL_TO_OBJECT(argv[3]);
+
+    owner = g_slice_new0(GjsJSDBusNameOwner);
+
+    owner->funcs.name = g_strdup(bus_name);
+    owner->funcs.type = name_type;
+    owner->funcs.acquired = on_name_acquired;
+    owner->funcs.lost = on_name_lost;
+    owner->bus_type = bus_type;
+
+    owner->acquired_closure =
+        gjs_closure_new(context, acquire_func, "acquired bus name");
+
+    g_closure_ref(owner->acquired_closure);
+    g_closure_sink(owner->acquired_closure);
+
+    owner->lost_closure =
+        gjs_closure_new(context, lost_func, "lost bus name");
+
+    g_closure_ref(owner->lost_closure);
+    g_closure_sink(owner->lost_closure);
+
+    /* Only add the invalidate notifier to one of the closures, should
+     * be enough */
+    g_closure_add_invalidate_notifier(owner->acquired_closure, owner,
+                                      owner_closure_invalidated);
+
+    id = gjs_dbus_acquire_name(bus_type,
+                               &owner->funcs,
+                               owner);
+
+    if (!JS_NewNumberValue(context, (jsdouble)id, retval)) {
+        gjs_throw(context, "Could not convert name owner id to jsval");
+        return JS_FALSE;
+    }
+
+    return JS_TRUE;
+}
+
+/* Args are name owner monitor id */
+static JSBool
+gjs_js_dbus_release_name_by_id (JSContext  *context,
+                                JSObject   *obj,
+                                uintN       argc,
+                                jsval      *argv,
+                                jsval      *retval)
+{
+    DBusBusType bus_type;
+    unsigned int id;
+
+    if (argc < 1) {
+        gjs_throw(context, "Not enough args, need name owner monitor id");
+        return JS_FALSE;
+    }
+
+    if (!get_bus_type_from_object(context, obj, &bus_type))
+        return JS_FALSE;
+
+    id = JSVAL_TO_INT(argv[0]);
+
+    gjs_dbus_release_name_by_id(bus_type,
+                                id);
+    return JS_TRUE;
+}
+
+typedef struct {
+    GClosure *appeared_closure;
+    GClosure *vanished_closure;
+    char *bus_name;
+    DBusBusType bus_type;
+} GjsJSDBusNameWatcher;
+
+static void
+on_name_appeared(DBusConnection *connection,
+                 const char     *name,
+                 const char     *owner_unique_name,
+                 void           *data)
+{
+    int argc;
+    jsval argv[2];
+    jsval rval;
+    JSContext *context;
+    GjsJSDBusNameWatcher *watcher;
+
+    watcher = data;
+
+    context = gjs_closure_get_context(watcher->appeared_closure);
+    if (context == NULL) {
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "Closure destroyed before we could notify name appeared");
+        return;
+    }
+
+    argc = 2;
+
+    gjs_set_values(context, argv, argc, JSVAL_VOID);
+    gjs_root_value_locations(context, argv, argc);
+
+    argv[0] = STRING_TO_JSVAL(JS_NewStringCopyZ(context, name));
+    argv[1] = STRING_TO_JSVAL(JS_NewStringCopyZ(context, owner_unique_name));
+
+    rval = JSVAL_VOID;
+    JS_AddRoot(context, &rval);
+
+    gjs_closure_invoke(watcher->appeared_closure,
+                          argc, argv, &rval);
+
+    JS_RemoveRoot(context, &rval);
+    gjs_unroot_value_locations(context, argv, argc);
+}
+
+static void
+on_name_vanished(DBusConnection *connection,
+                 const char     *name,
+                 const char     *owner_unique_name,
+                 void           *data)
+{
+    int argc;
+    jsval argv[2];
+    jsval rval;
+    JSContext *context;
+    GjsJSDBusNameWatcher *watcher;
+
+    watcher = data;
+
+    context = gjs_closure_get_context(watcher->vanished_closure);
+    if (context == NULL) {
+        gjs_debug(GJS_DEBUG_DBUS,
+                  "Closure destroyed before we could notify name vanished");
+        return;
+    }
+
+    argc = 2;
+
+    gjs_set_values(context, argv, argc, JSVAL_VOID);
+    gjs_root_value_locations(context, argv, argc);
+
+    argv[0] = STRING_TO_JSVAL(JS_NewStringCopyZ(context, name));
+    argv[1] = STRING_TO_JSVAL(JS_NewStringCopyZ(context, owner_unique_name));
+
+    rval = JSVAL_VOID;
+    JS_AddRoot(context, &rval);
+
+    gjs_closure_invoke(watcher->vanished_closure,
+                          argc, argv, &rval);
+
+    JS_RemoveRoot(context, &rval);
+    gjs_unroot_value_locations(context, argv, argc);
+}
+
+static const GjsDBusWatchNameFuncs watch_name_funcs = {
+    on_name_appeared,
+    on_name_vanished
+};
+
+static void
+watch_closure_invalidated(gpointer  data,
+                          GClosure *closure)
+{
+    GjsJSDBusNameWatcher *watcher;
+
+    watcher = (GjsJSDBusNameWatcher*)data;
+
+    if (watcher) {
+        gjs_dbus_unwatch_name(watcher->bus_type,
+                              watcher->bus_name,
+                              &watch_name_funcs,
+                              watcher);
+
+        g_free(watcher->bus_name);
+        g_closure_unref(watcher->appeared_closure);
+        g_closure_unref(watcher->vanished_closure);
+
+        g_slice_free(GjsJSDBusNameWatcher, watcher);
+    }
+
+}
+
+/* Args are bus_name, start_if_not_found, appeared_func, vanished_func */
+static JSBool
+gjs_js_dbus_watch_name(JSContext  *context,
+                       JSObject   *obj,
+                       uintN       argc,
+                       jsval      *argv,
+                       jsval      *retval)
+{
+    const char *bus_name;
+    JSBool start_if_not_found;
+    JSObject *appeared_func;
+    JSObject *vanished_func;
+    GjsJSDBusNameWatcher *watcher;
+    DBusBusType bus_type;
+
+    if (argc < 4) {
+        gjs_throw(context, "Not enough args, need bus name, acquired_func, lost_func");
+        return JS_FALSE;
+    }
+
+    if (!get_bus_type_from_object(context, obj, &bus_type))
+        return JS_FALSE;
+
+    bus_name = gjs_string_get_ascii_checked(context, argv[0]);
+    if (bus_name == NULL)
+        return JS_FALSE;
+
+    start_if_not_found = JS_FALSE;
+    if (!JS_ValueToBoolean(context, argv[1], &start_if_not_found)) {
+        if (!JS_IsExceptionPending(context))
+            gjs_throw(context, "Second arg is a bool for whether to start the name if not found");
+        return JS_FALSE;
+    }
+
+    if (!JSVAL_IS_OBJECT(argv[2])) {
+        gjs_throw(context, "Third arg is a callback to invoke on seeing the name");
+        return JS_FALSE;
+    }
+
+    appeared_func = JSVAL_TO_OBJECT(argv[2]);
+
+    if (!JSVAL_IS_OBJECT(argv[3])) {
+        gjs_throw(context, "Fourth arg is a callback to invoke when the name vanishes");
+        return JS_FALSE;
+    }
+
+    vanished_func = JSVAL_TO_OBJECT(argv[3]);
+
+    watcher = g_slice_new0(GjsJSDBusNameWatcher);
+
+    watcher->appeared_closure =
+        gjs_closure_new(context, appeared_func, "service appeared");
+
+    g_closure_ref(watcher->appeared_closure);
+    g_closure_sink(watcher->appeared_closure);
+
+    watcher->vanished_closure =
+        gjs_closure_new(context, vanished_func, "service vanished");
+
+    g_closure_ref(watcher->vanished_closure);
+    g_closure_sink(watcher->vanished_closure);
+
+    watcher->bus_type = bus_type;
+    watcher->bus_name = g_strdup(bus_name);
+
+    /* Only add the invalidate notifier to one of the closures, should
+     * be enough */
+    g_closure_add_invalidate_notifier(watcher->appeared_closure, watcher,
+                                      watch_closure_invalidated);
+
+    gjs_dbus_watch_name(bus_type,
+                        bus_name,
+                        start_if_not_found ?
+                        GJS_DBUS_NAME_START_IF_NOT_FOUND : 0,
+                        &watch_name_funcs,
+                        watcher);
+
+    return JS_TRUE;
+}
+
+/* a hook on getting a property; set value_p to override property's value.
+ * Return value is JS_FALSE on OOM/exception.
+ */
+static JSBool
+unique_name_getter(JSContext  *context,
+                   JSObject   *obj,
+                   jsval       id,
+                   jsval      *value_p)
+{
+    const char *name;
+    DBusConnection *bus_connection;
+    DBusBusType bus_type;
+
+    if (!get_bus_type_from_object(context, obj, &bus_type))
+        return JS_FALSE;
+
+    name = gjs_string_get_ascii(id);
+
+    gjs_debug_jsprop(GJS_DEBUG_DBUS, "Get prop '%s' on dbus object", name);
+
+    bus_check(context, bus_type);
+
+    bus_connection = DBUS_CONNECTION_FROM_TYPE(bus_type);
+
+    if (bus_connection == NULL) {
+        *value_p = JSVAL_NULL;
+    } else {
+        const char *unique_name;
+        JSString *s;
+
+        unique_name = dbus_bus_get_unique_name(bus_connection);
+
+        s = JS_NewStringCopyZ(context, unique_name);
+        *value_p = STRING_TO_JSVAL(s);
+    }
+
+    return JS_TRUE;
+}
+
+static JSBool
+gjs_js_dbus_signature_length(JSContext  *context,
+                             JSObject   *obj,
+                             uintN       argc,
+                             jsval      *argv,
+                             jsval      *retval)
+{
+    const char *signature;
+    DBusSignatureIter iter;
+    int length = 0;
+
+    if (argc < 1) {
+        gjs_throw(context, "Not enough args, need a dbus signature");
+        return JS_FALSE;
+    }
+
+    signature = gjs_string_get_ascii_checked(context, argv[0]);
+    if (signature == NULL)
+        return JS_FALSE;
+
+    if (!dbus_signature_validate(signature, NULL)) {
+        gjs_throw(context, "Invalid signature");
+        return JS_FALSE;
+    }
+
+    /* Empty sig is special 0-length case */
+    if (*signature == '\0')
+        goto out;
+
+    dbus_signature_iter_init(&iter, signature);
+
+    do {
+        length++;
+    } while (dbus_signature_iter_next(&iter));
+
+ out:
+    *retval = INT_TO_JSVAL(length);
+
+    return JS_TRUE;
+}
+
+static JSBool
+gjs_js_dbus_start_service(JSContext  *context,
+                          JSObject   *obj,
+                          uintN       argc,
+                          jsval      *argv,
+                          jsval      *retval)
+{
+    const char     *name;
+    DBusBusType     bus_type;
+    DBusConnection *bus_connection;
+
+    if (argc != 1) {
+        gjs_throw(context, "Wrong number of arguments, expected service name");
+        return JS_FALSE;
+    }
+
+    name = gjs_string_get_ascii_checked(context, argv[0]);
+    if (!name)
+        return JS_FALSE;
+
+    if (!get_bus_type_from_object(context, obj, &bus_type))
+        return JS_FALSE;
+
+    if (!bus_check(context, bus_type))
+        return JS_FALSE;
+
+    bus_connection = DBUS_CONNECTION_FROM_TYPE(bus_type);
+
+    gjs_dbus_start_service(bus_connection, name);
+
+    return JS_TRUE;
+}
+
+static JSBool
+gjs_js_dbus_get_machine_id(JSContext *context,
+                           JSObject  *obj,
+                           jsval      key,
+                           jsval     *value)
+{
+    char *machine_id;
+    JSString *machine_id_string;
+
+    if (value)
+        *value = JSVAL_VOID;
+
+    machine_id = dbus_get_local_machine_id();
+    machine_id_string = JS_NewStringCopyZ(context, machine_id);
+    dbus_free(machine_id);
+
+    if (!machine_id_string)
+        return JS_FALSE;
+
+    if (value)
+        *value = STRING_TO_JSVAL(machine_id_string);
+
+    return JS_TRUE;
+}
+
+static JSBool
+define_bus_proto(JSContext *context,
+                 JSObject *module_obj,
+                 JSObject **bus_proto_obj_out)
+{
+    JSObject *bus_proto_obj;
+    jsval bus_proto_val;
+    JSBool result;
+
+    result = JS_FALSE;
+
+    bus_proto_val = JSVAL_VOID;
+    JS_AddRoot(context, &bus_proto_val);
+
+    bus_proto_obj = JS_ConstructObject(context, NULL, NULL, NULL);
+    if (bus_proto_obj == NULL)
+        goto out;
+
+    bus_proto_val = OBJECT_TO_JSVAL(bus_proto_obj);
+
+    if (!JS_DefineProperty(context, bus_proto_obj,
+                           "unique_name",
+                           JSVAL_VOID,
+                           unique_name_getter,
+                           NULL,
+                           GJS_MODULE_PROP_FLAGS))
+        goto out;
+
+    if (!JS_DefineFunction(context, bus_proto_obj,
+                           "call",
+                           gjs_js_dbus_call,
+                           8, GJS_MODULE_PROP_FLAGS))
+        goto out;
+
+    if (!JS_DefineFunction(context, bus_proto_obj,
+                           "call_async",
+                           gjs_js_dbus_call_async,
+                           9, GJS_MODULE_PROP_FLAGS))
+        goto out;
+
+    if (!JS_DefineFunction(context, bus_proto_obj,
+                           "acquire_name",
+                           gjs_js_dbus_acquire_name,
+                           3, GJS_MODULE_PROP_FLAGS))
+        goto out;
+
+    if (!JS_DefineFunction(context, bus_proto_obj,
+                           "release_name_by_id",
+                           gjs_js_dbus_release_name_by_id,
+                           1, GJS_MODULE_PROP_FLAGS))
+        goto out;
+
+
+    if (!JS_DefineFunction(context, bus_proto_obj,
+                           "watch_name",
+                           gjs_js_dbus_watch_name,
+                           4, GJS_MODULE_PROP_FLAGS))
+        goto out;
+
+    if (!JS_DefineFunction(context, bus_proto_obj,
+                           "watch_signal",
+                           gjs_js_dbus_watch_signal,
+                           5, GJS_MODULE_PROP_FLAGS))
+        goto out;
+
+    if (!JS_DefineFunction(context, bus_proto_obj,
+                           "unwatch_signal_by_id",
+                           gjs_js_dbus_unwatch_signal_by_id,
+                           1, GJS_MODULE_PROP_FLAGS))
+        goto out;
+
+    if (!JS_DefineFunction(context, bus_proto_obj,
+                           "unwatch_signal",
+                           gjs_js_dbus_unwatch_signal,
+                           5, GJS_MODULE_PROP_FLAGS))
+        goto out;
+
+    if (!JS_DefineFunction(context, bus_proto_obj,
+                           "emit_signal",
+                           gjs_js_dbus_emit_signal,
+                           3, GJS_MODULE_PROP_FLAGS))
+        goto out;
+
+    if (!JS_DefineFunction(context, bus_proto_obj,
+                           "start_service",
+                           gjs_js_dbus_start_service,
+                           1, GJS_MODULE_PROP_FLAGS))
+        goto out;
+
+    /* Add the bus proto object inside the passed in module object */
+    if (!JS_DefineProperty(context, module_obj,
+                           "_busProto", OBJECT_TO_JSVAL(bus_proto_obj),
+                           NULL, NULL,
+                           GJS_MODULE_PROP_FLAGS))
+        goto out;
+
+    *bus_proto_obj_out = bus_proto_obj;
+    result = JS_TRUE;
+
+ out:
+    JS_RemoveRoot(context, &bus_proto_val);
+
+    return result;
+}
+
+static JSBool
+define_bus_object(JSContext  *context,
+                  JSObject   *module_obj,
+                  JSObject   *proto_obj,
+                  DBusBusType which_bus)
+{
+    JSObject *bus_obj;
+    jsval bus_val;
+    jsval bus_type_val;
+    const char *bus_name;
+    JSBool result;
+
+    bus_name = GJS_DBUS_NAME_FROM_TYPE(which_bus);
+
+    if (gjs_object_has_property(context, module_obj, bus_name))
+        return JS_TRUE;
+
+    result = JS_FALSE;
+
+    bus_val = JSVAL_VOID;
+    JS_AddRoot(context, &bus_val);
+
+    bus_obj = JS_ConstructObject(context, NULL, proto_obj, NULL);
+    if (bus_obj == NULL)
+        goto out;
+
+    bus_val = OBJECT_TO_JSVAL(bus_obj);
+
+    /* Store the bus type as a property of the object. This way
+     * we can have generic methods that will get the bus type
+     * from the object they are defined in
+     */
+    bus_type_val = INT_TO_JSVAL((int)which_bus);
+    if (!JS_DefineProperty(context, bus_obj, "_dbusBusType",
+                           bus_type_val,
+                           NULL, NULL,
+                           GJS_MODULE_PROP_FLAGS))
+        goto out;
+
+
+    /* When we get an incoming dbus call to path /com/litl/foo/bar
+     * then we map that to the object dbus.session.exports.com.litl.foo.bar
+     */
+    if (!gjs_js_define_dbus_exports(context, bus_obj, which_bus))
+        goto out;
+
+    /* Add the bus object inside the passed in module object */
+    if (!JS_DefineProperty(context, module_obj,
+                           bus_name, OBJECT_TO_JSVAL(bus_obj),
+                           NULL, NULL,
+                           GJS_MODULE_PROP_FLAGS))
+        goto out;
+
+    result = JS_TRUE;
+
+ out:
+    JS_RemoveRoot(context, &bus_val);
+
+    return result;
+}
+
+JSBool
+gjs_js_define_dbus_stuff(JSContext      *context,
+                         JSObject       *module_obj)
+{
+    JSObject *bus_proto_obj;
+    /* Note that we don't actually connect to dbus in here, since the
+     * JS program may not even be using it.
+     */
+
+    g_printerr ("RUNNING dbus init\n");
+
+    if (!JS_DefineFunction(context, module_obj,
+                           "signatureLength",
+                           gjs_js_dbus_signature_length,
+                           1, GJS_MODULE_PROP_FLAGS))
+        return JS_FALSE;
+
+    if (!JS_DefineProperty(context, module_obj,
+                           "BUS_SESSION",
+                           INT_TO_JSVAL(DBUS_BUS_SESSION),
+                           NULL, NULL,
+                           GJS_MODULE_PROP_FLAGS))
+        return JS_FALSE;
+
+    if (!JS_DefineProperty(context, module_obj,
+                           "BUS_SYSTEM",
+                           INT_TO_JSVAL(DBUS_BUS_SYSTEM),
+                           NULL, NULL,
+                           GJS_MODULE_PROP_FLAGS))
+        return JS_FALSE;
+
+    if (!JS_DefineProperty(context, module_obj,
+                           "BUS_STARTER",
+                           INT_TO_JSVAL(DBUS_BUS_STARTER),
+                           NULL, NULL,
+                           GJS_MODULE_PROP_FLAGS))
+        return JS_FALSE;
+
+    if (!JS_DefineProperty(context, module_obj,
+                           "localMachineID",
+                           JSVAL_VOID,
+                           gjs_js_dbus_get_machine_id, NULL,
+                           GJS_MODULE_PROP_FLAGS))
+        return JS_FALSE;
+
+    /* Define both the session and system objects */
+    if (!define_bus_proto(context, module_obj, &bus_proto_obj))
+        return JS_FALSE;
+
+    if (!define_bus_object(context, module_obj, bus_proto_obj, DBUS_BUS_SESSION))
+        return JS_FALSE;
+
+    if (!define_bus_object(context, module_obj, bus_proto_obj, DBUS_BUS_SYSTEM))
+        return JS_FALSE;
+
+    return JS_TRUE;
+}
+
+GJS_REGISTER_NATIVE_MODULE("dbusNative", gjs_js_define_dbus_stuff)
diff --git a/modules/dbus.h b/modules/dbus.h
new file mode 100644
index 0000000..72321e3
--- /dev/null
+++ b/modules/dbus.h
@@ -0,0 +1,39 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/* Copyright 2008 litl, LLC. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+#ifndef __BIG_JS_DBUS_H__
+#define __BIG_JS_DBUS_H__
+
+#include <config.h>
+#include <glib.h>
+
+#include <jsapi.h>
+
+#include <gjs/gjs.h>
+
+G_BEGIN_DECLS
+
+JSBool        big_js_define_dbus_stuff     (JSContext      *context,
+                                            JSObject       *in_object);
+
+G_END_DECLS
+
+#endif  /* __BIG_JS_DBUS_H__ */
diff --git a/modules/dbus.js b/modules/dbus.js
new file mode 100644
index 0000000..c98aa6d
--- /dev/null
+++ b/modules/dbus.js
@@ -0,0 +1,596 @@
+// Copyright 2008 litl, LLC. All Rights Reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+const Lang = imports.lang;
+
+// Parameters for acquire_name, keep in sync with
+// enum in util/dbus.h
+const SINGLE_INSTANCE = 0;
+const MANY_INSTANCES = 1;
+
+const NO_START_IF_NOT_FOUND = false;
+const START_IF_NOT_FOUND    = true;
+
+// Merge stuff defined in native code
+Lang.copyProperties(imports.dbusNative, this);
+
+var Introspectable = {
+    name: 'org.freedesktop.DBus.Introspectable',
+    methods: [
+        // Introspectable: return an XML description of the object.
+        // Out Args:
+        //   xml_data: XML description of the object.
+        { name: 'Introspect', inSignature: '', outSignature: 's' }
+    ],
+    signals: [],
+    properties: []
+};
+
+var Properties = {
+    name: 'org.freedesktop.DBus.Properties',
+    methods: [
+        { name: 'Get', inSignature: 'ss', outSignature: 'v' },
+        { name: 'Set', inSignature: 'ssv', outSignature: '' },
+        { name: 'GetAll', inSignature: 's', outSignature: 'a{sv}' }
+    ],
+    signals: [],
+    properties: []
+};
+
+function _proxyInvoker(obj, ifaceName, methodName, outSignature, inSignature, timeout, arg_array) {
+    let replyFunc;
+
+    /* Note: "this" in here is the module, "obj" is the proxy object
+     */
+
+    if (ifaceName == null)
+        ifaceName = obj._dbusInterface;
+
+    /* Convert arg_array to a *real* array */
+    arg_array = Array.prototype.slice.call(arg_array);
+
+    /* The default replyFunc only logs the responses */
+    replyFunc = _logReply;
+
+    let expectedNumberArgs = this.signatureLength(inSignature);
+
+    if (arg_array.length < expectedNumberArgs) {
+        throw new Error("Not enough arguments passed for method: " + methodName +
+                       ". Expected " + expectedNumberArgs + ", got " + arg_array.length);
+    } else if (arg_array.length == expectedNumberArgs + 1) {
+        /* If there's one more than the expected number
+         * of argument we asume the last one is the reply
+         * function
+         */
+        replyFunc = arg_array.pop();
+    } else if (arg_array.length != expectedNumberArgs) {
+        throw new Error("Too many arguments passed for method: " + methodName +
+                       ". Maximum is " + expectedNumberArgs + " + one callback");
+    }
+
+    /* Auto-start on method calls is too unpredictable; in particular if
+     * something crashes we want to cleanly restart it, not have it
+     * come back next time someone tries to use it.
+     */
+    obj._dbusBus.call_async(obj._dbusBusName,
+                            obj._dbusPath,
+                            ifaceName,
+                            methodName,
+                            outSignature,
+                            inSignature,
+                            NO_START_IF_NOT_FOUND,
+                            timeout,
+                            arg_array,
+                            replyFunc);
+}
+
+function _logReply(result, exc) {
+    if (result != null) {
+        log("Ignored reply to dbus method: " + result.toSource());
+    }
+    if (exc != null) {
+        log("Ignored exception from dbus method: " + exc.toString());
+    }
+}
+
+function _makeProxyMethod(member) {
+    return function() {
+        /* JSON methods are the default */
+        if (!("outSignature" in member))
+            member.outSignature = "a{sv}";
+
+        if (!("inSignature" in member))
+            member.inSignature = "a{sv}";
+
+        if (!("timeout" in member))
+            member.timeout = -1;
+
+        _proxyInvoker(this, null, member.name,
+                      member.outSignature, member.inSignature,
+                      member.timeout, arguments);
+    };
+}
+
+// stub we insert in all proxy prototypes
+function _getBusName() {
+    return this._dbusBusName;
+}
+
+// stub we insert in all proxy prototypes
+function _getPath() {
+    return this._dbusPath;
+}
+
+// stub we insert in all proxy prototypes
+function _getBus() {
+    return this._dbusBus;
+}
+
+// stub we insert in all proxy prototypes
+function _getInterface() {
+    return this._dbusInterface;
+}
+
+function _invokeSignalWatchCallback(callback, emitter, argsFromDBus) {
+    let argArray = [emitter];
+    let length = argsFromDBus.length;
+    for (let i = 0; i < length; ++i) {
+        argArray.push(argsFromDBus[i]);
+    }
+    callback.apply(null, argArray);
+}
+
+function _connect(signalName, callback) {
+    if (!this._signalNames || !(signalName in this._signalNames))
+        throw new Error("Signal " + signalName + " not defined in this object");
+
+    let me = this;
+    return this._dbusBus.watch_signal(this._dbusBusName,
+                                      this._dbusPath,
+                                      this._dbusInterface,
+                                      signalName,
+                                      function() {
+                                          _invokeSignalWatchCallback(callback,
+                                                                     me,
+                                                                     arguments);
+                                      });
+}
+
+function _disconnectByID(ID) {
+    this._dbusBus.unwatch_signal_by_id(ID);
+}
+
+function _getRemote(propName) {
+    // convert arguments to regular array
+    let argArray = [].splice.call(arguments, 0);
+    // prepend iface the property is on
+    argArray.splice(0, 0, this._dbusInterface);
+
+    _proxyInvoker(this, "org.freedesktop.DBus.Properties",
+                  "Get",
+                  "v",
+                  "ss",
+                  -1,
+                  argArray);
+}
+
+function _setRemote(propName, value) {
+    // convert arguments to regular array
+    let argArray = [].splice.call(arguments, 0);
+    // prepend iface the property is on
+    argArray.splice(0, 0, this._dbusInterface);
+
+    _proxyInvoker(this, "org.freedesktop.DBus.Properties",
+                  "Set",
+                  "",
+                  "ssv",
+                  -1,
+                  argArray);
+}
+
+function _getAllRemote() {
+    // convert arguments to regular array
+    let argArray = [].splice.call(arguments, 0);
+    // prepend iface the properties are on
+    argArray.splice(0, 0, this._dbusInterface);
+
+    _proxyInvoker(this, "org.freedesktop.DBus.Properties",
+                  "GetAll",
+                  "a{sv}",
+                  "s",
+                  -1,
+                 argArray);
+}
+
+// adds remote members to a prototype. The instances using
+// the prototype must be proxified with proxifyObject, too.
+// Note that the bus (system vs. session) is per-instance not
+// per-prototype so the bus should not be involved in this
+// function.
+// The "iface" arg is a special object that might have properties
+// for 'name', 'methods', and 'signals'
+function proxifyPrototype(proto, iface) {
+    if ('name' in iface)
+        proto._dbusInterface = iface.name;
+    else
+        proto._dbusInterface = null;
+
+    if ('methods' in iface) {
+        let methods = iface.methods;
+
+        for (let i = 0; i < methods.length; ++i) {
+            let method = methods[i];
+
+            if (!('name' in method))
+                throw new Error("Method definition must have a name");
+
+            /* To ease transition let's create two methods: Foo and FooRemote.
+             * Foo will just log a warning so we can catch people using the
+             * old naming system. FooRemote is the actual proxy method.
+             */
+            let methodName = method.name + "Remote";
+            log('conforming method: ' + methodName + ' for ' + iface.name);
+            proto[methodName] = _makeProxyMethod(method);
+            proto[method.name] = function() {
+                log("PROXY-ERROR: " + method.name + " called, you should be using " +
+                    methodName + " instead");
+            };
+        }
+    }
+
+    if ('signals' in iface) {
+        let signals = iface.signals;
+        let signalNames = {};
+
+        for (let i = 0; i < signals.length; i++) {
+            let signal = signals[i];
+
+            if (!('name' in signal))
+                throw new Error("Signal definition must have a name");
+
+            let name = signal.name;
+
+            /* This is a bit silly, but we might want to add more information
+             * later and it still beats an Array in checking if the object has
+             * a given signal
+             */
+            signalNames[name] = name;
+        }
+
+        proto['_signalNames'] = signalNames;
+        proto['connect'] = _connect;
+        proto['disconnect'] = _disconnectByID;
+    }
+
+    // properties just adds GetRemote, SetRemote, and GetAllRemote
+    // methods; using attributes would force synchronous API.
+    if ('properties' in iface &&
+        iface.properties.length > 0) {
+        proto['GetRemote'] = _getRemote;
+        proto['SetRemote'] = _setRemote;
+        proto['GetAllRemote'] = _getAllRemote;
+    }
+
+    proto['getBusName'] = _getBusName;
+    proto['getPath'] = _getPath;
+    proto['getBus'] = _getBus;
+    proto['getInterface'] = _getInterface;
+}
+
+// methods common to both session and system buses.
+// add them to the common prototype of this.session and this.system.
+
+// convert any object to a dbus proxy ... assumes its prototype is
+// also proxified
+this._busProto.proxifyObject = function(obj, busName, path) {
+    if (!busName) {
+        throw new Error('missing bus name proxifying object');
+    }
+    if (!path) {
+        throw new Error('missing path proxifying object');
+    }
+    if (path[0] != '/')
+        throw new Error("Path doesn't look like a path: " + path);
+
+    obj._dbusBus = this;
+    obj._dbusBusName = busName;
+    obj._dbusPath = path;
+};
+
+// add 'object' to the exports object at the given path
+let _addExports = function(node, path, object) {
+    if (path == '') {
+        if ('-impl-' in node)
+            throw new Error("Object already registered for path.");
+        node['-impl-'] = object;
+    } else {
+        let idx = path.indexOf('/');
+        let head = (idx >= 0) ? path.slice(0, idx) : path;
+        let tail = (idx >= 0) ? path.slice(idx+1) : '';
+        if (!(head in node))
+            node[head] = {};
+        _addExports(node[head], tail, object);
+    }
+};
+
+// remove any implementation from exports at the given path.
+let _removeExportsPath = function(node, path) {
+    if (path == '') {
+        delete node['-impl-'];
+    } else {
+        let idx = path.indexOf('/');
+        let head = (idx >= 0) ? path.slice(0, idx) : path;
+        let tail = (idx >= 0) ? path.slice(idx+1) : '';
+        // recursively delete next component
+        _removeExportsPath(node[head], tail);
+        // are we empty now?  if so, clean us up.
+        if ([x for (x in node[head])].length == 0) {
+            delete node[head];
+        }
+    }
+};
+
+
+// export the object at the specified object path
+this._busProto.exportObject = function(path, object) {
+    if (path.slice(0,1) != '/')
+        throw new Error("Bad path!  Must start with /");
+    // keep session and system paths separate, just in case we register on both
+    let pathProp = '_dbusPaths' + this._dbusBusType;
+    // optimize for the common one-path case by using a colon-delimited
+    // string rather than an array.
+    if (!(pathProp in object)) {
+        object[pathProp] = path;
+    } else {
+        object[pathProp] += ':' + path;
+    }
+    _addExports(this.exports, path.slice(1), object);
+};
+
+// unregister this object from all its paths
+this._busProto.unexportObject = function(object) {
+    // keep session and system paths separate, just in case we register on both
+    let pathProp = '_dbusPaths' + this._dbusBusType;
+    if (!(pathProp in object))
+        return; // already or never registered.
+    let dbusPaths = object[pathProp].split(':');
+    for (let i = 0; i < dbusPaths.length; ++i) {
+        let path = dbusPaths[i];
+        _removeExportsPath(this.exports, path.slice(1));
+    }
+    delete object[pathProp];
+};
+
+
+// given a "this" with _dbusInterfaces, return a dict from property
+// names to property signatures. Used as value of _dbus_signatures
+// when passing around a dict of properties.
+function _getPropertySignatures(ifaceName) {
+    let iface = this._dbusInterfaces[ifaceName];
+    if (!iface)
+        throw new Error("Object has no interface " + ifaceName);
+    if (!('_dbusPropertySignaturesCache' in iface)) {
+        let signatures = {};
+        if ('properties' in iface) {
+            let properties = iface.properties;
+            for (let i = 0; i < properties.length; ++i) {
+                signatures[properties[i].name] =
+                    properties[i].signature;
+            }
+        }
+        iface._dbusPropertySignaturesCache = signatures;
+    }
+    return iface._dbusPropertySignaturesCache;
+}
+
+// split a "single complete type" (SCT) from the rest of a dbus signature.
+// Returns the tail, and the head is added to acc.
+function _eatSCT(acc, s) {
+    // eat the first character
+    let c = s.charAt(0);
+    s = s.slice(1);
+    acc.push(c);
+    // if this is a compound type, loop eating until we reach the close paren
+    if (c=='(') {
+        while (s.charAt(0) != ')') {
+            s = _eatSCT(acc, s);
+        }
+        s = _eatSCT(acc, s); // the close paren
+    } else if (c=='{') {
+        s = _eatSCT(acc, s);
+        s = _eatSCT(acc, s);
+        if (s.charAt(0) != '}')
+            throw new Error("Bad DICT_ENTRY signature!");
+        s = _eatSCT(acc, s); // the close brace
+    } else if (c=='a') {
+        s = _eatSCT(acc, s); // element type
+    }
+    return s;
+}
+
+// parse signature string, generating a list of "single complete types"
+function _parseDBusSigs(sig) {
+    while (sig.length > 0) {
+        let one = [];
+        sig = _eatSCT(one, sig);
+        yield one.join('');
+    }
+}
+
+// given a "this" with _dbusInterfaces, returns DBus Introspection
+// format XML giving interfaces and methods implemented.
+function _getInterfaceXML() {
+    let result = '';
+    // iterate through defined interfaces
+    let ifaces = ('_dbusInterfaces' in this) ? this._dbusInterfaces : {};
+    ifaces = [i for each (i in ifaces)]; // convert to array
+    // add introspectable and properties
+    ifaces.push(Introspectable);
+    ifaces.push(Properties);
+
+    for (let i = 0; i < ifaces.length; i++) {
+        let iface = ifaces[i];
+        result += '  <interface name="' + iface.name + '">\n';
+        // describe methods.
+        let methods = ('methods' in iface) ? iface.methods : [];
+        for (let j = 0; j < methods.length; j++) {
+            let method = methods[j];
+            result += '    <method name="' + method.name + method.name + '">\n';
+            for each (let sig in _parseDBusSigs(method.inSignature)) {
+                result += '      <arg type="' + sig + '" direction="in"/>\n';
+            }
+            for each (let sig in _parseDBusSigs(method.outSignature)) {
+                result += '      <arg type="' + sig + '" direction="out"/>\n';
+            }
+            result += '    </method>\n';
+        }
+        // describe signals
+        let signals = ('signals' in iface) ? iface.signals : [];
+        for (let j = 0; j < signals.length; j++) {
+            let signal = signals[j];
+            result += '    <signal name="' + signal.name + '">\n';
+            for each (let sig in _parseDBusSigs(signal.inSignature)) {
+                result += '      <arg type="' + sig + '%s"/>\n';
+            }
+            result += '    </signal>\n';
+        }
+        // describe properties
+        let properties = ('properties' in iface) ? iface.properties : [];
+        for (let j = 0; j < properties.length; j++) {
+            let property = properties[j];
+            result += '    <property name="' + property.name + '" type="'
+                      + property.signature + '" access="'
+                      + property.accesss + '"/>\n';
+        }
+        // close <interface> tag
+        result += '  </interface>\n';
+    }
+    return result;
+}
+
+// Verifies that a given object conforms to a given interface,
+// and stores meta-info from the interface in the object as
+// required.
+// Note that an object can be exported on multiple buses, so conformExport()
+// should not be specific to the bus (session vs. system)
+function conformExport(object, iface) {
+
+    if (!('_dbusInterfaces' in object)) {
+        object._dbusInterfaces = {};
+    } else if (iface.name in object._dbusInterfaces) {
+        return;
+    }
+
+    object._dbusInterfaces[iface.name] = iface;
+
+    object.getDBusPropertySignatures =
+        _getPropertySignatures;
+
+    if (!('getDBusInterfaceXML' in object))
+        object.getDBusInterfaceXML = _getInterfaceXML;
+
+    if ('methods' in iface) {
+        let methods = iface.methods;
+
+        for (let i = 0; i < methods.length; ++i) {
+            let method = methods[i];
+
+            if (!('name' in method))
+                throw new Error("Method definition must have a name");
+
+            if (!('outSignature' in method))
+                method.outSignature = "a{sv}";
+
+            let name = method.name;
+            let asyncName = name +"Async";
+
+            let missing = [];
+            if (!(name in object) &&
+                !(asyncName in object)) {
+                missing.push(name);
+            } else {
+                if (asyncName in object)
+                    object[asyncName].outSignature = method.outSignature;
+                else
+                    object[name].outSignature = method.outSignature;
+            }
+
+            if (missing.length > 0) {
+                throw new Error("Exported object missing members " + missing.toSource());
+            }
+        }
+    }
+
+    if ('properties' in iface) {
+        let properties = iface.properties;
+
+        let missing = [];
+        for (let i = 0; i < properties.length; ++i) {
+            if (!(properties[i].name in object)) {
+                missing += properties[i];
+            }
+        }
+
+        if (missing.length > 0) {
+            throw new Error("Exported object missing properties " + missing.toSource());
+        }
+    }
+
+    // sanity-check signals
+    if ('signals' in iface) {
+        let signals = iface.signals;
+
+        for (let i = 0; i < signals.length; ++i) {
+            let signal = signals[i];
+            if (signal.name.indexOf('-') >= 0)
+                throw new Error("dbus signals cannot have a hyphen in them, use camelCase");
+        }
+    }
+}
+
+// DBusError object
+// Used to return dbus error with given name and mesage from
+// remote methods
+
+function DBusError(dbusErrorName, errorMessage) {
+    this.dbusErrorName = dbusErrorName;
+    this.errorMessage = errorMessage;
+}
+
+DBusError.prototype = {
+    toString: function() {
+        return this.errorMessage;
+    }
+};
+
+// Introspectable proxy
+function IntrospectableProxy(bus, busName, path) {
+    this._init(bus, busName, path);
+}
+
+IntrospectableProxy.prototype = {
+    _init : function(bus, busName, path) {
+        bus.proxifyObject(this, busName, path);
+    }
+
+};
+
+proxifyPrototype(IntrospectableProxy.prototype,
+                 Introspectable);
diff --git a/test/js/testDbus.js b/test/js/testDbus.js
new file mode 100644
index 0000000..2ea5cf8
--- /dev/null
+++ b/test/js/testDbus.js
@@ -0,0 +1,957 @@
+const DBus = imports.dbus;
+const Mainloop = imports.mainloop;
+
+/* Malarky is the proxy object */
+function Malarky() {
+    this._init();
+}
+
+Malarky.prototype = {
+    _init: function() {
+        DBus.session.proxifyObject(this, 'com.litl.Real', '/com/litl/Real');
+    }
+};
+
+/* The methods list with their signatures. We test both notations for json
+ * methods (only name or explicit signatures) */
+var realIface = {
+    name: 'com.litl.Real',
+    methods: [{ name: 'nonJsonFrobateStuff',
+                outSignature: 's', inSignature: 'i' },
+              { name: 'frobateStuff' },
+              { name: 'alwaysThrowException',
+                outSignature: 'a{sv}', inSignature: 'a{sv}' },
+              { name: 'thisDoesNotExist' },
+              { name: 'noInParameter',
+                outSignature: 's', inSignature: '' },
+              { name: 'multipleInArgs',
+                outSignature: 's', inSignature: 'iiiii' },
+              { name: 'noReturnValue',
+                outSignature: '', inSignature: '' },
+              { name: 'emitSignal',
+                outSignature: '', inSignature: '' },
+              { name: "multipleOutValues", outSignature: "sss",
+                inSignature: "" },
+              { name: "oneArrayOut", outSignature: "as",
+                inSignature: "" },
+              { name: "arrayOfArrayOut", outSignature: "aas",
+                inSignature: "" },
+              { name: "multipleArrayOut", outSignature: "asas",
+                inSignature: "" },
+              { name: "arrayOutBadSig", outSignature: "i",
+                inSignature: "" },
+              { name: "changingSig", outSignature: "",
+                inSignature: "b" },
+              { name: "byteArrayEcho", outSignature: "ay",
+                inSignature: "ay" },
+              { name: "byteEcho", outSignature: "y",
+                inSignature: "y" },
+              { name: "dictEcho", outSignature: "a{sv}",
+                inSignature: "a{sv}" },
+              { name: "echo", outSignature: "si",
+                inSignature: "si" }
+             ],
+    signals: [
+        { name: 'signalFoo', inSignature: 's' }
+    ],
+    properties: [
+        { name: 'PropReadOnly', signature: 'b', access: 'read' },
+        { name: 'PropWriteOnly', signature: 's', access: 'write' },
+        { name: 'PropReadWrite', signature: 'v', access: 'readwrite' }
+    ]
+};
+
+DBus.proxifyPrototype(Malarky.prototype,
+                      realIface);
+
+/* Real is the actual object exporting the dbus methods */
+function Real() {
+    this._init();
+}
+
+const PROP_READ_WRITE_INITIAL_VALUE = 58;
+const PROP_WRITE_ONLY_INITIAL_VALUE = "Initial value";
+
+Real.prototype = {
+    _init: function(){
+        this._propWriteOnly = PROP_WRITE_ONLY_INITIAL_VALUE;
+        this._propReadWrite = PROP_READ_WRITE_INITIAL_VALUE;
+    },
+
+    frobateStuff: function(args) {
+        return { hello: 'world' };
+    },
+
+    nonJsonFrobateStuff: function(i) {
+        if (i == 42) {
+            return "42 it is!";
+        } else {
+            return "Oops";
+        }
+    },
+
+    alwaysThrowException: function() {
+        throw "Exception!";
+    },
+
+    thisDoesNotExist: function () {
+        /* We'll remove this later! */
+    },
+
+    noInParameter: function() {
+        return "Yes!";
+    },
+
+    multipleInArgs: function(a, b, c, d, e) {
+        return a + " " + b + " " + c + " " + d + " " + e;
+    },
+
+    emitSignal: function() {
+        DBus.session.emit_signal('/com/litl/Real', 'com.litl.Real', 'signalFoo', 's', [ "foobar" ]);
+    },
+
+    noReturnValue: function() {
+        /* Empty! */
+    },
+
+    /* The following two functions have identical return values
+     * in JS, but the bus message will be different.
+     * multipleOutValues is "sss", while oneArrayOut is "as"
+     */
+    multipleOutValues: function() {
+        return [ "Hello", "World", "!" ];
+    },
+
+    oneArrayOut: function() {
+        return [ "Hello", "World", "!" ];
+    },
+
+    /* Same thing again. In this case multipleArrayOut is "asas",
+     * while arrayOfArrayOut is "aas".
+     */
+    multipleArrayOut: function() {
+        return [[ "Hello", "World" ], [ "World", "Hello" ]];
+    },
+
+    arrayOfArrayOut: function() {
+        return [[ "Hello", "World" ], [ "World", "Hello" ]];
+    },
+
+    arrayOutBadSig: function() {
+        return [ "Hello", "World", "!" ];
+    },
+
+    /* This is kinda scary, but we do it in getKeyForNetwork.
+     * Basically you can change the outSignature at runtime
+     * to have variable return value (in number and type) methods.
+     */
+    changingSig: function(b) {
+        if (b == true) {
+            arguments.callee.outSignature = "s";
+            return "CHANGE";
+        } else {
+            arguments.callee.outSignature = "i";
+            return 42;
+        }
+    },
+
+    byteArrayEcho: function(binaryString) {
+        return binaryString;
+    },
+
+    byteEcho: function(aByte) {
+        return aByte;
+    },
+
+    dictEcho: function(dict) {
+        return dict;
+    },
+
+    /* This one is implemented asynchronously. Returns
+     * the input arguments */
+    echoAsync: function(someString, someInt, callback) {
+        Mainloop.idle_add(function() {
+                              callback([someString, someInt]);
+                              return false;
+                          });
+    },
+
+    // boolean
+    get PropReadOnly() {
+        return true;
+    },
+
+    // string
+    set PropWriteOnly(value) {
+        this._propWriteOnly = value;
+    },
+
+    // variant
+    get PropReadWrite() {
+        return this._propReadWrite;
+    },
+
+    set PropReadWrite(value) {
+        this._propReadWrite = value;
+    }
+};
+
+DBus.conformExport(Real.prototype, realIface);
+DBus.session.exportObject('/com/litl/Real', new Real());
+DBus.session.acquire_name('com.litl.Real', DBus.SINGLE_INSTANCE,
+                          function(name){log("Acquired name " + name);},
+                          function(name){log("Lost name  " + name);});
+
+function testFrobateStuff() {
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.frobateStuffRemote({},
+                                                   function(result, excp) {
+                                                       theResult = result;
+                                                       theExcp = excp;
+                                                       Mainloop.quit('testDbus');
+                                                   });
+                      });
+
+    Mainloop.run('testDbus');
+
+    log(theResult.toSource());
+    assertEquals("world", theResult.hello);
+}
+
+/* excp must be exactly the exception thrown by the remote method */
+function testThrowException() {
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.alwaysThrowExceptionRemote({},
+                                                           function(result, excp) {
+                                                               theResult = result;
+                                                               theExcp = excp;
+                                                               Mainloop.quit('testDbus');
+                                                           });
+                      });
+
+    Mainloop.run('testDbus');
+
+    assertNotNull(theExcp);
+}
+
+/* We check that the exception in the answer is not null when we try to call
+ * a method that does not exist */
+function testDoesNotExist() {
+    let theResult, theExcp;
+
+    /* First remove the method from the object! */
+    delete Real.prototype.thisDoesNotExist;
+
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.thisDoesNotExistRemote({},
+                                                       function(result, excp) {
+                                                           theResult = result;
+                                                           theExcp = excp;
+                                                           Mainloop.quit('testDbus');
+                                                       });
+                      });
+
+    Mainloop.run('testDbus');
+
+    assertNotNull(theExcp);
+    assertNull(theResult);
+}
+
+function testNonJsonFrobateStuff() {
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.nonJsonFrobateStuffRemote(42,
+                                                          function(result, excp) {
+                                                              theResult = result;
+                                                              theExcp = excp;
+                                                              Mainloop.quit('testDbus');
+                                                          });
+                      });
+
+    Mainloop.run('testDbus');
+
+    assertEquals("42 it is!", theResult);
+    assertNull(theExcp);
+}
+
+function testNoInParameter() {
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.noInParameterRemote(function(result, excp) {
+                                                        theResult = result;
+                                                        theExcp = excp;
+                                                        Mainloop.quit('testDbus');
+                                                    });
+                      });
+
+    Mainloop.run('testDbus');
+
+    assertEquals("Yes!", theResult);
+    assertNull(theExcp);
+}
+
+function testMultipleInArgs() {
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.multipleInArgsRemote(1,2,3,4,5,
+                                                     function(result, excp) {
+                                                         theResult = result;
+                                                         theExcp = excp;
+                                                         Mainloop.quit('testDbus');
+                                                     });
+                      });
+
+    Mainloop.run('testDbus');
+
+    assertEquals("1 2 3 4 5", theResult);
+    assertNull(theExcp);
+}
+
+function testNoReturnValue() {
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.noReturnValueRemote(function(result, excp) {
+                                                        theResult = result;
+                                                        theExcp = excp;
+                                                        Mainloop.quit('testDbus');
+                                                    });
+                      });
+
+    Mainloop.run('testDbus');
+
+    assertEquals(undefined, theResult);
+    assertNull(theExcp);
+}
+
+function testEmitSignal() {
+    let theResult, theExcp;
+    let signalReceived = 0;
+    let signalArgument = null;
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          let id = proxy.connect('signalFoo',
+                            function(emitter, someString) {
+                              signalReceived ++;
+                              signalArgument = someString;
+
+                              proxy.disconnect(id);
+                          });
+                          proxy.emitSignalRemote(function(result, excp) {
+                                                     theResult = result;
+                                                     theExcp = excp;
+                                                     if (excp)
+                                                         log("Signal emission exception: " + excp);
+                                                     Mainloop.quit('testDbus');
+                                                 });
+                          });
+
+    Mainloop.run('testDbus');
+
+    assertUndefined('result should be undefined', theResult);
+    assertNull('no exception set', theExcp);
+    assertEquals('number of signals received', signalReceived, 1);
+    assertEquals('signal argument', signalArgument, "foobar");
+
+}
+
+function testMultipleOutValues() {
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.multipleOutValuesRemote(function(result, excp) {
+                                                            theResult = result;
+                                                            theExcp = excp;
+                                                            Mainloop.quit('testDbus');
+                                                        });
+                      });
+
+    Mainloop.run('testDbus');
+
+    assertEquals("Hello", theResult[0]);
+    assertEquals("World", theResult[1]);
+    assertEquals("!", theResult[2]);
+    assertNull(theExcp);
+}
+
+function testOneArrayOut() {
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.oneArrayOutRemote(function(result, excp) {
+                                                      theResult = result;
+                                                      theExcp = excp;
+                                                      Mainloop.quit('testDbus');
+                                                  });
+                      });
+
+    Mainloop.run('testDbus');
+
+    assertEquals("Hello", theResult[0]);
+    assertEquals("World", theResult[1]);
+    assertEquals("!", theResult[2]);
+    assertNull(theExcp);
+}
+
+function testArrayOfArrayOut() {
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.arrayOfArrayOutRemote(function(result, excp) {
+                                                          theResult = result;
+                                                          theExcp = excp;
+                                                          Mainloop.quit('testDbus');
+                                                      });
+                      });
+
+    Mainloop.run('testDbus');
+
+    let a1 = theResult[0];
+    let a2 = theResult[1];
+
+    assertEquals("Hello", a1[0]);
+    assertEquals("World", a1[1]);
+
+    assertEquals("World", a2[0]);
+    assertEquals("Hello", a2[1]);;
+
+    assertNull(theExcp);
+}
+
+function testMultipleArrayOut() {
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.multipleArrayOutRemote(function(result, excp) {
+                                                           theResult = result;
+                                                           theExcp = excp;
+                                                           Mainloop.quit('testDbus');
+                                                       });
+                          });
+
+    Mainloop.run('testDbus');
+
+    let a1 = theResult[0];
+    let a2 = theResult[1];
+
+    assertEquals("Hello", a1[0]);
+    assertEquals("World", a1[1]);
+
+    assertEquals("World", a2[0]);
+    assertEquals("Hello", a2[1]);;
+
+    assertNull(theExcp);
+}
+
+/* We are returning an array but the signature says it's an integer,
+ * so this should fail
+ */
+function testArrayOutBadSig() {
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.arrayOutBadSigRemote(function(result, excp) {
+                                                         theResult = result;
+                                                         theExcp = excp;
+                                                         Mainloop.quit('testDbus');
+                                                     });
+                      });
+
+    Mainloop.run('testDbus');
+    assertNull(theResult);
+    assertNotNull(theExcp);
+}
+
+function testSignatureLength() {
+    const sigsAndLengths = [
+        { sig: "", length: 0 },
+        { sig: "i", length: 1 },
+        { sig: "iii", length: 3 },
+        { sig: "ai", length: 1 },
+        { sig: "a{sv}", length: 1 },
+        { sig: "aiaia{sv}", length: 3 },
+        { sig: "(ai)as((ai)(a{sv}))", length: 3 },
+        { sig: "iiisssiiiaiaiai(aiai)svvviavi", length: 20 }
+    ];
+
+    for (let i = 0; i < sigsAndLengths.length; i++) {
+        let o = sigsAndLengths[i];
+        let sigLen = DBus.signatureLength(o.sig);
+        assertEquals(o.sig + ': ' + o.length + ' == ' + sigLen,
+                     o.length, sigLen);
+    }
+
+    const invalidSig = "a";
+    assertRaises('invalid signature', function () { DBus.signatureLength(invalidSig); });
+
+    assertRaises('trying to measure length of an int', function () { DBus.signatureLength(5); });
+    assertRaises('trying to measure length of undefined', function () { DBus.signatureLength(); });
+}
+
+function testChangingSig() {
+    let theResultA, theExcpA;
+    let theResultB, theExcpB;
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.changingSigRemote(true,
+                                                  function(result, excp) {
+                                                      theResultA = result;
+                                                      theExcpA = excp;
+                                                  });
+                      });
+
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.changingSigRemote(false,
+                                                  function(result, excp) {
+                                                      theResultB = result;
+                                                      theExcpB = excp;
+                                                      Mainloop.quit('testDbus');
+                                                  });
+                      });
+
+    Mainloop.run('testDbus');
+    assertEquals("CHANGE", theResultA);
+    assertNull(theExcpA);
+    assertEquals(42, theResultB);
+    assertNull(theExcpB);
+}
+
+function testAsyncImplementation() {
+    let someString = "Hello world!";
+    let someInt = 42;
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.echoRemote(someString, someInt,
+                                           function(result, excp) {
+                                               theResult = result;
+                                               theExcp = excp;
+                                               Mainloop.quit('testDbus');
+                                           });
+                      });
+
+    Mainloop.run('testDbus');
+    assertNull(theExcp);
+    assertNotNull(theResult);
+    assertEquals(theResult[0], someString);
+    assertEquals(theResult[1], someInt);
+}
+
+function testLocalMachineID() {
+    let machineID = DBus.localMachineID;
+
+    assertNotUndefined(machineID);
+    assertNotNull(machineID);
+}
+
+function testGetReadOnlyProperty() {
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.GetRemote("PropReadOnly",
+                                          function(result, excp) {
+                                              theResult = result;
+                                              theExcp = excp;
+                                              Mainloop.quit('testDbus');
+                                          });
+                      });
+
+    Mainloop.run('testDbus');
+    assertNull(theExcp);
+    assertNotNull(theResult);
+    assertEquals(true, theResult);
+}
+
+function testSetReadOnlyPropertyFails() {
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.SetRemote("PropReadOnly", "foo bar",
+                                          function(result, excp) {
+                                              theResult = result;
+                                              theExcp = excp;
+                                              Mainloop.quit('testDbus');
+                                          });
+                      });
+
+    Mainloop.run('testDbus');
+    assertNotNull(theExcp);
+    assertTrue(theExcp.message.indexOf('not writable') >= 0);
+    assertNull(theResult);
+}
+
+function testReadWriteProperty() {
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.GetRemote("PropReadWrite",
+                                          function(result, excp) {
+                                              theResult = result;
+                                              theExcp = excp;
+                                              Mainloop.quit('testDbus');
+                                          });
+                      });
+
+    Mainloop.run('testDbus');
+
+    assertEquals(PROP_READ_WRITE_INITIAL_VALUE,
+                 DBus.session.exports.com.litl.Real['-impl-']._propReadWrite);
+
+    assertNull(theExcp);
+    assertNotNull(theResult);
+    assertEquals(PROP_READ_WRITE_INITIAL_VALUE, theResult);
+
+    theResult = null;
+    theExcp = null;
+
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.SetRemote("PropReadWrite", 371,
+                                          function(result, excp) {
+                                              theResult = result;
+                                              theExcp = excp;
+                                              Mainloop.quit('testDbus');
+                                          });
+                      });
+
+    Mainloop.run('testDbus');
+    assertNull(theExcp);
+    assertUndefined(theResult);
+
+    theResult = null;
+    theExcp = null;
+
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.GetRemote("PropReadWrite",
+                                          function(result, excp) {
+                                              theResult = result;
+                                              theExcp = excp;
+                                              Mainloop.quit('testDbus');
+                                          });
+                      });
+
+    Mainloop.run('testDbus');
+
+    assertEquals(371, DBus.session.exports.com.litl.Real['-impl-']._propReadWrite);
+
+    assertNull(theExcp);
+    assertNotNull(theResult);
+    assertEquals(371, theResult);
+}
+
+function testWriteOnlyProperty() {
+    let theResult, theExcp;
+
+    assertEquals(PROP_WRITE_ONLY_INITIAL_VALUE,
+                 DBus.session.exports.com.litl.Real['-impl-']._propWriteOnly);
+
+
+    theResult = null;
+    theExcp = null;
+
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.SetRemote("PropWriteOnly", "a changed value",
+                                          function(result, excp) {
+                                              theResult = result;
+                                              theExcp = excp;
+                                              Mainloop.quit('testDbus');
+                                          });
+                      });
+
+    Mainloop.run('testDbus');
+
+    assertNull(theExcp);
+    assertUndefined(theResult);
+
+    assertEquals("a changed value",
+                 DBus.session.exports.com.litl.Real['-impl-']._propWriteOnly);
+
+    // Test that it's not writable
+
+    theResult = null;
+    theExcp = null;
+
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.GetRemote("PropWriteOnly",
+                                          function(result, excp) {
+                                              theResult = result;
+                                              theExcp = excp;
+                                              Mainloop.quit('testDbus');
+                                          });
+                      });
+
+    Mainloop.run('testDbus');
+    assertNotNull(theExcp);
+    assertNull(theResult);
+
+    assertTrue(theExcp.message.indexOf('not readable') >= 0);
+}
+
+// at the moment, the test asserts that GetAll is not implemented,
+// of course we'd make this test that it works once it is implemented
+function testGetAllProperties() {
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.GetAllRemote(function(result, excp) {
+                                                 theResult = result;
+                                                 theExcp = excp;
+                                                 Mainloop.quit('testDbus');
+                                             });
+                      });
+
+    Mainloop.run('testDbus');
+    assertNull(theExcp);
+    assertNotNull(theResult);
+    assertTrue('PropReadOnly' in theResult);
+    assertTrue('PropReadWrite' in theResult);
+    assertTrue('_dbus_sender' in theResult);
+    assertFalse('PropWriteOnly' in theResult);
+
+    assertEquals(true,
+                 theResult.PropReadOnly);
+    assertEquals(DBus.session.exports.com.litl.Real['-impl-']._propReadWrite,
+                 theResult.PropReadWrite);
+
+    let count = 0;
+    for (let p in theResult) {
+        count += 1;
+    }
+    // two props plus _dbus_sender
+    // (I think including _dbus_sender is an API bug in our dbus
+    // stuff, but for now some code may rely on it)
+    assertEquals(3, count);
+}
+
+function testByteArrays() {
+    let someString = "Hello\0world!\0\0\1\2\3";
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.byteArrayEchoRemote(someString,
+                                                    function(result, excp) {
+                                                        theResult = result;
+                                                        theExcp = excp;
+                                                        Mainloop.quit('testDbus');
+                                                    });
+                      });
+
+    Mainloop.run('testDbus');
+    assertNull(theExcp);
+    assertNotNull(theResult);
+    assertEquals(someString, theResult);
+}
+
+function testBytes() {
+    let someBytes = [ 0, 63, 234 ];
+    let theResult, theExcp;
+    for (let i = 0; i < someBytes.length; ++i) {
+        theResult = null;
+        theExcp = null;
+        Mainloop.idle_add(function() {
+                              let proxy = new Malarky();
+                              proxy.byteEchoRemote(someBytes[i],
+                                                   function(result, excp) {
+                                                       theResult = result;
+                                                       theExcp = excp;
+                                                       Mainloop.quit('testDbus');
+                                                   });
+                          });
+
+        Mainloop.run('testDbus');
+        assertNull(theExcp);
+        assertNotNull(theResult);
+        assertEquals(someBytes[i], theResult);
+    }
+}
+
+function testDictSignatures() {
+    let someDict = {
+        // should be a double after round trip except
+        // JS_NewNumberValue() will convert it back to an int anyway
+        // if the fractional part is 0
+        aDouble: 10,
+        // should be an integer after round trip
+        anInteger: 10.5,
+        // should remain a double
+        aDoubleBeforeAndAfter: 10.5,
+        // _dbus_signatures forces the wire type
+        _dbus_signatures: {
+            aDouble: 'd',
+            anInteger: 'i',
+            aDoubleBeforeAndAfter: 'd'
+        }
+    };
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new Malarky();
+                          proxy.dictEchoRemote(someDict,
+                                               function(result, excp) {
+                                                   theResult = result;
+                                                   theExcp = excp;
+                                                   Mainloop.quit('testDbus');
+                                               });
+                      });
+
+    Mainloop.run('testDbus');
+    assertNull(theExcp);
+    assertNotNull(theResult);
+
+    // assert we did not send _dbus_signatures over the wire
+    assertFalse('_dbus_signatures' in theResult);
+
+    // verify the fractional part was dropped off int
+    assertEquals(10, theResult['anInteger']);
+
+    // and not dropped off a double
+    assertEquals(10.5, theResult['aDoubleBeforeAndAfter']);
+
+    // this assertion is useless, it will work
+    // anyway if the result is really an int,
+    // but it at least checks we didn't lose data
+    assertEquals(10.0, theResult['aDouble']);
+}
+
+function testGetPropertySignatures() {
+    let real = DBus.session.exports.com.litl.Real['-impl-'];
+    let signatures = real.getDBusPropertySignatures(realIface.name);
+    assertTrue('PropReadOnly' in signatures);
+    assertTrue('PropWriteOnly' in signatures);
+    assertTrue('PropReadWrite' in signatures);
+    assertEquals('b', signatures.PropReadOnly);
+    assertEquals('s', signatures.PropWriteOnly);
+    assertEquals('v', signatures.PropReadWrite);
+
+    // run again because getDBusPropertySignatures caches
+    // the signatures, so be sure the caching doesn't
+    // break things somehow
+    signatures = real.getDBusPropertySignatures(realIface.name);
+    assertTrue('PropReadOnly' in signatures);
+    assertTrue('PropWriteOnly' in signatures);
+    assertTrue('PropReadWrite' in signatures);
+    assertEquals('b', signatures.PropReadOnly);
+    assertEquals('s', signatures.PropWriteOnly);
+    assertEquals('v', signatures.PropReadWrite);
+}
+
+function testIntrospectRoot() {
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new DBus.IntrospectableProxy(DBus.session,
+                                                                   "com.litl.Real",
+                                                                   "/");
+                          proxy.IntrospectRemote(function(result, excp) {
+                                                     theResult = result;
+                                                     theExcp = excp;
+                                                     Mainloop.quit('testDbus');
+                                                 });
+                      });
+
+    Mainloop.run('testDbus');
+
+    // JavaScript XML object apparently can't handle a DOCTYPE
+    // declaration because it wants an element not a document,
+    // so strip the doctype decl off
+    let xml = new XML(theResult.replace(/<!DOCTYPE[^>]+>/, ''));
+    assertEquals(1, xml.node.length());
+    assertEquals("com", xml node[0]  name toString());
+    assertEquals(0, xml.interface.length());
+}
+
+function testIntrospectCom() {
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new DBus.IntrospectableProxy(DBus.session,
+                                                                   "com.litl.Real",
+                                                                   "/com");
+                          proxy.IntrospectRemote(function(result, excp) {
+                                                     theResult = result;
+                                                     theExcp = excp;
+                                                     Mainloop.quit('testDbus');
+                                                 });
+                      });
+
+    Mainloop.run('testDbus');
+
+    // JavaScript XML object apparently can't handle a DOCTYPE
+    // declaration because it wants an element not a document,
+    // so strip the doctype decl off
+    let xml = new XML(theResult.replace(/<!DOCTYPE[^>]+>/, ''));
+    assertEquals(1, xml.node.length());
+    assertEquals("litl", xml node[0]  name toString());
+    assertEquals(0, xml.interface.length());
+}
+
+function testIntrospectLitl() {
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new DBus.IntrospectableProxy(DBus.session,
+                                                                   "com.litl.Real",
+                                                                   "/com/litl");
+                          proxy.IntrospectRemote(function(result, excp) {
+                                                     theResult = result;
+                                                     theExcp = excp;
+                                                     Mainloop.quit('testDbus');
+                                                 });
+                      });
+
+    Mainloop.run('testDbus');
+
+    // JavaScript XML object apparently can't handle a DOCTYPE
+    // declaration because it wants an element not a document,
+    // so strip the doctype decl off
+    let xml = new XML(theResult.replace(/<!DOCTYPE[^>]+>/, ''));
+    assertEquals(1, xml.node.length());
+    assertEquals("Real", xml node[0]  name toString());
+    assertEquals(0, xml.interface.length());
+}
+
+function testIntrospectReal() {
+    let theResult, theExcp;
+    Mainloop.idle_add(function() {
+                          let proxy = new DBus.IntrospectableProxy(DBus.session,
+                                                                   "com.litl.Real",
+                                                                   "/com/litl/Real");
+                          proxy.IntrospectRemote(function(result, excp) {
+                                                     theResult = result;
+                                                     theExcp = excp;
+                                                     Mainloop.quit('testDbus');
+                                                 });
+                      });
+
+    Mainloop.run('testDbus');
+
+    // JavaScript XML object apparently can't handle a DOCTYPE
+    // declaration because it wants an element not a document,
+    // so strip the doctype decl off
+    let xml = new XML(theResult.replace(/<!DOCTYPE[^>]+>/, ''));
+    assertEquals(0, xml.node.length());
+    // we get the 'realIface', plus 'Introspectable' and 'Properties'
+    assertEquals(3, xml.interface.length());
+    assertEquals('com.litl.Real', xml interface[0]  name toString());
+    assertEquals(18, xml.interface[0].method.length());
+    assertEquals(3, xml.interface[0].property.length());
+    assertEquals(1, xml.interface[0].signal.length());
+    assertEquals('org.freedesktop.DBus.Introspectable', xml interface[1]  name toString());
+    assertEquals(1, xml.interface[1].method.length());
+    assertEquals(0, xml.interface[1].property.length());
+    assertEquals(0, xml.interface[1].signal.length());
+    assertEquals('org.freedesktop.DBus.Properties', xml interface[2]  name toString());
+    assertEquals(3, xml.interface[2].method.length());
+    assertEquals(0, xml.interface[2].property.length());
+    assertEquals(0, xml.interface[2].signal.length());
+}
+
+gjstestRun();
diff --git a/util/log.c b/util/log.c
index 8b86083..7359c6a 100644
--- a/util/log.c
+++ b/util/log.c
@@ -223,6 +223,9 @@ gjs_debug(GjsDebugTopic topic,
     case GJS_DEBUG_DBUS:
         prefix = "JS DBUS";
         break;
+    case GJS_DEBUG_DBUS_MARSHAL:
+        prefix = "JS DBUS MARSHAL";
+        break;
     case GJS_DEBUG_KEEP_ALIVE:
         prefix = "JS KP ALV";
         break;
diff --git a/util/log.h b/util/log.h
index 36dfa3a..36700da 100644
--- a/util/log.h
+++ b/util/log.h
@@ -43,6 +43,7 @@ typedef enum {
     GJS_DEBUG_IMPORTER,
     GJS_DEBUG_NATIVE,
     GJS_DEBUG_DBUS,
+    GJS_DEBUG_DBUS_MARSHAL,
     GJS_DEBUG_KEEP_ALIVE,
     GJS_DEBUG_GREPO,
     GJS_DEBUG_GNAMESPACE,



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