summaryrefslogtreecommitdiff
path: root/gi
diff options
context:
space:
mode:
authorLucas Almeida Rocha <lucasr@src.gnome.org>2008-10-10 21:37:39 +0000
committerLucas Almeida Rocha <lucasr@src.gnome.org>2008-10-10 21:37:39 +0000
commitc94cbf18983fa5cb47fcfd118b8ff94d71b52361 (patch)
treeaf4241562bc8fe99588ef932f1c820e560b98867 /gi
downloadgjs-c94cbf18983fa5cb47fcfd118b8ff94d71b52361.tar.gz
Initial import.
svn path=/trunk/; revision=2
Diffstat (limited to 'gi')
-rw-r--r--gi/arg.c996
-rw-r--r--gi/arg.h54
-rw-r--r--gi/boxed.c582
-rw-r--r--gi/boxed.h57
-rw-r--r--gi/closure.c287
-rw-r--r--gi/closure.h45
-rw-r--r--gi/enumeration.c161
-rw-r--r--gi/enumeration.h44
-rw-r--r--gi/function.c554
-rw-r--r--gi/function.h48
-rw-r--r--gi/keep-alive.c440
-rw-r--r--gi/keep-alive.h83
-rw-r--r--gi/native.c183
-rw-r--r--gi/native.h91
-rw-r--r--gi/ns.c321
-rw-r--r--gi/ns.h42
-rw-r--r--gi/object.c1469
-rw-r--r--gi/object.h57
-rw-r--r--gi/param.c416
-rw-r--r--gi/param.h47
-rw-r--r--gi/repo.c607
-rw-r--r--gi/repo.h60
-rw-r--r--gi/value.c512
-rw-r--r--gi/value.h45
24 files changed, 7201 insertions, 0 deletions
diff --git a/gi/arg.c b/gi/arg.c
new file mode 100644
index 00000000..8e3c26a5
--- /dev/null
+++ b/gi/arg.c
@@ -0,0 +1,996 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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 "arg.h"
+#include "object.h"
+#include "boxed.h"
+#include "value.h"
+#include <gjs/jsapi-util.h>
+
+#include <util/log.h>
+
+static JSBool gjs_value_to_g_arg_with_type_info(JSContext *context,
+ jsval value,
+ GITypeInfo *type_info,
+ const char *arg_name,
+ gboolean is_return_value,
+ gboolean may_be_null,
+ GArgument *arg);
+
+static const char*
+type_tag_name(GITypeTag tag)
+{
+ switch (tag) {
+ case GI_TYPE_TAG_VOID:
+ return "VOID";
+ case GI_TYPE_TAG_BOOLEAN:
+ return "BOOLEAN";
+ case GI_TYPE_TAG_INT8:
+ return "INT8";
+ case GI_TYPE_TAG_UINT8:
+ return "UINT8";
+ case GI_TYPE_TAG_INT16:
+ return "INT16";
+ case GI_TYPE_TAG_UINT16:
+ return "UINT16";
+ case GI_TYPE_TAG_INT32:
+ return "INT32";
+ case GI_TYPE_TAG_UINT32:
+ return "UINT32";
+ case GI_TYPE_TAG_INT64:
+ return "INT64";
+ case GI_TYPE_TAG_UINT64:
+ return "UINT64";
+ case GI_TYPE_TAG_INT:
+ return "INT";
+ case GI_TYPE_TAG_UINT:
+ return "UINT";
+ case GI_TYPE_TAG_LONG:
+ return "LONG";
+ case GI_TYPE_TAG_ULONG:
+ return "ULONG";
+ case GI_TYPE_TAG_SSIZE:
+ return "SSIZE";
+ case GI_TYPE_TAG_SIZE:
+ return "SIZE";
+ case GI_TYPE_TAG_FLOAT:
+ return "FLOAT";
+ case GI_TYPE_TAG_DOUBLE:
+ return "DOUBLE";
+ case GI_TYPE_TAG_UTF8:
+ return "UTF8";
+ case GI_TYPE_TAG_FILENAME:
+ return "FILENAME";
+ case GI_TYPE_TAG_ARRAY:
+ return "ARRAY";
+ case GI_TYPE_TAG_INTERFACE:
+ return "INTERFACE";
+ case GI_TYPE_TAG_GLIST:
+ return "GLIST";
+ case GI_TYPE_TAG_GSLIST:
+ return "GSLIST";
+ case GI_TYPE_TAG_GHASH:
+ return "GHASH";
+ case GI_TYPE_TAG_ERROR:
+ return "GERROR";
+ }
+
+ return "???";
+}
+
+JSBool
+_gjs_flags_value_is_valid(JSContext *context,
+ GFlagsClass *klass,
+ guint value)
+{
+ GFlagsValue *v;
+ guint32 tmpval;
+
+ /* check all bits are defined for flags.. not necessarily desired */
+ tmpval = value;
+ while (tmpval) {
+ v = g_flags_get_first_value(klass, tmpval);
+ if (!v) {
+ gjs_throw(context,
+ "0x%x is not a valid value for flags %s",
+ value, g_type_name(G_TYPE_FROM_CLASS(klass)));
+ return JS_FALSE;
+ }
+
+ tmpval &= ~v->value;
+ }
+
+ return JS_TRUE;
+}
+
+static JSBool
+gjs_array_to_list(JSContext *context,
+ jsval array_value,
+ unsigned int length,
+ GITypeInfo *param_info,
+ GITypeTag list_type,
+ GList **list_p,
+ GSList **slist_p)
+{
+ guint32 i;
+ GList *list;
+ GSList *slist;
+ jsval elem;
+ GITypeTag param_tag;
+
+ param_tag = g_type_info_get_tag(param_info);
+
+ list = NULL;
+ slist = NULL;
+
+ for (i = 0; i < length; ++i) {
+ GArgument elem_arg;
+
+ elem = JSVAL_VOID;
+ if (!JS_GetElement(context, JSVAL_TO_OBJECT(array_value),
+ i, &elem)) {
+ gjs_throw(context,
+ "Missing array element %u",
+ i);
+ return JS_FALSE;
+ }
+
+ /* FIXME we don't know if the list elements can be NULL.
+ * gobject-introspection needs to tell us this.
+ * Always say they can't for now.
+ */
+ if (!gjs_value_to_g_arg_with_type_info(context,
+ elem,
+ param_info,
+ "list element",
+ FALSE,
+ FALSE,
+ &elem_arg)) {
+ return JS_FALSE;
+ }
+
+ if (list_type == GI_TYPE_TAG_GLIST) {
+ /* GList */
+ list = g_list_prepend(list, elem_arg.v_pointer);
+ } else {
+ /* GSList */
+ slist = g_slist_prepend(slist, elem_arg.v_pointer);
+ }
+ }
+
+ list = g_list_reverse(list);
+ slist = g_slist_reverse(slist);
+
+ *list_p = list;
+ *slist_p = slist;
+
+ return JS_TRUE;
+}
+
+static JSBool
+gjs_value_to_g_arg_with_type_info(JSContext *context,
+ jsval value,
+ GITypeInfo *type_info,
+ const char *arg_name,
+ gboolean is_return_value,
+ gboolean may_be_null,
+ GArgument *arg)
+{
+ GITypeTag type_tag;
+ gboolean wrong;
+ gboolean report_type_mismatch;
+ gboolean nullable_type;
+
+ type_tag = g_type_info_get_tag( (GITypeInfo*) type_info);
+
+ gjs_debug_marshal(GJS_DEBUG_GFUNCTION,
+ "Converting jsval to GArgument %s",
+ type_tag_name(type_tag));
+
+ nullable_type = FALSE;
+ wrong = FALSE; /* return JS_FALSE */
+ report_type_mismatch = FALSE; /* wrong=TRUE, and still need to gjs_throw a type problem */
+
+ switch (type_tag) {
+ case GI_TYPE_TAG_VOID:
+ nullable_type = TRUE;
+ arg->v_pointer = NULL; /* just so it isn't uninitialized */
+ break;
+
+ case GI_TYPE_TAG_INT8: {
+ gint32 i;
+ if (!JS_ValueToInt32(context, value, &i))
+ wrong = TRUE;
+ if (i > G_MAXINT8 || i < G_MININT8)
+ wrong = TRUE;
+ arg->v_int8 = (gint8)i;
+ break;
+ }
+ case GI_TYPE_TAG_UINT8: {
+ guint16 i;
+ if (!JS_ValueToUint16(context, value, &i))
+ wrong = TRUE;
+ if (i > G_MAXUINT8)
+ wrong = TRUE;
+ arg->v_uint8 = (guint8)i;
+ break;
+ }
+ case GI_TYPE_TAG_INT16: {
+ gint32 i;
+ if (!JS_ValueToInt32(context, value, &i))
+ wrong = TRUE;
+ if (i > G_MAXINT16 || i < G_MININT16)
+ wrong = TRUE;
+ arg->v_int16 = (gint16)i;
+ break;
+ }
+
+ case GI_TYPE_TAG_UINT16:
+ if (!JS_ValueToUint16(context, value, &arg->v_uint16))
+ wrong = TRUE;
+ break;
+
+#if (GLIB_SIZEOF_LONG == 4)
+ case GI_TYPE_TAG_LONG:
+ case GI_TYPE_TAG_SSIZE:
+#endif
+ case GI_TYPE_TAG_INT:
+ case GI_TYPE_TAG_INT32:
+ if (!JS_ValueToInt32(context, value, &arg->v_int))
+ wrong = TRUE;
+ break;
+
+#if (GLIB_SIZEOF_LONG == 4)
+ case GI_TYPE_TAG_ULONG:
+ case GI_TYPE_TAG_SIZE:
+#endif
+ case GI_TYPE_TAG_UINT:
+ case GI_TYPE_TAG_UINT32:
+ if (!JS_ValueToECMAUint32(context, value, &arg->v_uint))
+ wrong = TRUE;
+ break;
+
+#if (GLIB_SIZEOF_LONG == 8)
+ case GI_TYPE_TAG_LONG:
+ case GI_TYPE_TAG_SSIZE:
+#endif
+ case GI_TYPE_TAG_INT64: {
+ double v;
+ if (!JS_ValueToNumber(context, value, &v))
+ wrong = TRUE;
+ arg->v_int64 = v;
+ }
+ break;
+
+#if (GLIB_SIZEOF_LONG == 8)
+ case GI_TYPE_TAG_ULONG:
+ case GI_TYPE_TAG_SIZE:
+#endif
+ case GI_TYPE_TAG_UINT64: {
+ double v;
+ if (!JS_ValueToNumber(context, value, &v))
+ wrong = TRUE;
+ arg->v_uint64 = v;
+ }
+ break;
+
+ case GI_TYPE_TAG_BOOLEAN:
+ if (!JS_ValueToBoolean(context, value, &arg->v_boolean))
+ wrong = TRUE;
+ break;
+
+ case GI_TYPE_TAG_DOUBLE:
+ if (!JS_ValueToNumber(context, value, &arg->v_double))
+ wrong = TRUE;
+ break;
+
+ case GI_TYPE_TAG_UTF8:
+ nullable_type = TRUE;
+ if (JSVAL_IS_NULL(value)) {
+ arg->v_pointer = NULL;
+ } else if (JSVAL_IS_STRING(value)) {
+ if (!gjs_string_to_utf8(context, value, (char **)&arg->v_pointer))
+ wrong = TRUE;
+ } else {
+ wrong = TRUE;
+ report_type_mismatch = TRUE;
+ }
+ break;
+
+ case GI_TYPE_TAG_INTERFACE:
+ nullable_type = TRUE;
+ {
+ GIBaseInfo* symbol_info;
+ GType gtype;
+
+ symbol_info = g_type_info_get_interface(type_info);
+ g_assert(symbol_info != NULL);
+
+ gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)symbol_info);
+
+ gjs_debug_marshal(GJS_DEBUG_GFUNCTION,
+ "gtype of SYMBOL is %s", g_type_name(gtype));
+
+ if (gtype == G_TYPE_VALUE) {
+ GValue *gvalue;
+
+ gvalue = g_slice_new0(GValue);
+ if (!gjs_value_to_g_value(context, value, gvalue)) {
+ g_slice_free(GValue, gvalue);
+ arg->v_pointer = NULL;
+ wrong = TRUE;
+ }
+
+ arg->v_pointer = gvalue;
+
+ } else if (JSVAL_IS_NULL(value)) {
+ arg->v_pointer = NULL;
+ } else if (JSVAL_IS_OBJECT(value)) {
+ if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) {
+ arg->v_pointer = gjs_g_object_from_object(context,
+ JSVAL_TO_OBJECT(value));
+ if (arg->v_pointer != NULL) {
+ if (!g_type_is_a(G_TYPE_FROM_INSTANCE(arg->v_pointer),
+ gtype)) {
+ gjs_throw(context,
+ "Expected type '%s' but got '%s'",
+ g_type_name(gtype),
+ g_type_name(G_TYPE_FROM_INSTANCE(arg->v_pointer)));
+ arg->v_pointer = NULL;
+ wrong = TRUE;
+ }
+ }
+ } else if (g_type_is_a(gtype, G_TYPE_BOXED)) {
+ if (g_type_is_a(gtype, G_TYPE_CLOSURE)) {
+ arg->v_pointer = gjs_closure_new_marshaled(context,
+ JSVAL_TO_OBJECT(value),
+ "boxed");
+ } else {
+ arg->v_pointer = gjs_g_boxed_from_boxed(context,
+ JSVAL_TO_OBJECT(value));
+ }
+ } else {
+ gjs_throw(context, "Unhandled GType %s unpacking SYMBOL GArgument from Object",
+ g_type_name(gtype));
+ }
+
+ if (arg->v_pointer == NULL) {
+ gjs_debug(GJS_DEBUG_GFUNCTION,
+ "conversion of JSObject %p type %s to gtype %s failed",
+ JSVAL_TO_OBJECT(value),
+ JS_GetTypeName(context,
+ JS_TypeOfValue(context, value)),
+ g_type_name(gtype));
+
+ /* bis_js_throw should have been called already */
+ wrong = TRUE;
+ }
+
+ } else if (JSVAL_IS_NUMBER(value)) {
+ nullable_type = FALSE;
+ if (g_type_is_a(gtype, G_TYPE_ENUM)) {
+ if (!JS_ValueToInt32(context, value, &arg->v_int)) {
+ wrong = TRUE;
+ } else {
+ GEnumValue *v;
+ void *klass;
+
+ klass = g_type_class_ref(gtype);
+
+ v = g_enum_get_value(G_ENUM_CLASS(klass),
+ arg->v_int);
+
+ g_type_class_unref(klass);
+
+ if (v == NULL) {
+ gjs_throw(context,
+ "%d is not a valid value for enumeration %s",
+ arg->v_int, g_type_name(gtype));
+ wrong = TRUE;
+ }
+ }
+ } else if (g_type_is_a(gtype, G_TYPE_FLAGS)) {
+ if (!JS_ValueToInt32(context, value, &arg->v_int)) {
+ wrong = TRUE;
+ } else {
+ void *klass;
+
+ klass = g_type_class_ref(gtype);
+ if (!_gjs_flags_value_is_valid(context, klass, arg->v_int))
+ wrong = TRUE;
+ g_type_class_unref(klass);
+ }
+ } else {
+ gjs_throw(context, "Unhandled GType %s unpacking SYMBOL GArgument from Number",
+ g_type_name(gtype));
+ }
+
+ } else {
+ gjs_debug(GJS_DEBUG_GFUNCTION,
+ "JSObject type '%s' is neither null nor an object",
+ JS_GetTypeName(context,
+ JS_TypeOfValue(context, value)));
+ wrong = TRUE;
+ report_type_mismatch = TRUE;
+ }
+ g_base_info_unref( (GIBaseInfo*) symbol_info);
+ }
+ break;
+
+ case GI_TYPE_TAG_GLIST:
+ case GI_TYPE_TAG_GSLIST:
+ /* nullable_type=FALSE; while a list can be NULL in C, that
+ * means empty array in JavaScript, it doesn't mean null in
+ * JavaScript.
+ */
+ if (!JSVAL_IS_NULL(value) &&
+ JSVAL_IS_OBJECT(value) &&
+ gjs_object_has_property(context,
+ JSVAL_TO_OBJECT(value),
+ "length")) {
+ jsval length_value;
+ guint32 length;
+
+ if (!gjs_object_require_property(context,
+ JSVAL_TO_OBJECT(value),
+ "length",
+ &length_value) ||
+ !JS_ValueToECMAUint32(context, length_value, &length)) {
+ wrong = TRUE;
+ } else {
+ GList *list;
+ GSList *slist;
+ GITypeInfo *param_info;
+
+ param_info = g_type_info_get_param_type(type_info, 0);
+ g_assert(param_info != NULL);
+
+ list = NULL;
+ slist = NULL;
+
+ if (!gjs_array_to_list(context,
+ value,
+ length,
+ param_info,
+ type_tag,
+ &list, &slist)) {
+ wrong = TRUE;
+ }
+
+ if (type_tag == GI_TYPE_TAG_GLIST) {
+ arg->v_pointer = list;
+ } else {
+ arg->v_pointer = slist;
+ }
+
+ g_base_info_unref((GIBaseInfo*) param_info);
+ }
+ } else {
+ wrong = TRUE;
+ report_type_mismatch = TRUE;
+ }
+ break;
+
+ case GI_TYPE_TAG_ARRAY:
+ if (JSVAL_IS_NULL(value)) {
+ arg->v_pointer = NULL;
+ } else {
+ gjs_throw(context, "FIXME: Only supporting null ARRAYs");
+ wrong = TRUE;
+ }
+ break;
+
+ default:
+ gjs_debug(GJS_DEBUG_ERROR,
+ "Unhandled type %s for JavaScript to GArgument conversion",
+ type_tag_name(type_tag));
+ wrong = TRUE;
+ report_type_mismatch = TRUE;
+ break;
+ }
+
+ if (G_UNLIKELY(wrong)) {
+ if (report_type_mismatch) {
+ gjs_throw(context, "Expected type %s for %s '%s' but got type '%s' %p",
+ type_tag_name(type_tag),
+ is_return_value ? "return value" : "argument",
+ arg_name,
+ JS_GetTypeName(context,
+ JS_TypeOfValue(context, value)),
+ JSVAL_IS_OBJECT(value) ? JSVAL_TO_OBJECT(value) : NULL);
+ }
+ return JS_FALSE;
+ } else if (nullable_type &&
+ arg->v_pointer == NULL &&
+ !may_be_null) {
+ gjs_throw(context,
+ "%s '%s' (type %s) may not be null",
+ is_return_value ? "Return value" : "Argument",
+ arg_name,
+ type_tag_name(type_tag));
+ return JS_FALSE;
+ } else {
+ return JS_TRUE;
+ }
+}
+
+JSBool
+gjs_value_to_g_arg(JSContext *context,
+ jsval value,
+ GIArgInfo *arg_info,
+ GArgument *arg)
+{
+ GITypeInfo *type_info;
+ gboolean result;
+
+ type_info = g_arg_info_get_type(arg_info);
+
+ result =
+ gjs_value_to_g_arg_with_type_info(context, value,
+ type_info,
+ g_base_info_get_name( (GIBaseInfo*) arg_info),
+ g_arg_info_is_return_value(arg_info),
+ g_arg_info_may_be_null(arg_info),
+ arg);
+
+ g_base_info_unref((GIBaseInfo*) type_info);
+
+ return result;
+}
+
+static JSBool
+gjs_array_from_g_list (JSContext *context,
+ jsval *value_p,
+ GITypeTag list_tag,
+ GITypeInfo *param_info,
+ GList *list,
+ GSList *slist)
+{
+ JSObject *obj;
+ unsigned int i;
+ jsval elem;
+ GArgument arg;
+ JSBool result;
+ GITypeTag param_tag;
+
+ param_tag = g_type_info_get_tag(param_info);
+
+ obj = JS_NewArrayObject(context, 0, JSVAL_NULL);
+ if (obj == NULL)
+ return JS_FALSE;
+
+ *value_p = OBJECT_TO_JSVAL(obj);
+
+ elem = JSVAL_VOID;
+ JS_AddRoot(context, &elem);
+
+ result = JS_FALSE;
+
+ i = 0;
+ if (list_tag == GI_TYPE_TAG_GLIST) {
+ for ( ; list != NULL; list = list->next) {
+ arg.v_pointer = list->data;
+
+ if (!gjs_value_from_g_arg(context, &elem,
+ param_info, &arg))
+ goto out;
+
+ if (!JS_DefineElement(context, obj,
+ i, elem,
+ NULL, NULL, JSPROP_ENUMERATE)) {
+ goto out;
+ }
+ ++i;
+ }
+ } else {
+ for ( ; slist != NULL; slist = slist->next) {
+ arg.v_pointer = slist->data;
+
+ if (!gjs_value_from_g_arg(context, &elem,
+ param_info, &arg))
+ goto out;
+
+ if (!JS_DefineElement(context, obj,
+ i, elem,
+ NULL, NULL, JSPROP_ENUMERATE)) {
+ goto out;
+ }
+ ++i;
+ }
+ }
+
+ result = JS_TRUE;
+
+ out:
+ JS_RemoveRoot(context, &elem);
+
+ return result;
+}
+
+JSBool
+gjs_value_from_g_arg (JSContext *context,
+ jsval *value_p,
+ GITypeInfo *type_info,
+ GArgument *arg)
+{
+ GITypeTag type_tag;
+
+ type_tag = g_type_info_get_tag( (GITypeInfo*) type_info);
+
+ gjs_debug_marshal(GJS_DEBUG_GFUNCTION,
+ "Converting GArgument %s to jsval",
+ type_tag_name(type_tag));
+
+ *value_p = JSVAL_NULL;
+
+ switch (type_tag) {
+ case GI_TYPE_TAG_VOID:
+ *value_p = JSVAL_VOID; /* or JSVAL_NULL ? */
+ break;
+
+ case GI_TYPE_TAG_BOOLEAN:
+ *value_p = BOOLEAN_TO_JSVAL(arg->v_int);
+ break;
+
+#if (GLIB_SIZEOF_LONG == 4)
+ case GI_TYPE_TAG_LONG:
+ case GI_TYPE_TAG_SSIZE:
+#endif
+ case GI_TYPE_TAG_INT:
+ case GI_TYPE_TAG_INT32:
+ return JS_NewNumberValue(context, arg->v_int, value_p);
+
+#if (GLIB_SIZEOF_LONG == 4)
+ case GI_TYPE_TAG_ULONG:
+ case GI_TYPE_TAG_SIZE:
+#endif
+ case GI_TYPE_TAG_UINT:
+ case GI_TYPE_TAG_UINT32:
+ return JS_NewNumberValue(context, arg->v_uint, value_p);
+
+#if (GLIB_SIZEOF_LONG == 8)
+ case GI_TYPE_TAG_LONG:
+ case GI_TYPE_TAG_SSIZE:
+#endif
+ case GI_TYPE_TAG_INT64:
+ return JS_NewNumberValue(context, arg->v_int64, value_p);
+
+#if (GLIB_SIZEOF_LONG == 8)
+ case GI_TYPE_TAG_ULONG:
+ case GI_TYPE_TAG_SIZE:
+#endif
+ case GI_TYPE_TAG_UINT64:
+ return JS_NewNumberValue(context, arg->v_uint64, value_p);
+
+ case GI_TYPE_TAG_UINT16:
+ return JS_NewNumberValue(context, arg->v_uint16, value_p);
+
+ case GI_TYPE_TAG_INT16:
+ return JS_NewNumberValue(context, arg->v_int16, value_p);
+
+ case GI_TYPE_TAG_UINT8:
+ return JS_NewNumberValue(context, arg->v_uint8, value_p);
+
+ case GI_TYPE_TAG_INT8:
+ return JS_NewNumberValue(context, arg->v_int8, value_p);
+
+ case GI_TYPE_TAG_DOUBLE:
+ return JS_NewDoubleValue(context, arg->v_double, value_p);
+
+ case GI_TYPE_TAG_UTF8:
+ if (arg->v_pointer)
+ return gjs_string_from_utf8(context, arg->v_pointer, -1, value_p);
+ else {
+ /* For NULL we'll return JSVAL_NULL, which is already set
+ * in *value_p
+ */
+ return JS_TRUE;
+ }
+
+ case GI_TYPE_TAG_INTERFACE:
+ {
+ if (arg->v_pointer == NULL) {
+ /* OK, but no conversion to do */
+ } else {
+ jsval value;
+ GIBaseInfo* symbol_info;
+ GType gtype;
+
+ symbol_info = g_type_info_get_interface(type_info);
+ g_assert(symbol_info != NULL);
+
+ if (g_base_info_get_type(symbol_info) == GI_INFO_TYPE_UNRESOLVED) {
+ gjs_throw(context,
+ "Unable to resolve arg type '%s'",
+ g_base_info_get_name(symbol_info));
+ g_base_info_unref( (GIBaseInfo*) symbol_info);
+ return JS_FALSE;
+ }
+
+ gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)symbol_info);
+
+ gjs_debug_marshal(GJS_DEBUG_GFUNCTION,
+ "gtype of SYMBOL is %s", g_type_name(gtype));
+
+ value = JSVAL_VOID;
+
+ if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) {
+ JSObject *obj;
+ obj = gjs_object_from_g_object(context, G_OBJECT(arg->v_pointer));
+ if (obj)
+ value = OBJECT_TO_JSVAL(obj);
+ } else if (g_type_is_a(gtype, G_TYPE_VALUE)) {
+ if (!gjs_value_from_g_value(context, &value, arg->v_pointer)) {
+ g_base_info_unref( (GIBaseInfo*) symbol_info);
+ return JS_FALSE;
+ }
+ } else if (g_type_is_a(gtype, G_TYPE_BOXED)) {
+ JSObject *obj;
+ obj = gjs_boxed_from_g_boxed(context, gtype, arg->v_pointer);
+ if (obj)
+ value = OBJECT_TO_JSVAL(obj);
+ } else if (g_type_is_a(gtype, G_TYPE_ENUM)) {
+ GEnumValue *v;
+ void *klass;
+
+ klass = g_type_class_ref(gtype);
+
+ v = g_enum_get_value(G_ENUM_CLASS(klass),
+ arg->v_int);
+
+ g_type_class_unref(klass);
+
+ if (v == NULL) {
+ gjs_throw(context,
+ "%d is not a valid value for enumeration %s",
+ arg->v_int, g_type_name(gtype));
+ } else {
+ value = INT_TO_JSVAL(arg->v_int);
+ }
+ } else if (g_type_is_a(gtype, G_TYPE_FLAGS)) {
+ void *klass;
+
+ klass = g_type_class_ref(gtype);
+
+ if (_gjs_flags_value_is_valid(context, G_FLAGS_CLASS(klass), arg->v_int))
+ value = INT_TO_JSVAL(arg->v_int);
+
+ g_type_class_unref(klass);
+ } else {
+ gjs_throw(context, "Unhandled GType %s packing SYMBOL GArgument into jsval",
+ g_type_name(gtype));
+ }
+
+ g_base_info_unref( (GIBaseInfo*) symbol_info);
+
+ if (JSVAL_IS_VOID(value))
+ return JS_FALSE;
+
+ *value_p = value;
+ }
+ }
+ break;
+
+ case GI_TYPE_TAG_ARRAY:
+ if (arg->v_pointer == NULL) {
+ /* OK, but no conversion to do */
+ } else {
+ gjs_throw(context, "FIXME: Only supporting null ARRAYs");
+ return JS_FALSE;
+ }
+ break;
+
+ case GI_TYPE_TAG_GLIST:
+ case GI_TYPE_TAG_GSLIST:
+ {
+ GITypeInfo *param_info;
+ gboolean result;
+
+ param_info = g_type_info_get_param_type(type_info, 0);
+ g_assert(param_info != NULL);
+
+ result = gjs_array_from_g_list(context,
+ value_p,
+ type_tag,
+ param_info,
+ type_tag == GI_TYPE_TAG_GLIST ?
+ arg->v_pointer : NULL,
+ type_tag == GI_TYPE_TAG_GSLIST ?
+ arg->v_pointer : NULL);
+
+ g_base_info_unref((GIBaseInfo*) param_info);
+
+ return result;
+ }
+ break;
+
+ default:
+ gjs_debug(GJS_DEBUG_ERROR,
+ "Unhandled type %s converting GArgument to JavaScript",
+ type_tag_name(type_tag));
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
+JSBool
+gjs_g_arg_release(JSContext *context,
+ GITransfer transfer,
+ GITypeInfo *type_info,
+ GArgument *arg)
+{
+ GITypeTag type_tag;
+
+ if (transfer == GI_TRANSFER_NOTHING)
+ return JS_TRUE;
+
+ type_tag = g_type_info_get_tag( (GITypeInfo*) type_info);
+
+ gjs_debug_marshal(GJS_DEBUG_GFUNCTION,
+ "Releasing GArgument %s out param or return value",
+ type_tag_name(type_tag));
+
+ switch (type_tag) {
+ case GI_TYPE_TAG_VOID:
+ case GI_TYPE_TAG_BOOLEAN:
+ case GI_TYPE_TAG_INT8:
+ case GI_TYPE_TAG_UINT8:
+ case GI_TYPE_TAG_INT16:
+ case GI_TYPE_TAG_UINT16:
+ case GI_TYPE_TAG_INT:
+ case GI_TYPE_TAG_INT32:
+ case GI_TYPE_TAG_UINT:
+ case GI_TYPE_TAG_UINT32:
+ case GI_TYPE_TAG_INT64:
+ case GI_TYPE_TAG_UINT64:
+ case GI_TYPE_TAG_LONG:
+ case GI_TYPE_TAG_ULONG:
+ case GI_TYPE_TAG_DOUBLE:
+ break;
+
+ case GI_TYPE_TAG_UTF8:
+ g_free(arg->v_pointer);
+ break;
+
+ case GI_TYPE_TAG_INTERFACE:
+ {
+ if (arg->v_pointer == NULL) {
+ /* nothing to do */
+ } else {
+ jsval value;
+ GIBaseInfo* symbol_info;
+ GType gtype;
+
+ symbol_info = g_type_info_get_interface(type_info);
+ g_assert(symbol_info != NULL);
+
+ gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)symbol_info);
+
+ gjs_debug_marshal(GJS_DEBUG_GFUNCTION,
+ "gtype of SYMBOL is %s", g_type_name(gtype));
+
+ value = JSVAL_NULL;
+
+ if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) {
+ g_object_unref(G_OBJECT(arg->v_pointer));
+ } else if (g_type_is_a(gtype, G_TYPE_BOXED)) {
+ g_boxed_free(gtype, arg->v_pointer);
+ } else if (g_type_is_a(gtype, G_TYPE_VALUE)) {
+ GValue *value = arg->v_pointer;
+ g_value_unset(value);
+ g_slice_free(GValue, value);
+ } else if (g_type_is_a(gtype, G_TYPE_ENUM) || g_type_is_a(gtype, G_TYPE_FLAGS)) {
+ /* nothing to do */
+ } else {
+ gjs_throw(context, "Unhandled GType %s releasing SYMBOL GArgument",
+ g_type_name(gtype));
+ }
+
+ g_base_info_unref( (GIBaseInfo*) symbol_info);
+ }
+ }
+ break;
+
+ case GI_TYPE_TAG_GLIST:
+ if (transfer == GI_TRANSFER_EVERYTHING) {
+ GITypeInfo *param_info;
+ GList *list;
+
+ param_info = g_type_info_get_param_type(type_info, 0);
+ g_assert(param_info != NULL);
+
+ for (list = arg->v_pointer;
+ list != NULL;
+ list = list->next) {
+ GArgument elem;
+ elem.v_pointer = list->data;
+
+ if (!gjs_g_arg_release(context,
+ GI_TRANSFER_EVERYTHING,
+ param_info,
+ &elem)) {
+ /* no way to recover here, and errors should
+ * not be possible.
+ */
+ g_error("Failed to release list element");
+ }
+ }
+
+ g_base_info_unref((GIBaseInfo*) param_info);
+ }
+
+ g_list_free(arg->v_pointer);
+ break;
+
+ case GI_TYPE_TAG_ARRAY:
+ if (arg->v_pointer == NULL) {
+ /* OK */
+ } else {
+ gjs_throw(context, "FIXME: Only supporting null ARRAYs");
+ return JS_FALSE;
+ }
+ break;
+
+ case GI_TYPE_TAG_GSLIST:
+ if (transfer == GI_TRANSFER_EVERYTHING) {
+ GITypeInfo *param_info;
+ GSList *slist;
+
+ param_info = g_type_info_get_param_type(type_info, 0);
+ g_assert(param_info != NULL);
+
+ for (slist = arg->v_pointer;
+ slist != NULL;
+ slist = slist->next) {
+ GArgument elem;
+ elem.v_pointer = slist->data;
+
+ if (!gjs_g_arg_release(context,
+ GI_TRANSFER_EVERYTHING,
+ param_info,
+ &elem)) {
+ /* no way to recover here, and errors should
+ * not be possible.
+ */
+ g_error("Failed to release slist element");
+ }
+ }
+
+ g_base_info_unref((GIBaseInfo*) param_info);
+ }
+
+ g_slist_free(arg->v_pointer);
+ break;
+
+ default:
+ gjs_debug(GJS_DEBUG_ERROR,
+ "Unhandled type %s releasing GArgument",
+ type_tag_name(type_tag));
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
diff --git a/gi/arg.h b/gi/arg.h
new file mode 100644
index 00000000..bf86e86d
--- /dev/null
+++ b/gi/arg.h
@@ -0,0 +1,54 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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_ARG_H__
+#define __GJS_ARG_H__
+
+#include <glib.h>
+
+#include <jsapi.h>
+
+#include <girepository.h>
+
+G_BEGIN_DECLS
+
+JSBool gjs_value_to_g_arg (JSContext *context,
+ jsval value,
+ GIArgInfo *arg_info,
+ GArgument *arg);
+JSBool gjs_value_from_g_arg (JSContext *context,
+ jsval *value_p,
+ GITypeInfo *type_info,
+ GArgument *arg);
+JSBool gjs_g_arg_release (JSContext *context,
+ GITransfer transfer,
+ GITypeInfo *type_info,
+ GArgument *arg);
+
+JSBool _gjs_flags_value_is_valid (JSContext *context,
+ GFlagsClass *klass,
+ guint value);
+
+G_END_DECLS
+
+#endif /* __GJS_ARG_H__ */
diff --git a/gi/boxed.c b/gi/boxed.c
new file mode 100644
index 00000000..48608025
--- /dev/null
+++ b/gi/boxed.c
@@ -0,0 +1,582 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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 <string.h>
+
+#include "boxed.h"
+#include "arg.h"
+#include "object.h"
+#include <gjs/mem.h>
+#include <gjs/jsapi-util.h>
+#include "repo.h"
+#include "function.h"
+
+#include <util/log.h>
+
+#include <jsapi.h>
+
+#include <girepository.h>
+
+typedef struct {
+ GIBoxedInfo *info;
+ void *gboxed; /* NULL if we are the prototype and not an instance */
+} Boxed;
+
+static Boxed unthreadsafe_template_for_constructor = { NULL, NULL };
+
+static struct JSClass gjs_boxed_class;
+
+GJS_DEFINE_DYNAMIC_PRIV_FROM_JS(Boxed, gjs_boxed_class)
+
+/*
+ * 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 boxed 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
+boxed_new_resolve(JSContext *context,
+ JSObject *obj,
+ jsval id,
+ uintN flags,
+ JSObject **objp)
+{
+ Boxed *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_GBOXED, "Resolve prop '%s' hook obj %p priv %p", name, obj, priv);
+
+ if (priv == NULL)
+ return JS_FALSE; /* wrong class */
+
+ if (priv->gboxed == NULL) {
+ /* We are the prototype, so look for methods and other class properties */
+ GIFunctionInfo *method_info;
+
+ method_info = g_struct_info_find_method((GIStructInfo*) priv->info,
+ name);
+
+ if (method_info != NULL) {
+ JSObject *boxed_proto;
+ const char *method_name;
+
+#if GJS_VERBOSE_ENABLE_GI_USAGE
+ _gjs_log_info_usage((GIBaseInfo*) method_info);
+#endif
+
+ method_name = g_base_info_get_name( (GIBaseInfo*) method_info);
+
+ gjs_debug(GJS_DEBUG_GBOXED,
+ "Defining method %s in prototype for %s.%s",
+ method_name,
+ g_base_info_get_namespace( (GIBaseInfo*) priv->info),
+ g_base_info_get_name( (GIBaseInfo*) priv->info));
+
+ boxed_proto = obj;
+
+ if (gjs_define_function(context, boxed_proto, method_info) == NULL) {
+ g_base_info_unref( (GIBaseInfo*) method_info);
+ return JS_FALSE;
+ }
+
+ *objp = boxed_proto; /* we defined the prop in object_proto */
+
+ g_base_info_unref( (GIBaseInfo*) method_info);
+ }
+ } else {
+ /* We are an instance, not a prototype, so look for
+ * per-instance props that we want to define on the
+ * JSObject. Generally we do not want to cache these in JS, we
+ * want to always pull them from the C object, or JS would not
+ * see any changes made from C. So we use the get/set prop
+ * hooks, not this resolve hook.
+ */
+ }
+
+ return JS_TRUE;
+}
+
+static void*
+boxed_new(JSContext *context,
+ JSObject *obj, /* "this" for constructor */
+ GIBoxedInfo *info)
+{
+ int n_methods;
+ int i;
+
+ /* Find a zero-args constructor and call it */
+
+ n_methods = g_struct_info_get_n_methods(info);
+
+ for (i = 0; i < n_methods; ++i) {
+ GIFunctionInfo *func_info;
+ GIFunctionInfoFlags flags;
+
+ func_info = g_struct_info_get_method(info, i);
+
+ flags = g_function_info_get_flags(func_info);
+ if ((flags & GI_FUNCTION_IS_CONSTRUCTOR) != 0 &&
+ g_callable_info_get_n_args((GICallableInfo*) func_info) == 0) {
+
+ jsval rval;
+
+ rval = JSVAL_NULL;
+ gjs_invoke_c_function(context, func_info, obj,
+ 0, NULL, &rval);
+
+ g_base_info_unref((GIBaseInfo*) func_info);
+
+ /* We are somewhat wasteful here; invoke_c_function() above
+ * creates a JSObject wrapper for the boxed that we immediately
+ * discard.
+ */
+ if (JSVAL_IS_NULL(rval))
+ return NULL;
+ else
+ return gjs_g_boxed_from_boxed(context, JSVAL_TO_OBJECT(rval));
+ }
+
+ g_base_info_unref((GIBaseInfo*) func_info);
+ }
+
+ gjs_throw(context, "Unable to construct boxed type %s since it has no zero-args <constructor>, can only wrap an existing one",
+ g_base_info_get_name((GIBaseInfo*) info));
+
+ return NULL;
+}
+
+/* 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
+boxed_constructor(JSContext *context,
+ JSObject *obj,
+ uintN argc,
+ jsval *argv,
+ jsval *retval)
+{
+ Boxed *priv;
+ Boxed *proto_priv;
+ JSClass *obj_class;
+ JSClass *proto_class;
+ JSObject *proto;
+ gboolean is_proto;
+
+ priv = g_slice_new0(Boxed);
+
+ GJS_INC_COUNTER(boxed);
+
+ g_assert(priv_from_js(context, obj) == NULL);
+ JS_SetPrivate(context, obj, priv);
+
+ gjs_debug_lifecycle(GJS_DEBUG_GBOXED,
+ "boxed constructor, obj %p priv %p",
+ obj, priv);
+
+ proto = JS_GetPrototype(context, obj);
+ gjs_debug_lifecycle(GJS_DEBUG_GBOXED, "boxed instance __proto__ is %p", proto);
+
+ /* If we're constructing the prototype, its __proto__ is not the same
+ * class as us, but if we're constructing an instance, the prototype
+ * has the same class.
+ */
+ obj_class = JS_GetClass(context, obj);
+ proto_class = JS_GetClass(context, proto);
+
+ is_proto = (obj_class != proto_class);
+
+ gjs_debug_lifecycle(GJS_DEBUG_GBOXED,
+ "boxed instance constructing proto %d, obj class %s proto class %s",
+ is_proto, obj_class->name, proto_class->name);
+
+ if (!is_proto) {
+ GType gtype;
+
+ /* If we're the prototype, then post-construct we'll fill in priv->info.
+ * If we are not the prototype, though, then we'll get ->info from the
+ * prototype and then create a GObject if we don't have one already.
+ */
+ proto_priv = priv_from_js(context, proto);
+ if (proto_priv == NULL) {
+ gjs_debug(GJS_DEBUG_GBOXED,
+ "Bad prototype set on boxed? Must match JSClass of object. JS error should have been reported.");
+ return JS_FALSE;
+ }
+
+ priv->info = proto_priv->info;
+ g_base_info_ref( (GIBaseInfo*) priv->info);
+
+ gtype = g_registered_type_info_get_g_type( (GIRegisteredTypeInfo*) priv->info);
+
+ /* Since gobject-introspection is always creating new info
+ * objects, == is not meaningful on them, only comparison of
+ * their names. We prefer to use the info that is already ref'd
+ * by the prototype for the class.
+ */
+ g_assert(unthreadsafe_template_for_constructor.info == NULL ||
+ strcmp(g_base_info_get_name( (GIBaseInfo*) priv->info),
+ g_base_info_get_name( (GIBaseInfo*) unthreadsafe_template_for_constructor.info))
+ == 0);
+ unthreadsafe_template_for_constructor.info = NULL;
+
+ if (unthreadsafe_template_for_constructor.gboxed == NULL) {
+ void *gboxed;
+
+ /* boxed_new happens to be implemented by calling
+ * gjs_invoke_c_function(), which returns a jsval.
+ * The returned "gboxed" here is owned by that jsval,
+ * not by us.
+ */
+ gboxed = boxed_new(context, obj, priv->info);
+
+ if (gboxed == NULL) {
+ return JS_FALSE;
+ }
+
+ /* Because "gboxed" is owned by a jsval and will
+ * be garbage colleced, we make a copy here to be
+ * owned by us.
+ */
+ priv->gboxed = g_boxed_copy(gtype, gboxed);
+ } else {
+ priv->gboxed = g_boxed_copy(gtype, unthreadsafe_template_for_constructor.gboxed);
+ unthreadsafe_template_for_constructor.gboxed = NULL;
+ }
+
+ gjs_debug_lifecycle(GJS_DEBUG_GBOXED,
+ "JSObject created with boxed instance %p type %s",
+ priv->gboxed, g_type_name(gtype));
+ }
+
+ return JS_TRUE;
+}
+
+static void
+boxed_finalize(JSContext *context,
+ JSObject *obj)
+{
+ Boxed *priv;
+
+ priv = priv_from_js(context, obj);
+ gjs_debug_lifecycle(GJS_DEBUG_GBOXED,
+ "finalize, obj %p priv %p", obj, priv);
+ if (priv == NULL)
+ return; /* wrong class? */
+
+ if (priv->gboxed) {
+ g_boxed_free(g_registered_type_info_get_g_type( (GIRegisteredTypeInfo*) priv->info),
+ priv->gboxed);
+ priv->gboxed = NULL;
+ }
+
+ if (priv->info) {
+ g_base_info_unref( (GIBaseInfo*) priv->info);
+ priv->info = NULL;
+ }
+
+ GJS_DEC_COUNTER(boxed);
+ g_slice_free(Boxed, 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_boxed_class = {
+ NULL, /* dynamic class, no name here */
+ JSCLASS_HAS_PRIVATE |
+ JSCLASS_NEW_RESOLVE |
+ JSCLASS_NEW_RESOLVE_GETS_START |
+ JSCLASS_CONSTRUCT_PROTOTYPE,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_EnumerateStub,
+ (JSResolveOp) boxed_new_resolve, /* needs cast since it's the new resolve signature */
+ JS_ConvertStub,
+ boxed_finalize,
+ NULL,
+ NULL,
+ NULL,
+ NULL, NULL, NULL, NULL, NULL
+};
+
+static JSPropertySpec gjs_boxed_proto_props[] = {
+ { NULL }
+};
+
+static JSFunctionSpec gjs_boxed_proto_funcs[] = {
+ { NULL }
+};
+
+JSObject*
+gjs_lookup_boxed_constructor(JSContext *context,
+ GIBoxedInfo *info)
+{
+ JSObject *ns;
+ JSObject *constructor;
+
+ ns = gjs_lookup_namespace_object(context, (GIBaseInfo*) info);
+
+ if (ns == NULL)
+ return NULL;
+
+ constructor = NULL;
+ if (gjs_define_boxed_class(context, ns, info,
+ &constructor, NULL))
+ return constructor;
+ else
+ return NULL;
+}
+
+JSObject*
+gjs_lookup_boxed_prototype(JSContext *context,
+ GIBoxedInfo *info)
+{
+ JSObject *ns;
+ JSObject *proto;
+
+ ns = gjs_lookup_namespace_object(context, (GIBaseInfo*) info);
+
+ if (ns == NULL)
+ return NULL;
+
+ proto = NULL;
+ if (gjs_define_boxed_class(context, ns, info, NULL, &proto))
+ return proto;
+ else
+ return NULL;
+}
+
+JSClass*
+gjs_lookup_boxed_class(JSContext *context,
+ GIBoxedInfo *info)
+{
+ JSObject *prototype;
+
+ prototype = gjs_lookup_boxed_prototype(context, info);
+
+ return JS_GetClass(context, prototype);
+}
+
+JSBool
+gjs_define_boxed_class(JSContext *context,
+ JSObject *in_object,
+ GIBoxedInfo *info,
+ JSObject **constructor_p,
+ JSObject **prototype_p)
+{
+ const char *constructor_name;
+ JSObject *prototype;
+ jsval value;
+ Boxed *priv;
+
+ /* See the comment in gjs_define_object_class() for an
+ * explanation of how this all works; Boxed is pretty much the
+ * same as Object.
+ */
+
+ constructor_name = g_base_info_get_name( (GIBaseInfo*) info);
+
+ if (gjs_object_get_property(context, in_object, constructor_name, &value)) {
+ JSObject *constructor;
+
+ if (!JSVAL_IS_OBJECT(value)) {
+ gjs_throw(context, "Existing property '%s' does not look like a constructor",
+ constructor_name);
+ return JS_FALSE;
+ }
+
+ constructor = JSVAL_TO_OBJECT(value);
+
+ gjs_object_get_property(context, constructor, "prototype", &value);
+ if (!JSVAL_IS_OBJECT(value)) {
+ gjs_throw(context, "boxed %s prototype property does not appear to exist or has wrong type", constructor_name);
+ return JS_FALSE;
+ } else {
+ if (prototype_p)
+ *prototype_p = JSVAL_TO_OBJECT(value);
+ if (constructor_p)
+ *constructor_p = constructor;
+
+ return JS_TRUE;
+ }
+ }
+
+ prototype = gjs_init_class_dynamic(context, in_object,
+ /* parent prototype JSObject* for
+ * prototype; NULL for
+ * Object.prototype
+ */
+ NULL,
+ g_base_info_get_namespace( (GIBaseInfo*) info),
+ constructor_name,
+ &gjs_boxed_class,
+ /* constructor for instances (NULL for
+ * none - just name the prototype like
+ * Math - rarely correct)
+ */
+ boxed_constructor,
+ /* number of constructor args */
+ 0,
+ /* props of prototype */
+ &gjs_boxed_proto_props[0],
+ /* funcs of prototype */
+ &gjs_boxed_proto_funcs[0],
+ /* props of constructor, MyConstructor.myprop */
+ NULL,
+ /* funcs of constructor, MyConstructor.myfunc() */
+ NULL);
+ if (prototype == NULL)
+ gjs_fatal("Can't init class %s", constructor_name);
+
+ g_assert(gjs_object_has_property(context, in_object, constructor_name));
+
+ /* Put the info in the prototype */
+ priv = priv_from_js(context, prototype);
+ g_assert(priv != NULL);
+ g_assert(priv->info == NULL);
+ priv->info = info;
+ g_base_info_ref( (GIBaseInfo*) priv->info);
+
+ gjs_debug(GJS_DEBUG_GBOXED, "Defined class %s prototype is %p class %p in object %p",
+ constructor_name, prototype, JS_GetClass(context, prototype), in_object);
+
+ if (constructor_p) {
+ *constructor_p = NULL;
+ gjs_object_get_property(context, in_object, constructor_name, &value);
+ if (value != JSVAL_VOID) {
+ if (!JSVAL_IS_OBJECT(value)) {
+ gjs_throw(context, "Property '%s' does not look like a constructor",
+ constructor_name);
+ return JS_FALSE;
+ }
+ }
+
+ *constructor_p = JSVAL_TO_OBJECT(value);
+ }
+
+ if (prototype_p)
+ *prototype_p = prototype;
+
+ return JS_TRUE;
+}
+
+JSObject*
+gjs_boxed_from_g_boxed(JSContext *context,
+ GType gtype,
+ void *gboxed)
+{
+ JSObject *obj;
+ JSObject *proto;
+ GIBaseInfo *info;
+
+ if (gboxed == NULL)
+ return NULL;
+
+ gjs_debug_marshal(GJS_DEBUG_GBOXED,
+ "Wrapping boxed %s %p with JSObject",
+ g_type_name(gtype), gboxed);
+
+ info = g_irepository_find_by_gtype(g_irepository_get_default(),
+ gtype);
+
+ if (info == NULL) {
+ gjs_throw(context,
+ "Unknown boxed type %s",
+ g_type_name(gtype));
+ return NULL;
+ }
+
+ if (g_base_info_get_type( (GIBaseInfo*) info) != GI_INFO_TYPE_BOXED) {
+ gjs_throw(context,
+ "GType %s doesn't map to boxed in g-i?",
+ g_base_info_get_name( (GIBaseInfo*) info));
+ g_base_info_unref( (GIBaseInfo*) info);
+ return NULL;
+ }
+
+ proto = gjs_lookup_boxed_prototype(context, (GIBoxedInfo*) info);
+
+ /* can't come up with a better approach... */
+ unthreadsafe_template_for_constructor.info = (GIBoxedInfo*) info;
+ unthreadsafe_template_for_constructor.gboxed = gboxed;
+
+ obj = gjs_construct_object_dynamic(context, proto,
+ 0, NULL);
+
+ g_base_info_unref( (GIBaseInfo*) info);
+
+ return obj;
+}
+
+void*
+gjs_g_boxed_from_boxed(JSContext *context,
+ JSObject *obj)
+{
+ Boxed *priv;
+
+ if (obj == NULL)
+ return NULL;
+
+ priv = priv_from_js(context, obj);
+
+ if (priv == NULL)
+ return NULL;
+
+ if (priv->gboxed == NULL) {
+ gjs_throw(context,
+ "Object is %s.%s.prototype, not an object instance - cannot convert to a boxed instance",
+ g_base_info_get_namespace( (GIBaseInfo*) priv->info),
+ g_base_info_get_name( (GIBaseInfo*) priv->info));
+ return NULL;
+ }
+
+ return priv->gboxed;
+}
diff --git a/gi/boxed.h b/gi/boxed.h
new file mode 100644
index 00000000..9ebd52e1
--- /dev/null
+++ b/gi/boxed.h
@@ -0,0 +1,57 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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_BOXED_H__
+#define __GJS_BOXED_H__
+
+#include <glib.h>
+
+#include <jsapi.h>
+
+#include <girepository.h>
+
+G_BEGIN_DECLS
+
+/* Hack for now... why doesn't gobject-introspection have this? */
+typedef GIStructInfo GIBoxedInfo;
+
+JSBool gjs_define_boxed_class (JSContext *context,
+ JSObject *in_object,
+ GIBoxedInfo *info,
+ JSObject **constructor_p,
+ JSObject **prototype_p);
+JSObject* gjs_lookup_boxed_constructor (JSContext *context,
+ GIBoxedInfo *info);
+JSObject* gjs_lookup_boxed_prototype (JSContext *context,
+ GIBoxedInfo *info);
+JSClass* gjs_lookup_boxed_class (JSContext *context,
+ GIBoxedInfo *info);
+void* gjs_g_boxed_from_boxed (JSContext *context,
+ JSObject *obj);
+JSObject* gjs_boxed_from_g_boxed (JSContext *context,
+ GType gtype,
+ void *gboxed);
+
+G_END_DECLS
+
+#endif /* __GJS_BOXED_H__ */
diff --git a/gi/closure.c b/gi/closure.c
new file mode 100644
index 00000000..774ddaba
--- /dev/null
+++ b/gi/closure.c
@@ -0,0 +1,287 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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 <limits.h>
+#include <util/log.h>
+
+#include "closure.h"
+#include "keep-alive.h"
+#include <gjs/mem.h>
+#include <gjs/jsapi-util.h>
+
+typedef struct {
+ GClosure base;
+ JSRuntime *runtime;
+ JSContext *context;
+ JSObject *obj;
+} Closure;
+
+/*
+ * Memory management of closures is "interesting" because we're keeping around
+ * a JSContext* and then trying to use it spontaneously from the main loop.
+ * I don't think that's really quite kosher, and perhaps the problem is that
+ * (in xulrunner) we just need to save a different context.
+ *
+ * Or maybe the right fix is to create our own context just for this?
+ *
+ * But for the moment, we save the context that was used to create the closure.
+ *
+ * Here's the problem: this context can be destroyed. AFTER the
+ * context is destroyed, or at least potentially after, the objects in
+ * the context's global object may be garbage collected. Remember that
+ * JSObject* belong to a runtime, not a context.
+ *
+ * There is apparently no robust way to track context destruction in
+ * SpiderMonkey, because the context can be destroyed without running
+ * the garbage collector, and xulrunner takes over the JS_SetContextCallback()
+ * callback. So there's no callback for us.
+ *
+ * So, when we go to use our context, we iterate the contexts in the runtime
+ * and see if ours is still in the valid list, and decide to invalidate
+ * the closure if it isn't.
+ *
+ * The closure can thus be destroyed in several cases:
+ * - invalidation by say signal disconnection; we get invalidate callback
+ * - invalidation because we were invoked while the context was dead
+ * - invalidation through finalization (we were garbage collected)
+ *
+ * These don't have to happen in the same order; garbage collection can
+ * be either before, or after, context destruction.
+ *
+ */
+
+static void
+invalidate_js_pointers(Closure *c)
+{
+ if (c->obj == NULL)
+ return;
+
+ c->obj = NULL;
+ c->context = NULL;
+ c->runtime = NULL;
+
+ /* disconnects from signals, for example...
+ * potentially a dangerous re-entrancy but
+ * we'll have to risk it.
+ */
+ g_closure_invalidate(&c->base);
+}
+
+static void
+global_context_finalized(JSObject *obj,
+ void *data)
+{
+ Closure *c;
+
+ c = data;
+
+ gjs_debug(GJS_DEBUG_GCLOSURE,
+ "Context destroy notifier on closure %p which calls object %p",
+ c, c->obj);
+
+ if (c->obj != NULL) {
+ g_assert(c->obj == obj);
+
+ invalidate_js_pointers(c);
+ }
+
+ /* The "Keep Alive" (garbage collector) owns one reference. */
+ g_closure_unref(&c->base);
+}
+
+
+static void
+check_context_valid(Closure *c)
+{
+ JSContext *a_context;
+ JSContext *iter;
+
+ if (c->runtime == NULL)
+ return;
+
+ iter = NULL;
+ while ((a_context = JS_ContextIterator(c->runtime,
+ &iter)) != NULL) {
+ if (a_context == c->context) {
+ return;
+ }
+ }
+
+ gjs_debug(GJS_DEBUG_GCLOSURE,
+ "Context %p no longer exists, invalidating closure %p which calls object %p",
+ c->context, c, c->obj);
+
+ /* Did not find the context. */
+ invalidate_js_pointers(c);
+}
+
+/* Invalidation is like "dispose" - it happens on signal disconnect,
+ * is guaranteed to happen at finalize, but may happen before finalize
+ */
+static void
+closure_invalidated(gpointer data,
+ GClosure *closure)
+{
+ Closure *c;
+
+ c = (Closure*) closure;
+
+ gjs_debug(GJS_DEBUG_GCLOSURE,
+ "Invalidating closure %p which calls object %p",
+ closure, c->obj);
+
+ if (c->obj) {
+ gjs_keep_alive_remove_global_child(c->context,
+ global_context_finalized,
+ c->obj,
+ c);
+
+ c->obj = NULL;
+ c->context = NULL;
+ c->runtime = NULL;
+
+ /* The "Keep Alive" (garbage collector) owns one reference,
+ * since we removed ourselves from the keep-alive we'll
+ * never be collected so drop the ref here
+ */
+ g_closure_unref(&c->base);
+ }
+}
+
+static void
+closure_finalized(gpointer data,
+ GClosure *closure)
+{
+ GJS_DEC_COUNTER(closure);
+}
+
+void
+gjs_closure_invoke(GClosure *closure,
+ int argc,
+ jsval *argv,
+ jsval *retval)
+{
+ Closure *c;
+ JSContext *context;
+
+ c = (Closure*) closure;
+
+ check_context_valid(c);
+ context = c->context;
+
+ if (c->obj == NULL) {
+ /* We were destroyed; become a no-op */
+ c->context = NULL;
+ return;
+ }
+
+ if (JS_IsExceptionPending(context)) {
+ gjs_debug(GJS_DEBUG_GCLOSURE,
+ "Exception was pending before invoking callback??? Not expected");
+ gjs_log_exception(c->context, NULL);
+ }
+
+ if (!gjs_call_function_value(context,
+ NULL, /* "this" object; NULL is some kind of default presumably */
+ OBJECT_TO_JSVAL(c->obj),
+ argc,
+ argv,
+ retval)) {
+ /* Exception thrown... */
+ gjs_debug(GJS_DEBUG_GCLOSURE,
+ "Closure invocation failed (exception should have been thrown) closure %p callable %p",
+ closure, c->obj);
+ if (!gjs_log_exception(context, NULL))
+ gjs_debug(GJS_DEBUG_ERROR,
+ "Closure invocation failed but no exception was set?");
+ return;
+ }
+
+ if (gjs_log_exception(context, NULL)) {
+ gjs_debug(GJS_DEBUG_ERROR,
+ "Closure invocation succeeded but an exception was set");
+ }
+}
+
+JSContext*
+gjs_closure_get_context(GClosure *closure)
+{
+ Closure *c;
+
+ c = (Closure*) closure;
+
+ check_context_valid(c);
+
+ return c->context;
+}
+
+JSObject*
+gjs_closure_get_callable(GClosure *closure)
+{
+ Closure *c;
+
+ c = (Closure*) closure;
+
+ return c->obj;
+}
+
+GClosure*
+gjs_closure_new(JSContext *context,
+ JSObject *callable,
+ const char *description)
+{
+ Closure *c;
+
+ c = (Closure*) g_closure_new_simple(sizeof(Closure), NULL);
+ c->runtime = JS_GetRuntime(context);
+ /* Closure are executed in our special "load-context" (one per runtime).
+ * This ensures that the context is still alive when the closure
+ * is invoked (as long as the runtime lives)
+ */
+ c->context = gjs_runtime_get_load_context(c->runtime);
+ c->obj = callable;
+
+ GJS_INC_COUNTER(closure);
+ /* the finalize notifier right now is purely to track the counter
+ * of how many closures are alive.
+ */
+ g_closure_add_finalize_notifier(&c->base, NULL, closure_finalized);
+
+ gjs_keep_alive_add_global_child(c->context,
+ global_context_finalized,
+ c->obj,
+ c);
+
+ /* The "Keep Alive" (garbage collector) owns one reference. */
+ g_closure_ref(&c->base);
+
+ g_closure_add_invalidate_notifier(&c->base, NULL, closure_invalidated);
+
+ gjs_debug(GJS_DEBUG_GCLOSURE,
+ "Create closure %p which calls object %p '%s'",
+ c, c->obj, description);
+
+ return &c->base;
+}
diff --git a/gi/closure.h b/gi/closure.h
new file mode 100644
index 00000000..d8855c19
--- /dev/null
+++ b/gi/closure.h
@@ -0,0 +1,45 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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_CLOSURE_H__
+#define __GJS_CLOSURE_H__
+
+#include <glib-object.h>
+
+#include <jsapi.h>
+
+G_BEGIN_DECLS
+
+GClosure* gjs_closure_new (JSContext *context,
+ JSObject *callable,
+ const char *description);
+void gjs_closure_invoke (GClosure *closure,
+ int argc,
+ jsval *argv,
+ jsval *retval);
+JSContext* gjs_closure_get_context (GClosure *closure);
+JSObject* gjs_closure_get_callable (GClosure *closure);
+
+G_END_DECLS
+
+#endif /* __GJS_CLOSURE_H__ */
diff --git a/gi/enumeration.c b/gi/enumeration.c
new file mode 100644
index 00000000..a107cc0a
--- /dev/null
+++ b/gi/enumeration.c
@@ -0,0 +1,161 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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 <string.h>
+
+#include <gjs/jsapi-util.h>
+#include "repo.h"
+
+#include <util/log.h>
+
+#include <jsapi.h>
+
+#include <girepository.h>
+
+#include "enumeration.h"
+
+JSObject*
+gjs_lookup_enumeration(JSContext *context,
+ GIEnumInfo *info)
+{
+ JSObject *ns;
+ JSObject *enum_obj;
+
+ ns = gjs_lookup_namespace_object(context, (GIBaseInfo*) info);
+
+ if (ns == NULL)
+ return NULL;
+
+ if (gjs_define_enumeration(context, ns, info,
+ &enum_obj))
+ return enum_obj;
+ else
+ return NULL;
+}
+
+static JSBool
+gjs_define_enum_value(JSContext *context,
+ JSObject *in_object,
+ GIValueInfo *info)
+{
+ const char *value_name;
+ int value_val;
+
+ value_name = g_base_info_get_name( (GIBaseInfo*) info);
+ value_val = (int) g_value_info_get_value(info);
+
+ gjs_debug(GJS_DEBUG_GENUM,
+ "Defining enum value %s %d",
+ value_name, value_val);
+
+ if (!JS_DefineProperty(context, in_object,
+ value_name, INT_TO_JSVAL(value_val),
+ NULL, NULL,
+ GJS_MODULE_PROP_FLAGS)) {
+ gjs_throw(context, "Unable to define enumeration value %s %d (no memory most likely)",
+ value_name, value_val);
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
+JSBool
+gjs_define_enumeration(JSContext *context,
+ JSObject *in_object,
+ GIEnumInfo *info,
+ JSObject **enumeration_p)
+{
+ const char *enum_name;
+ JSObject *enum_obj;
+ jsval value;
+ int i;
+ int n_values;
+
+ /* An enumeration is simply an object containing integer attributes for
+ * each enum value. It does not have a special JSClass.
+ *
+ * We could make this more typesafe and also print enum values as strings
+ * if we created a class for each enum and made the enum values instances
+ * of that class. However, it would have a lot more overhead and just
+ * be more complicated in general. I think this is fine.
+ */
+
+ enum_name = g_base_info_get_name( (GIBaseInfo*) info);
+
+ if (gjs_object_get_property(context, in_object, enum_name, &value)) {
+ if (!JSVAL_IS_OBJECT(value)) {
+ gjs_throw(context, "Existing property '%s' does not look like an enum object",
+ enum_name);
+ return JS_FALSE;
+ }
+
+ enum_obj = JSVAL_TO_OBJECT(value);
+
+ if (enumeration_p)
+ *enumeration_p = enum_obj;
+
+ return JS_TRUE;
+ }
+
+ enum_obj = JS_ConstructObject(context, NULL, NULL, NULL);
+ if (enum_obj == NULL)
+ return JS_FALSE;
+
+ /* Fill in enum values first, so we don't define the enum itself until we're
+ * sure we can finish successfully.
+ */
+ n_values = g_enum_info_get_n_values(info);
+ for (i = 0; i < n_values; ++i) {
+ GIValueInfo *value_info = g_enum_info_get_value(info, i);
+ gboolean failed;
+
+ failed = !gjs_define_enum_value(context, enum_obj, value_info);
+
+ g_base_info_unref( (GIBaseInfo*) value_info);
+
+ if (failed) {
+ return JS_FALSE;
+ }
+ }
+
+ gjs_debug(GJS_DEBUG_GENUM,
+ "Defining %s.%s as %p",
+ g_base_info_get_namespace( (GIBaseInfo*) info),
+ enum_name, enum_obj);
+
+ if (!JS_DefineProperty(context, in_object,
+ enum_name, OBJECT_TO_JSVAL(enum_obj),
+ NULL, NULL,
+ GJS_MODULE_PROP_FLAGS)) {
+ gjs_throw(context, "Unable to define enumeration property (no memory most likely)");
+ return JS_FALSE;
+ }
+
+ if (enumeration_p)
+ *enumeration_p = enum_obj;
+
+ return JS_TRUE;
+}
diff --git a/gi/enumeration.h b/gi/enumeration.h
new file mode 100644
index 00000000..1e2c579d
--- /dev/null
+++ b/gi/enumeration.h
@@ -0,0 +1,44 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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_ENUMERATION_H__
+#define __GJS_ENUMERATION_H__
+
+#include <glib.h>
+
+#include <jsapi.h>
+
+#include <girepository.h>
+
+G_BEGIN_DECLS
+
+JSBool gjs_define_enumeration (JSContext *context,
+ JSObject *in_object,
+ GIEnumInfo *info,
+ JSObject **enumeration_p);
+JSObject* gjs_lookup_enumeration (JSContext *context,
+ GIEnumInfo *info);
+
+G_END_DECLS
+
+#endif /* __GJS_ENUMERATION_H__ */
diff --git a/gi/function.c b/gi/function.c
new file mode 100644
index 00000000..80d6d175
--- /dev/null
+++ b/gi/function.c
@@ -0,0 +1,554 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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 "function.h"
+#include "arg.h"
+#include "object.h"
+#include <gjs/jsapi-util.h>
+#include <gjs/mem.h>
+
+#include <util/log.h>
+
+#include <jsapi.h>
+
+#include <girepository.h>
+
+typedef struct {
+ GIFunctionInfo *info;
+
+} Function;
+
+static struct JSClass gjs_function_class;
+
+GJS_DEFINE_PRIV_FROM_JS(Function, gjs_function_class)
+
+/*
+ * 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 function 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
+function_new_resolve(JSContext *context,
+ JSObject *obj,
+ jsval id,
+ uintN flags,
+ JSObject **objp)
+{
+ Function *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_GFUNCTION, "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;
+}
+
+JSBool
+gjs_invoke_c_function(JSContext *context,
+ GIFunctionInfo *info,
+ JSObject *obj, /* "this" object */
+ uintN argc,
+ jsval *argv,
+ jsval *rval)
+{
+ GArgument *in_args;
+ GArgument *out_args;
+ GArgument *out_values;
+ GArgument return_arg;
+ int n_args;
+ int expected_in_argc;
+ int expected_out_argc;
+ int i;
+ int argv_pos;
+ int in_args_pos;
+ int out_args_pos;
+ GError *error;
+ gboolean failed;
+ GIFunctionInfoFlags flags;
+ gboolean is_method;
+
+ flags = g_function_info_get_flags(info);
+ is_method = (flags & GI_FUNCTION_IS_METHOD) != 0;
+
+ expected_in_argc = 0;
+ expected_out_argc = 0;
+
+ n_args = g_callable_info_get_n_args( (GICallableInfo*) info);
+ for (i = 0; i < n_args; i++) {
+ GIDirection direction;
+ GIArgInfo *arg_info;
+
+ arg_info = g_callable_info_get_arg( (GICallableInfo*) info, i);
+ direction = g_arg_info_get_direction(arg_info);
+ if (direction == GI_DIRECTION_IN || direction == GI_DIRECTION_INOUT)
+ expected_in_argc += 1;
+ if (direction == GI_DIRECTION_OUT || direction == GI_DIRECTION_INOUT)
+ expected_out_argc += 1;
+ g_base_info_unref( (GIBaseInfo*) arg_info);
+ }
+
+ gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Call is to %s %s.%s with argc %d, expected: %d in args, %d out args, %d total args",
+ is_method ? "method" : "function",
+ g_base_info_get_namespace( (GIBaseInfo*) info),
+ g_base_info_get_name( (GIBaseInfo*) info),
+ argc,
+ expected_in_argc,
+ expected_out_argc,
+ n_args);
+
+ /* We allow too many args; convenient for re-using a function as a callback.
+ * But we don't allow too few args, since that would break.
+ */
+ if (argc < (unsigned) expected_in_argc) {
+ gjs_throw(context, "Too few arguments to %s %s.%s expected %d got %d",
+ is_method ? "method" : "function",
+ g_base_info_get_namespace( (GIBaseInfo*) info),
+ g_base_info_get_name( (GIBaseInfo*) info),
+ expected_in_argc,
+ argc);
+ return JS_FALSE;
+ }
+
+ if (is_method)
+ expected_in_argc += 1;
+
+ in_args = g_newa(GArgument, expected_in_argc);
+ out_args = g_newa(GArgument, expected_out_argc);
+ /* each out arg is a pointer, they point to these values */
+ out_values = g_newa(GArgument, expected_out_argc);
+
+ failed = FALSE;
+ in_args_pos = 0; /* index into in_args */
+ out_args_pos = 0; /* into out_args */
+ argv_pos = 0; /* index into argv */
+
+ if (is_method) {
+ in_args[0].v_pointer = gjs_g_object_from_object(context, obj);
+ ++in_args_pos;
+ }
+
+ for (i = 0; i < n_args; i++) {
+ GIDirection direction;
+ GIArgInfo *arg_info;
+ GArgument *out_value;
+
+ /* gjs_debug(GJS_DEBUG_GFUNCTION, "i: %d in_args_pos: %d argv_pos: %d", i, in_args_pos, argv_pos); */
+
+ arg_info = g_callable_info_get_arg( (GICallableInfo*) info, i);
+ direction = g_arg_info_get_direction(arg_info);
+
+ out_value = NULL;
+ if (direction == GI_DIRECTION_OUT || direction == GI_DIRECTION_INOUT) {
+ g_assert(out_args_pos < expected_out_argc);
+
+ out_value = &out_values[out_args_pos];
+ out_args[out_args_pos].v_pointer = out_value;
+ ++out_args_pos;
+ }
+
+ if (direction == GI_DIRECTION_IN || direction == GI_DIRECTION_INOUT) {
+ GArgument in_value;
+
+ if (!gjs_value_to_g_arg(context, argv[argv_pos], arg_info,
+ &in_value)) {
+ failed = TRUE;
+ }
+
+ ++argv_pos;
+
+ if (direction == GI_DIRECTION_IN) {
+ in_args[in_args_pos] = in_value;
+ } else {
+ /* INOUT means we pass a pointer */
+ g_assert(out_value != NULL);
+ *out_value = in_value;
+ in_args[in_args_pos].v_pointer = out_value;
+ }
+
+ ++in_args_pos;
+ }
+
+ g_base_info_unref( (GIBaseInfo*) arg_info);
+
+ if (failed)
+ break;
+ }
+
+ /* gjs_value_to_g_arg should have reported a JS error if we failed, return FALSE */
+ if (failed)
+ return JS_FALSE;
+
+ g_assert(in_args_pos == expected_in_argc);
+ g_assert(out_args_pos == expected_out_argc);
+
+ error = NULL;
+ if (g_function_info_invoke( (GIFunctionInfo*) info,
+ in_args, expected_in_argc,
+ out_args, expected_out_argc,
+ &return_arg,
+ &error)) {
+ GITypeInfo *return_info;
+ GITypeTag return_tag;
+ int n_return_values;
+
+ failed = FALSE;
+
+ return_info = g_callable_info_get_return_type( (GICallableInfo*) info);
+ g_assert(return_info != NULL);
+
+ return_tag = g_type_info_get_tag(return_info);
+
+ *rval = JSVAL_VOID;
+
+ n_return_values = expected_out_argc;
+ if (return_tag != GI_TYPE_TAG_VOID)
+ n_return_values += 1;
+
+ if (n_return_values > 0) {
+ jsval *return_values;
+ int next_rval;
+
+ return_values = g_newa(jsval, n_return_values);
+ gjs_set_values(context, return_values, n_return_values, JSVAL_VOID);
+ gjs_root_value_locations(context, return_values, n_return_values);
+
+ next_rval = 0; /* index into return_values */
+
+ if (return_tag != GI_TYPE_TAG_VOID) {
+ if (!gjs_value_from_g_arg(context, &return_values[next_rval],
+ return_info, &return_arg)) {
+ failed = TRUE;
+ }
+
+ /* Free GArgument, the jsval should have ref'd or copied it */
+ if (!gjs_g_arg_release(context,
+ g_callable_info_get_caller_owns((GICallableInfo*) info),
+ return_info,
+ &return_arg))
+ failed = TRUE;
+
+ ++next_rval;
+ }
+
+ if (expected_out_argc > 0) {
+ /* We walk over all args (not just out args) and skip
+ * the non-out args
+ */
+ out_args_pos = 0;
+
+ for (i = 0; i < n_args; i++) {
+ GIDirection direction;
+ GIArgInfo *arg_info;
+ GITypeInfo *arg_type_info;
+
+ arg_info = g_callable_info_get_arg( (GICallableInfo*) info, i);
+ direction = g_arg_info_get_direction(arg_info);
+ if (direction == GI_DIRECTION_IN) {
+ g_base_info_unref( (GIBaseInfo*) arg_info);
+ continue;
+ }
+
+ /* INOUT or OUT */
+ arg_type_info = g_arg_info_get_type(arg_info);
+
+ g_assert(next_rval < n_return_values);
+ g_assert(out_args_pos < expected_out_argc);
+
+ if (!gjs_value_from_g_arg(context,
+ &return_values[next_rval],
+ arg_type_info,
+ out_args[out_args_pos].v_pointer)) {
+ failed = TRUE;
+ }
+
+ /* Free GArgument, the jsval should have ref'd or copied it */
+ if (!gjs_g_arg_release(context,
+ g_arg_info_get_ownership_transfer(arg_info),
+ arg_type_info,
+ out_args[out_args_pos].v_pointer))
+ failed = TRUE;
+
+ ++out_args_pos;
+
+ g_base_info_unref( (GIBaseInfo*) arg_type_info);
+ g_base_info_unref( (GIBaseInfo*) arg_info);
+
+ ++next_rval;
+ }
+ }
+
+ /* if we have 1 return value or out arg, return that item
+ * on its own, otherwise return a JavaScript array with
+ * [return value, out arg 1, out arg 2, ...]
+ */
+ if (n_return_values == 1) {
+ *rval = return_values[0];
+ } else {
+ JSObject *array;
+ array = JS_NewArrayObject(context,
+ n_return_values,
+ return_values);
+ if (array == NULL) {
+ failed = TRUE;
+ } else {
+ *rval = OBJECT_TO_JSVAL(array);
+ }
+ }
+
+ gjs_unroot_value_locations(context, return_values, n_return_values);
+ }
+
+ g_base_info_unref( (GIBaseInfo*) return_info);
+
+ return failed ? JS_FALSE : JS_TRUE;
+ } else {
+ g_assert(error != NULL);
+ gjs_throw(context, "Error invoking %s.%s: %s",
+ g_base_info_get_namespace( (GIBaseInfo*) info),
+ g_base_info_get_name( (GIBaseInfo*) info),
+ error->message);
+ g_error_free(error);
+ return JS_FALSE;
+ }
+}
+
+/* this macro was introduced with JSFastNative in 2007 */
+#ifndef JS_ARGV_CALLEE
+#define JS_ARGV_CALLEE(argv) ((argv)[-2])
+#endif
+
+static JSBool
+function_call(JSContext *context,
+ JSObject *obj, /* "this" object, not the function object */
+ uintN argc,
+ jsval *argv,
+ jsval *rval)
+{
+ Function *priv;
+ JSObject *callee;
+
+ callee = JSVAL_TO_OBJECT(JS_ARGV_CALLEE(argv)); /* Callee is the Function object being called */
+
+ priv = priv_from_js(context, callee);
+ gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Call callee %p priv %p this obj %p %s", callee, priv,
+ obj, JS_GetTypeName(context,
+ JS_TypeOfValue(context, OBJECT_TO_JSVAL(obj))));
+
+ if (priv == NULL)
+ return JS_TRUE; /* we are the prototype, or have the wrong class */
+
+ return gjs_invoke_c_function(context, priv->info, obj, argc, argv, rval);
+}
+
+/* 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
+function_constructor(JSContext *context,
+ JSObject *obj,
+ uintN argc,
+ jsval *argv,
+ jsval *retval)
+{
+ Function *priv;
+
+ priv = g_slice_new0(Function);
+
+ GJS_INC_COUNTER(function);
+
+ g_assert(priv_from_js(context, obj) == NULL);
+ JS_SetPrivate(context, obj, priv);
+
+ gjs_debug_lifecycle(GJS_DEBUG_GFUNCTION,
+ "function constructor, obj %p priv %p", obj, priv);
+
+ return JS_TRUE;
+}
+
+static void
+function_finalize(JSContext *context,
+ JSObject *obj)
+{
+ Function *priv;
+
+ priv = priv_from_js(context, obj);
+ gjs_debug_lifecycle(GJS_DEBUG_GFUNCTION,
+ "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->info)
+ g_base_info_unref( (GIBaseInfo*) priv->info);
+
+ GJS_DEC_COUNTER(function);
+ g_slice_free(Function, 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_function_class = {
+ "GIRepositoryFunction", /* means "new GIRepositoryFunction()" works */
+ JSCLASS_HAS_PRIVATE |
+ JSCLASS_NEW_RESOLVE |
+ JSCLASS_NEW_RESOLVE_GETS_START,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_EnumerateStub,
+ (JSResolveOp) function_new_resolve, /* needs cast since it's the new resolve signature */
+ JS_ConvertStub,
+ function_finalize,
+ NULL,
+ NULL,
+ function_call,
+ NULL, NULL, NULL, NULL, NULL
+};
+
+static JSPropertySpec gjs_function_proto_props[] = {
+ { NULL }
+};
+
+static JSFunctionSpec gjs_function_proto_funcs[] = {
+ { NULL }
+};
+
+static JSObject*
+function_new(JSContext *context,
+ GIFunctionInfo *info)
+{
+ JSObject *function;
+ JSObject *global;
+ Function *priv;
+
+ /* put constructor for GIRepositoryFunction() in the global namespace */
+ global = JS_GetGlobalObject(context);
+
+ if (!gjs_object_has_property(context, global, gjs_function_class.name)) {
+ JSObject *prototype;
+ JSObject *parent_proto;
+
+ parent_proto = NULL;
+
+ prototype = JS_InitClass(context, global,
+ /* parent prototype JSObject* for
+ * prototype; NULL for
+ * Object.prototype
+ */
+ parent_proto,
+ &gjs_function_class,
+ /* constructor for instances (NULL for
+ * none - just name the prototype like
+ * Math - rarely correct)
+ */
+ function_constructor,
+ /* number of constructor args */
+ 0,
+ /* props of prototype */
+ &gjs_function_proto_props[0],
+ /* funcs of prototype */
+ &gjs_function_proto_funcs[0],
+ /* props of constructor, MyConstructor.myprop */
+ NULL,
+ /* funcs of constructor, MyConstructor.myfunc() */
+ NULL);
+ if (prototype == NULL)
+ gjs_fatal("Can't init class %s", gjs_function_class.name);
+
+ g_assert(gjs_object_has_property(context, global, gjs_function_class.name));
+
+ gjs_debug(GJS_DEBUG_GFUNCTION, "Initialized class %s prototype %p",
+ gjs_function_class.name, prototype);
+ }
+
+ function = JS_ConstructObject(context, &gjs_function_class, NULL, NULL);
+ if (function == NULL) {
+ gjs_debug(GJS_DEBUG_GFUNCTION, "Failed to construct function");
+ return NULL;
+ }
+
+ priv = priv_from_js(context, function);
+ priv->info = info;
+ g_base_info_ref( (GIBaseInfo*) info );
+
+ return function;
+}
+
+JSObject*
+gjs_define_function(JSContext *context,
+ JSObject *in_object,
+ GIFunctionInfo *info)
+{
+ JSObject *function;
+ JSContext *load_context;
+
+ load_context = gjs_runtime_get_load_context(JS_GetRuntime(context));
+
+ function = function_new(load_context, info);
+ if (function == NULL) {
+ gjs_move_exception(load_context, context);
+ return NULL;
+ }
+
+ if (!JS_DefineProperty(context, in_object,
+ g_base_info_get_name( (GIBaseInfo*) info),
+ OBJECT_TO_JSVAL(function),
+ NULL, NULL,
+ GJS_MODULE_PROP_FLAGS)) {
+ gjs_debug(GJS_DEBUG_GFUNCTION, "Failed to define function");
+ return NULL;
+ }
+
+ return function;
+}
diff --git a/gi/function.h b/gi/function.h
new file mode 100644
index 00000000..cb8ff886
--- /dev/null
+++ b/gi/function.h
@@ -0,0 +1,48 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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_FUNCTION_H__
+#define __GJS_FUNCTION_H__
+
+#include <glib.h>
+
+#include <jsapi.h>
+
+#include <girepository.h>
+
+G_BEGIN_DECLS
+
+JSObject* gjs_define_function (JSContext *context,
+ JSObject *in_object,
+ GIFunctionInfo *info);
+JSBool gjs_invoke_c_function (JSContext *context,
+ GIFunctionInfo *info,
+ JSObject *obj,
+ uintN argc,
+ jsval *argv,
+ jsval *rval);
+
+
+G_END_DECLS
+
+#endif /* __GJS_FUNCTION_H__ */
diff --git a/gi/keep-alive.c b/gi/keep-alive.c
new file mode 100644
index 00000000..f2c62719
--- /dev/null
+++ b/gi/keep-alive.c
@@ -0,0 +1,440 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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 "keep-alive.h"
+
+#include <gjs/jsapi-util.h>
+
+#include <util/log.h>
+#include <util/glib.h>
+
+#include <jsapi.h>
+
+typedef struct {
+ GjsUnrootedFunc notify;
+ JSObject *child;
+ void *data;
+} Child;
+
+typedef struct {
+ GHashTable *children;
+ unsigned int inside_finalize : 1;
+ unsigned int inside_trace : 1;
+} KeepAlive;
+
+static struct JSClass gjs_keep_alive_class;
+
+GJS_DEFINE_PRIV_FROM_JS(KeepAlive, gjs_keep_alive_class)
+
+static guint
+child_hash(gconstpointer v)
+{
+ const Child *child = v;
+
+ return
+ GPOINTER_TO_UINT(child->notify) ^
+ GPOINTER_TO_UINT(child->child) ^
+ GPOINTER_TO_UINT(child->data);
+}
+
+static gboolean
+child_equal (gconstpointer v1,
+ gconstpointer v2)
+{
+ const Child *child1 = v1;
+ const Child *child2 = v2;
+
+ /* notify is most likely to be equal, so check it last */
+ return child1->data == child2->data &&
+ child1->child == child2->child &&
+ child1->notify == child2->notify;
+}
+
+static void
+child_free(void *data)
+{
+ Child *child = data;
+ g_slice_free(Child, child);
+}
+
+/* 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
+keep_alive_constructor(JSContext *context,
+ JSObject *obj,
+ uintN argc,
+ jsval *argv,
+ jsval *retval)
+{
+ KeepAlive *priv;
+
+ priv = g_slice_new0(KeepAlive);
+ priv->children = g_hash_table_new_full(child_hash, child_equal, NULL, child_free);
+
+ g_assert(priv_from_js(context, obj) == NULL);
+ JS_SetPrivate(context, obj, priv);
+
+ gjs_debug_lifecycle(GJS_DEBUG_KEEP_ALIVE,
+ "keep_alive constructor, obj %p priv %p", obj, priv);
+
+ return JS_TRUE;
+}
+
+static void
+keep_alive_finalize(JSContext *context,
+ JSObject *obj)
+{
+ KeepAlive *priv;
+ void *key;
+ void *value;
+
+ priv = priv_from_js(context, obj);
+
+ gjs_debug_lifecycle(GJS_DEBUG_KEEP_ALIVE,
+ "keep_alive finalizing, obj %p priv %p", obj, priv);
+
+ if (priv == NULL)
+ return; /* we are the prototype, not a real instance, so constructor never called */
+
+ priv->inside_finalize = TRUE;
+
+ while (gjs_g_hash_table_steal_one(priv->children,
+ &key, &value)) {
+ Child *child = value;
+ if (child->notify)
+ (* child->notify) (child->child, child->data);
+
+ child_free(child);
+ }
+
+ g_hash_table_destroy(priv->children);
+ g_slice_free(KeepAlive, priv);
+}
+
+static void
+trace_foreach(void *key,
+ void *value,
+ void *data)
+{
+ Child *child = value;
+ JSTracer *tracer = data;
+
+ if (child->child != NULL) {
+ JS_CallTracer(tracer, child->child, JSTRACE_OBJECT);
+ }
+}
+
+static void
+keep_alive_trace(JSTracer *tracer,
+ JSObject *obj)
+{
+ KeepAlive *priv;
+
+ priv = priv_from_js(tracer->context, obj);
+
+ if (priv == NULL) /* prototype */
+ return;
+
+ g_assert(!priv->inside_trace);
+ priv->inside_trace = TRUE;
+ g_hash_table_foreach(priv->children, trace_foreach, tracer);
+ priv->inside_trace = FALSE;
+}
+
+/* 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_keep_alive_class = {
+ "__private_GjsKeepAlive", /* means "new __private_GjsKeepAlive()" works */
+ JSCLASS_HAS_PRIVATE |
+ JSCLASS_MARK_IS_TRACE, /* TraceOp not MarkOp */
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_EnumerateStub,
+ JS_ResolveStub,
+ JS_ConvertStub,
+ keep_alive_finalize,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ JS_CLASS_TRACE(keep_alive_trace),
+ NULL
+};
+
+static JSPropertySpec gjs_keep_alive_proto_props[] = {
+ { NULL }
+};
+
+static JSFunctionSpec gjs_keep_alive_proto_funcs[] = {
+ { NULL }
+};
+
+JSObject*
+gjs_keep_alive_new(JSContext *context)
+{
+ JSObject *keep_alive;
+ JSObject *global;
+
+ g_assert(context != NULL);
+
+ /* put constructor in the global namespace */
+ global = JS_GetGlobalObject(context);
+
+ g_assert(global != NULL);
+
+ if (!gjs_object_has_property(context, global, gjs_keep_alive_class.name)) {
+ JSObject *prototype;
+
+ gjs_debug(GJS_DEBUG_KEEP_ALIVE,
+ "Initializing keep-alive class in context %p global %p",
+ context, global);
+
+ prototype = JS_InitClass(context, global,
+ /* parent prototype JSObject* for
+ * prototype; NULL for
+ * Object.prototype
+ */
+ NULL,
+ &gjs_keep_alive_class,
+ /* constructor for instances (NULL for
+ * none - just name the prototype like
+ * Math - rarely correct)
+ */
+ keep_alive_constructor,
+ /* number of constructor args */
+ 0,
+ /* props of prototype */
+ &gjs_keep_alive_proto_props[0],
+ /* funcs of prototype */
+ &gjs_keep_alive_proto_funcs[0],
+ /* props of constructor, MyConstructor.myprop */
+ NULL,
+ /* funcs of constructor, MyConstructor.myfunc() */
+ NULL);
+ if (prototype == NULL)
+ gjs_fatal("Can't init class %s", gjs_keep_alive_class.name);
+
+ g_assert(gjs_object_has_property(context, global, gjs_keep_alive_class.name));
+
+ gjs_debug(GJS_DEBUG_KEEP_ALIVE, "Initialized class %s prototype %p",
+ gjs_keep_alive_class.name, prototype);
+ }
+
+ gjs_debug(GJS_DEBUG_KEEP_ALIVE,
+ "Creating new keep-alive object for context %p global %p",
+ context, global);
+
+ /* Without the "global" parent object, this craters inside of
+ * xulrunner because in jsobj.c:js_ConstructObject it looks up
+ * VOID as the constructor. Exploring in gdb, it is walking up
+ * the scope chain in a way that involves scary xpconnect-looking
+ * stuff. Having "global" as parent seems to fix it. But, it would
+ * not hurt to understand this better.
+ */
+ keep_alive = JS_ConstructObject(context, &gjs_keep_alive_class, NULL, global);
+ if (keep_alive == NULL) {
+ gjs_log_exception(context, NULL);
+ gjs_fatal("Failed to create keep_alive object");
+ }
+
+ return keep_alive;
+}
+
+void
+gjs_keep_alive_add_child(JSContext *context,
+ JSObject *keep_alive,
+ GjsUnrootedFunc notify,
+ JSObject *obj,
+ void *data)
+{
+ KeepAlive *priv;
+ Child *child;
+
+ g_assert(keep_alive != NULL);
+
+ priv = priv_from_js(context, keep_alive);
+
+ g_assert(priv != NULL);
+
+ g_return_if_fail(!priv->inside_trace);
+ g_return_if_fail(!priv->inside_finalize);
+
+ child = g_slice_new0(Child);
+ child->notify = notify;
+ child->child = obj;
+ child->data = data;
+
+ /* this is sort of an expensive check, probably */
+ g_return_if_fail(g_hash_table_lookup(priv->children, child) == NULL);
+
+ /* this overwrites any identical-by-value previous child,
+ * but there should not be one.
+ */
+ g_hash_table_replace(priv->children, child, child);
+}
+
+void
+gjs_keep_alive_remove_child(JSContext *context,
+ JSObject *keep_alive,
+ GjsUnrootedFunc notify,
+ JSObject *obj,
+ void *data)
+{
+ KeepAlive *priv;
+ Child child;
+
+ priv = priv_from_js(context, keep_alive);
+
+ g_assert(priv != NULL);
+
+ g_return_if_fail(!priv->inside_trace);
+ g_return_if_fail(!priv->inside_finalize);
+
+ child.notify = notify;
+ child.child = obj;
+ child.data = data;
+
+ g_hash_table_remove(priv->children,
+ &child);
+}
+
+#define GLOBAL_KEEP_ALIVE_NAME "__gc_this_on_context_destroy"
+
+JSObject*
+gjs_keep_alive_get_global(JSContext *context)
+{
+ jsval value;
+ JSObject *global;
+
+ global = JS_GetGlobalObject(context);
+
+ gjs_object_get_property(context, global, GLOBAL_KEEP_ALIVE_NAME, &value);
+
+ if (JSVAL_IS_OBJECT(value))
+ return JSVAL_TO_OBJECT(value);
+
+ return NULL;
+}
+
+static JSObject*
+gjs_keep_alive_create_in_global(JSContext *context)
+{
+ JSObject *keep_alive;
+ JSObject *global;
+
+ global = JS_GetGlobalObject(context);
+
+ keep_alive = gjs_keep_alive_new(context);
+
+ if (!JS_DefineProperty(context, global,
+ GLOBAL_KEEP_ALIVE_NAME,
+ OBJECT_TO_JSVAL(keep_alive),
+ NULL, NULL,
+ /* No ENUMERATE since this is a hidden
+ * implementation detail kind of property
+ */
+ JSPROP_READONLY | JSPROP_PERMANENT))
+ gjs_fatal("no memory to define keep_alive property");
+
+ return keep_alive;
+}
+
+void
+gjs_keep_alive_add_global_child(JSContext *context,
+ GjsUnrootedFunc notify,
+ JSObject *child,
+ void *data)
+{
+ JSObject *keep_alive;
+
+ keep_alive = gjs_keep_alive_get_global(context);
+
+ if (!keep_alive)
+ keep_alive = gjs_keep_alive_create_in_global(context);
+
+ if (!keep_alive)
+ gjs_fatal("could not create keep_alive on global object, no memory?");
+
+ gjs_keep_alive_add_child(context,
+ keep_alive,
+ notify, child, data);
+}
+
+void
+gjs_keep_alive_remove_global_child(JSContext *context,
+ GjsUnrootedFunc notify,
+ JSObject *child,
+ void *data)
+{
+ JSObject *keep_alive;
+
+ keep_alive = gjs_keep_alive_get_global(context);
+
+ if (!keep_alive)
+ gjs_fatal("no keep_alive property on the global object, have you "
+ "previously added this child?");
+
+ gjs_keep_alive_remove_child(context,
+ gjs_keep_alive_get_global(context),
+ notify, child, data);
+}
+
+JSObject*
+gjs_keep_alive_get_for_load_context(JSRuntime *runtime)
+{
+ JSContext *context;
+ JSObject *keep_alive;
+
+ context = gjs_runtime_get_load_context(runtime);
+
+ g_assert(context != NULL);
+
+ keep_alive = gjs_keep_alive_get_global(context);
+
+ if (!keep_alive)
+ keep_alive = gjs_keep_alive_create_in_global(context);
+
+ if (!keep_alive)
+ gjs_fatal("could not create keep_alive on global object, no memory?");
+
+ return keep_alive;
+}
diff --git a/gi/keep-alive.h b/gi/keep-alive.h
new file mode 100644
index 00000000..ab2a5a99
--- /dev/null
+++ b/gi/keep-alive.h
@@ -0,0 +1,83 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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_KEEP_ALIVE_H__
+#define __GJS_KEEP_ALIVE_H__
+
+#include <glib.h>
+#include <jsapi.h>
+
+G_BEGIN_DECLS
+
+/* This is an alternative to JS_AddRoot().
+ *
+ * This "keep alive" object holds a collection of child objects and
+ * traces them when GC occurs. If the keep alive object is collected,
+ * it calls a notification function on all the child objects.
+ *
+ * The "global keep alive" is stuck on the global object as a property,
+ * so its children only get notified when the entire JSContext is
+ * blown away (or its global object replaced, I suppose, but that
+ * won't happen afaik).
+ *
+ * The problem with JS_AddRoot() is that it has no notification when the
+ * JSContext is destroyed. Also, it can be annoying to wrap a C type purely
+ * to put a finalizer on it, this lets you avoid that.
+ *
+ * All three fields (notify, child, and data) are optional, so you can have
+ * no JSObject - just notification+data - and you can have no notifier,
+ * only the keep-alive capability.
+ */
+
+typedef void (* GjsUnrootedFunc) (JSObject *obj,
+ void *data);
+
+
+JSObject* gjs_keep_alive_new (JSContext *context);
+void gjs_keep_alive_add_child (JSContext *context,
+ JSObject *keep_alive,
+ GjsUnrootedFunc notify,
+ JSObject *child,
+ void *data);
+void gjs_keep_alive_remove_child (JSContext *context,
+ JSObject *keep_alive,
+ GjsUnrootedFunc notify,
+ JSObject *child,
+ void *data);
+JSObject* gjs_keep_alive_get_global (JSContext *context);
+void gjs_keep_alive_add_global_child (JSContext *context,
+ GjsUnrootedFunc notify,
+ JSObject *child,
+ void *data);
+void gjs_keep_alive_remove_global_child (JSContext *context,
+ GjsUnrootedFunc notify,
+ JSObject *child,
+ void *data);
+JSObject* gjs_keep_alive_get_for_load_context (JSRuntime *runtime);
+
+
+
+
+G_END_DECLS
+
+#endif /* __GJS_KEEP_ALIVE_H__ */
diff --git a/gi/native.c b/gi/native.c
new file mode 100644
index 00000000..36efb1fd
--- /dev/null
+++ b/gi/native.c
@@ -0,0 +1,183 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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 <gmodule.h>
+
+#include <util/log.h>
+
+#include "native.h"
+#include <gjs/jsapi-util.h>
+
+typedef struct {
+ GjsDefineModuleFunc func;
+ GjsNativeFlags flags;
+} GjsNativeModule;
+
+static GHashTable *modules = NULL;
+
+static void
+native_module_free(void *data)
+{
+ g_slice_free(GjsNativeModule, data);
+}
+
+void
+gjs_register_native_module (const char *module_id,
+ GjsDefineModuleFunc func,
+ GjsNativeFlags flags)
+{
+ GjsNativeModule *module;
+
+ if (modules == NULL) {
+ modules = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, native_module_free);
+ }
+
+ if (g_hash_table_lookup(modules, module_id) != NULL) {
+ g_warning("A second native module tried to register the same id '%s'",
+ module_id);
+ return;
+ }
+
+ module = g_slice_new(GjsNativeModule);
+ module->func = func;
+ module->flags = flags;
+
+ g_hash_table_replace(modules,
+ g_strdup(module_id),
+ module);
+
+ gjs_debug(GJS_DEBUG_NATIVE,
+ "Registered native JS module '%s'",
+ module_id);
+}
+
+static JSObject*
+module_get_parent(JSContext *context,
+ JSObject *module_obj)
+{
+ jsval value;
+
+ if (gjs_object_get_property(context, module_obj, "__parentModule__", &value) &&
+ value != JSVAL_NULL &&
+ JSVAL_IS_OBJECT(value)) {
+ return JSVAL_TO_OBJECT(value);
+ } else {
+ return NULL;
+ }
+}
+
+JSBool
+gjs_import_native_module(JSContext *context,
+ JSObject *module_obj,
+ const char *filename,
+ GjsNativeFlags *flags_p)
+{
+ GModule *gmodule;
+ GString *module_id;
+ JSObject *parent;
+ GjsNativeModule *native_module;
+
+ if (flags_p)
+ *flags_p = 0;
+
+ /* Vital to load in global scope so any dependent libs
+ * are loaded into the main app. We don't want a second
+ * private copy of GTK or something.
+ */
+ gmodule = g_module_open(filename, 0);
+ if (gmodule == NULL) {
+ gjs_throw(context,
+ "Failed to load '%s': %s",
+ filename, g_module_error());
+ return JS_FALSE;
+ }
+
+ /* dlopen() as a side effect should have registered us as
+ * a native module. We just have to reverse-engineer
+ * the module id from module_obj.
+ */
+ module_id = g_string_new(NULL);
+ parent = module_obj;
+ while (parent != NULL) {
+ jsval value;
+
+ if (gjs_object_get_property(context, parent, "__moduleName__", &value) &&
+ JSVAL_IS_STRING(value)) {
+ const char *name;
+ name = gjs_string_get_ascii(value);
+
+ if (module_id->len > 0)
+ g_string_prepend(module_id, ".");
+
+ g_string_prepend(module_id, name);
+ }
+
+ /* Move up to parent */
+ parent = module_get_parent(context, parent);
+ }
+
+ gjs_debug(GJS_DEBUG_NATIVE,
+ "Defining native module '%s'",
+ module_id->str);
+
+ if (modules != NULL)
+ native_module = g_hash_table_lookup(modules, module_id->str);
+ else
+ native_module = NULL;
+
+ if (native_module == NULL) {
+ gjs_throw(context,
+ "No native module '%s' has registered itself",
+ module_id->str);
+ g_string_free(module_id, TRUE);
+ g_module_close(gmodule);
+ return JS_FALSE;
+ }
+
+ g_string_free(module_id, TRUE);
+
+ if (flags_p)
+ *flags_p = native_module->flags;
+
+ /* make the module resident, which makes the close() a no-op
+ * (basically we leak the module permanently)
+ */
+ g_module_make_resident(gmodule);
+ g_module_close(gmodule);
+
+ if (native_module->flags & GJS_NATIVE_SUPPLIES_MODULE_OBJ) {
+
+ /* In this case we just throw away "module_obj" eventually,
+ * since the native module defines itself in the parent of
+ * module_obj directly.
+ */
+ parent = module_get_parent(context, module_obj);
+ return (* native_module->func) (context, parent);
+ } else {
+ return (* native_module->func) (context, module_obj);
+ }
+}
+
diff --git a/gi/native.h b/gi/native.h
new file mode 100644
index 00000000..0542ef0c
--- /dev/null
+++ b/gi/native.h
@@ -0,0 +1,91 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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_NATIVE_H__
+#define __GJS_NATIVE_H__
+
+#include <glib.h>
+
+#include <jsapi.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+ /* This means that the GjsDefineModuleFunc defines the module
+ * name in the parent module, as opposed to the normal process
+ * where the GjsDefineModuleFunc defines module contents. When
+ * importing imports.foo.bar, this flag means the native module is
+ * given foo and defines bar in it, while normally the native
+ * module is given bar and defines stuff in that.
+ *
+ * The purpose of this is to allow a module with lazy properties
+ * by allowing module objects to be custom classes. It's used for
+ * the gobject-introspection module for example.
+ */
+ GJS_NATIVE_SUPPLIES_MODULE_OBJ = 1 << 0
+
+} GjsNativeFlags;
+
+/*
+ * In a native module, you define a GjsDefineModuleFunc that
+ * adds your stuff to module_obj.
+ *
+ * You then declare GJS_REGISTER_NATIVE_MODULE("my.module.path", my_module_func)
+ *
+ * This declaration will call gjs_register_native_module() when your
+ * module is dlopen'd. We can't just use a well-known symbol name
+ * in your module, because we need to dlopen modules with
+ * global symbols.
+ */
+
+typedef JSBool (* GjsDefineModuleFunc) (JSContext *context,
+ JSObject *module_obj);
+
+#define GJS_REGISTER_NATIVE_MODULE_WITH_FLAGS(module_id_string, module_func, flags) \
+ __attribute__((constructor)) static void \
+ register_native_module (void) \
+ { \
+ gjs_register_native_module(module_id_string, module_func, flags); \
+ }
+
+
+#define GJS_REGISTER_NATIVE_MODULE(module_id_string, module_func) \
+ GJS_REGISTER_NATIVE_MODULE_WITH_FLAGS(module_id_string, module_func, 0)
+
+/* called in constructor function on dlopen() load */
+void gjs_register_native_module (const char *module_id,
+ GjsDefineModuleFunc func,
+ GjsNativeFlags flags);
+
+/* called by importer.c to load a native module once it finds
+ * it in the search path
+ */
+JSBool gjs_import_native_module (JSContext *context,
+ JSObject *module_obj,
+ const char *filename,
+ GjsNativeFlags *flags_p);
+
+
+G_END_DECLS
+
+#endif /* __GJS_NATIVE_H__ */
diff --git a/gi/ns.c b/gi/ns.c
new file mode 100644
index 00000000..d8f6574d
--- /dev/null
+++ b/gi/ns.c
@@ -0,0 +1,321 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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 "ns.h"
+#include "repo.h"
+#include "param.h"
+#include <gjs/mem.h>
+
+#include <util/log.h>
+
+#include <jsapi.h>
+
+#include <girepository.h>
+
+#include <string.h>
+
+typedef struct {
+ GIRepository *repo;
+ char *namespace;
+
+} Ns;
+
+static struct JSClass gjs_ns_class;
+
+GJS_DEFINE_PRIV_FROM_JS(Ns, gjs_ns_class)
+
+/*
+ * 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 function 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
+ns_new_resolve(JSContext *context,
+ JSObject *obj,
+ jsval id,
+ uintN flags,
+ JSObject **objp)
+{
+ Ns *priv;
+ const char *name;
+ GIRepository *repo;
+ GIBaseInfo *info;
+ JSContext *load_context;
+
+ *objp = NULL;
+
+ if (!gjs_get_string_id(id, &name))
+ return JS_TRUE; /* not resolved, but no error */
+
+ /* let Object.prototype resolve these */
+ if (strcmp(name, "valueOf") == 0 ||
+ strcmp(name, "toString") == 0)
+ return JS_TRUE;
+
+ priv = priv_from_js(context, obj);
+ gjs_debug_jsprop(GJS_DEBUG_GNAMESPACE, "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 */
+
+ load_context = gjs_runtime_get_load_context(JS_GetRuntime(context));
+
+ repo = g_irepository_get_default();
+
+ info = g_irepository_find_by_name(repo, priv->namespace, name);
+ if (info == NULL) {
+ /* Special-case fallback hack for GParamSpec */
+ if (strcmp(name, "ParamSpec") == 0 &&
+ strcmp(priv->namespace, "GLib") == 0) {
+ gjs_define_param_class(load_context,
+ obj,
+ NULL);
+ if (gjs_move_exception(load_context, context)) {
+ return JS_FALSE;
+ } else {
+ *objp = obj; /* we defined the property in this object */
+ return JS_TRUE;
+ }
+ } else {
+ gjs_throw(context,
+ "No symbol '%s' in namespace '%s'",
+ name, priv->namespace);
+ return JS_FALSE;
+ }
+ }
+
+ gjs_debug(GJS_DEBUG_GNAMESPACE,
+ "Found info type %s for '%s' in namespace '%s'",
+ gjs_info_type_name(g_base_info_get_type(info)),
+ g_base_info_get_name(info),
+ g_base_info_get_namespace(info));
+
+ if (gjs_define_info(load_context, obj, info)) {
+ g_base_info_unref(info);
+ *objp = obj; /* we defined the property in this object */
+ return JS_TRUE;
+ } else {
+ gjs_debug(GJS_DEBUG_GNAMESPACE,
+ "Failed to define info '%s'",
+ g_base_info_get_name(info));
+
+ g_base_info_unref(info);
+
+ if (!gjs_move_exception(load_context, context)) {
+ /* set an exception if none was set */
+ gjs_throw(context,
+ "Defining info failed but no exception set");
+ }
+
+ return JS_FALSE;
+ }
+}
+
+/* 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
+ns_constructor(JSContext *context,
+ JSObject *obj,
+ uintN argc,
+ jsval *argv,
+ jsval *retval)
+{
+ Ns *priv;
+
+ priv = g_slice_new0(Ns);
+
+ GJS_INC_COUNTER(ns);
+
+ g_assert(priv_from_js(context, obj) == NULL);
+ JS_SetPrivate(context, obj, priv);
+
+ gjs_debug_lifecycle(GJS_DEBUG_GNAMESPACE, "ns constructor, obj %p priv %p", obj, priv);
+
+ return JS_TRUE;
+}
+
+static void
+ns_finalize(JSContext *context,
+ JSObject *obj)
+{
+ Ns *priv;
+
+ priv = priv_from_js(context, obj);
+ gjs_debug_lifecycle(GJS_DEBUG_GNAMESPACE,
+ "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->namespace)
+ g_free(priv->namespace);
+ if (priv->repo)
+ g_object_unref(priv->repo);
+
+ GJS_DEC_COUNTER(ns);
+ g_slice_free(Ns, 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_ns_class = {
+ "GIRepositoryNamespace",
+ JSCLASS_HAS_PRIVATE |
+ JSCLASS_NEW_RESOLVE |
+ JSCLASS_NEW_RESOLVE_GETS_START,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_EnumerateStub,
+ (JSResolveOp) ns_new_resolve, /* needs cast since it's the new resolve signature */
+ JS_ConvertStub,
+ ns_finalize,
+ JSCLASS_NO_OPTIONAL_MEMBERS
+};
+
+static JSPropertySpec gjs_ns_proto_props[] = {
+ { NULL }
+};
+
+static JSFunctionSpec gjs_ns_proto_funcs[] = {
+ { NULL }
+};
+
+static JSObject*
+ns_new(JSContext *context,
+ const char *ns_name,
+ GIRepository *repo)
+{
+ JSObject *ns;
+ JSObject *global;
+ Ns *priv;
+
+ /* put constructor in the global namespace */
+ global = JS_GetGlobalObject(context);
+
+ if (!gjs_object_has_property(context, global, gjs_ns_class.name)) {
+ JSObject *prototype;
+ prototype = JS_InitClass(context, global,
+ /* parent prototype JSObject* for
+ * prototype; NULL for
+ * Object.prototype
+ */
+ NULL,
+ &gjs_ns_class,
+ /* constructor for instances (NULL for
+ * none - just name the prototype like
+ * Math - rarely correct)
+ */
+ ns_constructor,
+ /* number of constructor args */
+ 0,
+ /* props of prototype */
+ &gjs_ns_proto_props[0],
+ /* funcs of prototype */
+ &gjs_ns_proto_funcs[0],
+ /* props of constructor, MyConstructor.myprop */
+ NULL,
+ /* funcs of constructor, MyConstructor.myfunc() */
+ NULL);
+ if (prototype == NULL)
+ gjs_fatal("Can't init class %s", gjs_ns_class.name);
+
+ g_assert(gjs_object_has_property(context, global, gjs_ns_class.name));
+
+ gjs_debug(GJS_DEBUG_GNAMESPACE, "Initialized class %s prototype %p",
+ gjs_ns_class.name, prototype);
+ }
+
+ ns = JS_ConstructObject(context, &gjs_ns_class, NULL, NULL);
+ if (ns == NULL)
+ gjs_fatal("No memory to create ns object");
+
+ priv = priv_from_js(context, ns);
+ priv->repo = g_object_ref(repo);
+ priv->namespace = g_strdup(ns_name);
+
+ return ns;
+}
+
+JSObject*
+gjs_define_ns(JSContext *context,
+ JSObject *in_object,
+ const char *ns_name,
+ GIRepository *repo)
+{
+ JSObject *ns;
+ char *fixed_ns_name, *unfixed_ns_name;
+
+ /* The idea here is to always define properties for both
+ * fixed (MyModule) and unfixed (myModule) namespace name
+ * format
+ */
+
+ fixed_ns_name = gjs_fix_ns_name(ns_name);
+ unfixed_ns_name = gjs_unfix_ns_name(ns_name);
+
+ ns = ns_new(context, fixed_ns_name, repo);
+
+ if (!JS_DefineProperty(context, in_object,
+ fixed_ns_name, OBJECT_TO_JSVAL(ns),
+ NULL, NULL,
+ GJS_MODULE_PROP_FLAGS))
+ gjs_fatal("no memory to define ns property");
+
+ if (!JS_DefineProperty(context, in_object,
+ unfixed_ns_name, OBJECT_TO_JSVAL(ns),
+ NULL, NULL,
+ GJS_MODULE_PROP_FLAGS))
+ gjs_fatal("no memory to define ns property");
+
+ gjs_debug(GJS_DEBUG_GNAMESPACE,
+ "Defined namespace '%s' %p in GIRepository %p", fixed_ns_name, ns, in_object);
+
+ g_free(fixed_ns_name);
+ g_free(unfixed_ns_name);
+
+ return ns;
+}
diff --git a/gi/ns.h b/gi/ns.h
new file mode 100644
index 00000000..d7eb7509
--- /dev/null
+++ b/gi/ns.h
@@ -0,0 +1,42 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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_NS_H__
+#define __GJS_NS_H__
+
+#include <glib.h>
+
+#include <jsapi.h>
+
+#include <girepository.h>
+
+G_BEGIN_DECLS
+
+JSObject* gjs_define_ns(JSContext *context,
+ JSObject *in_object,
+ const char *ns_name,
+ GIRepository *repo);
+
+G_END_DECLS
+
+#endif /* __GJS_NS_H__ */
diff --git a/gi/object.c b/gi/object.c
new file mode 100644
index 00000000..089810fb
--- /dev/null
+++ b/gi/object.c
@@ -0,0 +1,1469 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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 <string.h>
+
+#include "object.h"
+#include "arg.h"
+#include "repo.h"
+#include "function.h"
+#include "value.h"
+#include "keep-alive.h"
+
+#include <gjs/mem.h>
+
+#include <util/log.h>
+
+#include <jsapi.h>
+
+#include <girepository.h>
+
+typedef struct {
+ GIObjectInfo *info;
+ GObject *gobj; /* NULL if we are the prototype and not an instance */
+ JSObject *keep_alive; /* NULL if we are not added to it */
+ GType gtype;
+} ObjectInstance;
+
+static ObjectInstance unthreadsafe_template_for_constructor = { NULL, NULL };
+
+static struct JSClass gjs_object_instance_class;
+
+GJS_DEFINE_DYNAMIC_PRIV_FROM_JS(ObjectInstance, gjs_object_instance_class)
+
+static JSObject* peek_js_obj (JSContext *context,
+ GObject *gobj);
+static void set_js_obj (JSContext *context,
+ GObject *gobj,
+ JSObject *obj);
+
+typedef enum {
+ SOME_ERROR_OCCURRED = JS_FALSE,
+ NO_SUCH_G_PROPERTY,
+ VALUE_WAS_SET
+} ValueFromPropertyResult;
+
+static ValueFromPropertyResult
+init_g_param_from_property(JSContext *context,
+ const char *js_prop_name,
+ jsval js_value,
+ GType gtype,
+ GParameter *parameter)
+{
+ char *gname;
+ GParamSpec *param_spec;
+ void *klass;
+
+ gname = gjs_hyphen_from_camel(js_prop_name);
+ gjs_debug_jsprop(GJS_DEBUG_GOBJECT,
+ "Hyphen name %s on %s", gname, g_type_name(gtype));
+
+ klass = g_type_class_ref(gtype);
+ param_spec = g_object_class_find_property(G_OBJECT_CLASS(klass),
+ gname);
+ g_type_class_unref(klass);
+ g_free(gname);
+
+ if (param_spec == NULL) {
+ /* not a GObject prop, so nothing else to do */
+ return NO_SUCH_G_PROPERTY;
+ }
+
+ if ((param_spec->flags & G_PARAM_WRITABLE) == 0) {
+ /* prevent setting the prop even in JS */
+ gjs_throw(context, "Property %s (GObject %s) is not writable",
+ js_prop_name, param_spec->name);
+ return SOME_ERROR_OCCURRED;
+ }
+
+ gjs_debug_jsprop(GJS_DEBUG_GOBJECT,
+ "Syncing %s to GObject prop %s",
+ js_prop_name, param_spec->name);
+
+ g_value_init(&parameter->value, G_PARAM_SPEC_VALUE_TYPE(param_spec));
+ if (!gjs_value_to_g_value(context, js_value, &parameter->value)) {
+ g_value_unset(&parameter->value);
+ return SOME_ERROR_OCCURRED;
+ }
+
+ parameter->name = param_spec->name;
+
+ return VALUE_WAS_SET;
+}
+
+/* a hook on getting a property; set value_p to override property's value.
+ * Return value is JS_FALSE on OOM/exception.
+ */
+static JSBool
+object_instance_get_prop(JSContext *context,
+ JSObject *obj,
+ jsval id,
+ jsval *value_p)
+{
+ ObjectInstance *priv;
+ const char *name;
+ char *gname;
+ GParamSpec *param;
+ GValue gvalue = { 0, };
+
+ 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_GOBJECT,
+ "Get prop '%s' hook obj %p priv %p", name, obj, priv);
+
+ if (priv == NULL)
+ return JS_FALSE; /* wrong class passed in */
+ if (priv->gobj == NULL)
+ return JS_TRUE; /* prototype, not an instance. */
+
+ gname = gjs_hyphen_from_camel(name);
+ param = g_object_class_find_property(G_OBJECT_GET_CLASS(priv->gobj),
+ gname);
+ g_free(gname);
+
+ if (param == NULL) {
+ /* leave value_p as it was */
+ return JS_TRUE;
+ }
+
+ if ((param->flags & G_PARAM_READABLE) == 0) {
+ return JS_TRUE;
+ }
+
+ gjs_debug_jsprop(GJS_DEBUG_GOBJECT,
+ "Overriding %s with GObject prop %s",
+ name, param->name);
+
+ g_value_init(&gvalue, G_PARAM_SPEC_VALUE_TYPE(param));
+ g_object_get_property(priv->gobj, param->name,
+ &gvalue);
+ if (!gjs_value_from_g_value(context, value_p, &gvalue)) {
+ g_value_unset(&gvalue);
+ return JS_FALSE;
+ }
+ g_value_unset(&gvalue);
+
+ return JS_TRUE;
+}
+
+/* a hook on setting a property; set value_p to override property value to
+ * be set. Return value is JS_FALSE on OOM/exception.
+ */
+static JSBool
+object_instance_set_prop(JSContext *context,
+ JSObject *obj,
+ jsval id,
+ jsval *value_p)
+{
+ ObjectInstance *priv;
+ const char *name;
+ GParameter param = { NULL, { 0, }};
+
+ 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_GOBJECT,
+ "Set prop '%s' hook obj %p priv %p", name, obj, priv);
+
+ if (priv == NULL)
+ return JS_FALSE; /* wrong class passed in */
+ if (priv->gobj == NULL)
+ return JS_TRUE; /* prototype, not an instance. */
+
+ switch (init_g_param_from_property(context, name,
+ *value_p,
+ G_TYPE_FROM_INSTANCE(priv->gobj),
+ &param)) {
+ case SOME_ERROR_OCCURRED:
+ return JS_FALSE;
+ case NO_SUCH_G_PROPERTY:
+ return JS_TRUE;
+ case VALUE_WAS_SET:
+ break;
+ }
+
+ g_object_set_property(priv->gobj, param.name,
+ &param.value);
+
+ g_value_unset(&param.value);
+
+ /* note that the prop will also have been set in JS, which I think
+ * is OK, since we hook get and set so will always override that
+ * value. We could also use JS_DefineProperty though and specify a
+ * getter/setter maybe, don't know if that is better.
+ */
+
+ return JS_TRUE;
+}
+
+/*
+ * 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 object 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
+object_instance_new_resolve(JSContext *context,
+ JSObject *obj,
+ jsval id,
+ uintN flags,
+ JSObject **objp)
+{
+ ObjectInstance *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_GOBJECT,
+ "Resolve prop '%s' hook obj %p priv %p gobj %p %s",
+ name, obj, priv, priv ? priv->gobj : NULL,
+ (priv && priv->gobj) ?
+ g_type_name_from_instance((GTypeInstance*) priv->gobj) : "(type unknown)");
+
+ if (priv == NULL)
+ return JS_FALSE; /* we are the wrong class */
+
+ if (priv->gobj == NULL) {
+ /* We are the prototype, so look for methods and other class properties */
+ GIFunctionInfo *method_info;
+
+ /* find_method does not look at methods on parent classes,
+ * we rely on javascript to walk up the __proto__ chain
+ * and find those and define them in the right prototype.
+ */
+ method_info = g_object_info_find_method(priv->info,
+ name);
+
+ /* If it isn't a method on the object, see if it's one on an
+ * iface the object implements. Note that since JS lacks
+ * multiple inheritance, we stick the iface methods in the
+ * object prototype, which means there are many copies of the
+ * iface methods (one per object class node that introduces
+ * the iface)
+ */
+ if (method_info == NULL) {
+ int n_interfaces;
+ int i;
+
+ n_interfaces = g_object_info_get_n_interfaces(priv->info);
+
+ for (i = 0; i < n_interfaces; ++i) {
+ GIInterfaceInfo *iface_info;
+
+ iface_info = g_object_info_get_interface(priv->info, i);
+
+ method_info = g_interface_info_find_method(iface_info, name);
+
+ g_base_info_unref( (GIBaseInfo*) iface_info);
+
+ if (method_info != NULL) {
+ gjs_debug(GJS_DEBUG_GOBJECT,
+ "Found method %s in interface %d implemented by object",
+ name, i);
+ break;
+ }
+ }
+ }
+
+ if (method_info == NULL) {
+ GType *interfaces;
+ guint n_interfaces;
+ guint i;
+
+ interfaces = g_type_interfaces (priv->gtype, &n_interfaces);
+ for (i = 0; i < n_interfaces; i++) {
+ GIBaseInfo *base_info;
+ GIInterfaceInfo *iface_info;
+
+ base_info = g_irepository_find_by_gtype(g_irepository_get_default(),
+ interfaces[i]);
+ if (!base_info)
+ continue;
+
+ if (g_base_info_get_type(base_info) != GI_INFO_TYPE_INTERFACE) {
+ g_base_info_unref(base_info);
+ continue;
+ }
+
+ iface_info = (GIInterfaceInfo*) base_info;
+
+ method_info = g_interface_info_find_method(iface_info, name);
+
+ g_base_info_unref(base_info);
+
+ if (method_info != NULL) {
+ gjs_debug(GJS_DEBUG_GOBJECT,
+ "Found method %s in native interface %s",
+ name, g_type_name(interfaces[i]));
+ break;
+ }
+ }
+ g_free(interfaces);
+ }
+
+ if (method_info != NULL) {
+ const char *method_name;
+
+#if GJS_VERBOSE_ENABLE_GI_USAGE
+ _gjs_log_info_usage((GIBaseInfo*) method_info);
+#endif
+
+ method_name = g_base_info_get_name( (GIBaseInfo*) method_info);
+
+ gjs_debug(GJS_DEBUG_GOBJECT,
+ "Defining method %s in prototype for %s (%s.%s)",
+ method_name,
+ g_type_name(priv->gtype),
+ g_base_info_get_namespace( (GIBaseInfo*) priv->info),
+ g_base_info_get_name( (GIBaseInfo*) priv->info));
+
+ if (gjs_define_function(context, obj, method_info) == NULL) {
+ g_base_info_unref( (GIBaseInfo*) method_info);
+ return JS_FALSE;
+ }
+
+ *objp = obj; /* we defined the prop in obj */
+
+ g_base_info_unref( (GIBaseInfo*) method_info);
+ }
+ } else {
+ /* We are an instance, not a prototype, so look for per-instance props that
+ * we want to define on the JSObject. Generally we do not want to cache
+ * these in JS, we want to always pull them from the GObject, or
+ * JS would not see any changes made from C. So we use the get/set prop hooks,
+ * not this resolve hook.
+ */
+
+ JSObject *proto;
+ ObjectInstance *proto_priv;
+
+ proto = JS_GetPrototype(context, obj);
+ proto_priv = priv_from_js(context, proto);
+ if (proto_priv->gtype == G_TYPE_INVALID) {
+ gjs_debug(GJS_DEBUG_GOBJECT,
+ "storing gtype %s (%d) to prototype %p",
+ G_OBJECT_TYPE_NAME(priv->gobj),
+ (int) G_OBJECT_TYPE(priv->gobj),
+ proto);
+ proto_priv->gtype = G_OBJECT_TYPE(priv->gobj);
+ } else if (proto_priv->gtype != G_OBJECT_TYPE(priv->gobj)) {
+ gjs_fatal("conflicting gtypes for prototype %s (%d) (was %s (%d))",
+ G_OBJECT_TYPE_NAME(priv->gobj),
+ (int) G_OBJECT_TYPE(priv->gobj),
+ g_type_name(proto_priv->gtype),
+ (int) proto_priv->gtype);
+ }
+ }
+
+ return JS_TRUE;
+}
+
+static void
+free_g_params(GParameter *params,
+ int n_params)
+{
+ int i;
+
+ for (i = 0; i < n_params; ++i) {
+ g_value_unset(&params[i].value);
+ }
+ g_free(params);
+}
+
+/* Set properties from args to constructor (argv[0] is supposed to be
+ * a hash)
+ */
+static JSBool
+object_instance_props_to_g_parameters(JSContext *context,
+ JSObject *obj,
+ uintN argc,
+ jsval *argv,
+ GType gtype,
+ GParameter **gparams_p,
+ int *n_gparams_p)
+{
+ JSObject *props;
+ JSObject *iter;
+ jsid prop_id;
+ GArray *gparams;
+
+ if (gparams_p)
+ *gparams_p = NULL;
+ if (n_gparams_p)
+ *n_gparams_p = 0;
+
+ if (argc == 0)
+ return JS_TRUE;
+
+ if (!JSVAL_IS_OBJECT(argv[0])) {
+ gjs_throw(context, "argument should be a hash with props to set");
+ return JS_FALSE;
+ }
+
+ props = JSVAL_TO_OBJECT(argv[0]);
+
+ iter = JS_NewPropertyIterator(context, props);
+ if (iter == NULL) {
+ gjs_throw(context, "Failed to create property iterator for object props hash");
+ return JS_FALSE;
+ }
+
+ prop_id = JSVAL_VOID;
+ if (!JS_NextProperty(context, iter, &prop_id))
+ return JS_FALSE;
+
+ if (prop_id != JSVAL_VOID) {
+ gparams = g_array_new(/* nul term */ FALSE, /* clear */ TRUE,
+ sizeof(GParameter));
+ } else {
+ return JS_TRUE;
+ }
+
+ while (prop_id != JSVAL_VOID) {
+ jsval nameval;
+ const char *name;
+ jsval value;
+ GParameter gparam = { NULL, { 0, }};
+
+ if (!JS_IdToValue(context, prop_id, &nameval))
+ goto free_array_and_fail;
+
+ if (!gjs_get_string_id(nameval, &name))
+ goto free_array_and_fail;
+
+ if (!gjs_object_require_property(context, props, name, &value))
+ goto free_array_and_fail;
+
+ switch (init_g_param_from_property(context, name,
+ value,
+ gtype,
+ &gparam)) {
+ case SOME_ERROR_OCCURRED:
+ goto free_array_and_fail;
+ case NO_SUCH_G_PROPERTY:
+ gjs_throw(context, "No property %s on this GObject %s",
+ name, g_type_name(gtype));
+ goto free_array_and_fail;
+ case VALUE_WAS_SET:
+ break;
+ }
+
+ g_array_append_val(gparams, gparam);
+
+ prop_id = JSVAL_VOID;
+ if (!JS_NextProperty(context, iter, &prop_id))
+ goto free_array_and_fail;
+ }
+
+ if (n_gparams_p)
+ *n_gparams_p = gparams->len;
+ if (gparams_p)
+ *gparams_p = (void*) g_array_free(gparams, FALSE);
+
+ return JS_TRUE;
+
+ free_array_and_fail:
+ {
+ GParameter *to_free;
+ int count;
+ count = gparams->len;
+ to_free = (void*) g_array_free(gparams, FALSE);
+ free_g_params(to_free, count);
+ }
+ return JS_FALSE;
+}
+
+#define DEBUG_DISPOSE 0
+#if DEBUG_DISPOSE
+static void
+wrapped_gobj_dispose_notify(gpointer data,
+ GObject *where_the_object_was)
+{
+ gjs_debug(GJS_DEBUG_GOBJECT, "JSObject %p GObject %p disposed", data, where_the_object_was);
+}
+#endif
+
+static void
+gobj_no_longer_kept_alive_func(JSObject *obj,
+ void *data)
+{
+ ObjectInstance *priv;
+
+ priv = data;
+
+ gjs_debug_lifecycle(GJS_DEBUG_GOBJECT,
+ "GObject wrapper %p will no longer be kept alive, eligible for collection",
+ obj);
+
+ priv->keep_alive = NULL;
+}
+
+static void
+wrapped_gobj_toggle_notify(gpointer data,
+ GObject *gobj,
+ gboolean is_last_ref)
+{
+ JSRuntime *runtime;
+ JSContext *context;
+ JSObject *obj;
+ ObjectInstance *priv;
+
+ runtime = data;
+
+ context = gjs_runtime_get_load_context(runtime);
+
+ obj = peek_js_obj(context, gobj);
+
+ g_assert(obj != NULL);
+
+ priv = priv_from_js(context, obj);
+
+ gjs_debug_lifecycle(GJS_DEBUG_GOBJECT,
+ "Toggle notify gobj %p obj %p is_last_ref %d keep-alive %p",
+ gobj, obj, is_last_ref, priv->keep_alive);
+
+ if (is_last_ref) {
+ /* Change to weak ref so the wrapper-wrappee pair can be
+ * collected by the GC
+ */
+ if (priv->keep_alive != NULL) {
+ gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Removing object from keep alive");
+ gjs_keep_alive_remove_child(context, priv->keep_alive,
+ gobj_no_longer_kept_alive_func,
+ obj,
+ priv);
+ priv->keep_alive = NULL;
+ }
+ } else {
+ /* Change to strong ref so the wrappee keeps the wrapper alive
+ * in case the wrapper has data in it that the app cares about
+ */
+ if (priv->keep_alive == NULL) {
+ gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Adding object to keep alive");
+ priv->keep_alive = gjs_keep_alive_get_for_load_context(runtime);
+ gjs_keep_alive_add_child(context, priv->keep_alive,
+ gobj_no_longer_kept_alive_func,
+ obj,
+ priv);
+ }
+ }
+}
+
+/* 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.
+ */
+static JSBool
+object_instance_constructor(JSContext *context,
+ JSObject *obj,
+ uintN argc,
+ jsval *argv,
+ jsval *retval)
+{
+ ObjectInstance *priv;
+ ObjectInstance *proto_priv;
+ JSObject *proto;
+ gboolean is_proto;
+ JSClass *obj_class;
+ JSClass *proto_class;
+
+ priv = g_slice_new0(ObjectInstance);
+
+ GJS_INC_COUNTER(object);
+
+ g_assert(priv_from_js(context, obj) == NULL);
+ JS_SetPrivate(context, obj, priv);
+
+ gjs_debug_lifecycle(GJS_DEBUG_GOBJECT,
+ "obj instance constructor, obj %p priv %p retval %p", obj, priv,
+ JSVAL_IS_OBJECT(*retval) ?
+ JSVAL_TO_OBJECT(*retval) : NULL);
+
+ proto = JS_GetPrototype(context, obj);
+ gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "obj instance __proto__ is %p", proto);
+
+ /* If we're constructing the prototype, its __proto__ is not the same
+ * class as us, but if we're constructing an instance, the prototype
+ * has the same class.
+ */
+ obj_class = JS_GetClass(context, obj);
+ proto_class = JS_GetClass(context, proto);
+
+ is_proto = (obj_class != proto_class);
+
+ gjs_debug_lifecycle(GJS_DEBUG_GOBJECT,
+ "obj instance constructing proto %d, obj class %s proto class %s",
+ is_proto, obj_class->name, proto_class->name);
+
+ if (!is_proto) {
+ GType gtype;
+
+ /* If we're the prototype, then post-construct we'll fill in priv->info.
+ * If we are not the prototype, though, then we'll get ->info from the
+ * prototype and then create a GObject if we don't have one already.
+ */
+ proto_priv = priv_from_js(context, proto);
+ if (proto_priv == NULL) {
+ gjs_debug(GJS_DEBUG_GOBJECT,
+ "Bad prototype set on object? Must match JSClass of object. JS error should have been reported.");
+ return JS_FALSE;
+ }
+
+ priv->info = proto_priv->info;
+ g_base_info_ref( (GIBaseInfo*) priv->info);
+
+ /* Since gobject-introspection is always creating new info
+ * objects, == is not meaningful on them, only comparison of
+ * their names. We prefer to use the info that is already ref'd
+ * by the prototype for the class.
+ */
+ g_assert(unthreadsafe_template_for_constructor.info == NULL ||
+ strcmp(g_base_info_get_name( (GIBaseInfo*) priv->info),
+ g_base_info_get_name( (GIBaseInfo*) unthreadsafe_template_for_constructor.info))
+ == 0);
+ unthreadsafe_template_for_constructor.info = NULL;
+
+ if (unthreadsafe_template_for_constructor.gobj == NULL) {
+ GParameter *params;
+ int n_params;
+
+ gtype = g_registered_type_info_get_g_type( (GIRegisteredTypeInfo*) priv->info);
+ if (gtype == G_TYPE_NONE) {
+ gjs_throw(context,
+ "No GType for object '%s'???",
+ g_base_info_get_name( (GIBaseInfo*) priv->info));
+ return JS_FALSE;
+ }
+
+ if (!object_instance_props_to_g_parameters(context, obj, argc, argv,
+ gtype,
+ &params, &n_params)) {
+ return JS_FALSE;
+ }
+
+ priv->gobj = g_object_newv(gtype, n_params, params);
+
+ if (G_IS_INITIALLY_UNOWNED(priv->gobj) &&
+ !g_object_is_floating(priv->gobj)) {
+ /* GtkWindow does not return a ref to caller of g_object_new.
+ * Need a flag in gobject-introspection to tell us this.
+ */
+ gjs_debug(GJS_DEBUG_GOBJECT,
+ "Newly-created object is initially unowned but we did not get the "
+ "floating ref, probably GtkWindow, using hacky workaround");
+ g_object_ref(priv->gobj);
+ } else if (g_object_is_floating(priv->gobj)) {
+ g_object_ref_sink(priv->gobj);
+ } else {
+ /* we should already have a ref */
+ }
+ } else {
+ priv->gobj = unthreadsafe_template_for_constructor.gobj;
+ unthreadsafe_template_for_constructor.gobj = NULL;
+
+ g_object_ref_sink(priv->gobj);
+ }
+
+ g_assert(peek_js_obj(context, priv->gobj) == NULL);
+ set_js_obj(context, priv->gobj, obj);
+
+#if DEBUG_DISPOSE
+ g_object_weak_ref(priv->gobj, wrapped_gobj_dispose_notify, obj);
+#endif
+
+ /* OK, here is where things get complicated. We want the
+ * wrapped gobj to keep the JSObject* wrapper alive, because
+ * people might set properties on the JSObject* that they care
+ * about. Therefore, whenever the refcount on the wrapped gobj
+ * is >1, i.e. whenever something other than the wrapper is
+ * referencing the wrapped gobj, the wrapped gobj has a strong
+ * ref (gc-roots the wrapper). When the refcount on the
+ * wrapped gobj is 1, then we change to a weak ref to allow
+ * the wrapper to be garbage collected (and thus unref the
+ * wrappee).
+ */
+ priv->keep_alive = gjs_keep_alive_get_for_load_context(JS_GetRuntime(context));
+ gjs_keep_alive_add_child(context,
+ priv->keep_alive,
+ gobj_no_longer_kept_alive_func,
+ obj,
+ priv);
+
+ g_object_add_toggle_ref(priv->gobj,
+ wrapped_gobj_toggle_notify,
+ JS_GetRuntime(context));
+
+ /* We now have both a ref and a toggle ref, we only want the
+ * toggle ref. This may immediately remove the GC root
+ * we just added, since refcount may drop to 1.
+ */
+ g_object_unref(priv->gobj);
+
+ gjs_debug_lifecycle(GJS_DEBUG_GOBJECT,
+ "JSObject created with GObject %p %s",
+ priv->gobj, g_type_name_from_instance((GTypeInstance*) priv->gobj));
+ }
+
+ return JS_TRUE;
+}
+
+static void
+object_instance_finalize(JSContext *context,
+ JSObject *obj)
+{
+ ObjectInstance *priv;
+
+ priv = priv_from_js(context, obj);
+ gjs_debug_lifecycle(GJS_DEBUG_GOBJECT,
+ "finalize obj %p priv %p gtype %s gobj %p", obj, priv,
+ (priv && priv->gobj) ?
+ g_type_name_from_instance( (GTypeInstance*) priv->gobj) :
+ "<no gobject>",
+ priv ? priv->gobj : NULL);
+ if (priv == NULL)
+ return; /* we are the prototype, not a real instance, so constructor never called */
+
+ if (priv->gobj) {
+ g_assert(priv->gobj->ref_count > 0);
+ set_js_obj(context, priv->gobj, NULL);
+ g_object_remove_toggle_ref(priv->gobj, wrapped_gobj_toggle_notify,
+ JS_GetRuntime(context));
+ priv->gobj = NULL;
+ }
+
+ if (priv->keep_alive != NULL) {
+ /* This happens when the refcount on the object is still >1,
+ * for example with global objects GDK never frees like GdkDisplay,
+ * when we close down the JS runtime.
+ */
+ gjs_debug(GJS_DEBUG_GOBJECT,
+ "Wrapper was finalized despite being kept alive, has refcount >1");
+
+ gjs_debug_lifecycle(GJS_DEBUG_GOBJECT,
+ "Removing from keep alive");
+
+ /* We're in a finalizer while the runtime is about to be
+ * destroyed. This is not the safest time to be calling back
+ * into jsapi, but we have to do this or the keep alive could
+ * be finalized later and call gobj_no_longer_kept_alive_func.
+ */
+ gjs_keep_alive_remove_child(context, priv->keep_alive,
+ gobj_no_longer_kept_alive_func,
+ obj,
+ priv);
+ }
+
+ if (priv->info) {
+ g_base_info_unref( (GIBaseInfo*) priv->info);
+ priv->info = NULL;
+ }
+
+ GJS_DEC_COUNTER(object);
+ g_slice_free(ObjectInstance, priv);
+}
+
+JSObject*
+gjs_lookup_object_constructor(JSContext *context,
+ GType gtype,
+ GIObjectInfo *info)
+{
+ JSObject *ns;
+ JSObject *constructor;
+
+ ns = gjs_lookup_namespace_object(context, (GIBaseInfo*) info);
+
+ if (ns == NULL)
+ return NULL;
+
+ if (gjs_define_object_class(context, ns, gtype, info,
+ &constructor, NULL))
+ return constructor;
+ else
+ return NULL;
+}
+
+JSObject*
+gjs_lookup_object_prototype(JSContext *context,
+ GType gtype,
+ GIObjectInfo *info)
+{
+ JSObject *ns;
+ JSObject *proto;
+
+ ns = gjs_lookup_namespace_object(context, (GIBaseInfo*) info);
+
+ if (ns == NULL)
+ return NULL;
+
+ if (gjs_define_object_class(context, ns, gtype, info, NULL, &proto))
+ return proto;
+ else
+ return NULL;
+}
+
+JSClass*
+gjs_lookup_object_class(JSContext *context,
+ GType gtype,
+ GIObjectInfo *info)
+{
+ JSObject *prototype;
+
+ prototype = gjs_lookup_object_prototype(context, gtype, info);
+
+ return JS_GetClass(context, prototype);
+}
+
+static JSBool
+real_connect_func(JSContext *context,
+ JSObject *obj,
+ uintN argc,
+ jsval *argv,
+ jsval *retval,
+ gboolean after)
+{
+ ObjectInstance *priv;
+ GClosure *closure;
+ gulong id;
+ const char *signal_name;
+
+ *retval = INT_TO_JSVAL(0);
+
+ priv = priv_from_js(context, obj);
+ gjs_debug(GJS_DEBUG_GOBJECT,
+ "connect obj %p priv %p argc %d", obj, priv, argc);
+ if (priv == NULL)
+ return JS_FALSE; /* wrong class passed in */
+ if (priv->gobj == NULL) {
+ /* prototype, not an instance. */
+ gjs_throw(context, "Can't connect to signals on %s.%s.prototype; only on instances",
+ g_base_info_get_namespace( (GIBaseInfo*) priv->info),
+ g_base_info_get_name( (GIBaseInfo*) priv->info));
+ return JS_FALSE;
+ }
+
+ /* Best I can tell, there is no way to know if argv[1] is really
+ * callable other than to just try it. Checking whether it's a
+ * function will not detect native objects that provide
+ * JSClass::call, for example.
+ */
+
+ if (argc != 2 ||
+ !JSVAL_IS_STRING(argv[0]) ||
+ !JSVAL_IS_OBJECT(argv[1])) {
+ gjs_throw(context, "connect() takes two args, the signal name and the callback");
+ return JS_FALSE;
+ }
+
+ closure = gjs_closure_new_marshaled(context, JSVAL_TO_OBJECT(argv[1]), "signal callback");
+ if (closure == NULL)
+ return JS_FALSE;
+
+ signal_name = gjs_string_get_ascii_checked(context, argv[0]);
+ if (signal_name == NULL) {
+ g_closure_sink(closure);
+ return JS_FALSE;
+ }
+
+ id = g_signal_connect_closure(priv->gobj,
+ signal_name,
+ closure,
+ after);
+
+ if (!JS_NewNumberValue(context, id, retval)) {
+ g_signal_handler_disconnect(priv->gobj, id);
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
+static JSBool
+connect_after_func(JSContext *context,
+ JSObject *obj,
+ uintN argc,
+ jsval *argv,
+ jsval *retval)
+{
+ return real_connect_func(context, obj, argc, argv, retval, TRUE);
+}
+
+static JSBool
+connect_func(JSContext *context,
+ JSObject *obj,
+ uintN argc,
+ jsval *argv,
+ jsval *retval)
+{
+ return real_connect_func(context, obj, argc, argv, retval, FALSE);
+}
+
+static JSBool
+disconnect_func(JSContext *context,
+ JSObject *obj,
+ uintN argc,
+ jsval *argv,
+ jsval *retval)
+{
+ ObjectInstance *priv;
+ gulong id;
+
+ *retval = JSVAL_VOID;
+
+ priv = priv_from_js(context, obj);
+ gjs_debug(GJS_DEBUG_GOBJECT,
+ "disconnect obj %p priv %p argc %d", obj, priv, argc);
+
+ if (priv == NULL)
+ return JS_FALSE; /* wrong class passed in */
+
+ if (priv->gobj == NULL) {
+ /* prototype, not an instance. */
+ gjs_throw(context, "Can't disconnect signal on %s.%s.prototype; only on instances",
+ g_base_info_get_namespace( (GIBaseInfo*) priv->info),
+ g_base_info_get_name( (GIBaseInfo*) priv->info));
+ return JS_FALSE;
+ }
+
+ if (argc != 1 ||
+ !JSVAL_IS_INT(argv[0])) {
+ gjs_throw(context, "disconnect() takes one arg, the signal handler id");
+ return JS_FALSE;
+ }
+
+ id = JSVAL_TO_INT(argv[0]);
+
+ g_signal_handler_disconnect(priv->gobj, id);
+
+ return JS_TRUE;
+}
+
+static JSBool
+emit_func(JSContext *context,
+ JSObject *obj,
+ uintN argc,
+ jsval *argv,
+ jsval *retval)
+{
+ ObjectInstance *priv;
+ guint signal_id;
+ GQuark signal_detail;
+ GSignalQuery signal_query;
+ const char *signal_name;
+ GValue *instance_and_args;
+ GValue rvalue;
+ unsigned int i;
+ gboolean failed;
+
+ *retval = JSVAL_VOID;
+
+ priv = priv_from_js(context, obj);
+ gjs_debug(GJS_DEBUG_GOBJECT,
+ "emit obj %p priv %p argc %d", obj, priv, argc);
+
+ if (priv == NULL)
+ return JS_FALSE; /* wrong class passed in */
+
+ if (priv->gobj == NULL) {
+ /* prototype, not an instance. */
+ gjs_throw(context, "Can't emit signal on %s.%s.prototype; only on instances",
+ g_base_info_get_namespace( (GIBaseInfo*) priv->info),
+ g_base_info_get_name( (GIBaseInfo*) priv->info));
+ return JS_FALSE;
+ }
+
+ if (argc < 1 ||
+ !JSVAL_IS_STRING(argv[0])) {
+ gjs_throw(context, "emit() first arg is the signal name");
+ return JS_FALSE;
+ }
+
+ signal_name = gjs_string_get_ascii_checked(context,
+ argv[0]);
+ if (signal_name == NULL)
+ return JS_FALSE;
+
+ if (!g_signal_parse_name(signal_name,
+ G_OBJECT_TYPE(priv->gobj),
+ &signal_id,
+ &signal_detail,
+ FALSE)) {
+ gjs_throw(context, "No signal '%s' on object '%s'",
+ signal_name,
+ g_type_name(G_OBJECT_TYPE(priv->gobj)));
+ return JS_FALSE;
+ }
+
+ g_signal_query(signal_id, &signal_query);
+
+ if ((argc - 1) != signal_query.n_params) {
+ gjs_throw(context, "Signal '%s' on %s requires %d args got %d",
+ signal_name,
+ g_type_name(G_OBJECT_TYPE(priv->gobj)),
+ signal_query.n_params,
+ argc - 1);
+ return JS_FALSE;
+ }
+
+ if (signal_query.return_type != G_TYPE_NONE) {
+ g_value_init(&rvalue, signal_query.return_type & ~G_SIGNAL_TYPE_STATIC_SCOPE);
+ }
+
+ instance_and_args = g_newa(GValue, signal_query.n_params + 1);
+ memset(instance_and_args, 0, sizeof(GValue) * (signal_query.n_params + 1));
+
+ g_value_init(&instance_and_args[0], G_TYPE_FROM_INSTANCE(priv->gobj));
+ g_value_set_instance(&instance_and_args[0], priv->gobj);
+
+ failed = FALSE;
+ for (i = 0; i < signal_query.n_params; ++i) {
+ GValue *value;
+ value = &instance_and_args[i + 1];
+
+ g_value_init(value, signal_query.param_types[i] & ~G_SIGNAL_TYPE_STATIC_SCOPE);
+ if (!gjs_value_to_g_value(context, argv[i+1],
+ value)) {
+ failed = TRUE;
+ break;
+ }
+ }
+
+ if (!failed) {
+ g_signal_emitv(instance_and_args, signal_id, signal_detail,
+ &rvalue);
+ }
+
+ if (signal_query.return_type != G_TYPE_NONE) {
+ if (!gjs_value_from_g_value(context,
+ retval,
+ &rvalue))
+ failed = TRUE;
+
+ g_value_unset(&rvalue);
+ }
+
+ for (i = 0; i < (signal_query.n_params + 1); ++i) {
+ g_value_unset(&instance_and_args[i]);
+ }
+
+ return !failed;
+}
+
+/* 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_object_instance_class = {
+ NULL, /* We copy this class struct with multiple names */
+ JSCLASS_HAS_PRIVATE |
+ JSCLASS_NEW_RESOLVE |
+ JSCLASS_NEW_RESOLVE_GETS_START |
+ JSCLASS_CONSTRUCT_PROTOTYPE,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ object_instance_get_prop,
+ object_instance_set_prop,
+ JS_EnumerateStub,
+ (JSResolveOp) object_instance_new_resolve, /* needs cast since it's the new resolve signature */
+ JS_ConvertStub,
+ object_instance_finalize,
+ JSCLASS_NO_OPTIONAL_MEMBERS
+};
+
+static JSPropertySpec gjs_object_instance_proto_props[] = {
+ { NULL }
+};
+
+static JSFunctionSpec gjs_object_instance_proto_funcs[] = {
+ { "connect", connect_func, 0, 0 },
+ { "connect_after", connect_after_func, 0, 0 },
+ { "disconnect", disconnect_func, 0, 0 },
+ { "emit", emit_func, 0, 0 },
+ { NULL }
+};
+
+JSBool
+gjs_define_object_class(JSContext *context,
+ JSObject *in_object,
+ GType gtype,
+ GIObjectInfo *info,
+ JSObject **constructor_p,
+ JSObject **prototype_p)
+{
+ const char *constructor_name;
+ JSObject *prototype;
+ GIObjectInfo *parent_info;
+ JSObject *parent_proto;
+ jsval value;
+ ObjectInstance *priv;
+
+ /* http://egachine.berlios.de/embedding-sm-best-practice/apa.html
+ * http://www.sitepoint.com/blogs/2006/01/17/javascript-inheritance/
+ * http://www.cs.rit.edu/~atk/JavaScript/manuals/jsobj/
+ *
+ * What we want is:
+ *
+ * repoobj.Gtk.Window is constructor for a GtkWindow wrapper JSObject
+ * (gjs_define_object_constructor() is supposed to define Window in Gtk)
+ *
+ * Window.prototype contains the methods on Window, e.g. set_default_size()
+ * mywindow.__proto__ is Window.prototype
+ * mywindow.__proto__.__proto__ is Bin.prototype
+ * mywindow.__proto__.__proto__.__proto__ is Container.prototype
+ *
+ * Because Window.prototype is an instance of Window in a sense,
+ * Window.prototype.__proto__ is Window.prototype, just as
+ * mywindow.__proto__ is Window.prototype
+ *
+ * If we do "mywindow = new Window()" then we should get:
+ * mywindow.__proto__ == Window.prototype
+ * which means "mywindow instanceof Window" is true.
+ *
+ * Remember "Window.prototype" is "the __proto__ of stuff
+ * constructed with new Window()"
+ *
+ * __proto__ is used to search for properties if you do "this.foo"
+ * while __parent__ defines the scope to search if you just have
+ * "foo".
+ *
+ * __proto__ is used to look up properties, while .prototype is only
+ * relevant for constructors and is used to set __proto__ on new'd
+ * objects. So .prototype only makes sense on constructors.
+ *
+ * JS_SetPrototype() and JS_GetPrototype() are for __proto__.
+ * To set/get .prototype, just use the normal property accessors,
+ * or JS_InitClass() sets it up automatically.
+ *
+ * JavaScript is SO AWESOME
+ */
+
+ constructor_name = g_base_info_get_name( (GIBaseInfo*) info);
+
+ /* 'gtype' is the GType of a concrete class (if any) which may or may not
+ * be defined in the GIRepository. 'info' corresponds to the first known
+ * ancestor of 'gtype' (or the gtype itself.)
+ *
+ * For example:
+ * gtype=GtkWindow info=Gtk.Window (defined)
+ * gtype=GLocalFile info=GLib.Object (not defined)
+ * gtype=GHalMount info=GLib.Object (not defined)
+ *
+ * Each GType needs to have distinct JS class, otherwise the JS class for
+ * first common parent in GIRepository gets used with conflicting gtypes
+ * when resolving GTypeInterface methods.
+ *
+ * In case 'gtype' is not defined in GIRepository use the type name as
+ * constructor assuming it is unique enough instead of sharing
+ * 'Object' (or whatever the first known ancestor is)
+ *
+ * 'gtype' can be invalid when called from gjs_define_info()
+ */
+ if (gtype != G_TYPE_INVALID) {
+ GIBaseInfo *gtype_info;
+
+ gtype_info = g_irepository_find_by_gtype(g_irepository_get_default(),
+ gtype);
+ if (gtype_info != NULL) {
+ g_base_info_unref(gtype_info);
+ } else {
+ /* defining a class not known to GIRepository */
+ constructor_name = g_type_name(gtype);
+ }
+ }
+
+ if (gjs_object_get_property(context, in_object, constructor_name, &value)) {
+ JSObject *constructor;
+
+ if (!JSVAL_IS_OBJECT(value)) {
+ gjs_throw(context, "Existing property '%s' does not look like a constructor",
+ constructor_name);
+ return JS_FALSE;
+ }
+
+ constructor = JSVAL_TO_OBJECT(value);
+
+ gjs_object_get_property(context, constructor, "prototype", &value);
+ if (!JSVAL_IS_OBJECT(value)) {
+ gjs_throw(context, "prototype property does not appear to exist or has wrong type");
+ return JS_FALSE;
+ } else {
+ if (prototype_p)
+ *prototype_p = JSVAL_TO_OBJECT(value);
+ if (constructor_p)
+ *constructor_p = constructor;
+
+ return JS_TRUE;
+ }
+ }
+
+ parent_proto = NULL;
+
+ /* FIXME: this traverses GIObjectInfo hierarchy too quickly. Should go up
+ * the GType hierarchy to find the closest parent GIObjectInfo, but we
+ * don't always have valid 'gtype' to do that. (This is only a problem when
+ * g-i has only partial knowledge about the GType hierarchy, for example
+ * with GIO where most concrete types are private and meant to be accessed
+ * through interfaces only.)
+ */
+ parent_info = g_object_info_get_parent(info);
+ if (parent_info != NULL) {
+ GType parent_gtype;
+
+ parent_gtype = g_type_parent(gtype);
+ parent_proto = gjs_lookup_object_prototype(context, parent_gtype, parent_info);
+
+ g_base_info_unref( (GIBaseInfo*) parent_info);
+ parent_info = NULL;
+ }
+
+ prototype = gjs_init_class_dynamic(context, in_object,
+ /* parent prototype JSObject* for
+ * prototype; NULL for
+ * Object.prototype
+ */
+ parent_proto,
+ g_base_info_get_namespace( (GIBaseInfo*) info),
+ constructor_name,
+ &gjs_object_instance_class,
+ /* constructor for instances (NULL for
+ * none - just name the prototype like
+ * Math - rarely correct)
+ */
+ object_instance_constructor,
+ /* number of constructor args */
+ 0,
+ /* props of prototype */
+ &gjs_object_instance_proto_props[0],
+ /* funcs of prototype */
+ &gjs_object_instance_proto_funcs[0],
+ /* props of constructor, MyConstructor.myprop */
+ NULL,
+ /* funcs of constructor, MyConstructor.myfunc() */
+ NULL);
+ if (prototype == NULL)
+ gjs_fatal("Can't init class %s", constructor_name);
+
+ g_assert(gjs_object_has_property(context, in_object, constructor_name));
+
+ /* Put the info in the prototype */
+ priv = priv_from_js(context, prototype);
+ g_assert(priv != NULL);
+ g_assert(priv->info == NULL);
+ priv->info = info;
+ g_base_info_ref( (GIBaseInfo*) priv->info);
+ priv->gtype = gtype;
+
+ gjs_debug(GJS_DEBUG_GOBJECT, "Defined class %s prototype is %p class %p in object %p",
+ constructor_name, prototype, JS_GetClass(context, prototype), in_object);
+
+ if (constructor_p) {
+ *constructor_p = NULL;
+ gjs_object_get_property(context, in_object, constructor_name, &value);
+ if (value != JSVAL_VOID) {
+ if (!JSVAL_IS_OBJECT(value)) {
+ gjs_throw(context, "Property '%s' does not look like a constructor",
+ constructor_name);
+ return JS_FALSE;
+ }
+
+ *constructor_p = JSVAL_TO_OBJECT(value);
+ }
+ }
+
+ if (prototype_p)
+ *prototype_p = prototype;
+
+ return JS_TRUE;
+}
+
+/* multiple JSRuntime could have a proxy to the same GObject, in theory
+ */
+#define OBJ_KEY_PREFIX_LEN 3
+#define OBJ_KEY_LEN (OBJ_KEY_PREFIX_LEN+sizeof(void*)*2)
+static void
+get_obj_key(JSRuntime *runtime,
+ char *buf)
+{
+ /* not thread safe, but that's fine for now - just nuke the
+ * cache thingy if we ever need thread safety
+ */
+ static char cached_buf[OBJ_KEY_LEN];
+ static JSRuntime *cached_for = NULL;
+
+ if (cached_for != runtime) {
+ unsigned int i;
+ union {
+ const unsigned char bytes[sizeof(void*)];
+ void *ptr;
+ } d;
+ g_assert(sizeof(d) == sizeof(void*));
+
+ buf[0] = 'j';
+ buf[1] = 's';
+ buf[2] = '-';
+ d.ptr = runtime;
+ for (i = 0; i < sizeof(void*)*2; i += 2) {
+ buf[OBJ_KEY_PREFIX_LEN+i] = 'a' + ((d.bytes[i] & 0xf0) >> 4);
+ buf[OBJ_KEY_PREFIX_LEN+i+1] = 'a' + (d.bytes[i] & 0x0f);
+ }
+ buf[OBJ_KEY_LEN] = '\0';
+ strcpy(cached_buf, buf);
+ cached_for = runtime;
+ g_assert(strlen(buf) == OBJ_KEY_LEN);
+ } else {
+ strcpy(buf, cached_buf);
+ }
+}
+
+static JSObject*
+peek_js_obj(JSContext *context,
+ GObject *gobj)
+{
+ char buf[OBJ_KEY_LEN+1];
+
+ get_obj_key(JS_GetRuntime(context), buf);
+
+ return g_object_get_data(gobj, buf);
+}
+
+static void
+set_js_obj(JSContext *context,
+ GObject *gobj,
+ JSObject *obj)
+{
+ char buf[OBJ_KEY_LEN+1];
+
+ get_obj_key(JS_GetRuntime(context), buf);
+
+ g_object_set_data(gobj, buf, obj);
+}
+
+JSObject*
+gjs_object_from_g_object(JSContext *context,
+ GObject *gobj)
+{
+ JSObject *obj;
+
+ if (gobj == NULL)
+ return NULL;
+
+ obj = peek_js_obj(context, gobj);
+
+ if (obj == NULL) {
+ /* We have to create a wrapper */
+ JSObject *proto;
+ GIBaseInfo *info;
+ GType gtype;
+
+ gjs_debug_marshal(GJS_DEBUG_GOBJECT,
+ "Wrapping %s with JSObject",
+ g_type_name_from_instance((GTypeInstance*) gobj));
+
+ info = NULL;
+ gtype = G_TYPE_FROM_INSTANCE(gobj);
+ while (info == NULL) {
+ info = g_irepository_find_by_gtype(g_irepository_get_default(),
+ gtype);
+ if (info != NULL)
+ break;
+
+ if (gtype == G_TYPE_OBJECT)
+ gjs_fatal("No introspection data on GObject - pretty much screwed");
+
+ gjs_debug(GJS_DEBUG_GOBJECT,
+ "No introspection data on '%s' so trying parent type '%s'",
+ g_type_name(gtype), g_type_name(g_type_parent(gtype)));
+ gtype = g_type_parent(gtype);
+ }
+
+ if (info == NULL) {
+ gjs_throw(context,
+ "Unknown object type %s",
+ g_type_name(G_TYPE_FROM_INSTANCE(gobj)));
+ return NULL;
+ }
+
+ proto = gjs_lookup_object_prototype(context, G_TYPE_FROM_INSTANCE(gobj), (GIObjectInfo*) info);
+
+ /* can't come up with a better approach... */
+ unthreadsafe_template_for_constructor.info = (GIObjectInfo*) info;
+ unthreadsafe_template_for_constructor.gobj = gobj;
+
+ obj = gjs_construct_object_dynamic(context, proto,
+ 0, NULL);
+
+ g_base_info_unref( (GIBaseInfo*) info);
+
+ g_assert(peek_js_obj(context, gobj) == obj);
+ }
+
+ return obj;
+}
+
+GObject*
+gjs_g_object_from_object(JSContext *context,
+ JSObject *obj)
+{
+ ObjectInstance *priv;
+
+ if (obj == NULL)
+ return NULL;
+
+ priv = priv_from_js(context, obj);
+
+ if (priv == NULL)
+ return NULL;
+
+ if (priv->gobj == NULL) {
+ gjs_throw(context,
+ "Object is %s.%s.prototype, not an object instance - cannot convert to GObject*",
+ g_base_info_get_namespace( (GIBaseInfo*) priv->info),
+ g_base_info_get_name( (GIBaseInfo*) priv->info));
+ return NULL;
+ }
+
+ return priv->gobj;
+}
diff --git a/gi/object.h b/gi/object.h
new file mode 100644
index 00000000..8380af30
--- /dev/null
+++ b/gi/object.h
@@ -0,0 +1,57 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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_OBJECT_H__
+#define __GJS_OBJECT_H__
+
+#include <glib.h>
+
+#include <jsapi.h>
+
+#include <girepository.h>
+
+G_BEGIN_DECLS
+
+JSBool gjs_define_object_class (JSContext *context,
+ JSObject *in_object,
+ GType gtype,
+ GIObjectInfo *info,
+ JSObject **constructor_p,
+ JSObject **prototype_p);
+JSClass* gjs_lookup_object_class (JSContext *context,
+ GType gtype,
+ GIObjectInfo *info);
+JSObject* gjs_lookup_object_prototype (JSContext *context,
+ GType gtype,
+ GIObjectInfo *info);
+JSObject* gjs_lookup_object_constructor (JSContext *context,
+ GType gtype,
+ GIObjectInfo *info);
+JSObject* gjs_object_from_g_object (JSContext *context,
+ GObject *gobj);
+GObject* gjs_g_object_from_object (JSContext *context,
+ JSObject *obj);
+
+G_END_DECLS
+
+#endif /* __GJS_OBJECT_H__ */
diff --git a/gi/param.c b/gi/param.c
new file mode 100644
index 00000000..333751c0
--- /dev/null
+++ b/gi/param.c
@@ -0,0 +1,416 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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 <string.h>
+
+#include "param.h"
+#include "repo.h"
+#include <gjs/jsapi-util.h>
+#include <gjs/mem.h>
+
+#include <util/log.h>
+
+#include <jsapi.h>
+
+typedef struct {
+ GParamSpec *gparam; /* NULL if we are the prototype and not an instance */
+} Param;
+
+static Param unthreadsafe_template_for_constructor = { NULL };
+
+static struct JSClass gjs_param_class;
+
+GJS_DEFINE_DYNAMIC_PRIV_FROM_JS(Param, gjs_param_class)
+
+/* a hook on getting a property; set value_p to override property's value.
+ * Return value is JS_FALSE on OOM/exception.
+ */
+static JSBool
+param_get_prop(JSContext *context,
+ JSObject *obj,
+ jsval id,
+ jsval *value_p)
+{
+ Param *priv;
+ const char *name;
+ const char *value_str;
+
+ if (!gjs_get_string_id(id, &name))
+ return JS_TRUE; /* not something we affect, but no error */
+
+ priv = priv_from_js(context, obj);
+
+ gjs_debug_jsprop(GJS_DEBUG_GPARAM,
+ "Get prop '%s' hook obj %p priv %p", name, obj, priv);
+
+ if (priv == NULL)
+ return JS_FALSE; /* wrong class */
+
+ value_str = NULL;
+ if (strcmp(name, "name") == 0)
+ value_str = g_param_spec_get_name(priv->gparam);
+ else if (strcmp(name, "nick") == 0)
+ value_str = g_param_spec_get_nick(priv->gparam);
+ else if (strcmp(name, "blurb") == 0)
+ value_str = g_param_spec_get_blurb(priv->gparam);
+
+ if (value_str != NULL) {
+ *value_p = STRING_TO_JSVAL(JS_NewStringCopyZ(context, value_str));
+ }
+
+ return JS_TRUE;
+}
+
+/*
+ * 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 param 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
+param_new_resolve(JSContext *context,
+ JSObject *obj,
+ jsval id,
+ uintN flags,
+ JSObject **objp)
+{
+ Param *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_GPARAM, "Resolve prop '%s' hook obj %p priv %p", name, obj, priv);
+
+ if (priv == NULL)
+ return JS_FALSE; /* wrong class */
+
+ if (priv->gparam == NULL) {
+ /* We are the prototype, so implement any methods or other class properties */
+
+ } else {
+ /* We are an instance, not a prototype, so look for
+ * per-instance props that we want to define on the
+ * JSObject. Generally we do not want to cache these in JS, we
+ * want to always pull them from the C object, or JS would not
+ * see any changes made from C. So we use the get/set prop
+ * hooks, not this resolve hook.
+ */
+ }
+
+ 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
+param_constructor(JSContext *context,
+ JSObject *obj,
+ uintN argc,
+ jsval *argv,
+ jsval *retval)
+{
+ Param *priv;
+ Param *proto_priv;
+ JSClass *obj_class;
+ JSClass *proto_class;
+ JSObject *proto;
+ gboolean is_proto;
+
+ priv = g_slice_new0(Param);
+
+ GJS_INC_COUNTER(param);
+
+ g_assert(priv_from_js(context, obj) == NULL);
+ JS_SetPrivate(context, obj, priv);
+
+ gjs_debug_lifecycle(GJS_DEBUG_GPARAM,
+ "param constructor, obj %p priv %p", obj, priv);
+
+ proto = JS_GetPrototype(context, obj);
+ gjs_debug_lifecycle(GJS_DEBUG_GPARAM, "param instance __proto__ is %p", proto);
+
+ /* If we're constructing the prototype, its __proto__ is not the same
+ * class as us, but if we're constructing an instance, the prototype
+ * has the same class.
+ */
+ obj_class = JS_GetClass(context, obj);
+ proto_class = JS_GetClass(context, proto);
+
+ is_proto = (obj_class != proto_class);
+
+ gjs_debug_lifecycle(GJS_DEBUG_GPARAM,
+ "param instance constructing proto %d, obj class %s proto class %s",
+ is_proto, obj_class->name, proto_class->name);
+
+ if (!is_proto) {
+ /* If we're the prototype, then post-construct we'll fill in priv->info.
+ * If we are not the prototype, though, then we'll get ->info from the
+ * prototype and then create a GObject if we don't have one already.
+ */
+ proto_priv = priv_from_js(context, proto);
+ if (proto_priv == NULL) {
+ gjs_debug(GJS_DEBUG_GPARAM,
+ "Bad prototype set on object? Must match JSClass of object. JS error should have been reported.");
+ return JS_FALSE;
+ }
+
+ if (unthreadsafe_template_for_constructor.gparam == NULL) {
+ /* To construct these we'd have to wrap all the annoying subclasses.
+ * Since we only bind ParamSpec for purposes of the GObject::notify signal,
+ * there isn't much point.
+ */
+ gjs_throw(context, "Unable to construct ParamSpec, can only wrap an existing one");
+ return JS_FALSE;
+ } else {
+ priv->gparam = g_param_spec_ref(unthreadsafe_template_for_constructor.gparam);
+ unthreadsafe_template_for_constructor.gparam = NULL;
+ }
+
+ gjs_debug(GJS_DEBUG_GPARAM,
+ "JSObject created with param instance %p type %s",
+ priv->gparam, g_type_name(G_TYPE_FROM_INSTANCE((GTypeInstance*) priv->gparam)));
+ }
+
+ return JS_TRUE;
+}
+
+static void
+param_finalize(JSContext *context,
+ JSObject *obj)
+{
+ Param *priv;
+
+ priv = priv_from_js(context, obj);
+ gjs_debug_lifecycle(GJS_DEBUG_GPARAM,
+ "finalize, obj %p priv %p", obj, priv);
+ if (priv == NULL)
+ return; /* wrong class? */
+
+ if (priv->gparam) {
+ g_param_spec_unref(priv->gparam);
+ priv->gparam = NULL;
+ }
+
+ GJS_DEC_COUNTER(param);
+ g_slice_free(Param, 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_param_class = {
+ NULL, /* dynamic */
+ JSCLASS_HAS_PRIVATE |
+ JSCLASS_NEW_RESOLVE |
+ JSCLASS_NEW_RESOLVE_GETS_START |
+ JSCLASS_CONSTRUCT_PROTOTYPE,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ param_get_prop,
+ JS_PropertyStub,
+ JS_EnumerateStub,
+ (JSResolveOp) param_new_resolve, /* needs cast since it's the new resolve signature */
+ JS_ConvertStub,
+ param_finalize,
+ NULL,
+ NULL,
+ NULL,
+ NULL, NULL, NULL, NULL, NULL
+};
+
+static JSPropertySpec gjs_param_proto_props[] = {
+ { NULL }
+};
+
+static JSFunctionSpec gjs_param_proto_funcs[] = {
+ { NULL }
+};
+
+JSObject*
+gjs_lookup_param_prototype(JSContext *context)
+{
+ JSObject *ns;
+ JSObject *proto;
+
+ ns = gjs_lookup_namespace_object_by_name(context, "GLib");
+
+ if (ns == NULL)
+ return NULL;
+
+ if (gjs_define_param_class(context, ns, &proto))
+ return proto;
+ else
+ return NULL;
+}
+
+JSBool
+gjs_define_param_class(JSContext *context,
+ JSObject *in_object,
+ JSObject **prototype_p)
+{
+ const char *constructor_name;
+ JSObject *prototype;
+ jsval value;
+
+ constructor_name = "ParamSpec";
+
+ gjs_object_get_property(context, in_object, constructor_name, &value);
+ if (value != JSVAL_VOID) {
+ JSObject *constructor;
+
+ if (!JSVAL_IS_OBJECT(value)) {
+ gjs_throw(context, "Existing property '%s' does not look like a constructor",
+ constructor_name);
+ return JS_FALSE;
+ }
+
+ constructor = JSVAL_TO_OBJECT(value);
+
+ gjs_object_get_property(context, constructor, "prototype", &value);
+ if (!JSVAL_IS_OBJECT(value)) {
+ gjs_throw(context, "prototype property does not appear to exist or has wrong type");
+ return JS_FALSE;
+ } else {
+ if (prototype_p)
+ *prototype_p = JSVAL_TO_OBJECT(value);
+
+ return JS_TRUE;
+ }
+
+ return JS_TRUE;
+ }
+
+ /* we could really just use JS_InitClass for this since we have one class instead of
+ * N classes on-demand. But, this deals with namespacing and such for us.
+ */
+ prototype = gjs_init_class_dynamic(context, in_object,
+ /* parent prototype JSObject* for
+ * prototype; NULL for
+ * Object.prototype
+ */
+ NULL,
+ "GLib",
+ constructor_name,
+ &gjs_param_class,
+ /* constructor for instances (NULL for
+ * none - just name the prototype like
+ * Math - rarely correct)
+ */
+ param_constructor,
+ /* number of constructor args */
+ 0,
+ /* props of prototype */
+ &gjs_param_proto_props[0],
+ /* funcs of prototype */
+ &gjs_param_proto_funcs[0],
+ /* props of constructor, MyConstructor.myprop */
+ NULL,
+ /* funcs of constructor, MyConstructor.myfunc() */
+ NULL);
+ if (prototype == NULL)
+ gjs_fatal("Can't init class %s", constructor_name);
+
+ g_assert(gjs_object_has_property(context, in_object, constructor_name));
+
+ if (prototype_p)
+ *prototype_p = prototype;
+
+ gjs_debug(GJS_DEBUG_GPARAM, "Defined class %s prototype is %p class %p in object %p",
+ constructor_name, prototype, JS_GetClass(context, prototype), in_object);
+
+ return JS_TRUE;
+}
+
+JSObject*
+gjs_param_from_g_param(JSContext *context,
+ GParamSpec *gparam)
+{
+ JSObject *obj;
+ JSObject *proto;
+
+ if (gparam == NULL)
+ return NULL;
+
+ gjs_debug(GJS_DEBUG_GPARAM,
+ "Wrapping %s with JSObject",
+ g_type_name(G_TYPE_FROM_INSTANCE((GTypeInstance*) gparam)));
+
+ proto = gjs_lookup_param_prototype(context);
+
+ /* can't come up with a better approach... */
+ unthreadsafe_template_for_constructor.gparam = gparam;
+
+ obj = gjs_construct_object_dynamic(context, proto,
+ 0, NULL);
+
+ return obj;
+}
+
+GParamSpec*
+gjs_g_param_from_param(JSContext *context,
+ JSObject *obj)
+{
+ Param *priv;
+
+ if (obj == NULL)
+ return NULL;
+
+ priv = priv_from_js(context, obj);
+
+ if (priv == NULL)
+ return NULL;
+
+ if (priv->gparam == NULL) {
+ gjs_throw(context,
+ "Object is a prototype, not an object instance - cannot convert to a paramspec instance");
+ return NULL;
+ }
+
+ return priv->gparam;
+}
diff --git a/gi/param.h b/gi/param.h
new file mode 100644
index 00000000..204eb9b4
--- /dev/null
+++ b/gi/param.h
@@ -0,0 +1,47 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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_PARAM_H__
+#define __GJS_PARAM_H__
+
+#include <glib.h>
+
+#include <jsapi.h>
+
+#include <girepository.h>
+
+G_BEGIN_DECLS
+
+JSBool gjs_define_param_class (JSContext *context,
+ JSObject *in_object,
+ JSObject **prototype_p);
+GParamSpec* gjs_g_param_from_param (JSContext *context,
+ JSObject *obj);
+JSObject* gjs_param_from_g_param (JSContext *context,
+ GParamSpec *param);
+JSObject* gjs_lookup_param_prototype (JSContext *context);
+
+
+G_END_DECLS
+
+#endif /* __GJS_PARAM_H__ */
diff --git a/gi/repo.c b/gi/repo.c
new file mode 100644
index 00000000..84b3d5bd
--- /dev/null
+++ b/gi/repo.c
@@ -0,0 +1,607 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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 "repo.h"
+#include "ns.h"
+#include "function.h"
+#include "object.h"
+#include "boxed.h"
+#include "enumeration.h"
+#include "arg.h"
+
+#include <gjs/mem.h>
+
+#include <util/log.h>
+#include <util/dirs.h>
+#include <util/misc.h>
+
+#include <jsapi.h>
+
+#include <girepository.h>
+#include <string.h>
+
+typedef struct {
+ void *dummy;
+
+} Repo;
+
+static struct JSClass gjs_repo_class;
+
+GJS_DEFINE_PRIV_FROM_JS(Repo, gjs_repo_class)
+
+static JSObject*
+resolve_namespace_object(JSContext *context,
+ JSObject *repo_obj,
+ const char *ns_name)
+{
+ GIRepository *repo;
+ GError *error;
+ char *fixed_ns_name;
+
+ fixed_ns_name = gjs_fix_ns_name(ns_name);
+
+ repo = g_irepository_get_default();
+
+ error = NULL;
+ g_irepository_require(repo, fixed_ns_name, 0, &error);
+ if (error != NULL) {
+ gjs_throw(context,
+ "Requiring %s fixed as %s: %s",
+ ns_name, fixed_ns_name, error->message);
+ g_error_free(error);
+ g_free(fixed_ns_name);
+ return JS_FALSE;
+ }
+
+ g_free(fixed_ns_name);
+
+ /* Defines a property on "obj" (the javascript repo object)
+ * with the given namespace name, pointing to that namespace
+ * in the repo.
+ */
+ return gjs_define_ns(context, repo_obj, ns_name, repo);
+}
+
+/*
+ * 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 function 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
+repo_new_resolve(JSContext *context,
+ JSObject *obj,
+ jsval id,
+ uintN flags,
+ JSObject **objp)
+{
+ Repo *priv;
+ const char *name;
+ JSContext *load_context;
+
+ *objp = NULL;
+
+ if (!gjs_get_string_id(id, &name))
+ return JS_TRUE; /* not resolved, but no error */
+
+ /* let Object.prototype resolve these */
+ if (strcmp(name, "valueOf") == 0 ||
+ strcmp(name, "toString") == 0)
+ return JS_TRUE;
+
+ priv = priv_from_js(context, obj);
+ gjs_debug_jsprop(GJS_DEBUG_GREPO, "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 */
+
+ load_context = gjs_runtime_get_load_context(JS_GetRuntime(context));
+ resolve_namespace_object(load_context, obj, name);
+ if (gjs_move_exception(load_context, context)) {
+ return JS_FALSE;
+ } else {
+ *objp = obj; /* store the object we defined the prop in */
+ 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
+repo_constructor(JSContext *context,
+ JSObject *obj,
+ uintN argc,
+ jsval *argv,
+ jsval *retval)
+{
+ Repo *priv;
+
+ priv = g_slice_new0(Repo);
+
+ GJS_INC_COUNTER(repo);
+
+ g_assert(priv_from_js(context, obj) == NULL);
+ JS_SetPrivate(context, obj, priv);
+
+ gjs_debug_lifecycle(GJS_DEBUG_GREPO,
+ "repo constructor, obj %p priv %p", obj, priv);
+
+ return JS_TRUE;
+}
+
+static void
+repo_finalize(JSContext *context,
+ JSObject *obj)
+{
+ Repo *priv;
+
+ priv = priv_from_js(context, obj);
+ gjs_debug_lifecycle(GJS_DEBUG_GREPO,
+ "finalize, obj %p priv %p", obj, priv);
+ if (priv == NULL)
+ return; /* we are the prototype, not a real instance, so constructor never called */
+
+ GJS_DEC_COUNTER(repo);
+ g_slice_free(Repo, 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_repo_class = {
+ "GIRepository", /* means "new GIRepository()" works */
+ JSCLASS_HAS_PRIVATE |
+ JSCLASS_NEW_RESOLVE |
+ JSCLASS_NEW_RESOLVE_GETS_START,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_EnumerateStub,
+ (JSResolveOp) repo_new_resolve, /* needs cast since it's the new resolve signature */
+ JS_ConvertStub,
+ repo_finalize,
+ JSCLASS_NO_OPTIONAL_MEMBERS
+};
+
+static JSPropertySpec gjs_repo_proto_props[] = {
+ { NULL }
+};
+
+static JSFunctionSpec gjs_repo_proto_funcs[] = {
+ { NULL }
+};
+
+static JSObject*
+repo_new(JSContext *context)
+{
+ JSObject *repo;
+ JSObject *global;
+
+ /* We have to define the class in the global object so we can JS_ConstructObject */
+
+ global = JS_GetGlobalObject(context);
+
+ if (!gjs_object_has_property(context, global, gjs_repo_class.name)) {
+ JSObject *prototype;
+ prototype = JS_InitClass(context, global,
+ /* parent prototype JSObject* for
+ * prototype; NULL for
+ * Object.prototype
+ */
+ NULL,
+ &gjs_repo_class,
+ /* constructor for instances (NULL for
+ * none - just name the prototype like
+ * Math - rarely correct)
+ */
+ repo_constructor,
+ /* number of constructor args */
+ 0,
+ /* props of prototype */
+ &gjs_repo_proto_props[0],
+ /* funcs of prototype */
+ &gjs_repo_proto_funcs[0],
+ /* props of constructor, MyConstructor.myprop */
+ NULL,
+ /* funcs of constructor, MyConstructor.myfunc() */
+ NULL);
+ if (prototype == NULL)
+ gjs_fatal("Can't init class %s", gjs_repo_class.name);
+
+ g_assert(gjs_object_has_property(context, global, gjs_repo_class.name));
+
+ gjs_debug(GJS_DEBUG_GREPO, "Initialized class %s prototype %p",
+ gjs_repo_class.name, prototype);
+ }
+
+ repo = JS_ConstructObject(context, &gjs_repo_class, NULL, NULL);
+ if (repo == NULL) {
+ gjs_throw(context, "No memory to create repo object");
+ return JS_FALSE;
+ }
+
+ /* FIXME - hack to make namespaces load, since
+ * gobject-introspection does not yet search a path properly.
+ */
+ {
+ jsval value;
+ JS_GetProperty(context, repo, "GLib", &value);
+ }
+
+ return repo;
+}
+
+JSBool
+gjs_define_repo(JSContext *context,
+ JSObject *module_obj,
+ const char *name)
+{
+ JSObject *repo;
+
+ if (gjs_environment_variable_is_set("GJS_USE_UNINSTALLED_FILES")) {
+ const char *builddir = g_getenv("BUILDDIR");
+
+ if (builddir != NULL) {
+ g_irepository_prepend_search_path(builddir);
+ }
+ }
+
+ repo = repo_new(context);
+
+ if (!JS_DefineProperty(context, module_obj,
+ name, OBJECT_TO_JSVAL(repo),
+ NULL, NULL,
+ GJS_MODULE_PROP_FLAGS))
+ return JS_FALSE;
+
+ return JS_TRUE;
+}
+
+static JSBool
+gjs_define_constant(JSContext *context,
+ JSObject *in_object,
+ GIConstantInfo *info)
+{
+ jsval value;
+ GArgument garg = { 0, };
+ GITypeInfo *type_info;
+ const char *name;
+
+ type_info = g_constant_info_get_type(info);
+ g_constant_info_get_value(info, &garg);
+
+ if (!gjs_value_from_g_arg(context, &value, type_info, &garg)) {
+ g_base_info_unref((GIBaseInfo*) type_info);
+ return JS_FALSE;
+ }
+
+ g_base_info_unref((GIBaseInfo*) type_info);
+
+ name = g_base_info_get_name((GIBaseInfo*) info);
+
+ if (!JS_DefineProperty(context, in_object,
+ name, value,
+ NULL, NULL,
+ GJS_MODULE_PROP_FLAGS))
+ return JS_FALSE;
+
+ return JS_TRUE;
+}
+
+#if GJS_VERBOSE_ENABLE_GI_USAGE
+void
+_gjs_log_info_usage(GIBaseInfo *info)
+{
+#define DIRECTION_STRING(d) ( ((d) == GI_DIRECTION_IN) ? "IN" : ((d) == GI_DIRECTION_OUT) ? "OUT" : "INOUT" )
+#define TRANSFER_STRING(t) ( ((t) == GI_TRANSFER_NOTHING) ? "NOTHING" : ((t) == GI_TRANSFER_CONTAINER) ? "CONTAINER" : "EVERYTHING" )
+
+ {
+ char *details;
+ GIInfoType info_type;
+ GIBaseInfo *container;
+
+ info_type = g_base_info_get_type(info);
+
+ if (info_type == GI_INFO_TYPE_FUNCTION) {
+ GString *args;
+ int n_args;
+ int i;
+ GITransfer retval_transfer;
+
+ args = g_string_new("{ ");
+
+ n_args = g_callable_info_get_n_args((GICallableInfo*) info);
+ for (i = 0; i < n_args; ++i) {
+ GIArgInfo *arg;
+ GIDirection direction;
+ GITransfer transfer;
+
+ arg = g_callable_info_get_arg((GICallableInfo*)info, i);
+ direction = g_arg_info_get_direction(arg);
+ transfer = g_arg_info_get_ownership_transfer(arg);
+
+ g_string_append_printf(args,
+ "{ GI_DIRECTION_%s, GI_TRANSFER_%s }, ",
+ DIRECTION_STRING(direction),
+ TRANSFER_STRING(transfer));
+
+ g_base_info_unref((GIBaseInfo*) arg);
+ }
+ if (args->len > 2)
+ g_string_truncate(args, args->len - 2); /* chop comma */
+
+ g_string_append(args, " }");
+
+ retval_transfer = g_callable_info_get_caller_owns((GICallableInfo*) info);
+
+ details = g_strdup_printf(".details = { .func = { .retval_transfer = GI_TRANSFER_%s, .n_args = %d, .args = %s } }",
+ TRANSFER_STRING(retval_transfer), n_args, args->str);
+ g_string_free(args, TRUE);
+ } else {
+ details = g_strdup_printf(".details = { .nothing = {} }");
+ }
+
+ container = g_base_info_get_container(info);
+
+ gjs_debug_gi_usage("{ GI_INFO_TYPE_%s, \"%s\", \"%s\", \"%s\", %s },",
+ gjs_info_type_name(info_type),
+ g_base_info_get_namespace(info),
+ container ? g_base_info_get_name(container) : "",
+ g_base_info_get_name(info),
+ details);
+ g_free(details);
+ }
+}
+#endif /* GJS_VERBOSE_ENABLE_GI_USAGE */
+
+JSBool
+gjs_define_info(JSContext *context,
+ JSObject *in_object,
+ GIBaseInfo *info)
+{
+#if GJS_VERBOSE_ENABLE_GI_USAGE
+ _gjs_log_info_usage(info);
+#endif
+
+ switch (g_base_info_get_type(info)) {
+ case GI_INFO_TYPE_FUNCTION:
+ {
+ JSObject *f;
+ f = gjs_define_function(context, in_object, (GIFunctionInfo*) info);
+ if (f == NULL)
+ return JS_FALSE;
+ }
+ break;
+ case GI_INFO_TYPE_OBJECT:
+ if (!gjs_define_object_class(context, in_object, G_TYPE_INVALID, (GIObjectInfo*) info, NULL, NULL))
+ return JS_FALSE;
+ break;
+ case GI_INFO_TYPE_BOXED:
+ if (!gjs_define_boxed_class(context, in_object, (GIBoxedInfo*) info, NULL, NULL))
+ return JS_FALSE;
+ break;
+ case GI_INFO_TYPE_ENUM:
+ case GI_INFO_TYPE_FLAGS:
+ if (!gjs_define_enumeration(context, in_object, (GIEnumInfo*) info, NULL))
+ return JS_FALSE;
+ break;
+ case GI_INFO_TYPE_CONSTANT:
+ if (!gjs_define_constant(context, in_object, (GIConstantInfo*) info))
+ return JS_FALSE;
+ break;
+ default:
+ gjs_throw(context, "API of type %s not implemented, cannot define %s.%s",
+ gjs_info_type_name(g_base_info_get_type(info)),
+ g_base_info_get_namespace(info),
+ g_base_info_get_name(info));
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
+/* Get the namespace object that the GIBaseInfo should be inside */
+JSObject*
+gjs_lookup_namespace_object(JSContext *context,
+ GIBaseInfo *info)
+{
+ const char *ns;
+
+ ns = g_base_info_get_namespace(info);
+ if (ns == NULL) {
+ gjs_throw(context, "%s '%s' does not have a namespace",
+ gjs_info_type_name(g_base_info_get_type(info)),
+ g_base_info_get_name(info));
+
+ return NULL;
+ }
+
+ return gjs_lookup_namespace_object_by_name(context, ns);
+}
+
+JSObject*
+gjs_lookup_namespace_object_by_name(JSContext *context,
+ const char *ns)
+{
+ JSContext *load_context;
+ JSObject *global;
+ JSObject *repo_obj;
+ jsval importer;
+ jsval girepository;
+ jsval ns_obj;
+
+ /* This is a little bit of a hack, we hardcode an assumption that
+ * the only repo object that exists is called "imports.gi" and is
+ * in the load context.
+ */
+
+ load_context = gjs_runtime_get_load_context(JS_GetRuntime(context));
+ global = JS_GetGlobalObject(load_context);
+
+ importer = JSVAL_VOID;
+ if (!gjs_object_require_property(load_context, global, "imports", &importer) ||
+ !JSVAL_IS_OBJECT(importer)) {
+ gjs_log_exception(load_context, NULL);
+ gjs_throw(context, "No imports property in global object");
+ return NULL;
+ }
+
+ girepository = JSVAL_VOID;
+ if (!gjs_object_require_property(load_context, JSVAL_TO_OBJECT(importer),
+ "gi", &girepository) ||
+ !JSVAL_IS_OBJECT(girepository)) {
+ gjs_log_exception(load_context, NULL);
+ gjs_throw(context, "No gi property in importer");
+ return NULL;
+ }
+
+ repo_obj = JSVAL_TO_OBJECT(girepository);
+
+ if (!gjs_object_require_property(context, repo_obj, ns, &ns_obj))
+ return NULL;
+
+ if (!JSVAL_IS_OBJECT(ns_obj)) {
+ gjs_throw(context, "Namespace '%s' is not an object?", ns);
+ return NULL;
+ }
+
+ return JSVAL_TO_OBJECT(ns_obj);
+}
+
+const char*
+gjs_info_type_name(GIInfoType type)
+{
+ switch (type) {
+ case GI_INFO_TYPE_INVALID:
+ return "INVALID";
+ case GI_INFO_TYPE_FUNCTION:
+ return "FUNCTION";
+ case GI_INFO_TYPE_CALLBACK:
+ return "CALLBACK";
+ case GI_INFO_TYPE_STRUCT:
+ return "STRUCT";
+ case GI_INFO_TYPE_BOXED:
+ return "BOXED";
+ case GI_INFO_TYPE_ENUM:
+ return "ENUM";
+ case GI_INFO_TYPE_FLAGS:
+ return "FLAGS";
+ case GI_INFO_TYPE_OBJECT:
+ return "OBJECT";
+ case GI_INFO_TYPE_INTERFACE:
+ return "INTERFACE";
+ case GI_INFO_TYPE_CONSTANT:
+ return "CONSTANT";
+ case GI_INFO_TYPE_ERROR_DOMAIN:
+ return "ERROR_DOMAIN";
+ case GI_INFO_TYPE_UNION:
+ return "UNION";
+ case GI_INFO_TYPE_VALUE:
+ return "VALUE";
+ case GI_INFO_TYPE_SIGNAL:
+ return "SIGNAL";
+ case GI_INFO_TYPE_VFUNC:
+ return "VFUNC";
+ case GI_INFO_TYPE_PROPERTY:
+ return "PROPERTY";
+ case GI_INFO_TYPE_FIELD:
+ return "FIELD";
+ case GI_INFO_TYPE_ARG:
+ return "ARG";
+ case GI_INFO_TYPE_TYPE:
+ return "TYPE";
+ case GI_INFO_TYPE_UNRESOLVED:
+ return "UNRESOLVED";
+ }
+
+ return "???";
+}
+
+char*
+gjs_camel_from_hyphen(const char *hyphen_name)
+{
+ GString *s;
+ const char *p;
+ gboolean next_upper;
+
+ s = g_string_sized_new(strlen(hyphen_name) + 1);
+
+ next_upper = FALSE;
+ for (p = hyphen_name; *p; ++p) {
+ if (*p == '-' || *p == '_') {
+ next_upper = TRUE;
+ } else {
+ if (next_upper) {
+ g_string_append_c(s, g_ascii_toupper(*p));
+ next_upper = FALSE;
+ } else {
+ g_string_append_c(s, *p);
+ }
+ }
+ }
+
+ return g_string_free(s, FALSE);
+}
+
+char*
+gjs_hyphen_from_camel(const char *camel_name)
+{
+ GString *s;
+ const char *p;
+
+ /* four hyphens should be reasonable guess */
+ s = g_string_sized_new(strlen(camel_name) + 4 + 1);
+
+ for (p = camel_name; *p; ++p) {
+ if (g_ascii_isupper(*p)) {
+ g_string_append_c(s, '-');
+ g_string_append_c(s, g_ascii_tolower(*p));
+ } else {
+ g_string_append_c(s, *p);
+ }
+ }
+
+ return g_string_free(s, FALSE);
+}
diff --git a/gi/repo.h b/gi/repo.h
new file mode 100644
index 00000000..b2f3daea
--- /dev/null
+++ b/gi/repo.h
@@ -0,0 +1,60 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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_REPO_H__
+#define __GJS_REPO_H__
+
+#include <glib.h>
+
+#include <jsapi.h>
+
+#include <girepository.h>
+
+#include <gjs/jsapi-util.h>
+
+G_BEGIN_DECLS
+
+JSBool gjs_define_repo (JSContext *context,
+ JSObject *module_obj,
+ const char *name);
+const char* gjs_info_type_name (GIInfoType type);
+JSObject* gjs_lookup_namespace_object (JSContext *context,
+ GIBaseInfo *info);
+JSObject* gjs_lookup_namespace_object_by_name (JSContext *context,
+ const char *name);
+JSObject* gjs_lookup_function_object (JSContext *context,
+ GIFunctionInfo *info);
+JSBool gjs_define_info (JSContext *context,
+ JSObject *in_object,
+ GIBaseInfo *info);
+char* gjs_camel_from_hyphen (const char *hyphen_name);
+char* gjs_hyphen_from_camel (const char *camel_name);
+
+
+#if GJS_VERBOSE_ENABLE_GI_USAGE
+void _gjs_log_info_usage(GIBaseInfo *info);
+#endif
+
+G_END_DECLS
+
+#endif /* __GJS_REPO_H__ */
diff --git a/gi/value.c b/gi/value.c
new file mode 100644
index 00000000..ceafb4d7
--- /dev/null
+++ b/gi/value.c
@@ -0,0 +1,512 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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 <util/log.h>
+
+#include "value.h"
+#include "closure.h"
+#include "arg.h"
+#include "param.h"
+#include "object.h"
+#include "boxed.h"
+#include <gjs/jsapi-util.h>
+
+static void
+closure_marshal(GClosure *closure,
+ GValue *return_value,
+ guint n_param_values,
+ const GValue *param_values,
+ gpointer invocation_hint,
+ gpointer marshal_data)
+{
+ JSContext *context;
+ int argc;
+ jsval *argv;
+ jsval rval;
+ int i;
+
+ gjs_debug_marshal(GJS_DEBUG_GCLOSURE,
+ "Marshal closure %p",
+ closure);
+
+ context = gjs_closure_get_context(closure);
+ if (context == NULL) {
+ /* We were destroyed; become a no-op */
+ return;
+ }
+
+ argc = n_param_values;
+ argv = g_newa(jsval, n_param_values);
+ rval = JSVAL_VOID;
+
+ gjs_set_values(context, argv, argc, JSVAL_VOID);
+ gjs_root_value_locations(context, argv, argc);
+ JS_AddRoot(context, &rval);
+
+ for (i = 0; i < argc; ++i) {
+ const GValue *gval = &param_values[i];
+ if (!gjs_value_from_g_value(context, &argv[i], gval)) {
+ gjs_debug(GJS_DEBUG_GCLOSURE,
+ "Unable to convert arg %d in order to invoke closure",
+ i);
+ gjs_log_exception(context, NULL);
+ goto cleanup;
+ }
+ }
+
+ gjs_closure_invoke(closure, argc, argv, &rval);
+
+ if (return_value != NULL) {
+ if (rval == JSVAL_VOID) {
+ /* something went wrong invoking, error should be set already */
+ goto cleanup;
+ }
+
+ if (!gjs_value_to_g_value(context, rval, return_value)) {
+ gjs_debug(GJS_DEBUG_GCLOSURE,
+ "Unable to convert return value when invoking closure");
+ gjs_log_exception(context, NULL);
+ goto cleanup;
+ }
+ }
+
+ cleanup:
+ gjs_unroot_value_locations(context, argv, argc);
+ JS_RemoveRoot(context, &rval);
+}
+
+GClosure*
+gjs_closure_new_marshaled (JSContext *context,
+ JSObject *callable,
+ const char *description)
+{
+ GClosure *closure;
+
+ closure = gjs_closure_new(context, callable, description);
+
+ g_closure_set_marshal(closure, closure_marshal);
+
+ return closure;
+}
+
+static GType
+gjs_value_guess_g_type(jsval value)
+{
+ if (JSVAL_IS_NULL(value))
+ return G_TYPE_POINTER;
+
+ if (JSVAL_IS_STRING(value))
+ return G_TYPE_STRING;
+
+ if (JSVAL_IS_INT(value))
+ return G_TYPE_INT;
+
+ if (JSVAL_IS_DOUBLE(value))
+ return G_TYPE_DOUBLE;
+
+ if (JSVAL_IS_BOOLEAN(value))
+ return G_TYPE_BOOLEAN;
+
+ if (JSVAL_IS_OBJECT(value)) {
+ return G_TYPE_OBJECT;
+ }
+
+ return G_TYPE_INVALID;
+}
+
+JSBool
+gjs_value_to_g_value(JSContext *context,
+ jsval value,
+ GValue *gvalue)
+{
+ GType gtype;
+
+ gtype = G_VALUE_TYPE(gvalue);
+
+ if (gtype == 0) {
+ gtype = gjs_value_guess_g_type(value);
+
+ if (gtype == G_TYPE_INVALID) {
+ gjs_throw(context, "Could not guess unspecified GValue type");
+ return JS_FALSE;
+ }
+
+ gjs_debug_marshal(GJS_DEBUG_GCLOSURE,
+ "Guessed GValue type %s from JS Value",
+ g_type_name(gtype));
+
+ g_value_init(gvalue, gtype);
+ }
+
+ gjs_debug_marshal(GJS_DEBUG_GCLOSURE,
+ "Converting jsval to gtype %s",
+ g_type_name(gtype));
+
+
+ if (gtype == G_TYPE_STRING) {
+ /* Don't use ValueToString since we don't want to just toString()
+ * everything automatically
+ */
+ if (JSVAL_IS_NULL(value)) {
+ g_value_set_string(gvalue, NULL);
+ } else if (JSVAL_IS_STRING(value)) {
+ gchar *utf8_string;
+
+ if (!gjs_string_to_utf8(context, value, &utf8_string))
+ return JS_FALSE;
+
+ g_value_set_string(gvalue, utf8_string);
+ g_free(utf8_string);
+ } else {
+ gjs_throw(context,
+ "Wrong type %s; string expected",
+ gjs_get_type_name(value));
+ return JS_FALSE;
+ }
+ } else if (gtype == G_TYPE_CHAR) {
+ gint32 i;
+ if (JS_ValueToInt32(context, value, &i) && i >= SCHAR_MIN && i <= SCHAR_MAX) {
+ g_value_set_char(gvalue, (signed char)i);
+ } else {
+ gjs_throw(context,
+ "Wrong type %s; char expected",
+ gjs_get_type_name(value));
+ return JS_FALSE;
+ }
+ } else if (gtype == G_TYPE_UCHAR) {
+ guint16 i;
+ if (JS_ValueToUint16(context, value, &i) && i <= UCHAR_MAX) {
+ g_value_set_uchar(gvalue, (unsigned char)i);
+ } else {
+ gjs_throw(context,
+ "Wrong type %s; unsigned char expected",
+ gjs_get_type_name(value));
+ return JS_FALSE;
+ }
+ } else if (gtype == G_TYPE_INT) {
+ gint32 i;
+ if (JS_ValueToInt32(context, value, &i)) {
+ g_value_set_int(gvalue, i);
+ } else {
+ gjs_throw(context,
+ "Wrong type %s; integer expected",
+ gjs_get_type_name(value));
+ return JS_FALSE;
+ }
+ } else if (gtype == G_TYPE_DOUBLE) {
+ gdouble d;
+ if (JS_ValueToNumber(context, value, &d)) {
+ g_value_set_double(gvalue, d);
+ } else {
+ gjs_throw(context,
+ "Wrong type %s; double expected",
+ gjs_get_type_name(value));
+ return JS_FALSE;
+ }
+ } else if (gtype == G_TYPE_FLOAT) {
+ gdouble d;
+ if (JS_ValueToNumber(context, value, &d)) {
+ g_value_set_float(gvalue, d);
+ } else {
+ gjs_throw(context,
+ "Wrong type %s; float expected",
+ gjs_get_type_name(value));
+ return JS_FALSE;
+ }
+ } else if (gtype == G_TYPE_UINT) {
+ guint32 i;
+ if (JS_ValueToECMAUint32(context, value, &i)) {
+ g_value_set_uint(gvalue, i);
+ } else {
+ gjs_throw(context,
+ "Wrong type %s; unsigned integer expected",
+ gjs_get_type_name(value));
+ return JS_FALSE;
+ }
+ } else if (gtype == G_TYPE_BOOLEAN) {
+ JSBool b;
+
+ /* JS_ValueToBoolean() pretty much always succeeds,
+ * which is maybe surprising sometimes, but could
+ * be handy also...
+ */
+ if (JS_ValueToBoolean(context, value, &b)) {
+ g_value_set_boolean(gvalue, b);
+ } else {
+ gjs_throw(context,
+ "Wrong type %s; boolean expected",
+ gjs_get_type_name(value));
+ return JS_FALSE;
+ }
+ } else if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) {
+ GObject *gobj;
+
+ gobj = NULL;
+ if (JSVAL_IS_NULL(value)) {
+ /* nothing to do */
+ } else if (JSVAL_IS_OBJECT(value)) {
+ JSObject *obj;
+ obj = JSVAL_TO_OBJECT(value);
+ gobj = gjs_g_object_from_object(context, obj);
+ } else {
+ gjs_throw(context,
+ "Wrong type %s; object %s expected",
+ gjs_get_type_name(value),
+ g_type_name(gtype));
+ return JS_FALSE;
+ }
+
+ g_value_set_object(gvalue, gobj);
+ } else if (g_type_is_a(gtype, G_TYPE_BOXED)) {
+ void *gboxed;
+
+ gboxed = NULL;
+ if (JSVAL_IS_NULL(value)) {
+ /* nothing to do */
+ } else if (JSVAL_IS_OBJECT(value)) {
+ JSObject *obj;
+ obj = JSVAL_TO_OBJECT(value);
+ gboxed = gjs_g_boxed_from_boxed(context, obj);
+ } else {
+ gjs_throw(context,
+ "Wrong type %s; boxed type %s expected",
+ gjs_get_type_name(value),
+ g_type_name(gtype));
+ return JS_FALSE;
+ }
+
+ g_value_set_boxed(gvalue, gboxed);
+ } else if (g_type_is_a(gtype, G_TYPE_ENUM)) {
+ if (JSVAL_IS_INT(value)) {
+ GEnumValue *v;
+
+ v = g_enum_get_value(G_ENUM_CLASS(g_type_class_peek(gtype)),
+ JSVAL_TO_INT(value));
+ if (v == NULL) {
+ gjs_throw(context,
+ "%d is not a valid value for enumeration %s",
+ JSVAL_TO_INT(value), g_type_name(gtype));
+ return JS_FALSE;
+ }
+
+ g_value_set_enum(gvalue, v->value);
+ } else {
+ gjs_throw(context,
+ "Wrong type %s; enum %s expected",
+ gjs_get_type_name(value),
+ g_type_name(gtype));
+ return JS_FALSE;
+ }
+ } else if (g_type_is_a(gtype, G_TYPE_FLAGS)) {
+ if (JSVAL_IS_INT(value)) {
+ if (!_gjs_flags_value_is_valid(context,
+ G_FLAGS_CLASS(g_type_class_peek(gtype)),
+ JSVAL_TO_INT(value)))
+ return JS_FALSE;
+
+ g_value_set_flags(gvalue, value);
+ } else {
+ gjs_throw(context,
+ "Wrong type %s; flags %s expected",
+ gjs_get_type_name(value),
+ g_type_name(gtype));
+ return JS_FALSE;
+ }
+ } else if (g_type_is_a(gtype, G_TYPE_PARAM)) {
+ void *gparam;
+
+ gparam = NULL;
+ if (JSVAL_IS_NULL(value)) {
+ /* nothing to do */
+ } else if (JSVAL_IS_OBJECT(value)) {
+ JSObject *obj;
+ obj = JSVAL_TO_OBJECT(value);
+ gparam = gjs_g_param_from_param(context, obj);
+ } else {
+ gjs_throw(context,
+ "Wrong type %s; param type %s expected",
+ gjs_get_type_name(value),
+ g_type_name(gtype));
+ return JS_FALSE;
+ }
+
+ g_value_set_param(gvalue, gparam);
+ } else if (g_type_is_a(gtype, G_TYPE_POINTER)) {
+ if (JSVAL_IS_NULL(value)) {
+ /* Nothing to do */
+ } else {
+ gjs_throw(context,
+ "Cannot convert non-null JS value to G_POINTER");
+ return JS_FALSE;
+ }
+ } else if (JSVAL_IS_NUMBER(value) &&
+ g_value_type_transformable(G_TYPE_INT, gtype)) {
+ /* Only do this crazy gvalue transform stuff after we've
+ * exhausted everything else. Adding this for
+ * e.g. ClutterUnit.
+ */
+ gint32 i;
+ if (JS_ValueToInt32(context, value, &i)) {
+ GValue int_value = { 0, };
+ g_value_init(&int_value, G_TYPE_INT);
+ g_value_set_int(&int_value, i);
+ g_value_transform(&int_value, gvalue);
+ } else {
+ gjs_throw(context,
+ "Wrong type %s; integer expected",
+ gjs_get_type_name(value));
+ return JS_FALSE;
+ }
+ } else {
+ gjs_debug(GJS_DEBUG_GCLOSURE, "jsval is number %d gtype fundamental %d transformable to int %d from int %d",
+ JSVAL_IS_NUMBER(value),
+ G_TYPE_IS_FUNDAMENTAL(gtype),
+ g_value_type_transformable(gtype, G_TYPE_INT),
+ g_value_type_transformable(G_TYPE_INT, gtype));
+
+ gjs_throw(context,
+ "Don't know how to convert JavaScript object to GType %s",
+ g_type_name(gtype));
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
+JSBool
+gjs_value_from_g_value(JSContext *context,
+ jsval *value_p,
+ const GValue *gvalue)
+{
+ GType gtype;
+
+ gtype = G_VALUE_TYPE(gvalue);
+
+ gjs_debug_marshal(GJS_DEBUG_GCLOSURE,
+ "Converting gtype %s to jsval",
+ g_type_name(gtype));
+
+ if (gtype == G_TYPE_STRING) {
+ const char *v;
+ v = g_value_get_string(gvalue);
+ if (v == NULL) {
+ gjs_debug_marshal(GJS_DEBUG_GCLOSURE,
+ "Converting NULL string to JSVAL_NULL");
+ *value_p = JSVAL_NULL;
+ } else {
+ if (!gjs_string_from_utf8(context, v, -1, value_p))
+ return JS_FALSE;
+ }
+ } else if (gtype == G_TYPE_CHAR) {
+ char v;
+ v = g_value_get_char(gvalue);
+ *value_p = INT_TO_JSVAL(v);
+ } else if (gtype == G_TYPE_UCHAR) {
+ unsigned char v;
+ v = g_value_get_uchar(gvalue);
+ *value_p = INT_TO_JSVAL(v);
+ } else if (gtype == G_TYPE_INT) {
+ int v;
+ v = g_value_get_int(gvalue);
+ return JS_NewNumberValue(context, v, value_p);
+ } else if (gtype == G_TYPE_UINT) {
+ uint v;
+ v = g_value_get_uint(gvalue);
+ return JS_NewNumberValue(context, v, value_p);
+ } else if (gtype == G_TYPE_DOUBLE) {
+ double d;
+ d = g_value_get_double(gvalue);
+ return JS_NewNumberValue(context, d, value_p);
+ } else if (gtype == G_TYPE_FLOAT) {
+ double d;
+ d = g_value_get_float(gvalue);
+ return JS_NewNumberValue(context, d, value_p);
+ } else if (gtype == G_TYPE_BOOLEAN) {
+ gboolean v;
+ v = g_value_get_boolean(gvalue);
+ *value_p = BOOLEAN_TO_JSVAL(v);
+ } else if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) {
+ GObject *gobj;
+ JSObject *obj;
+
+ gobj = g_value_get_object(gvalue);
+
+ obj = gjs_object_from_g_object(context, gobj);
+ *value_p = OBJECT_TO_JSVAL(obj);
+ } else if (g_type_is_a(gtype, G_TYPE_BOXED)) {
+ void *gboxed;
+ JSObject *obj;
+
+ gboxed = g_value_get_boxed(gvalue);
+
+ obj = gjs_boxed_from_g_boxed(context, gtype, gboxed);
+ *value_p = OBJECT_TO_JSVAL(obj);
+ } else if (g_type_is_a(gtype, G_TYPE_ENUM)) {
+ int v;
+ v = g_value_get_enum(gvalue);
+ *value_p = INT_TO_JSVAL(v);
+ } else if (g_type_is_a(gtype, G_TYPE_PARAM)) {
+ GParamSpec *gparam;
+ JSObject *obj;
+
+ gparam = g_value_get_param(gvalue);
+
+ obj = gjs_param_from_g_param(context, gparam);
+ *value_p = OBJECT_TO_JSVAL(obj);
+ } else if (g_type_is_a(gtype, G_TYPE_POINTER)) {
+ gpointer pointer;
+
+ pointer = g_value_get_pointer(gvalue);
+
+ if (pointer == NULL) {
+ *value_p = JSVAL_NULL;
+ } else {
+ gjs_throw(context,
+ "Can't convert non-null pointer to JS value");
+ return JS_FALSE;
+ }
+ } else if (g_value_type_transformable(gtype, G_TYPE_DOUBLE)) {
+ GValue double_value = { 0, };
+ double v;
+ g_value_init(&double_value, G_TYPE_DOUBLE);
+ g_value_transform(gvalue, &double_value);
+ v = g_value_get_double(&double_value);
+ return JS_NewNumberValue(context, v, value_p);
+ } else if (g_value_type_transformable(gtype, G_TYPE_INT)) {
+ GValue int_value = { 0, };
+ int v;
+ g_value_init(&int_value, G_TYPE_INT);
+ g_value_transform(gvalue, &int_value);
+ v = g_value_get_int(&int_value);
+ return JS_NewNumberValue(context, v, value_p);
+ } else {
+ gjs_throw(context,
+ "Don't know how to convert GType %s to JavaScript object",
+ g_type_name(gtype));
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
diff --git a/gi/value.h b/gi/value.h
new file mode 100644
index 00000000..06f5a259
--- /dev/null
+++ b/gi/value.h
@@ -0,0 +1,45 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 LiTL, LLC
+ *
+ * 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_VALUE_H__
+#define __GJS_VALUE_H__
+
+#include <glib-object.h>
+
+#include <jsapi.h>
+
+G_BEGIN_DECLS
+
+JSBool gjs_value_to_g_value (JSContext *context,
+ jsval value,
+ GValue *gvalue);
+JSBool gjs_value_from_g_value (JSContext *context,
+ jsval *value_p,
+ const GValue *gvalue);
+GClosure* gjs_closure_new_marshaled (JSContext *context,
+ JSObject *callable,
+ const char *description);
+
+G_END_DECLS
+
+#endif /* __GJS_VALUE_H__ */