Creating a multilevel application with GObject Introspection
This tutorial will introduce a small Clutter application using C/GObject for low level graphics work, and JavaScript for composite widgets, configuration, prototyping, etc. Some familiarity with all of these technologies is assumed; if not, see the GObject tutorial, Clutter tutorial, the Mozilla JavaScript Reference, and the Gjs page.
Getting set up
For the purposes of this tutorial, you'll need to have installed Gjs, the Mozilla SpiderMonkey binding. You can pick from among many different languages and runtimes; see the GObject Introspection page. Most of the concepts in this tutorial should translate fairly easily into another language/runtime. We'll assume at this point that you can now do something like the following:
$ gjs-console gjs> 1+1 2 gjs>
Checking out and building the tutorial project
git clone git://gitorious.org/gjs-clutter-sample/mainline.git gjs-clutter-sample cd gjs-clutter-sample
Now, we're going go back to the beginning of this project, so we can follow along as it evolves. You may find it convenient to browse the Gitorious page for the tutorial.
Run:
git checkout ae114c2b
Examining Tutorial Triangle
Right now the project just depends on Clutter. It's a modified version of the triangle example from the Clutter tutorial. You should take a minute to look over the structure of the code; it's quite simple.
src/main.c sets up the stage, mainloop, and creates an instance of our custom triangle actor.
src/tut-triangle.c and src/tut-triangle.h implement a custom Clutter Actor subclass. Note in particular that in comparison we've changed the namespace prefix to TUT_; having a unique namespace is a requirement for GObject Introspection.
Let's build it.
python ./waf configure python ./waf build
(If you're not familiar with waf; don't worry, it's not really important in this tutorial)
Now, run it:
./build/default/mainapp
You should see a small window with a triangle appear on the screen.
Introspecting the project
Now, we're going to add a dependency on GObject Introspection. In this step, all we will do is run g-ir-scanner on our source code to generate an XML representation of our C TutTriangle class.
git checkout c5332a5c
Let's go through the diff this commit introduced.
git show
In the changes to main.c, we're now using a GOptionGroup to process command line arguments, and we're using g_irepository_get_option_group to add an option. The reason this is necessary is because the introspection program g-ir-scanner actually runs our program to dump data from it. To see how that works, let's go to the next change, to the wscript file.
You can see in this first part that we're adding a dependency on the GObject Introspection library.
The next change is telling the build system to scan our tut-triangle.c and tut-triangle.h files. Note that we picked a namespace Tut and a namespace version 1.0. In a large program, you may have multiple namespaces, but here we just have one. The --program argument gives the path to our built program. This is necessary in order to get complete information about our GObjects.
Side note: If you're more familiar with Automake, there are some sample rules in gir-repository, gobject-introspection (both libraries), and gnome-shell (application).
Let's build the project again. We need to both reconfigure and build, because we changed the wscript file as well as code.
python ./waf configure python ./waf build
You should now have a .gir file, build/default/Tut-1.0.gir. Take a minute to examine the contents of this file. Note that it has both the public API from the headers, as well as the GObject property color. These .gir files are always automatically generated from the source code - don't try to edit it! We'll see how we can affect the scanning process later.
There's not much point to running the program again, since .we didn't introduce any runtime differences. Remember in this commit, we're just generating the .gir file.
Switching to JavaScript
This next step is a big one. We've rewritten the presentation logic from main.c in JavaScript, but kept our custom triangle class in C. Let's take a look.
git checkout 4a8ee2a
As before, let's inspect the changes.
git show
Looking at the change to wscript first, we're introducing a dependency on Gjs. The next part uses g-ir-compiler to compiles the .gir into a .typelib file. The typelib is a compact, efficient representation of the gir. It will be used at runtime by Gjs to dynamically look up information about our application's C side.
The changes to main.c are much larger. In the big picture what we're doing here is setting up the Gjs runtime and pointing it at our JavaScript side code. The changes here are slightly fragile in that they only work when running directly from the toplevel source directory; getting this whole thing set up so it works correctly when installed and uninstalled is a bit tricky and was not done for this tutorial.
You can see though that we're still initializing Clutter from our C side, as well as running the mainloop. This would also be the place to initialize any other C libraries you take a dependency on, etc.
A key part of the change here is:
const Main = imports.main; Main.start()
There's nothing particularly special about the name "Main"; this just happens to be the name of our first JavaScript file, main.js.
Moving on to main.js, we see the Gjs-specific "import" mechanism. We're importing the Clutter library (as exposed through GObject Introspection), as well as our application itself - Tut. The start function is being called from the main.c fragment above.
In this file we can see how JavaScript offers a lot of convenience for creating our Tut.Triangle class. GObject properties can all be specified in a big group as a literal hash.
Let's rebuild and run to verify everything works:
python ./waf configure python ./waf build
If you got a build failure here, make sure you have gjs-console working first (see the Gjs page).
And now run:
./build/default/mainapp
The display should be unchanged; but now we're doing most of the setup logic from JavaScript.
Using Annotations
git checkout 4bb1bcc git show
In this commit, we've added a method to our TutTriangle class, tut_triangle_get_centroid (and a u variant similar to other Clutter APIs). See the Wikipedia article on Centroid for a definition. What is very nice about GObject Introspection is that normally after adding a method all we need to do is rebuild, and both the .gir and .typelib files (and thus our higher level language) will pick up the change immediately.
To get our new method in the .gir file, let's build now.
python ./waf build
(Note we only need to build, not reconfigure, since we didn't change wscript).
Normally, adding methods to the C .h, adding GObject properties in the .c files, etc. is enough. However, in some cases, the introspection system needs additional information. Our new tut_triangle_get_centroid method is one of these cases. Unassisted, the introspection system doesn't know whether the int * parameters we're using are purely return values (or if they're both input and output, or they could be arrays). The way this is handled is through Annotations. Note specifically the annotation used here is (out). This ensures that our binding knows these parameters are just return values.
Now in our modifications to main.js, we call the new method:
let [cx, cy] = actor.get_centroid();
Notice how the binding converts multiple return values to an array, which we then "destructure". We then use this to draw a simple cross targeting the centroid.
End Tutorial
There's quite a bit more to learn about the details of Gjs bindings (or whichever binding you end up choosing), but hopefully this tutorial demystified the big picture somewhat and gave some useful, targeted sample code to work with.