Liens
Structure des fichiers
Dans le dossier ~/.local/share/cinnamon/applets
metadata.json
Contient les informations de l'applet.
Le champs icon correspond à l'icone qui sera visible lors de l'ajout de l'applet.
La valeur du champs icon correspond au nom de l'icone qui est dans le thème de l'utilisateur courant.
metadata.json
|
{
"uuid": "MyApp@AUTHOR",
"name": "My app",
"description": "My app is the best.",
"icon": "",
"version": 1.0
}
|
applet.js
Contient le code de l'applet.
Import
Les fichiers javascript correspondant se trouvent dans
- /usr/share/cinnamon/js (UI et MISC)
- /usr/share/gjs-1.0 (cairo, dbus, format, gettext, jsUnit, lang, promise, signals)
|
const Gettext = imports.gettext.domain('cinnamon-applets');
const _ = Gettext.gettext;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Applet = imports.ui.applet;
const PopupMenu = imports.ui.popupMenu;
const Util = imports.misc.util;
const Cinnamon = imports.gi.Cinnamon;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
const Soup = imports.gi.Soup;
const St = imports.gi.St;
|
Constructeur
|
function MyApplet(metadata, orientation, panel_height, instanceId) {
this._init(metadata, orientation, panel_height, instanceId);
}
|
The “orientation” is given to you by Cinnamon. It tells you whether the panel the applet is located in is at the top or at the bottom (this has an impact on the orientation of menus your applet might need).
Type d'applet
- TextApplet (which show a label in the panel)
- IconApplet (which show an icon in the panel)
- TextIconApplet (which show both an icon and a label in the panel)
- Applet (for hardcore developers, which show an empty box you can fill in yourself)
Corps de la classe
|
MyApplet.prototype = {
__proto__: Applet.IconApplet.prototype,
_init: function(metadata, orientation, panel_height, instanceId) {
Applet.IconApplet.prototype._init.call(this, orientation);
try {
this.set_applet_icon_name("Nom");
this.set_applet_icon_path("chemin absolu");
this.set_applet_tooltip("Texte");
this.set_applet_tooltip(_("Texte"));
this.set_applet_label("Label");
}
catch (e) {
global.logError(e);
};
},
on_applet_clicked: function(event)
{
}
}
|
Fonction main
|
function main(metadata, orientation, panel_height, instanceId)
{
let myApplet = new MyApplet(metadata, orientation, panel_height, instanceId);
return myApplet;
}
|
|
const Gtk = imports.gi.Gtk;
const GLib = imports.gi.GLib;
const Applet = imports.ui.applet;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
_createContextMenu: function () {
this.edit_menu_item = new Applet.MenuItem(_('Edit'), Gtk.STOCK_EDIT, function() {
Main.Util.spawnCommandLine("xdg-open " +
GLib.build_filenamev([global.userdatadir, "applets/placescustom@nikus/applet.js"]));
});
this._applet_context_menu.addMenuItem(this.edit_menu_item);
this._applet_context_menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
},
|
Icônes :
- Gtk.STOCK_EDIT
- Gtk.STOCK_HELP
- Gtk.STOCK_ABOUT
settings-schema.json
|
{
"nom_du_setting1": {
"type": "header",
"description": "Titre"
},
"nom_du_setting2" : {
"type" : "checkbox",
"description" : "Description affichée à droite de la checkbox",
"default" : true
},
"nom_du_setting3" : {
"type" : "filechooser",
"description" : "Description affichée à droite du filechooser",
"default" : "chemin absolu",
"select-dir" : false,
"dependency" : "nom_du_setting2"
}
}
|
Types:
- header : titre en gras
- checkbox
- filechooser
 |
