Hooking up JavaScript
Now we're going to embed a little bit of JavaScript into our program. This is useful if you want to build your program in layers, for example if you want a core written in C for efficiency, and a GUI layer written in JavaScript for ease of use. Read Havoc's blog post about the benifits of this approach. It's also a very good way to make your program extendable through plugins. If this is what you want to do, you might want to take a look at Libpeas which is a plugins engine based on GObject Introspection.
There are two JavaScript bindings available for GObject Introspection, seed which uses the WebKit JavaScript engine and Gjs which uses Mozilla's SpiderMonkey engine. The latter is the one used by for example the Gnome Shell, and is the one we're going to use here.
We add a simple JavaScript file, main.js:
const Tut = imports.gi.Tut; let greeter = new Tut.Greeter({ greetee: 'JavaScript programmer' }); greeter.greet();
The binding allows to send in a JavaScript object as an argument to GObject constructors for initial values of properties. We use this to set the greetee property to 'JavaScript programmer'.
To call this code from our main program, we need to add a dependency on gjs. FIXME. We now add an include to main.c:
#include <gjs/gjs.h>
And declare some new variables at the top of the main function:
GjsContext *gjs; int status = 0;
Instead of the code that creates a TutGreeter and calls tut_greeter_greet, we write:
gjs = gjs_context_new (); if (!gjs_context_eval_file (gjs, "main.js", &status, &error)) { g_print ("greeter: couldn't evaluate JavaScript: %s\n", error->message); g_clear_error (&error); } g_object_unref (gjs);
There is no reference documentation for how to use the Gjs library, so it might be helpful to have a look in the sources, context.h and context.c.
The gjs_context_eval_file function evaluates, i.e. runs, the JavaScript code in the specified file; there is a similar function called gjs_context_eval that evaluates JavaScript code from a string. Besides any error, these functions also deliver a status code from the JavaScript. This is similar to the exit code returned by regular executables, i.e., 0 means success. We propagate this from our C main function:
return status;
To build with Gjs, we also need to add gjs-1.0 to the list of packages sent to pkg-config in the Makefile.
Compile with make and run it. Now this happens:
$ ./greeter JS ERROR: !!! Exception was: Error: Requiring Tut, version none: Typelib file for namespace 'Tut' (any version) not found JS ERROR: !!! message = '"Requiring Tut, version none: Typelib file for namespace 'Tut' (any version) not found"' JS ERROR: !!! fileName = '"main.js"' JS ERROR: !!! lineNumber = '15' JS ERROR: !!! stack = '"@main.js:15"'
We need to make sure Gjs can find the typelib file. Usually, this will be installed in a standard location where the gi_repository loader looks for typelib files. However, for experimenting we can set the environment variable GI_TYPELIB_PATH:
$ export GI_TYPELIB_PATH=`pwd` $ ./greeter Hello, JavaScript user!
Success!
Exercises
Let's return to the tut_greeter_greet_many and tut_greeter_get_greet_log methods introduced in previous exercises. See if you can figure out how to use them from JavaScript.