[libpeas] Implemented GJS plugin loader



commit 1dfe5804501b8e785e4a58e5d180a02ca7a9d531
Author: Garrett Regier <alias301 gmail com>
Date:   Mon Mar 21 07:29:13 2011 -0700

    Implemented GJS plugin loader

 .gitignore                                         |    1 +
 configure.ac                                       |   41 ++
 loaders/Makefile.am                                |    4 +
 loaders/gjs/Makefile.am                            |   21 +
 loaders/gjs/peas-extension-gjs.c                   |  403 ++++++++++++++++++++
 loaders/gjs/peas-extension-gjs.h                   |   59 +++
 loaders/gjs/peas-plugin-loader-gjs.c               |  307 +++++++++++++++
 loaders/gjs/peas-plugin-loader-gjs.h               |   55 +++
 peas-demo/peas-demo.c                              |    1 +
 peas-demo/plugins/Makefile.am                      |    4 +
 peas-demo/plugins/gjshello/Makefile.am             |    7 +
 peas-demo/plugins/gjshello/gjshello.js             |   33 ++
 peas-demo/plugins/gjshello/gjshello.plugin         |    9 +
 tests/libpeas/Makefile.am                          |    7 +
 tests/libpeas/extension-gjs.c                      |   80 ++++
 tests/libpeas/plugins/Makefile.am                  |    4 +
 tests/libpeas/plugins/extension-gjs/Makefile.am    |    5 +
 .../libpeas/plugins/extension-gjs/extension-gjs.js |   24 ++
 .../plugins/extension-gjs/extension-gjs.plugin     |    9 +
 19 files changed, 1074 insertions(+), 0 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 70d75ad..87b5f14 100644
--- a/.gitignore
+++ b/.gitignore
@@ -74,6 +74,7 @@ Makefile.in
 /tests/*/vgdump-*
 /tests/libpeas/engine
 /tests/libpeas/extension-c
+/tests/libpeas/extension-gjs
 /tests/libpeas/extension-python
 /tests/libpeas/extension-seed
 /tests/libpeas/extension-set
diff --git a/configure.ac b/configure.ac
index 0f1cebd..667f3b0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -266,6 +266,43 @@ fi
 AM_CONDITIONAL([ENABLE_SEED],[test "x$found_seed" = "xyes"])
 
 dnl ================================================================
+dnl GJS Javascript Engine
+dnl ================================================================
+
+GJS_REQUIRED=0.7.8
+
+AC_ARG_ENABLE(gjs,
+	      AS_HELP_STRING([--enable-gjs],[Enable GJS support]),
+	      [enable_gjs=$enableval],
+	      [enable_gjs=auto])
+
+AC_MSG_CHECKING([for GJS JS availability.])
+
+if test "x$enable_gjs" = "xno"; then
+	found_gjs="no (disabled, use --enable-gjs to enable)"
+else
+	PKG_CHECK_EXISTS([gjs-internals-1.0 >= $GJS_REQUIRED gjs-gi-1.0],
+			 [found_gjs=yes],
+			 [found_gjs=no])
+fi
+
+if test "$enable_gjs" = "yes" -a "$found_gjs" = "no"; then
+	AC_MSG_ERROR([You need to have gjs-internals-1.0 >= $GJS_REQUIRED installed to build libpeas])
+fi
+AC_MSG_RESULT([$found_gjs])
+
+if test "$found_gjs" = "yes"; then
+	GJS_CFLAGS=`$PKG_CONFIG --cflags gjs-internals-1.0 gjs-gi-1.0`
+	GJS_LIBS=`$PKG_CONFIG --libs gjs-internals-1.0 gjs-gi-1.0`
+	AC_SUBST(GJS_CFLAGS)
+	AC_SUBST(GJS_LIBS)
+
+	AC_DEFINE(ENABLE_GJS,1,[Define to compile with GJS support])
+fi
+
+AM_CONDITIONAL([ENABLE_GJS],[test "x$found_gjs" = "xyes"])
+
+dnl ================================================================
 dnl Python
 dnl ================================================================
 
@@ -434,6 +471,7 @@ libpeas/Makefile
 libpeas-gtk/Makefile
 loaders/Makefile
 loaders/c/Makefile
+loaders/gjs/Makefile
 loaders/python/Makefile
 loaders/seed/Makefile
 data/Makefile
@@ -443,6 +481,7 @@ data/libpeas-1.0.pc
 data/libpeas-gtk-1.0.pc
 peas-demo/Makefile
 peas-demo/plugins/Makefile
+peas-demo/plugins/gjshello/Makefile
 peas-demo/plugins/helloworld/Makefile
 peas-demo/plugins/pythonhello/Makefile
 peas-demo/plugins/secondtime/Makefile
@@ -453,6 +492,7 @@ tests/Makefile
 tests/libpeas/Makefile
 tests/libpeas/plugins/Makefile
 tests/libpeas/plugins/extension-c/Makefile
+tests/libpeas/plugins/extension-gjs/Makefile
 tests/libpeas/plugins/extension-python/Makefile
 tests/libpeas/plugins/extension-seed/Makefile
 tests/libpeas/introspection/Makefile
@@ -484,6 +524,7 @@ Configuration:
         Coverage testing              : ${enable_gcov}
         Glade Catalog                 : ${found_glade_catalog}
         Seed JS support               : ${found_seed}
+        GJS JS support                : ${found_gjs}
         Python support                : ${found_python}
         Vala support                  : ${found_vala}
 "
diff --git a/loaders/Makefile.am b/loaders/Makefile.am
index 6e6c67a..f7832ae 100644
--- a/loaders/Makefile.am
+++ b/loaders/Makefile.am
@@ -1,5 +1,9 @@
 SUBDIRS = c
 
+if ENABLE_GJS
+SUBDIRS += gjs
+endif
+
 if ENABLE_PYTHON
 SUBDIRS += python
 endif
diff --git a/loaders/gjs/Makefile.am b/loaders/gjs/Makefile.am
new file mode 100644
index 0000000..f768d16
--- /dev/null
+++ b/loaders/gjs/Makefile.am
@@ -0,0 +1,21 @@
+# GJS plugin loader
+
+loaderdir = $(libdir)/libpeas-1.0/loaders
+
+INCLUDES = \
+	-I$(top_srcdir)			\
+	$(PEAS_CFLAGS)			\
+	$(WARN_CFLAGS)			\
+	$(DISABLE_DEPRECATED)		\
+	$(GJS_CFLAGS)
+
+loader_LTLIBRARIES = libgjsloader.la
+
+libgjsloader_la_SOURCES = \
+	peas-extension-gjs.c		\
+	peas-extension-gjs.h		\
+	peas-plugin-loader-gjs.c 	\
+	peas-plugin-loader-gjs.h
+
+libgjsloader_la_LDFLAGS = $(LOADER_LIBTOOL_FLAGS)
+libgjsloader_la_LIBADD = $(PEAS_LIBS) $(GJS_LIBS)
diff --git a/loaders/gjs/peas-extension-gjs.c b/loaders/gjs/peas-extension-gjs.c
new file mode 100644
index 0000000..f93da35
--- /dev/null
+++ b/loaders/gjs/peas-extension-gjs.c
@@ -0,0 +1,403 @@
+/*
+ * peas-extension-gjs.c
+ * This file is part of libpeas
+ *
+ * Copyright (C) 2011 - Garrett Regier, Steve Frécinaux
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Library General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <girepository.h>
+#include <gjs/gi/arg.h>
+#include <gjs/gi/value.h>
+
+#include <libpeas/peas-introspection.h>
+#include <libpeas/peas-extension-subclasses.h>
+
+#include "peas-extension-gjs.h"
+
+G_DEFINE_TYPE (PeasExtensionGjs, peas_extension_gjs, PEAS_TYPE_EXTENSION);
+
+typedef struct {
+  GIArgInfo arg_info;
+  GITypeInfo type_info;
+
+  /* Only used by out arguments */
+  gpointer ptr;
+} CachedArg;
+
+static void
+peas_extension_gjs_init (PeasExtensionGjs *gexten)
+{
+}
+
+static gchar *
+convert_property_name (const gchar *pname)
+{
+  gint i;
+  gchar *prop_name;
+
+  prop_name = g_strdup (pname);
+
+  for (i = 0; prop_name[i] != '\0'; ++i)
+    {
+      if (prop_name[i] == '-')
+        prop_name[i] = '_';
+    }
+
+  return prop_name;
+}
+
+static void
+peas_extension_gjs_set_property (GObject      *object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  PeasExtensionGjs *gexten = PEAS_EXTENSION_GJS (object);
+  gchar *prop_name;
+  jsval js_value;
+
+  prop_name = convert_property_name (g_param_spec_get_name (pspec));
+
+  if (!gjs_value_from_g_value (gexten->js_context, &js_value, value))
+    {
+      g_warning ("Error: failed to convert GValue to "
+                 "jsval for property '%s'", prop_name);
+    }
+  else if (!JS_SetProperty (gexten->js_context, gexten->js_object,
+                            prop_name, &js_value))
+    {
+      g_warning ("Error: failed to set property '%s'", prop_name);
+    }
+
+  g_free (prop_name);
+}
+
+static void
+peas_extension_gjs_get_property (GObject    *object,
+                                 guint       prop_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+  PeasExtensionGjs *gexten = PEAS_EXTENSION_GJS (object);
+  gchar *prop_name;
+  jsval js_value;
+
+  prop_name = convert_property_name (g_param_spec_get_name (pspec));
+
+  if (!JS_GetProperty (gexten->js_context, gexten->js_object,
+                       prop_name, &js_value))
+    {
+      g_warning ("Error: failed to get property '%s'", prop_name);
+    }
+  else if (!gjs_value_to_g_value (gexten->js_context, js_value, value))
+    {
+      g_warning ("Error: failed to convert jsval to "
+                 "GValue for property '%s'", prop_name);
+    }
+
+  g_free (prop_name);
+}
+
+static void
+peas_extension_gjs_dispose (GObject *object)
+{
+  PeasExtensionGjs *gexten = PEAS_EXTENSION_GJS (object);
+
+  if (gexten->js_context != NULL)
+    {
+      JS_RemoveObjectRoot (gexten->js_context, &gexten->js_object);
+      gexten->js_context = NULL;
+      gexten->js_object = NULL;
+    }
+}
+
+static gboolean
+set_out_arg (JSContext      *js_context,
+             GIFunctionInfo *func_info,
+             gboolean        is_return_value,
+             GIArgInfo      *arg_info,
+             GITypeInfo     *type_info,
+             gpointer        ptr,
+             jsval           js_value)
+{
+  gboolean nullable;
+  GITransfer transfer;
+  GIArgument argument;
+
+  if (is_return_value)
+    {
+      nullable = g_callable_info_may_return_null (func_info);
+      transfer = g_callable_info_get_caller_owns (func_info);
+    }
+  else
+    {
+      nullable = g_arg_info_may_be_null (arg_info);
+      transfer = g_arg_info_get_ownership_transfer (arg_info);
+    }
+
+  if (!gjs_value_to_g_argument (js_context, js_value, type_info, NULL,
+                                GJS_ARGUMENT_RETURN_VALUE, /* ? */
+                                transfer, nullable, &argument))
+    {
+      if (is_return_value)
+        {
+          g_warning ("Error failed to convert return value to GIArgument");
+        }
+      else
+        {
+          g_warning ("Error failed to convert OUT argument '%s' from "
+                     "jsval to GIArgument", g_base_info_get_name (type_info));
+        }
+
+      return FALSE;
+    }
+
+  peas_gi_argument_to_pointer (type_info, &argument, ptr);
+
+  return TRUE;
+}
+
+static gboolean
+peas_extension_gjs_call (PeasExtension *exten,
+                         const gchar   *method_name,
+                         GIArgument    *args,
+                         GIArgument    *retval)
+{
+  PeasExtensionGjs *gexten = PEAS_EXTENSION_GJS (exten);
+  GType exten_type;
+  gboolean success = FALSE;
+  jsval js_method, js_retval;
+  GICallableInfo *func_info;
+  jsval *js_args;
+  CachedArg *arg_cache;
+  gint i, n_args, nth_out_arg;
+  gint n_in_args = 0;
+  gint n_out_args = 0;
+  gint cached_args = 0;
+
+  exten_type = peas_extension_get_extension_type (exten);
+
+  /* Fetch the JS method we want to call */
+  if (!JS_GetProperty (gexten->js_context, gexten->js_object,
+                       method_name, &js_method) ||
+      JSVAL_IS_VOID (js_method))
+    {
+      g_warning ("Method '%s.%s' was not found",
+                 g_type_name (exten_type), method_name);
+      return FALSE;
+    }
+
+  if (JSVAL_IS_NULL (js_method) || !JSVAL_IS_OBJECT (js_method) ||
+      !JS_ObjectIsFunction (gexten->js_context, JSVAL_TO_OBJECT (js_method)))
+    {
+      g_warning ("Method '%s.%s' in not a function",
+                 g_type_name (exten_type), method_name);
+      return FALSE;
+    }
+
+  /* Prepare the arguments */
+  func_info = peas_gi_get_method_info (exten_type, method_name);
+  if (func_info == NULL)
+    return FALSE;
+
+  n_args = g_callable_info_get_n_args (func_info);
+  if (n_args < 0)
+    {
+      g_warn_if_fail (n_args >= 0);
+      goto out;
+    }
+
+  js_args = g_newa (jsval, n_args);
+  arg_cache = g_newa (CachedArg, n_args + 1);
+
+  /* Return value is an out arg */
+  g_callable_info_load_return_type (func_info, &arg_cache[0].type_info);
+  if (g_type_info_get_tag (&arg_cache[0].type_info) != GI_TYPE_TAG_VOID)
+    {
+      ++n_out_args;
+      arg_cache[cached_args++].ptr = &retval->v_pointer;
+    }
+
+  /* Handle the arguments */
+  for (i = 0; i < n_args; ++i, ++cached_args)
+    {
+      GIDirection direction;
+
+      g_callable_info_load_arg (func_info, i, &arg_cache[cached_args].arg_info);
+      direction = g_arg_info_get_direction (&arg_cache[cached_args].arg_info);
+      g_arg_info_load_type (&arg_cache[cached_args].arg_info,
+                            &arg_cache[cached_args].type_info);
+
+      if ((direction == GI_DIRECTION_IN || direction == GI_DIRECTION_INOUT) &&
+          !gjs_value_from_g_argument (gexten->js_context, &js_args[n_in_args++],
+                                      &arg_cache[cached_args].type_info,
+                                      &args[i]))
+        {
+          g_warning ("Error failed to convert argument '%s'",
+                     g_base_info_get_name (&arg_cache[cached_args].arg_info));
+          goto out;
+        }
+
+      if (direction == GI_DIRECTION_OUT || direction == GI_DIRECTION_INOUT)
+        {
+          ++n_out_args;
+          arg_cache[cached_args].ptr = args[i].v_pointer;
+        }
+    }
+
+  success = JS_CallFunctionValue (gexten->js_context, gexten->js_object,
+                                  js_method, n_in_args, js_args, &js_retval);
+
+  if (!success)
+    {
+      g_warning ("Error while calling '%s.%s'",
+                 g_type_name (exten_type), method_name);
+      goto out;
+    }
+
+  /* First we need to release in argument */
+  for (i = 0; i < cached_args; ++i)
+    {
+      GIDirection direction;
+
+      /* First cached argument may be the return value */
+      if (i == 0 && cached_args > n_args)
+        continue;
+
+      direction = g_arg_info_get_direction (&arg_cache[i].arg_info);
+
+      if (direction == GI_DIRECTION_IN || direction == GI_DIRECTION_INOUT)
+        {
+          GITransfer transfer;
+
+          transfer = g_arg_info_get_ownership_transfer (&arg_cache[i].arg_info);
+
+          if (!gjs_g_argument_release_in_arg (gexten->js_context, transfer,
+                                              &arg_cache[i].type_info,
+                                              &args[i]))
+            {
+              g_warning ("Error failed to release IN argument '%s'",
+                         g_base_info_get_name (&arg_cache[i].arg_info));
+            }
+        }
+    }
+
+  /* Check that we have a valid return value */
+  if (n_out_args > 1)
+    {
+      if (!JSVAL_IS_OBJECT (js_retval) ||
+          !JS_IsArrayObject (gexten->js_context, JSVAL_TO_OBJECT (js_retval)))
+        {
+          g_warning ("Error return value is not an array");
+          success = FALSE;
+          goto out;
+        }
+    }
+
+  /* Set out arguments */
+  for (i = 0, nth_out_arg = 0; i < cached_args && success; ++i, ++nth_out_arg)
+    {
+      gboolean is_return_value;
+
+      is_return_value = i == 0 && cached_args > n_args;
+
+      /* Return value does not have a GIArgInfo and is always out */
+      if (!is_return_value)
+        {
+          GIDirection direction;
+
+          direction = g_arg_info_get_direction (&arg_cache[i].arg_info);
+
+          if (direction == GI_DIRECTION_IN)
+            continue;
+        }
+
+      if (n_out_args == 1)
+        {
+          success = set_out_arg (gexten->js_context, func_info, is_return_value,
+                                 &arg_cache[i].arg_info, &arg_cache[i].type_info,
+                                 arg_cache[i].ptr, js_retval);
+          break;
+        }
+      else if (n_out_args > 1)
+        {
+          jsval js_value;
+
+          if (!JS_GetElement (gexten->js_context, JSVAL_TO_OBJECT (js_retval),
+                              nth_out_arg, &js_value))
+            {
+              g_warning ("Error failed to get out argument %i", nth_out_arg);
+              success = FALSE;
+            }
+          else
+            {
+              success = set_out_arg (gexten->js_context, func_info,
+                                     is_return_value, &arg_cache[i].arg_info,
+                                     &arg_cache[i].type_info, arg_cache[i].ptr,
+                                     js_value);
+            }
+        }
+    }
+
+out:
+
+  g_base_info_unref (func_info);
+
+  return success;
+}
+
+static void
+peas_extension_gjs_class_init (PeasExtensionGjsClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  PeasExtensionClass *extension_class = PEAS_EXTENSION_CLASS (klass);
+
+  object_class->set_property = peas_extension_gjs_set_property;
+  object_class->get_property = peas_extension_gjs_get_property;
+  object_class->dispose = peas_extension_gjs_dispose;
+
+  extension_class->call = peas_extension_gjs_call;
+}
+
+PeasExtension *
+peas_extension_gjs_new (GType      exten_type,
+                        JSContext *js_context,
+                        JSObject  *js_object)
+{
+  PeasExtensionGjs *gexten;
+  GType real_type;
+
+  g_return_val_if_fail (js_context != NULL, NULL);
+  g_return_val_if_fail (js_object != NULL, NULL);
+
+  real_type = peas_extension_register_subclass (PEAS_TYPE_EXTENSION_GJS, exten_type);
+  gexten = PEAS_EXTENSION_GJS (g_object_new (real_type,
+                                             "extension-type", exten_type,
+                                             NULL));
+
+  gexten->js_context = js_context;
+  gexten->js_object = js_object;
+  JS_AddObjectRoot (gexten->js_context, &gexten->js_object);
+
+  return PEAS_EXTENSION (gexten);
+}
diff --git a/loaders/gjs/peas-extension-gjs.h b/loaders/gjs/peas-extension-gjs.h
new file mode 100644
index 0000000..2f8ddfc
--- /dev/null
+++ b/loaders/gjs/peas-extension-gjs.h
@@ -0,0 +1,59 @@
+/*
+ * peas-extension-gjs.h
+ * This file is part of libpeas
+ *
+ * Copyright (C) 2011 - Garrett Regier, Steve Frécinaux
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Library General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PEAS_EXTENSION_GJS_H__
+#define __PEAS_EXTENSION_GJS_H__
+
+#include <libpeas/peas-extension-priv.h>
+#include <gjs/gjs-module.h>
+
+G_BEGIN_DECLS
+
+#define PEAS_TYPE_EXTENSION_GJS            (peas_extension_gjs_get_type ())
+#define PEAS_EXTENSION_GJS(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), PEAS_TYPE_EXTENSION_GJS, PeasExtensionGjs))
+#define PEAS_EXTENSION_GJS_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), PEAS_TYPE_EXTENSION_GJS, PeasExtensionGjsClass))
+#define PEAS_IS_EXTENSION_GJS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PEAS_TYPE_EXTENSION_GJS))
+#define PEAS_IS_EXTENSION_GJS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PEAS_TYPE_EXTENSION_GJS))
+#define PEAS_EXTENSION_GJS_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), PEAS_TYPE_EXTENSION_GJS, PeasExtensionGjsClass))
+
+typedef struct _PeasExtensionGjs       PeasExtensionGjs;
+typedef struct _PeasExtensionGjsClass  PeasExtensionGjsClass;
+
+struct _PeasExtensionGjs {
+  PeasExtension parent;
+
+  JSContext *js_context;
+  JSObject *js_object;
+};
+
+struct _PeasExtensionGjsClass {
+  PeasExtensionClass parent_class;
+};
+
+GType            peas_extension_gjs_get_type (void) G_GNUC_CONST;
+
+PeasExtension   *peas_extension_gjs_new      (GType      exten_type,
+                                              JSContext *js_context,
+                                              JSObject  *js_object);
+
+G_END_DECLS
+
+#endif /* __PEAS_EXTENSION_GJS_H__ */
diff --git a/loaders/gjs/peas-plugin-loader-gjs.c b/loaders/gjs/peas-plugin-loader-gjs.c
new file mode 100644
index 0000000..c3a4539
--- /dev/null
+++ b/loaders/gjs/peas-plugin-loader-gjs.c
@@ -0,0 +1,307 @@
+/*
+ * peas-plugin-loader-gjs.c
+ * This file is part of libpeas
+ *
+ * Copyright (C) 2011 - Garrett Regier, Steve Frécinaux
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Library General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gjs/gjs-module.h>
+#include <gjs/gi/object.h>
+#include <gjs/gi/repo.h>
+#include <gjs/gi/value.h>
+
+#include "peas-plugin-loader-gjs.h"
+#include "peas-extension-gjs.h"
+
+typedef struct {
+  JSObject *extensions;
+  GjsContext *context;
+} GjsInfo;
+
+G_DEFINE_TYPE (PeasPluginLoaderGjs, peas_plugin_loader_gjs, PEAS_TYPE_PLUGIN_LOADER);
+
+static gchar *
+get_script_filename_for_plugin_info (PeasPluginInfo *info)
+{
+  gchar *basename;
+  gchar *filename;
+
+  basename = g_strconcat (peas_plugin_info_get_module_name (info), ".js", NULL);
+  filename = g_build_filename (peas_plugin_info_get_module_dir (info), basename, NULL);
+
+  g_free (basename);
+
+  return filename;
+}
+
+static gboolean
+peas_plugin_loader_gjs_load (PeasPluginLoader *loader,
+                             PeasPluginInfo   *info)
+{
+  PeasPluginLoaderGjs *gloader = PEAS_PLUGIN_LOADER_GJS (loader);
+  gchar **search_paths;
+  const gchar *version;
+  GjsContext *context;
+  gchar *filename;
+  GjsInfo *ginfo;
+  GError *error = NULL;
+  JSContext *js_context;
+  JSObject *global;
+  jsval extensions;
+
+  filename = get_script_filename_for_plugin_info (info);
+
+  g_debug ("GJS script filename is '%s'", filename);
+
+  search_paths = g_new (gchar *, 2);
+  search_paths[0] = g_strdup (peas_plugin_info_get_module_dir (info));
+  search_paths[1] = NULL;
+
+  version = gjs_context_scan_file_for_js_version (filename);
+
+  context = g_object_new (GJS_TYPE_CONTEXT,
+                          "search-path", search_paths,
+                          "js-version", version,
+                          NULL);
+
+  gjs_context_eval_file (context, filename, NULL, &error);
+
+  g_free (search_paths);
+  g_free (filename);
+
+  if (error != NULL)
+    {
+      g_warning ("Error: %s", error->message);
+      g_error_free (error);
+      g_object_unref (context);
+      return FALSE;
+    }
+
+  js_context = gjs_context_get_native_context (context);
+  global = JS_GetGlobalObject (js_context);
+
+  if (!JS_GetProperty (js_context, global, "extensions", &extensions))
+    {
+      g_warning ("Error: could not find extensions");
+      return FALSE;
+    }
+
+  if (!JSVAL_IS_OBJECT (extensions) || JSVAL_IS_NULL (extensions))
+    {
+      g_warning ("Error: 'extensions' is of invalid type '%s'",
+                 gjs_get_type_name (extensions));
+      return FALSE;
+    }
+
+  ginfo = g_slice_new (GjsInfo);
+  ginfo->context = g_object_ref (context);
+  ginfo->extensions = JSVAL_TO_OBJECT (extensions);
+  JS_AddObjectRoot (js_context, &ginfo->extensions);
+
+  g_hash_table_insert (gloader->loaded_plugins, info, ginfo);
+
+  g_object_unref (context);
+
+  return TRUE;
+}
+
+static gboolean
+peas_plugin_loader_gjs_provides_extension  (PeasPluginLoader *loader,
+                                            PeasPluginInfo   *info,
+                                            GType             exten_type)
+{
+  PeasPluginLoaderGjs *gloader = PEAS_PLUGIN_LOADER_GJS (loader);
+  GjsInfo *ginfo;
+  JSContext *js_context;
+  jsval extension;
+
+  ginfo = g_hash_table_lookup (gloader->loaded_plugins, info);
+
+  js_context = gjs_context_get_native_context (ginfo->context);
+
+  return JS_GetProperty (js_context, ginfo->extensions,
+                         g_type_name (exten_type), &extension) &&
+         JSVAL_IS_OBJECT (extension) && !JSVAL_IS_NULL (extension);
+}
+
+static PeasExtension *
+peas_plugin_loader_gjs_create_extension (PeasPluginLoader *loader,
+                                         PeasPluginInfo   *info,
+                                         GType             exten_type,
+                                         guint             n_parameters,
+                                         GParameter       *parameters)
+{
+  PeasPluginLoaderGjs *gloader = PEAS_PLUGIN_LOADER_GJS (loader);
+  GjsInfo *ginfo;
+  JSContext *js_context;
+  jsval extension_methods;
+  JSObject *extension;
+  guint i;
+  jsval js_value;
+  GValue gvalue = { 0 };
+
+  ginfo = g_hash_table_lookup (gloader->loaded_plugins, info);
+
+  js_context = gjs_context_get_native_context (ginfo->context);
+
+  if (!JS_GetProperty (js_context, ginfo->extensions,
+                       g_type_name (exten_type), &extension_methods) ||
+      JSVAL_IS_VOID (extension_methods) || JSVAL_IS_NULL (extension_methods))
+    return NULL;
+
+  if (!JSVAL_IS_OBJECT (extension_methods))
+    {
+      g_warning ("Extension '%s' in plugin '%s' in not a valid object",
+                 g_type_name (exten_type),
+                 peas_plugin_info_get_module_name (info));
+      return NULL;
+    }
+
+  /* Copy the original extension_methods object to a new specific object. */
+  extension = JS_NewObject (js_context, NULL,
+                            JSVAL_TO_OBJECT (extension_methods), NULL);
+
+  /* Cannot use g_object_set_property()
+   * because the property may be construct-only
+   */
+  for (i = 0; i < n_parameters; i++)
+    {
+      guint j;
+      gchar *prop_name;
+
+      prop_name = g_strdup (parameters[i].name);
+      for (j = 0; prop_name[j] != '\0'; ++j)
+        {
+          if (prop_name[j] == '-')
+            prop_name[j] = '_';
+        }
+
+      if (!gjs_value_from_g_value (js_context, &js_value, &parameters[i].value))
+        {
+          g_warning ("Error: failed to convert GValue to "
+                     "jsval for property '%s'", prop_name);
+        }
+      else if (!JS_SetProperty (js_context, extension, prop_name, &js_value))
+        {
+          g_warning ("Error: failed to set property '%s'", prop_name);
+        }
+
+      g_free (prop_name);
+    }
+
+  /* Set the plugin info as an attribute of the instance */
+  g_value_init (&gvalue, PEAS_TYPE_PLUGIN_INFO);
+  g_value_set_boxed (&gvalue, info);
+
+  if (!gjs_value_from_g_value (js_context, &js_value, &gvalue))
+    {
+      g_warning ("Error: failed to convert PeasPluginInfo GValue to jsvalue");
+    }
+  else if (!JS_SetProperty (js_context, extension, "plugin_info", &js_value))
+    {
+      g_warning ("Error: failed to set property 'plugin_info'");
+    }
+
+  g_value_unset (&gvalue);
+
+  return peas_extension_gjs_new (exten_type, js_context, extension);
+}
+
+static void
+peas_plugin_loader_gjs_unload (PeasPluginLoader *loader,
+                               PeasPluginInfo   *info)
+{
+  PeasPluginLoaderGjs *gloader = PEAS_PLUGIN_LOADER_GJS (loader);
+
+  g_hash_table_remove (gloader->loaded_plugins, info);
+}
+
+static void
+garbage_collect (PeasPluginInfo *info,
+                 GjsInfo        *ginfo)
+{
+  JS_GC (gjs_context_get_native_context (ginfo->context));
+}
+
+static void
+peas_plugin_loader_gjs_garbage_collect (PeasPluginLoader *loader)
+{
+  PeasPluginLoaderGjs *gloader = PEAS_PLUGIN_LOADER_GJS (loader);
+
+  g_hash_table_foreach (gloader->loaded_plugins,
+                        (GHFunc) garbage_collect,
+                        NULL);
+}
+
+static void
+peas_plugin_loader_gjs_finalize (GObject *object)
+{
+  PeasPluginLoaderGjs *gloader = PEAS_PLUGIN_LOADER_GJS (object);
+
+  g_hash_table_destroy (gloader->loaded_plugins);
+
+  G_OBJECT_CLASS (peas_plugin_loader_gjs_parent_class)->finalize (object);
+}
+
+static void
+peas_plugin_loader_gjs_class_init (PeasPluginLoaderGjsClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  PeasPluginLoaderClass *loader_class = PEAS_PLUGIN_LOADER_CLASS (klass);
+
+  gobject_class->finalize = peas_plugin_loader_gjs_finalize;
+
+  loader_class->load = peas_plugin_loader_gjs_load;
+  loader_class->provides_extension = peas_plugin_loader_gjs_provides_extension;
+  loader_class->create_extension = peas_plugin_loader_gjs_create_extension;
+  loader_class->unload = peas_plugin_loader_gjs_unload;
+  loader_class->garbage_collect = peas_plugin_loader_gjs_garbage_collect;
+}
+
+static void
+destroy_gjs_info (GjsInfo *ginfo)
+{
+  JSContext *js_context;
+
+  js_context = gjs_context_get_native_context (ginfo->context);
+
+  JS_RemoveObjectRoot (js_context, &ginfo->extensions);
+  g_object_unref (ginfo->context);
+
+  g_slice_free (GjsInfo, ginfo);
+}
+
+static void
+peas_plugin_loader_gjs_init (PeasPluginLoaderGjs *gloader)
+{
+  gloader->loaded_plugins = g_hash_table_new_full (g_direct_hash,
+                                                   g_direct_equal,
+                                                   NULL,
+                                                   (GDestroyNotify) destroy_gjs_info);
+}
+
+G_MODULE_EXPORT void
+peas_register_types (PeasObjectModule *module)
+{
+  peas_object_module_register_extension_type (module,
+                                              PEAS_TYPE_PLUGIN_LOADER,
+                                              PEAS_TYPE_PLUGIN_LOADER_GJS);
+}
diff --git a/loaders/gjs/peas-plugin-loader-gjs.h b/loaders/gjs/peas-plugin-loader-gjs.h
new file mode 100644
index 0000000..c42ee4a
--- /dev/null
+++ b/loaders/gjs/peas-plugin-loader-gjs.h
@@ -0,0 +1,55 @@
+/*
+ * peas-plugin-loader-gjs.h
+ * This file is part of libpeas
+ *
+ * Copyright (C) 2011 - Garrett Regier, Steve Frécinaux
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Library General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PEAS_PLUGIN_LOADER_GJS_H__
+#define __PEAS_PLUGIN_LOADER_GJS_H__
+
+#include <libpeas/peas-plugin-loader.h>
+#include <libpeas/peas-object-module.h>
+
+G_BEGIN_DECLS
+
+#define PEAS_TYPE_PLUGIN_LOADER_GJS            (peas_plugin_loader_gjs_get_type ())
+#define PEAS_PLUGIN_LOADER_GJS(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), PEAS_TYPE_PLUGIN_LOADER_GJS, PeasPluginLoaderGjs))
+#define PEAS_PLUGIN_LOADER_GJS_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), PEAS_TYPE_PLUGIN_LOADER_GJS, PeasPluginLoaderGjsClass))
+#define PEAS_IS_PLUGIN_LOADER_GJS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PEAS_TYPE_PLUGIN_LOADER_GJS))
+#define PEAS_IS_PLUGIN_LOADER_GJS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PEAS_TYPE_PLUGIN_LOADER_GJS))
+#define PEAS_PLUGIN_LOADER_GJS_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), PEAS_TYPE_PLUGIN_LOADER_GJS, PeasPluginLoaderGjsClass))
+
+typedef struct _PeasPluginLoaderGjs       PeasPluginLoaderGjs;
+typedef struct _PeasPluginLoaderGjsClass  PeasPluginLoaderGjsClass;
+
+struct _PeasPluginLoaderGjs {
+  PeasPluginLoader parent;
+
+  GHashTable *loaded_plugins;
+};
+
+struct _PeasPluginLoaderGjsClass {
+  PeasPluginLoaderClass parent_class;
+};
+
+GType                    peas_plugin_loader_gjs_get_type  (void) G_GNUC_CONST;
+G_MODULE_EXPORT void     peas_register_types              (PeasObjectModule *module);
+
+G_END_DECLS
+
+#endif /* __PEAS_PLUGIN_LOADER_GJS_H__ */
diff --git a/peas-demo/peas-demo.c b/peas-demo/peas-demo.c
index 83cd6a3..debe8a9 100644
--- a/peas-demo/peas-demo.c
+++ b/peas-demo/peas-demo.c
@@ -125,6 +125,7 @@ main (int    argc,
   peas_engine_add_search_path (engine, plugin_dir, plugin_dir);
   g_free (plugin_dir);
 
+  peas_engine_enable_loader (engine, "gjs");
   peas_engine_enable_loader (engine, "python");
   peas_engine_enable_loader (engine, "seed");
 
diff --git a/peas-demo/plugins/Makefile.am b/peas-demo/plugins/Makefile.am
index 37ca9db..1298928 100644
--- a/peas-demo/plugins/Makefile.am
+++ b/peas-demo/plugins/Makefile.am
@@ -1,5 +1,9 @@
 SUBDIRS = helloworld secondtime
 
+if ENABLE_GJS
+SUBDIRS += gjshello
+endif
+
 if ENABLE_PYTHON
 SUBDIRS += pythonhello
 endif
diff --git a/peas-demo/plugins/gjshello/Makefile.am b/peas-demo/plugins/gjshello/Makefile.am
new file mode 100644
index 0000000..1a688da
--- /dev/null
+++ b/peas-demo/plugins/gjshello/Makefile.am
@@ -0,0 +1,7 @@
+plugindir = $(libdir)/peas-demo/plugins/gjshello
+
+plugin_DATA = \
+	gjshello.js	\
+	gjshello.plugin
+
+EXTRA_DIST = $(plugin_DATA)
diff --git a/peas-demo/plugins/gjshello/gjshello.js b/peas-demo/plugins/gjshello/gjshello.js
new file mode 100644
index 0000000..6301289
--- /dev/null
+++ b/peas-demo/plugins/gjshello/gjshello.js
@@ -0,0 +1,33 @@
+const Gtk = imports.gi.Gtk;
+
+var LABEL_STRING = "GJS Also Says Hello!";
+
+print("LABEL_STRING=" +  LABEL_STRING);
+
+var activatable_extension = {
+  activate: function() {
+    print("GJSHelloPlugin.activate");
+    this.object._gjshello_label = new Gtk.Label({ label: LABEL_STRING });
+    this.object._gjshello_label.show();
+    this.object.get_child().add(this.object._gjshello_label);
+  },
+  deactivate: function() {
+    print("GJSHelloPlugin.deactivate");
+    this.object.get_child().remove(this.object._gjshello_label);
+    this.object._gjshello_label.destroy();
+  },
+  update_state: function() {
+    print("GJSHelloPlugin.update_state");
+  }
+};
+
+var configurable_extension = {
+  create_configure_widget: function () {
+    return new Gtk.Label({ label: "Example of configuration dialog for a GJS plugin" });
+  }
+};
+
+var extensions = {
+  'PeasActivatable': activatable_extension,
+  'PeasGtkConfigurable': configurable_extension,
+};
diff --git a/peas-demo/plugins/gjshello/gjshello.plugin b/peas-demo/plugins/gjshello/gjshello.plugin
new file mode 100644
index 0000000..8d8f6f0
--- /dev/null
+++ b/peas-demo/plugins/gjshello/gjshello.plugin
@@ -0,0 +1,9 @@
+[Plugin]
+Module=gjshello
+Loader=gjs
+IAge=2
+Name=GJS Also Says Hello
+Description=Inserts a box containing "GJS Also Says Hello" in every windows.
+Authors=Steve Frécinaux <code istique net>
+Copyright=Copyright © 2009 Steve Frécinaux
+Website=http://code.istique.net/
diff --git a/tests/libpeas/Makefile.am b/tests/libpeas/Makefile.am
index a859306..de308bf 100644
--- a/tests/libpeas/Makefile.am
+++ b/tests/libpeas/Makefile.am
@@ -28,6 +28,13 @@ TEST_PROGS          += extension-c
 extension_c_SOURCES  = extension-c.c
 extension_c_LDADD    = $(progs_ldadd)
 
+if ENABLE_GJS
+TEST_PROGS            += extension-gjs
+extension_gjs_SOURCES  = extension-gjs.c
+extension_gjs_CFLAGS   = $(GJS_CFLAGS)
+extension_gjs_LDADD    = $(progs_ldadd) $(GJS_LIBS)
+endif
+
 if ENABLE_PYTHON
 TEST_PROGS               += extension-python
 extension_python_SOURCES  = extension-python.c
diff --git a/tests/libpeas/extension-gjs.c b/tests/libpeas/extension-gjs.c
new file mode 100644
index 0000000..deff079
--- /dev/null
+++ b/tests/libpeas/extension-gjs.c
@@ -0,0 +1,80 @@
+/*
+ * extension-gjs.c
+ * This file is part of libpeas
+ *
+ * Copyright (C) 2011 - Garrett Regier
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Library General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU Library General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Library General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <gjs/gi/value.h>
+
+#include "loaders/gjs/peas-extension-gjs.h"
+
+#include "testing/testing-extension.h"
+#include "introspection/introspection-callable.h"
+
+static void
+test_extension_gjs_plugin_info (PeasEngine *engine)
+{
+  PeasPluginInfo *info;
+  PeasExtension *extension;
+  PeasExtensionGjs *gexten;
+  jsval js_value;
+  GValue gvalue = { 0 };
+
+  info = peas_engine_get_plugin_info (engine, "extension-gjs");
+
+  g_assert (peas_engine_load_plugin (engine, info));
+
+  extension = peas_engine_create_extension (engine, info,
+                                            INTROSPECTION_TYPE_CALLABLE,
+                                            NULL);
+
+  g_assert (PEAS_IS_EXTENSION (extension));
+
+  gexten = (PeasExtensionGjs *) extension;
+
+  g_value_init (&gvalue, PEAS_TYPE_PLUGIN_INFO);
+
+  g_assert (JS_GetProperty (gexten->js_context, gexten->js_object,
+                            "plugin_info", &js_value));
+  g_assert (gjs_value_to_g_value (gexten->js_context, js_value, &gvalue));
+
+  g_assert (g_value_get_boxed (&gvalue) == info);
+
+  g_value_unset (&gvalue);
+
+  g_object_unref (extension);
+}
+
+int
+main (int   argc,
+      char *argv[])
+{
+  g_test_init (&argc, &argv, NULL);
+
+  g_type_init ();
+
+  EXTENSION_TESTS (gjs);
+
+  EXTENSION_TEST (gjs, "plugin-info", plugin_info);
+
+  return g_test_run ();
+}
diff --git a/tests/libpeas/plugins/Makefile.am b/tests/libpeas/plugins/Makefile.am
index 0d9037d..2c7bac0 100644
--- a/tests/libpeas/plugins/Makefile.am
+++ b/tests/libpeas/plugins/Makefile.am
@@ -2,6 +2,10 @@ include $(top_srcdir)/tests/Makefile.plugin
 
 SUBDIRS = extension-c
 
+if ENABLE_GJS
+SUBDIRS += extension-gjs
+endif
+
 if ENABLE_PYTHON
 SUBDIRS += extension-python
 endif
diff --git a/tests/libpeas/plugins/extension-gjs/Makefile.am b/tests/libpeas/plugins/extension-gjs/Makefile.am
new file mode 100644
index 0000000..c6ea14d
--- /dev/null
+++ b/tests/libpeas/plugins/extension-gjs/Makefile.am
@@ -0,0 +1,5 @@
+noinst_DATA = \
+	extension-gjs.js	\
+	extension-gjs.plugin
+
+EXTRA_DIST = $(noinst_DATA)
diff --git a/tests/libpeas/plugins/extension-gjs/extension-gjs.js b/tests/libpeas/plugins/extension-gjs/extension-gjs.js
new file mode 100644
index 0000000..e547f18
--- /dev/null
+++ b/tests/libpeas/plugins/extension-gjs/extension-gjs.js
@@ -0,0 +1,24 @@
+var callable_extension = {
+  call_with_return: function() {
+    return "Hello, World!"
+  },
+  call_no_args: function() {
+  },
+  call_single_arg: function() {
+    return true
+  },
+  call_multi_args: function() {
+    return [ true, true, true ]
+  }
+};
+
+var properties_extension = {
+  read_only: "read-only",
+  readwrite: "readwrite"
+};
+
+var extensions = {
+  "IntrospectionCallable": callable_extension,
+  "IntrospectionProperties" : properties_extension
+};
+
diff --git a/tests/libpeas/plugins/extension-gjs/extension-gjs.plugin b/tests/libpeas/plugins/extension-gjs/extension-gjs.plugin
new file mode 100644
index 0000000..dba5504
--- /dev/null
+++ b/tests/libpeas/plugins/extension-gjs/extension-gjs.plugin
@@ -0,0 +1,9 @@
+[Plugin]
+Module=extension-gjs
+Loader=gjs
+IAge=2
+Name=Extension GJS
+Description=This plugin is for the GJS PeasExtension tests.
+Authors=Garrett Regier
+Copyright=Copyright © 2011 Garrett Regier
+Website=http://live.gnome.org/Libpeas



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