File: write-applet.xml

package info (click to toggle)
cinnamon 6.4.13-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 13,300 kB
  • sloc: javascript: 54,298; ansic: 51,499; python: 21,971; xml: 2,803; sh: 96; makefile: 27; perl: 13
file content (298 lines) | stat: -rw-r--r-- 13,849 bytes parent folder | download | duplicates (2)
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
<?xml version='1.0'?>
<!DOCTYPE book PUBLIC '-//OASIS//DTD DocBook XML V4.3//EN'
'http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd'
[
<!ENTITY % local.common.attrib "xmlns:xi  CDATA  #FIXED 'http://www.w3.org/2003/XInclude'">
]>

<chapter id="write-applet">
  <title>Writing applets</title>
  <sect2>
    <title>Introduction</title>
    <para>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.</para>

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

  </sect2>
  <sect2> <title>Creating the basic structure of the applet</title>
    <para>
      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.
    </para>

    <para>
      An applet is basically a directory, whose name is the UUID, containing two files:
    </para>

    <itemizedlist>
      <listitem>
        A “<code>metadata.json</code>” file which contains information about the applet, such as its name, description etc..
      </listitem>
      <listitem>
        An “<code>applet.js</code>” file which contains its code.
      </listitem>
    </itemizedlist>
    <para>
      Applets go in <code>~/.local/share/cinnamon/applets</code> (or in <code>/usr/share/cinnamon/applets</code> if you want them installed system-wide). So let's go ahead and create the files and folders required for any Cinnamon applet.

    </para>

    <screen>
      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

    </screen>
  </sect2>
  <sect2>
    <title>Defining the applet metadata</title>
    <para>
      Let's open <code>metadata.json</code> 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.
    </para>

    <informalexample>
      <programlisting>
        {
            "uuid": "notify-me@cinnamon.org",
            "name": "Notify Me",
            "description": "Click on the applet to send a notification",
            "icon": "cs-notifications",
            "version": "1.0.0"
        }
      </programlisting>
    </informalexample>

    <para>
      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 <code>max-instance</code> property. We can specify any number we want (eg <code>3</code>), or we can make it unlimited by making it <code>-1</code>. In this case, our new <code>metadata.json</code> would be
    </para>

    <informalexample>
      <programlisting>
        {
            "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"
        }
      </programlisting>
    </informalexample>

    <para>
      There are more things we can add to <code>metadata.json</code>, but they will not be covered here.
    </para>
  </sect2>
  <sect2>
    <title>Writing our applet</title>
    <para> Here is the code for our applet:
    </para>
    <informalexample>
      <programlisting>
        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);
        }
      </programlisting>
    </informalexample>
    <para>
      Now we'll go through the code line by line, starting from the bottom.
    </para>

    <informalexample>
      <programlisting>
        function main(metadata, orientation, panel_height, instance_id) {
            return new NotifyMe(orientation, panel_height, instance_id);
        }
      </programlisting>
    </informalexample>
    <para>
      The <code>main</code> function is the only thing Cinnamon understands. To load an applet, Cinnamon calls the <code>main</code> 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 <code>NotifyMe</code> object (whose details are defined above), and return it.
    </para>

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

    <para>
      <code>orientation</code> 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.
    </para>

    <para>
      <code>panel_height</code> 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.
    </para>

    <para>
      <code>instance_id</code> 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).
    </para>

    <para>
      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.
    </para>

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

    <informalexample>
      <programlisting>
        const Applet = imports.ui.applet;
        const Util = imports.misc.util;
        const GLib = imports.gi.GLib;
      </programlisting>
    </informalexample>

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

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

    </para>
    <informalexample>
      <programlisting>
        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");
        }
      </programlisting>
    </informalexample>
    <para>
      This is the standard constructor of a Javascript Object. It is called each time the object is instantiated. Here, instantiation takes place in the <code>main()</code> function.
    </para>

    <para>
      Also note that we pass all the <code>metadata</code>, <code>orientation</code> etc. information down the chain until it ultimately reaches the applet API.
    </para>

    <para>
      <code>super</code> refers to the constructor of the parent of this object (here, <code>Applet.IconApplet</code>).
    </para>

    <para>
      The next two lines initialize your applet, defining its icon and tooltip.
    </para>
    <informalexample>
      <programlisting>
            this.set_applet_icon_symbolic_name("bell-notif");
            this.set_applet_tooltip("Click here to send a notification");
      </programlisting>
    </informalexample>

    <para>
      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
    </para>

    <informalexample>
      <programlisting>
        this.set_applet_icon_symbolic_name("bell-notif");
      </programlisting>
    </informalexample>

    <para>
      Note that <link linkend="cinnamon-js-ui-Applet-IconApplet-set_applet_icon_symbolic_name"><code>set_applet_icon_symbolic_name</code></link> is a function defined inside <code>Applet.IconApplet</code>, 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. (<code>bell-notif</code> is the name of an icon from the user's icon set. The icons available for use can be found in <code>/usr/share/icons/</code> or in the <code>icons/</code> folder of this applet)
    </para>

    <para>
      The next line is:
    </para>

    <informalexample>
      <programlisting>
        this.set_applet_tooltip("Click here to send a notification");
      </programlisting>
    </informalexample>

    <para>
      which says that the applet should have a tooltip called <code>Click here to send a notification</code>.
    </para>

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

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

    <informalexample>
        <programlisting>
            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)
            }
        </programlisting>
    </informalexample>

    <para>
      <code>on_applet_clicked</code> is a part of the applet API and is called whenever the applet is clicked.
    </para>

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

    <para>
      Last, <code>Util.spawnCommandLineAsync(command)</code> 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).
    </para>

    <para>
      Here we see what I've referred to as the "applet API" all the time. A few barebone applet objects are defined in <code>applet.js</code>, and we inherit one of them. Here we choose to inherit <link linkend="cinnamon-js-ui-Applet-IconApplet"><code>Applet.IconApplet</code></link>, which is an applet that displays an icon.
    </para>


    <para>
      Inheritance in JavaScript is slightly weird. We first copy over <code>Applet.IconApplet</code>'s prototype (or class) using the
    </para>
    <informalexample>
      <programlisting>
        class NotifyMe extends Applet.IconApplet {
      </programlisting>
    </informalexample>
    <para>
      line. This copies all the functions found in <code>Applet.IconApplet</code> to our applet, which we are going to use.
    </para>

    <para>
      Next, in our <code>constructor</code> function, we call the <link linkend="cinnamon-js-ui-Applet-IconApplet-_init"><code>_init</code></link> (or <code>constructor</code>) function of <code>Applet.IconApplet</code>. Here we pass on all the information about <code>orientation</code> etc. to this <code>_init</code> (or<code>constructor</code>) function, and this function will help us sort out all the mess required to make the applet display properly.
    </para>
  </sect2>
</chapter>