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