Writing applets: Cinnamon Tutorials

## Writing applets

### Introduction

In this tutorial, we will go through the process of creating a simple applet to learn about the Applet API. Here we will create a Notify Me applet.

Clicking on this applet will send a notification containing the user name as returned by the “`echo -n $USER`” command.

### Creating the basic structure of the applet

An applet has to be given a unique ID (uuid). In this tutorial we will name it "notify-me@cinnamon.org". Of course, you should give your applets your own UUIDs, using either your name or your domain name behind the @ sign.

An applet is basically a directory, whose name is the UUID, containing two files:

- A “`metadata.json`” file which contains information about the applet, such as its name, description etc..
- An “`applet.js`” file which contains its code.

Applets go in `~/.local/share/cinnamon/applets` (or in `/usr/share/cinnamon/applets` if you want them installed system-wide). So let's go ahead and create the files and folders required for any Cinnamon applet.

          cd
          mkdir -p .local/share/cinnamon/applets/notify-me@cinnamon.org/icons
          cd .local/share/cinnamon/applets/notify-me@cinnamon.org
          touch metadata.json
          touch applet.js
          # Only for this applet:
          cp /usr/share/cinnamon/applets/notifications@cinnamon.org/icons/empty-notif-symbolic.svg ./icons/bell-notif-symbolic.svg

### Defining the applet metadata

Let's open `metadata.json` and describe our applet. This file defines the UUID, name, description, and icon of your applet and is used by Cinnamon to identify it and display it to the user in Cinnamon Settings.

    1
    2
    3
    4
    5
    6
    7
    {
        "uuid": "notify-me@cinnamon.org",
        "name": "Notify Me",
        "description": "Click on the applet to send a notification",
        "icon": "cs-notifications",
        "version": "1.0.0"
    }

By default, only one instance of every applet can be placed on the user's panel. But if the user has multiple panels, they cannot have one notify-me applet on each panel, which is bad. Hence we should also add a `max-instance` property. We can specify any number we want (eg `3`), or we can make it unlimited by making it `-1`. In this case, our new `metadata.json` would be

    1
    2
    3
    4
    5
    6
    7
    8
    {
        "uuid": "notify-me@cinnamon.org",
        "name": "Notify Me",
        "description": "Click on the applet to send a notification",
        "icon": "cs-notifications",
        "version": "1.0.0",
        "max-instances": "-1"
    }

There are more things we can add to `metadata.json`, but they will not be covered here.

### Writing our applet

Here is the code for our applet:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    const Applet = imports.ui.applet;
    const Util = imports.misc.util;
    const GLib = imports.gi.GLib;

    /**
     * Variables (and other constants)
     */
    var user = GLib.get_user_name();
    // String.capitalize() is a function implemented by Cinnamon.
    user = user.capitalize(); // Turns the first character into uppercase.

    /**
     * Declaration of the 'NotifyMe' class, which extends
     * the 'IconApplet' class of the 'Applet' library.
     */
    class NotifyMe extends Applet.IconApplet {
        constructor (metadata, orientation, panelHeight, instance_id) {
            super(orientation, panelHeight, instance_id);
            this.set_applet_icon_symbolic_name("bell-notif");
            this.set_applet_tooltip("Click here to send a notification");
        }

        on_applet_clicked(event) {
            // Please consult 'man notify-send'.
            let command = `bash -c 'notify-send -u normal `;
            command += `"Thank you ${user}" "for improving Cinnamon!"'`;
            Util.spawnCommandLineAsync(command)
        }
    }

    function main(metadata, orientation, panelHeight, instance_id) {
        return new NotifyMe(metadata, orientation, panelHeight, instance_id);
    }

Now we'll go through the code line by line, starting from the bottom.

    1
    2
    3
    function main(metadata, orientation, panel_height, instance_id) {
        return new NotifyMe(orientation, panel_height, instance_id);
    }

The `main` function is the only thing Cinnamon understands. To load an applet, Cinnamon calls the `main` function in the applet's code, and expects to get an Applet object, which it will add to the panel. So here we instantiate a `NotifyMe` object (whose details are defined above), and return it.

You will notice that there are a lot of parameters floating around. `metadata` contains, basically, the information inside `metadata.json` plus some more. This is not very helpful in general, but can sometimes provide some useful information.

`orientation` is whether the panel is at the top or the bottom, at the left or the right. The applet can behave differently depending on its position, eg. to make the popup menus show up on the right side.

`panel_height` tells you, unsurprisingly, the height of the panel. This way we can scale the icons up and down depending on how large the panel is.

