[gjs] Bug 580948 - Add DBus support
- From: Colin Walters <walters src gnome org>
- To: svn-commits-list gnome org
- Subject: [gjs] Bug 580948 - Add DBus support
- Date: Wed, 27 May 2009 12:59:19 -0400 (EDT)
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, ¤t_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]