Le type spinbutton rend les settings invalides |
applet.js
|
const Settings = imports.ui.settings;
MyApplet.prototype = {
...
_init: function(metadata, orientation, panel_height, instanceId) {
...
this.settings = new Settings.AppletSettings(this, appletUUID, instanceId);
this.settings.bindProperty(Settings.BindingDirection.IN,
"nom_du_setting2", "setting2_value", this.on_setting2_changed, null);
},
on_setting2_changed: function() {
let temp = this.setting2_value;
...
}
}
|
Settings
|
const Gio = imports.gi.Gio;
const AppletDirectory = imports.ui.appletManager.appletMeta["MonApplet"].path;
const SettingsFile = AppletDirectory + "/settings.json";
let file = Gio.file_new_for_path(SettingsFile);
this._monitor = file.monitor(Gio.FileMonitorFlags.NONE, null);
this._monitor.connect('changed', Lang.bind(this, this._on_settingsfile_changed));
_on_settingsfile_changed: function()
{
this._readSettings();
},
_readSettings: function()
{
let jsonFileContent = Cinnamon.get_file_contents_utf8_sync(SettingsFile);
this.settings = JSON.parse(jsonFileContent);
},
for (var i=0; i<this.settings.length; i++)
{
var setting = this.settings[i];
setting.key1;
setting.key2;
}
|
settings.json
|
[
{ "key1":"value1", "key2":"value2" },
{ "key1":"value11", "key2":"value12" }
]
|
XML
Gjs utilise ECMAScript for XML (E4X)
|
<person>
<name attribut="test">Bob Smith</name>
<likes>
<os>Linux</os>
<browser>Firefox</browser>
<language>JavaScript</language>
<language>Python</language>
</likes>
</person>
|
|
var parser = new XML("code XML");
parser.name
parser.likes.browser
parser.name.@attribut
|
Opérations sur les fichiers
Tester si un fichier existe
|
const GLib = imports.gi.GLib;
if (GLib.file_test("chemin vers le fichier ou le dossier", GLib.FileTest.EXISTS))
{ }
|
Lire le contenu d'un fichier texte
|
const Cinnamon = imports.gi.Cinnamon;
let fileContent = Cinnamon.get_file_contents_utf8_sync("chemin vers le fichier");
|
Écrire dans un fichier texte
|
const Cinnamon = imports.gi.Cinnamon;
const Gio = imports.gi.Gio;
let file = Gio.file_new_for_path("chemin vers un fichier");
let raw = file.replace(null, false, Gio.FileCreateFlags.NONE, null);
let out = Gio.BufferedOutputStream.new_sized (raw, 4096);
Cinnamon.write_string_to_stream(out, "texte à écrire");
out.close(null);
|
Traduction
|
const Gettext = imports.gettext;
Gettext.bindtextdomain("cinnamon", "/usr/share/cinnamon/locale");
Gettext.textdomain("cinnamon");
Gettext.bindtextdomain("gnome-applets-3.0", "/usr/share/locale");
Gettext.textdomain("gnome-applets-3.0");
const _ = Gettext.gettext;
print(_("Panel"));
|
Pour visualiser les textes traduits : il faut décompiler le fichier mo en fichier po
|
msgunfmt /usr/share/cinnamon/locale/fr/LC_MESSAGES/cinnamon.mo -o cinnamon-fr.po
|
Charger un fichier Glade
GtkWindow
|
const Gtk = imports.gi.Gtk;
Gtk.init(null, 0);
let builder = new Gtk.Builder();
builder.add_from_file( "interface.glade" );
let main_window = builder.get_object("window");
main_window.connect( "destroy", function() { Gtk.main_quit(); } );
let entry = builder.get_object("entry");
entry.connect("activate", function() {
let text = entry.text;
});
let button = builder.get_object("button");
button.connect("clicked", on_button_clicked);
function on_button_clicked() {
...
}
Gtk.main();
|
 |
