GJS
One of the ways to code GTK apps is using JavaScript. The GJS project enables that via bindings to various Gnome libraries (GTK being just one of them).
The code execution is handled by the Mozilla SpiderMonkey engine, which is a JavaScript engine, just like V8 is one. It means that we can use pretty much the latest ECMAScript features in our GTK apps.
App Dependencies
JS developers are used to pulling dependencies from npm. The bad news is that many of packages there will not work, simply because they rely on Node.js APIs, which are not there in GJS. However, there are also a bunch of libraries that do work, as long as they do not call any Node.js functions.
GJS, as a language binding for the Gnome ecosystem, enables the use of GObject-based libraries. The full list of APIs and their documentation may be found at GJS API Docs.
Dev Dependencies
To build GJS apps we need:
gjs
(comes with gnome)- Flatpak Builder -
flatpak install org.flatpak.Builder
- Gnome Builder IDE -
flatpak install flathub org.gnome.Builder
Commands
Building the app:
Running the app:
JS Code
Imports
We can use the ECMAScript import syntax:
We can also specify the exact version of the library we’re imporing:
Global Object
There is a global object called globalThis
, which is similar to window
in
the browsers. We can assign values to it and access them from anywhere else.
Files
Our app will consist of:
-
JS code (duh..):
-
Entry Point (
app.id.js
) - starting point of the app.Example:
-
window.js
- an app typically subclasses a baseGtkWindow
to define code and UI of a window -
application.js
- an app typically subclasses a baseGtkApplication
:
-
main.js
- most likely, you will instantiate the app here:
-
-
GResource - an XML listing of files that will be included in the final binary. Usually, there are separate GResource files for the source code, and for the other data (images, CSS, UI, icons, etc.). There are APIs to access GResources (like icons) from the JS code.
Example:
-
assets like icons, images
-
CSS - GTK apps may be styled with CSS, similar to HTML. The difference is that GTK CSS does not support positioning (e.g. with flexbox). That is done via specific widget containers, similarly to how WPF does it in the .NET Platform.
-
UI files - XML describing the layout of various views, very similar to WPF.
-
meson build file(s)
Widgets
GTK toolkit wouldn’t be of much use without its widgets. Their documenatation can be found here.
Widgets have a tree of inheritance (GObject
is always a root), making the
whole thing very similar to WPF.
Widgets include also layout containers, which help with positioning of other widgets on the screen. In web development, we’d typically use CSS for that. In GTK, widgets are the way to do it.
Widgets may be placed on the screen in two ways:
-
in JS code
-
in UI file
The XML above also shows an example of binding.
A UI file like this needs its own class to be defined in JS as well:
Properties
Our widgets may have their own properties, and we might set them in UI files of the widget. These properties are similar to Angular’s @Inputs.
We can bind to that property from a UI file like this:
Then, when some parent widget displays MyWidget
, it can set the value of the
property, like this:
CSS
We can create our own CSS classes to style widgets, and we can use already
existing classes, which come with the GTK widgets. Documentation of GTK
specifies these classes under “CSS Nodes” section of each widget. For example, a
Label widget has a node label
with the following classes:
.selection
- when selected.link
- for each URL included in the label’s text
Also, GTK defines a bunch of classes that we can freely apply to various
widgets. For example, the .keycap
class makes label look like a keyboard key.
Signals
GObject
brings over the concept of Signals, which is analogical to events
from the .NET world. Various widgets have their own signals defined (like a
clicked
signal of a button), which we can subscribe and react to. Custom
widgets also can have custom signals defined.
Her’s how to use a signal:
Connecting to a signal
Defining the handler:
Some signals have additional parameters that can be used in handlers. The first parameter is always the object emitting it.
Custom Signals
In order to define our own signals, we have to:
-
Specify the signal to be a part of our widget:
-
Emit it
The parent of the widget with our signal may subscribe to it via the UI file, as shown before, or in code:
Actions
Relying on signals too much can quickly bring us at the similar problem that we have in web frameworks with bubbling signals/events up the components tree - it becomes cumbersome.
This is why GTK defines another concept - Actions. These are defined in once place and can be called from another (even from another app!).
If an Action is defined on a Window, any widget within that window can invoke it. If an Action is defined on an Application, we can call it from anywhere. It is kind of like a global function. Individual widgets can also add actions (and then, who can call them?)
Custom Action
Defining an Action:
Activating an Action:
We can either do it in code with the gtk_widget_activate_action()
function of
any widget, or in the UI file, with a button:
Application Settings
The Gio.Settings (GSettings) may be used to store the app’s settings.
GSettings stores values as GVariant
, with any type. GSettings can be bound to
a widget, making changes to settings automatic with the UI.
Settings of our app should have a predefined schema, in XML:
This should be defined in a file named like
com.marcinjahn.my-app.gschema.xml
.
Now, we can use settings in various ways:
-
bind to them in our widgets
-
read a setting
-
subscribe to changes
References
This “guide” has been heavily inspired by the GJS & GTK 4 tutorial. It also contains a bunch of information taken from: