a blog archive
kd@ 1/17/2017

A Compulsive Minimalist’s Hello World For GJS

The official Hello World is a bit long, involves an embedded web browser, and mixes in a combination of necessary and un-necessary things. So- what is the least amount of code for GJS Hello World?

The command line

The all-text Hello World is a good start, if a little un-exciting. The point of all this is to write graphical applications. Still, it’s as short as you can get-

    #!/usr/bin/gjs
    print("Hello World");

There’s also a built-in log function, which goes to stderr by default, and adds some decoration

$ gjs -c 'log("hello world")'
Gjs-Message: JS LOG: hello world

A graphical display

    #!/usr/bin/gjs

    const Gtk = imports.gi.Gtk;
    const Lang = imports.lang;

    const Application = new Lang.Class ({
        Name: 'Application',
        _init: function() {
            this.application = new Gtk.Application();

            this.application.connect('activate', Lang.bind(this, this._onActivate));
        },

        _onActivate: function() {
            this._MyWindow = new Gtk.Window({title: "Hello"});
            this._MyWindow.add(new Gtk.Label({ label: "World" }));

            this._MyWindow.show_all();
            this.application.add_window(this._MyWindow);
        }
    });

    (new Application()).application.run(ARGV);

There is a lot, as they say, to unpack here. Let’s start where the starting is good though-

    const Gtk = imports.gi.Gtk;

This is pretty straightforward. This is how you use the module system. gi, I suspect, stands for GObject Introspection, though I can’t find it written down anywhere. Gtk is the Gnome graphical toolkit, and you’re going to use it anywhere you do anything graphical.

    const Lang = imports.lang;

So, we’re in Javascript, which has its own peculiar prototype system for doing object oriented things, but we’re interested in interop with Gnome and its tooling. So we need a layer somewhere to let us work with that. That’s Lang. It provides Lang.bind, which lets you have callbacks with a this object that makes sense, and Lang.Class, which lets you build GObject-compatible classes. This is also going to be in everything.

    const Application = new Lang.Class ({
        Name: 'Application',

This is the minimum amount you need to build a class. new Lang.Class({Name: "XYZ"}) works, new Lang.Class() does not. I haven’t really tested it, but I strongly suspect that the Name attribute matters.

        _init: function() {
            this.application = new Gtk.Application();
            this.application.connect('activate', Lang.bind(this, this._onActivate));
        }

This is a Lang-layer constructor. You’re registering a callback (or really, a Gtk-layer constructor), for later.

        _onActivate: function() {
            this._MyWindow = new Gtk.Window({title: "Hello"});
            this._MyWindow.add(new Gtk.Label({ label: "World" }));

            this._MyWindow.show_all();
            this.application.add_window(this._MyWindow);
        }

If you’ve worked with Gtk, or other desktop UI libraries before, this should seem pretty familiar. Build a window, give it a title, put a Label (a box with text) in it, turn them on.

    (new Application()).application.run(ARGV);

This is sort of ugly short hand for golfing purposes. Still, at 23 lines, we’re doing pretty good. Not much you could get rid of and still get a window.

new Application() does what you think- builds your class, and runs your constructor. It does not, however, kick off Gtk. .application.run(ARGV) does that. It’s worth noting that when writing a Gtk app, you are not writing a script, that starts at main and goes until completion. You’re writing a series of callbacks, that will get called by the “main loop” when it’s ready. If you’re familiar with web development, this may sound a lot like an explanation of how node.js works.

If you’re looking for proper documentation, with useful examples, the platform demos are where it’s at.

kd@ 1/5/2017

Safety Margins and Sympathy for the Devil

I remember writing this title down, but I have no recollection of what the content was supposed to be.

Great title, though.

kd@ 11/27/2016

I’ve been poking around the mongrel2 source again, and am thinking of setting up regular builds. Also, Ubuntu Xenial and Fedora 25 binaries, and coverage reports.

I’ve never worked anywhere that was good enough at testing to seriously worry about test coverage, and I suspect that it would be pretty easy to run up the numbers for some of the more self contained things.

Could be something else to do.

kd@ 9/24/2016

let in Javascript

I’ve noticed in the past that Mozilla and Gnome code was religious about using let instead of var, but I’d never really looked into it. I am often skeptical about such absolutist style declarations, having been burned by a few, but it generally seems like a good idea.

Javascript has two other ways of declaring variables, one implicit

foo = 'bar'

one explicit

var foo = 'bar'

The implicit declaration is clearly the easiest, requiring no ceremony or planning. Unfortunately, this means the variable is global. That’s right, completely, totally global with no insulation from anything else. The second, var, is function-scoped. That seems reasonable, but it leaves out constructs that create their own scope in every other civilized language- like for loops. for loop index variables leaking out is a classic Javascript beginner trap. let fixes this, and has the block-scoped semantics you might expect.

Honestly, it’s a little more block scoped than most- if blocks count as blocks, unlike, say, Python, where this works-

>>> y
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>> if True:
...     y = 22
... 
>>> y
22

let is not special cased in Javascript-

gjs> y
ReferenceError: y is not defined
gjs> if ( true ) { let y = 22; }
gjs> y
ReferenceError: y is not defined
gjs> 
kd@ 9/4/2016

GTK & GJS

I’m going to make a hash of this, because I’m not really a desktop developer, but if you squint, the details are going to be correct. GTK is the Gnome project’s graphical toolkit, the set of libraries that let you write Gnome-y applications. Gjs is the name of the official Gnome Javascript toolkit- a set of libraries bundled up with Spidermonkey, Mozilla’s Javascript runtime. Gnome Shell, Gnome Documents, and some other things are written with it. If you want to write Gnome apps, it’s a pretty reasonable place to start- especially since you probably already know a little Javascript.

Javascript also fits in well with GTK because they’re both built around an asynchronous programming model. There is a big, main loop that you can’t see, but you can feed in functions to get executed in the future. As an example, let’s write a Gnome-ecosystem thing to make HTTP calls.

#!/usr/bin/env gjs
const Soup = imports.gi.Soup; // Gnome's HTTP library is 'libsoup'

var request = Soup.Message.new('GET', 'http://example.org');

var session = new Soup.SessionAsync();

session.queue_message(request, function() { log("done"); });

So that should be fairly readable- we want to import the HTTP library, we ask it to make an HTTP call, and we put it on a queue to get executed by main loop, the thing driving the application, and when it’s done, our callback will fire. Not zero boilerplate, but reasonable. So let’s run it!

# ./get.js 
# 

Underwhelming. There’s a simple thing we’re missing though- remember the “main loop” I was talking about earlier? We’re writing a stand a lone program, and not a library, which means that we have to, at some point, tell Gjs to run a main loop. This is an easy fix:

session.queue_message(request, function() { log("done"); });

const Mainloop = imports.mainloop;
Mainloop.run();

and now we run it:

# ./miniget.js 
Gjs-Message: JS LOG: done
^C

Cool! Everything is happening like we’d hoped, but then it just sits there, looping forever. So let’s tell it to quit when done:

#!/usr/bin/env gjs
const Soup = imports.gi.Soup; // Gnome's HTTP library is 'libsoup'
var request = Soup.Message.new('GET', 'http://example.org')
var session = new Soup.SessionAsync();
const Mainloop = imports.mainloop;
session.queue_message(request, function() { log("done"); Mainloop.quit(); });
Mainloop.run();

and now when we run it:

# ./miniget.js 
Gjs-Message: JS LOG: done

So this is great, but there’s definitely something missing- the content returned by the server, maybe.

session.queue_message(request, function(session, message) {
log(message.response_body.data);
log("HTTP status code " + message.status_code);
log("done");
Mainloop.quit();
});