window est un mot-clé. Il ne doit pas être utilisé comme nom de variable. |
GtkDialog
|
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
function PasswordDialog() {
this.password = "";
this.init();
}
PasswordDialog.prototype = {
init: function() {
Gtk.init(null, 0);
this.builder = new Gtk.Builder();
this.builder.add_from_file( "PasswordDialog.glade" );
this._main_window = this.builder.get_object("dialog");
this._entry = this.builder.get_object("entry");
this._entry.connect("activate", Lang.bind(this, this.validate_on_enter));
let button_ok = this.builder.get_object("button_ok");
button_ok.connect("clicked", Lang.bind(this, this.validate_password));
this._main_window.run();
},
validate_on_enter: function() {
this.validate_password();
this._main_window.response(0);
},
validate_password: function() {
this.password = this._entry.text;
},
destroy: function() {
this._main_window.destroy();
}
}
|
|
imports.searchPath.unshift('.');
const PasswordDialog = imports.PasswordDialog;
let password_dialog = new PasswordDialog.PasswordDialog();
print("password: " + password_dialog.password);
password_dialog.destroy();
|
GObject Introspection
Permet d'exploiter les bibliothèques C (fichiers *.so) depuis son code préféré.
- /usr/lib/gjs-1.0 contient les fichiers *.so
- /usr/lib/gjs/girepository-1.0 contient les fichiers *.typelib
Exemple d'utilisation d'une bibliothèque écrite en Vala et importée dans du code Gjs grâce au système d'introspection.
|
valac --enable-experimental -X -fPIC -X -shared --library=vala-object --gir=ValaObject-0.1.gir \
-o vala-object.so object.vala
g-ir-compiler --shared-library=vala-object.so --output=ValaObject-0.1.typelib ValaObject-0.1.gir
|
ValaBinding.js
|
var ValaObject = imports.gi.ValaObject;
ValaObject.StaticMethod()
var instance = new ValaObject.Class()
instance.Method()
|
|
export LD_LIBRARY_PATH=.
export GI_TYPELIB_PATH=.
gjs ValaBinding.js
|
Astuces
Chemin du répertoire de l'applet
|
const AppletDirectory = imports.ui.appletManager.appletMeta["nom de l'applet"].path;
|
Éxecuter une commande bash et récupérer le résultat
|
const GLib = imports.gi.GLib;
const Main = imports.ui.main;
const Util = imports.misc.util;
let [res, out, err, status] = GLib.spawn_command_line_sync("cat applet.cfg");
Main.Util.spawnCommandLine("commande");
Util.spawnCommandLine("commande");
|
Écrire dans le fichier de log
 |
~/.xsession-errors remplace ~/.cinnamon/glass.log |
|
global.logError("Texte");
global.log("Texte");
|
|
tail -f ~/.xsession-errors
|
 |
