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 (orconstructor) function, and this function will help us sort out all the mess required to make the applet display properly.