`instance_id` tells you which instance of the applet you are, since there can be multiple instances of the applet present. While this is just a number assigned to the applet and doesn't have much meaning by itself, it is required to access, say, the individual settings of an applet (which will be described later).

If that sounds like a lot of things to take care of, you should be relieved that the applet API handles all that for you! All you have to do is to pass it to the applet API. Here we are passing this information to the constructor, and the constructor will later pass it to the applet API.

So what exactly happens when we create the applet? We first look at the first three lines:

    1
    2
    3
    const Applet = imports.ui.applet;
    const Util = imports.misc.util;
    const GLib = imports.gi.GLib;

Here we import certain APIs provided by Cinnamon. The most important one is, of course, the `Applet` API. We will also need `Util` in order to run an external program (`notify-send`). When we write `imports.ui.applet`, we are accessing the functions defined in `/usr/share/cinnamon/js/ui/applet.js`. Similarly, `imports.misc.util` accesses `/usr/share/cinnamon/js/misc/util.js`. We then call these things `Applet` and `Util` respectively to avoid typing the long bunch of code.

The third line imports the GLib, which contains the get_user_name() method to access to the Linux user name. Next!

    1
    2
    3
    4
    5
    constructor (metadata, orientation, panelHeight, instance_id) {
        super(orientation, panelHeight, instance_id);
        this.set_applet_icon_symbolic_name("bell-notif");
        this.set_applet_tooltip("Click here to send a notification");
    }

This is the standard constructor of a Javascript Object. It is called each time the object is instantiated. Here, instantiation takes place in the `main()` function.

Also note that we pass all the `metadata`, `orientation` etc. information down the chain until it ultimately reaches the applet API.

`super` refers to the constructor of the parent of this object (here, `Applet.IconApplet`).

The next two lines initialize your applet, defining its icon and tooltip.

    1
    2
    this.set_applet_icon_symbolic_name("bell-notif");
    this.set_applet_tooltip("Click here to send a notification");

However, contrary to popular belief, the applet API is not psychic. It has no idea what your applet wants to do (apart from displaying an icon). You have to first tell it what icon you want, in the line

    1
    this.set_applet_icon_symbolic_name("bell-notif");

Note that `set_applet_icon_symbolic_name` is a function defined inside `Applet.IconApplet`, which makes the applet display the corresponding symbolic icon. By using the applet API, we have saved ourselves from the hassle of creating icons and putting them in the right place. (`bell-notif` is the name of an icon from the user's icon set. The icons available for use can be found in `/usr/share/icons/` or in the `icons/` folder of this applet)

The next line is:

    1
    this.set_applet_tooltip("Click here to send a notification");

which says that the applet should have a tooltip called `Click here to send a notification`.

There is a way to translate messages such as "Click here to send a notification". Please see Translating applets.

The class not only defines the constructor, but also methods, which are functions accessible by the instantiated object. Like the `on_applet_clicked` function, which is all we need.

    1
    2
    3
    4
    5
    6
    on_applet_clicked(event) {
        // Please consult 'man notify-send'.
        let command = `bash -c 'notify-send -u normal `;
        command += `"Thank you ${user}" "for improving Cinnamon!"'`;
        Util.spawnCommandLineAsync(command)
    }

`on_applet_clicked` is a part of the applet API and is called whenever the applet is clicked.

`command` is the command to execute. Please note that three types of string delimiters are used here: ```, `'` and `"`.

Last, `Util.spawnCommandLineAsync(command)` executes this command in an asynchronous manner (i.e "as soon as the computer is ready for that"). Cinnamon's UI runs in a single thread, and synchronous methods that involve i/o can block this thread, causing the desktop to stop responding. Asynchronous methods are preferable, but can sometimes be more complicated to use (though that is not the case here).

Here we see what I've referred to as the "applet API" all the time. A few barebone applet objects are defined in `applet.js`, and we inherit one of them. Here we choose to inherit `Applet.IconApplet`, which is an applet that displays an icon.

Inheritance in JavaScript is slightly weird. We first copy over `Applet.IconApplet`'s prototype (or class) using the

    1
    class NotifyMe extends Applet.IconApplet {

line. This copies all the functions found in `Applet.IconApplet` to our applet, which we are going to use.

Next, in our `constructor` function, we call the `_init` (or `constructor`) function of `Applet.IconApplet`. Here we pass on all the information about `orientation` etc. to this `_init` (or`constructor`) function, and this function will help us sort out all the mess required to make the applet display properly.