Creating the Project
This section contains a lot a boilerplate code, but that is necessary in order to have an easy to work with environment!
# Choosing an Application ID
The application ID will identify our application across the platform. It is in the reverse DNS form, and should be composed of a domain we own, and the application name.
Let’s say we control the example.org
domain, and our application name is “File Browser”. The application ID is then org.example.filebrowser
.
# Directory Structure
It’s time to create a source directory for our application. We name it by the ID of our application:
1mkdir org.example.filebrowser
Inside, we create the directory structure that will contain our project files:
src
will contain our JavaScript modulesdata
will contain our additional application data, such as icons, UI definitions, CSS files
You can use these commands to create these directories in your application directory:
1cd org.example.filebrowser
2mkdir src data
# Necessary Files
Now let’s create all the basic files required for running our application.
# Main Module
This file is the heart of our application. It must contain a main()
function that will be run when it starts. Let’s create it in src/main.js
, and print a simple message to the console inside:
1export function main(argv) {
2 console.log('Hello World!');
3}
# Resources
The build system will compile our sources and data files into GResources. We have to create two XML files describing our resources.
src/org.example.filebrowser.src.gresource.xml
:
1<?xml version="1.0" encoding="UTF-8"?>
2<gresources>
3 <gresource prefix="/org/example/filebrowser/js">
4 <file>main.js</file>
5 </gresource>
6</gresources>
data/org.example.filebrowser.data.gresource.xml
:
1<?xml version="1.0" encoding="UTF-8"?>
2<gresources>
3 <gresource prefix="/org/example/filebrowser">
4 </gresource>
5</gresources>
Don’t forget to update these files when we add files to the application!
# Entry Point
This is a special file that will initialize our GJS package and start the application. Create a src/org.example.filebrowser.js
file and put this inside:
1#!@GJS@ -m
2
3import GLib from 'gi://GLib';
4
5// Initialize the package
6imports.package.init({
7 name: '@PACKAGE_NAME@',
8 version: '@PACKAGE_VERSION@',
9 prefix: '@PREFIX@',
10 libdir: '@LIBDIR@',
11});
12
13// Import the main module and run the main function
14const loop = new GLib.MainLoop(null, false);
15import('resource:///org/example/filebrowser/js/main.js')
16 .then((main) => {
17 GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
18 loop.quit();
19 imports.package.run(main);
20 return GLib.SOURCE_REMOVE;
21 });
22 })
23 .catch(logError);
24loop.run();
All the values between @
will be replaced by the build system when compiling the application.
So what does it do? package
is a special GJS module containing functions that will initialize our package (package.init()
) by setting some directories and loading resources, and run our application (package.run()
).
After initializing the package, we create a loop to asynchronously load our main module from the resources. The loop is then stopped, and our application takes over from there.
# Build System
Now, we have to tell Meson, our build system, what to do with all these files. Let’s create all the meson.build
files with our instructions.
In meson.build
, we give some information about our project, set the APP_ID
variable to reuse the application ID elsewhere without having to retype it, import the gnome
module, and load meson.build
files from the data
and src
directories:
1# Define our project
2project(
3 'filebrowser',
4 version: '0.0.1',
5 license: ['GPL-3.0-or-later'],
6 meson_version: '>= 0.59.0'
7)
8
9APP_ID = 'org.example.filebrowser'
10
11# Import the modules
12gnome = import('gnome')
13
14# Load instructions from subdirectories
15subdir('data')
16subdir('src')
In data/meson.build
, we compile the data resources with the gnome.compile_resources()
function and install them in our application data directory:
1# Compile the resources
2gnome.compile_resources(
3 APP_ID + '.data',
4 APP_ID + '.data.gresource.xml',
5 gresource_bundle: true,
6 install: true,
7 install_dir: get_option('datadir') / APP_ID
8)
In src/meson.build
, we also compile the source resources, and we configure our entry point file and install it as an executable in the binaries directory:
1# Configure the entry point
2configure_file(
3 input : APP_ID + '.js',
4 output : APP_ID,
5 configuration: {
6 'GJS': find_program('gjs').full_path(),
7 'PACKAGE_NAME': APP_ID,
8 'PACKAGE_VERSION': meson.project_version(),
9 'PREFIX': get_option('prefix'),
10 'LIBDIR': get_option('prefix') / get_option('libdir')
11 },
12 install_dir: get_option('bindir'),
13 install_mode: 'rwxr-xr-x'
14)
15
16# Compile the resources
17app_resource = gnome.compile_resources(
18 APP_ID + '.src',
19 APP_ID + '.src.gresource.xml',
20 gresource_bundle: true,
21 install: true,
22 install_dir : get_option('datadir') / APP_ID
23)
# Flatpak
Finally, let’s write the Flatpak manifest that will allow us to build and run the app in a well-known environment. Put this in the org.example.filebrowser.yml
file:
1app-id: org.example.filebrowser
2runtime: org.gnome.Platform
3runtime-version: '43'
4sdk: org.gnome.Sdk
5command: org.example.filebrowser
6
7finish-args:
8 - --socket=wayland
9 - --socket=fallback-x11
10 - --share=ipc
11 - --device=dri
12
13cleanup:
14 - /include
15 - /lib/pkgconfig
16 - /share/doc
17 - /share/man
18 - '*.a'
19 - '*.la'
20
21modules:
22
23 - name: filebrowser
24 buildsystem: meson
25 sources:
26 - type: dir
27 path: .
# Building and Running Our Application
It’s now time to test that our app is working! First install the necessary Flatpak runtime and SDK:
1flatpak install --user org.gnome.Platform//43 org.gnome.Sdk//43
Then install the Flatpak Builder tool:
1flatpak install org.flatpak.Builder
Now we can build the application:
1flatpak run org.flatpak.Builder --force-clean --user --install build-dir org.example.filebrowser.yml
And run it:
1flatpak run org.example.filebrowser
If you did everything correctly, Hello World!
should be printed in the console.
All this work for that? It’s now time to do some more interesting things!