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();
});