Applet, desklet and extension settings

Introduction

Once you have written an applet, desklet or extension you will often find that you want to offer some configuration options to your users. Fortunately, Cinnamon provides an easy-to use API to do just that. In this tutorial, we will go over the ins and outs of the settings API.

Overview

Part of the API for applet settings is an automatically generated settings window for user configuration. This means that you will not have to worry about building the application, the settings widgets, or the back-end that notifies your applet when something changes. Because of this, there are really only two things that you will have to worry about: defining the settings, and using them in your applet.

The settings-schema.json file

The first thing you will need to do is create a file named settings-schema.json in your applet directory. This file is where you will define all your settings. It is also the place where the API will go to figure out how to generate the settings window.

We will talk about some of the more advanced options we can use in this file later, but for now we will keep it simple. Let's suppose we want to define two settings which will allow the user to configure the width and height of our applet. In that case our settings-schema.json file might look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
    "width": {
        "type" : "scale",
        "default" : 50,
        "min" : 10,
        "max" : 400,
        "step" : 1,
        "description" : "Width of the applet in pixels"
    },
    "height": {
        "type" : "scale",
        "default" : 50,
        "min" : 10,
        "max" : 400,
        "step" : 1,
        "description" : "Height of the applet in pixels"
    }
}

The first thing you will need is the outer brackets {}. All of your settings will be placed inside these brackets. If you have more than one, they must be separated with commas, just like a javascript object.

Each setting consists of a key, followed by a colon, and then the definition in it's own set of brackets. The purpose of the key is to identify which setting is being referenced. The key can be whatever you want, but it must be unique. So, for example, we could just as easily use "John" and "Fred" for our keys instead of "height" and "width", or "height1" and "height2", but we could not call them both "height".

Each definition contains it's own set of key/value pairs. The keys we include in each definition will vary depending on the type of setting we use, but there are a few that are always or almost always used:

  • type: This defines what type of setting, widget, or layout element you are defining. This is always required.
  • default: Every key that stores a setting must specify a default value. Unless you are using a non-setting type, you must include this. It is your responsibility to make sure this value is supported by the setting type.
  • description: Any type that will show up in the settings window needs a description to specify the text that will appear. For settings, this will describe the purpose

For a complete list of all settings types and the keys they require, see the Applet, desklet and extension settings reference page.

Using the settings in your applet

Now that you have your settings defined in settings-schema.json, you will need to set up your applet to use them. The first thing you will need to do is import the settings library. To do so, simply place the following line with the other imports at the beginning of your file:

1
const Settings = imports.ui.settings;

Now you will need to initialize the settings provider. It is recommended that you do this early in the _init function of your applet, since you can't use any of the settings until the provider is initialized. We do this by using the AppletSettings class as follows:

1
this.settings = new Settings.AppletSettings(bindObject, uuid, instanceId);

Now let's take a look at these arguments and what they do.

  • bindObject: This is an object (usually the applet itself though it can be anything you want) to which the settings will be bound by default. We'll talk about the ins and outs of bindings later, but for now, suffice it to say that they allow you to access the value directly from the bind object without having to make a call to the settings provider.
  • uuid: This is your applet uuid. It can be obtained from the metadata, which is passed as the first argument in the main function of your applet, or you can hard-code it directly with a string. This is needed so that the settings provider can find the settings-schema.json file you created earlier.
  • instanceId: This is the unique identifier that represents a particular instance of the applet. It is passed to the main function as the fourth argument. This is what the settings provider will use to differentiate the settings of one instance of the applet from another.
  • Note: you will want to save a reference to your settings provider as we did above. This is what you will use to access and change your settings.

Now that you've initialized the settings provider, you're ready to start accessing your settings so you can use them in your applet.

Accessing your settings in your applet

There are three different ways you can interface with the settings provider to access and change your settings:

Getters and Setters

The settings provider has methods for getting and setting values directly. In our example above, if we want to set the applet height when we first load the applet, we could do something like

1
this.actor.set_height(this.settings.getValue("height"));

This is useful for setting the initial state, but we probably want to update the height of our applet if the user changes the height in settings. In that case we would either have to poll the settings frequently to see if anything changed (very costly in resources), or else find another way to interface with the settings provider.

Connecting to signals

Fortunately, the next interface method allows us to react when the setting changes. The are two different types of signals that the provider emits. One is a generic changed signal that is emitted by any key that's changed. The other is only emitted when a particular setting is changed, and has a signal name of "changed::" followed by the key name.

Now lets suppose that we write a function to update the applet height, and we want to call it every time the user changes the height setting. We would include a line like

1
this.settings.connect("changed::height", Lang.bind(this, this.setAppletHeight));

Binding your settings

Often we want to be able to both access the setting and react to changes. Binding is our third method for interfacing with the settings, and provides us an easy way to do both at once. Because of it's versatility, this is generally the method you will use.

The settings provider offers several methods for binding a setting, but for this tutorial we will only use bind as it is sufficient for almost all situations. In our example it would look something like this:

1
this.settings.bind("height", "appletHeight", this.setAppletHeight);

Now lets take a close look at what's happening here.

The first argument is of course the name of the key in your settings-schema.json file.

The third argument is a callback function which, like before, is called whenever the value changes. You don't need to pass a callback, but you wont be able to react to changes if you don't.

However, it's the second argument that makes bindings so powerful. During the binding process, a new property is created on the bindObject. The bindObject is usually the one we passed to the provider when we initialized it, but there is now a bind function that will allow us to bind to something else if we want. The property can be used just like any variable, but it always reflects the current value of the settings, and if you assign a value to it, it will update the settings accordingly. This means that we can do things like this:

1
2
3
this.settings.bind("height", "appletHeight", this.setAppletHeight);
this.actor.set_height(this.height);
this.height = 100;  //now our height is 100

Overrides

As of Cinnamon 4.4, it is now possible to override individual settings and their properties. This is to allow distributions to make changes that help cinnamon feel more 'at home' in that distribution, such as default icons, stings, etc.

To create an override, place a file called settings-override.json, in the folder of the applet, desklet or extension you wish to override. This file will have the same format as settings-schema.json, but you only need to include the settings you wish to override. Note: the settings need to have the same settings 'key' as the one you're overriding - otherwise it will add a new, unused setting rather than replacing the old.

If you would just like to add or replace one or two properties, there is an additional option as well. In the settings key, include the property override-prop (it's value can be anything - even false), and it will use the original setting definition and only add or change the properties you define. This is particularly useful if you want to change a default, add a tooltip, or other minor changes like that.

Tip: If you wish to remove a setting, you can do that too! Because the applet almost certainly relies on there being a value for that setting, you will need to replace it, rather than removing it completely. All you need to do, is redefine the setting with a type of generic. Be sure to also define the default property. Please note that if a user already has a value set that is not the default, it will continue to use the old value rather than the default, but the user will no longer be able to set it.

More information

The available settings items and widgets can be found an the Applet, desklet and extension settings reference, alongside with some extra options. The settings objects themselves are Settings.AppletSettings, Settings.DeskletSettings and Settings.ExtensionSettings, which all inherit Settings._provider. Most useful functions will be found inside the _provider object.