Une fois le code modifié, il faut relancer cinnamon pour qu'il soit pris en compte:
clique-droit dans la barre des tâches → Troubleshoot → Restart Cinnamon. |
Ne semble plus nécessaire:
Pour activer l'écriture dans le fichier ~/.cinnamon/glass.log :
Paramètres de Cinnamon → Général → Logger ...
Parser un fichier JSON
|
const Cinnamon = imports.gi.Cinnamon;
let jsonFileContent = Cinnamon.get_file_contents_utf8_sync("chemin vers le fichier JSON");
let json = JSON.parse(jsonFileContent);
|
Applications
Les applications installées ont un fichier *.desktop dans le répertoire /usr/share/applications ou ~/.local/share/applications.
Il est possible d'exploiter ce fichier pour obtenir des informations sur l'application tels que :
- le nom de l'application
- le nom de l'icone associée à l'application
- la commande qui permet de lancer l'application
|
const Cinnamon = imports.gi.Cinnamon;
const Gio = imports.gi.Gio;
let appSys = Cinnamon.AppSystem.get_default();
let desktopFile = "firefox.desktop";
let app = appSys.lookup_app(desktopFile);
if (!app)
{
app = appSys.lookup_settings_app(desktopFile);
}
if (app)
{
let appInfo = app.get_app_info();
let command = appInfo.get_commandline();
let appName = appInfo.get_name();
let icon = appInfo.get_icon();
let iconName = null;
if (icon)
{
if (icon instanceof Gio.FileIcon)
{
iconName = icon.get_file().get_path();
}
else
{
iconName = icon.get_names().toString();
}
}
}
|
metadata.json
|
"icon": "nom de l'image du thème courant sans l'extension"
|
Ou si icon n'est pas défini, le fichier icon.png.
Charger d'autres fichiers Javascript
|
imports.searchPath.push('.');
imports.searchPath.unshift('.');
const Add = imports.NomFichierJavascript;
|
Notification
|
const Util = imports.misc.util;
Util.spawnCommandLine("notify-send --icon=mail-read \"titre\" \"message\"");
|
Les icônes sont recherchées dans /usr/share/icons/gnome/32x32 et /usr/share/notify-osd/icons
Portée de this
this est un mot-clé et non une variable. Ainsi il ne peut être utilisé dans une méthode anonyme.
La solution est d'utiliser Lang.bind
|
const Lang = imports.lang;
MyPrototype = {
ma_methode: function() {
objet.connect('event', Lang.bind(this, this.on_event));
},
on_event: function() { ... },
ma_methode: function() {
objet.connect('event', Lang.bind(this, function() { ... }));
},
};
|
Code source: /usr/share/cinnamon/js/ui/popupMenu.js
|
function CreateIcon(iconName) {
if (iconName[0] === '/') {
var file = Gio.file_new_for_path(iconName);
var iconFile = new Gio.FileIcon({ file: file });
return new St.Icon({ gicon: iconFile, icon_size: 24, style_class: 'popup-menu-icon' });
}
else
return new St.Icon({ icon_name: iconName, icon_size: 24, icon_type: St.IconType.FULLCOLOR, style_class: 'popup-menu-icon' });
}
function PopupIconSubMenuMenuItem() {
this._init.apply(this, arguments);
}
PopupIconSubMenuMenuItem.prototype = {
__proto__: PopupMenu.PopupSubMenuMenuItem.prototype,
_init: function(text, iconName) {
PopupMenu.PopupSubMenuMenuItem.prototype._init.call(this, text);
this.removeActor(this.label);
this.removeActor(this._triangleBin);
this._icon = CreateIcon(iconName);
this.addActor(this._icon);
this.addActor(this.label);
this.addActor(this._triangleBin);
}
};
|
Enum GLib.UserDirectory
GLib.UserDirectory.DIRECTORY_DESKTOP : 0
GLib.UserDirectory.DIRECTORY_DOCUMENTS : 1
GLib.UserDirectory.DIRECTORY_DOWNLOAD : 2
GLib.UserDirectory.DIRECTORY_MUSIC : 3
GLib.UserDirectory.DIRECTORY_PICTURES : 4
GLib.UserDirectory.DIRECTORY_PUBLIC_SHARE : 5
GLib.UserDirectory.DIRECTORY_TEMPLATES : 6
GLib.UserDirectory.DIRECTORY_VIDEOS : 7
GLib.UserDirectory.N_DIRECTORIES : 8
|
const GLib = imports.gi.GLib;
for (let directoryId = 0; directoryId < GLib.UserDirectory.N_DIRECTORIES; directoryId++) {
let directoryPath = GLib.get_user_special_dir(directoryId);
}
|
|
const Gio = imports.gi.Gio;
var gFile = Gio.file_new_for_path("chemin absolu vers un fichier");
if(gFile.query_exists(null)) {
}
|
|
const Gio = imports.gi.Gio;
var gFile = Gio.file_new_for_path("chemin absolu vers un fichier");
var gFileIcon = new Gio.FileIcon({ file: gFile });
|
JavaScript GTK tutorial
|
const Gtk = imports.gi.Gtk;
let icon_theme = Gtk.IconTheme.get_default();
if (icon_theme.has_icon("mail-receive"))
...
|
Liste des icônes
Exemples :
- Gtk.STOCK_EDIT
- Gtk.STOCK_HELP
- Gtk.STOCK_ABOUT
|
const St = imports.gi.St;
const Gio = imports.gi.Gio;
var icon = new St.Icon({ icon_name: "user-home", icon_size: 22, icon_type: St.IconType.FULLCOLOR });
var file = Gio.file_new_for_path("chemin absolu vers un fichier image");
var iconFile = new Gio.FileIcon({ file: file });
var icon = new St.Icon({ gicon: iconFile, icon_size: 24 });
|
icon_type: par défaut il est à St.IconType.SYMBOLIC et va chercher les icones avec le préfixe *-symbolic.* dans le dossier scalable du thème actuel (par défaut: /usr/share/icons/gnome/scalable)
L'utilisation de St.IconType.FULLCOLOR permet d'avoir les icones classiques.
|
const Goa = imports.gi.Goa;
let goaClient = Goa.Client.new_sync(null);
let accounts = goaClient.get_accounts();
for (let i = 0; i < accounts.length; i++) {
accounts[i].get_account().provider_name
accounts[i].get_account().id
accounts[i].get_account().identity
accounts[i].get_account().presentation_identity
accounts[i].get_mail().email_address
accounts[i].get_oauth_based().consumer_secret
accounts[i].get_oauth2_based()
}
|
Clutter
Création de fenêtres.
|
const Clutter = imports.gi.Clutter;
Clutter.init (null);
let stage = Clutter.Stage.get_default ();
stage.title = "Test";
stage.set_color(new Clutter.Color( {red:150, blue:0, green:0, alpha:255} ));
stage.show ();
Clutter.main ();
stage.destroy ();
|
|
const GnomeKeyring = imports.gi.GnomeKeyring;
var keyrings = GnomeKeyring.list_keyring_names_sync();
for (var i = 0; i < keyrings[1].length; i++) {
var keyring = keyrings[1][i];
var ids = GnomeKeyring.list_item_ids_sync(keyring);
for (var j = 0; j < ids[1].length; j++) {
var id = ids[1][j];
var item = GnomeKeyring.item_get_info_sync(keyring, id);
print(item[1].get_display_name() + " = " + item[1].get_secret());
}
}
|
Les méthodes list_keyring_names_sync, list_item_ids_sync et item_get_info_sync renvoient un tableau de 2 éléments :
- le premier est un entier correspondant à l'Enum GnomeKeyring.Result (0:OK , 5:BAD_ARGUMENTS)
- le deuxième est un objet contenant le retour de la méthode
|
const Secret = imports.gi.Secret;
const EXAMPLE_SCHEMA = new Secret.Schema(
"org.gnome.Application.Password",
Secret.SchemaFlags.NONE,
{
"string": Secret.SchemaAttributeType.STRING,
"integer": Secret.SchemaAttributeType.INTEGER,
"bool": Secret.SchemaAttributeType.BOOLEAN,
}
);
var attributes = {
"string": "test",
"integer": 10,
"bool": true
};
Secret.password_store_sync(
EXAMPLE_SCHEMA,
attributes,
Secret.COLLECTION_DEFAULT,
"Label-Description",
"le mot de passe",
null);
var password = Secret.password_lookup_sync(
EXAMPLE_SCHEMA, { "string": "test", "integer": 10, "bool": true }, null);
|
- NONE no flags for the schema
- DONT_MATCH_NAME don't match the schema name when looking up or removing passwords
 |
libgnome-keyring n'enregistre pas les noms de schema. Il faut doc utiliser DONT_MATCH_NAME pour récupérer les mots de passes stockés par gnome-keyring. |
SecretSchema
Errors
JS ERROR: Exception in callback for signal: activate: TypeError: Main.Util is undefined
|
const Util = imports.misc.util;
Util.spawnCommandLine("...");
|