« Cinnamon applet » : différence entre les versions
Aucun résumé des modifications |
|||
(5 versions intermédiaires par le même utilisateur non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
[[Category:Linux]] | |||
= Liens = | = Liens = | ||
* [http://www.roojs.com/seed/gir-1.2-gtk-3.0/gjs/index.html Documentation détaillée des bibliothèques] | * [http://www.roojs.com/seed/gir-1.2-gtk-3.0/gjs/index.html Documentation détaillée des bibliothèques] | ||
Ligne 19 : | Ligne 20 : | ||
Le champs icon correspond à l'icone qui sera visible lors de l'ajout de l'applet.<br /> | Le champs icon correspond à l'icone qui sera visible lors de l'ajout de l'applet.<br /> | ||
La valeur du champs icon correspond au nom de l'icone qui est dans le thème de l'utilisateur courant. | La valeur du champs icon correspond au nom de l'icone qui est dans le thème de l'utilisateur courant. | ||
<filebox fn=metadata.json | <filebox fn=metadata.json> | ||
{ | { | ||
"uuid": "", | "uuid": "MyApp@AUTHOR", | ||
"name": "", | "name": "My app", | ||
"description": "", | "description": "My app is the best.", | ||
"icon": "" | "icon": "", | ||
"version": 1.0 | |||
} | } | ||
</filebox> | </filebox> | ||
Ligne 788 : | Ligne 790 : | ||
= [https://developer.gnome.org/libsoup/stable Soup] = | = [https://developer.gnome.org/libsoup/stable Soup] = | ||
= Errors = | |||
== [https://github.com/zpydr/gnome-shell-extension-taskbar/issues/217 Main.Util is undefined] == | |||
<pre> | |||
JS ERROR: Exception in callback for signal: activate: TypeError: Main.Util is undefined | |||
</pre> | |||
<kode lang='js'> | |||
//const Main = imports.ui.main; | |||
const Util = imports.misc.util; | |||
// replace Main.Util.spawnCommandLine("...") by | |||
Util.spawnCommandLine("..."); | |||
</kode> |
Dernière version du 9 février 2024 à 16:18
Liens
- Documentation détaillée des bibliothèques
- GNOME shell: UI and Misc javascript files
- Bibliothèque GLib en C
- Fonctionnement de base d'un applet
- GNOME Shell/Cinnamon Extension Configuration Persistence
- Gjs: Javascript Bindings for GNOME
- Gio exemple de code en Vala
- tutorial to create GNOME Shell extensions
- How to make a Cinnamon applet
- Cinnamon applet wiki
- Cinnamon tutorials: Extension system
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.
applet.js |
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)
// gettext sert à traduire les chaine de texte const Gettext = imports.gettext.domain('cinnamon-applets'); const _ = Gettext.gettext; const Lang = imports.lang; const Mainloop = imports.mainloop; // UI const Applet = imports.ui.applet; const PopupMenu = imports.ui.popupMenu; // MISC const Util = imports.misc.util; // Gnome Introspection 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 = { // héritage de IconApplet (affiche une icone dans le panneau) __proto__: Applet.IconApplet.prototype, _init: function(metadata, orientation, panel_height, instanceId) { Applet.IconApplet.prototype._init.call(this, orientation); try { // définit l'icone à partir du nom d'une icone du thème this.set_applet_icon_name("Nom"); // définit l'icone à partir d'un chemin this.set_applet_icon_path("chemin absolu"); this.set_applet_tooltip("Texte"); // texte avec traduction possible en utilisabt gettext this.set_applet_tooltip(_("Texte")); // définit le label pour les applets de type TextApplet et TextIconApplet this.set_applet_label("Label"); } catch (e) { global.logError(e); }; }, on_applet_clicked: function(event) { // action à réaliser lors du clique sur l'icone } } |
Fonction main
function main(metadata, orientation, panel_height, instanceId) { let myApplet = new MyApplet(metadata, orientation, panel_height, instanceId); return myApplet; } |
Menu contextuel
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; // Méthode dans le corps de l'applet, // ne pas oublier d'appeler la méthode dans _init _createContextMenu: function () { // Edit 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); // séparateur this._applet_context_menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); }, |
- Gtk.STOCK_EDIT
- Gtk.STOCK_HELP
- Gtk.STOCK_ABOUT
GUI pour les settings
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, /* interdit la selection de dossiers */ "dependency" : "nom_du_setting2" /* ce setting n'est actif que si le setting nom_du_setting2 l'est également */ } } |
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"; // watch settings file for changes 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(); }, // parse the settings.json file into the "settings" variable _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 // Bob Smith parser.likes.browser // Firefox parser.name.@attribut // test |
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; // pour le fichier /usr/share/cinnamon/locale/fr/LC_MESSAGES/cinnamon.mo Gettext.bindtextdomain("cinnamon", "/usr/share/cinnamon/locale"); Gettext.textdomain("cinnamon"); // pour le fichier /usr/share/locale/fr/LC_MESSAGES/gnome-applets-3.0.mo Gettext.bindtextdomain("gnome-applets-3.0", "/usr/share/locale"); Gettext.textdomain("gnome-applets-3.0"); const _ = Gettext.gettext; print(_("Panel")); // Tableau de bord |
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; // Initialize the gtk Gtk.init(null, 0); // Create builder and load interface let builder = new Gtk.Builder(); builder.add_from_file( "interface.glade" ); // lier l’événement destroy de la fenêtre principale à la fermeture de la fenêtre let main_window = builder.get_object("window"); main_window.connect( "destroy", function() { Gtk.main_quit(); } ); // lier l’événement appuie sur la touche entrée pendant la saisie dans la TextBox à une méthode let entry = builder.get_object("entry"); entry.connect("activate", function() { let text = entry.text; // le texte saisie dans la TextBox }); // lier l’événement click du bouton à une méthode 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() { // Initialize the gtk Gtk.init(null, 0); // Create builder and load interface 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(); } } |
// code appelant 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
Vala
Exemple d'utilisation d'une bibliothèque écrite en Vala et importée dans du code Gjs grâce au système d'introspection.
# génération des fichiers gir, so et vapi à partir du code VALA valac --enable-experimental -X -fPIC -X -shared --library=vala-object --gir=ValaObject-0.1.gir \ -o vala-object.so object.vala # génération du fichier typelib à partir des fichier so et gir 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() |
# path to vala-object.so export LD_LIBRARY_PATH=. # path to ValaObject-0.1.typelib 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"); // Autres méthodes : Main.Util.spawnCommandLine("commande"); Util.spawnCommandLine("commande"); |
Écrire dans le fichier de log
~/.xsession-errors remplace ~/.cinnamon/glass.log |
// error global.logError("Texte"); // debug global.log("Texte"); |
# afficher le contenu du log 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) { // pour l'application cinnamon-settings par exemple 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
// pour un fichier se trouvant dans le même répertoire // on ajoute le répertoire courant aux chemins de recherche pour les imports imports.searchPath.push('.'); imports.searchPath.unshift('.'); // puis on importe le fichier javascript 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() { ... })); }, }; |
PopupMenu
Code source: /usr/share/cinnamon/js/ui/popupMenu.js
// Manage theme icons and image files function CreateIcon(iconName) { // if the iconName is a path to an icon 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 // use a themed icon return new St.Icon({ icon_name: iconName, icon_size: 24, icon_type: St.IconType.FULLCOLOR, style_class: 'popup-menu-icon' }); } /********** PopupIconSubMenuMenuItem **********/ // Inherits from PopupSubMenuMenuItem to add a colored image to the left side function PopupIconSubMenuMenuItem() { this._init.apply(this, arguments); } PopupIconSubMenuMenuItem.prototype = { __proto__: PopupMenu.PopupSubMenuMenuItem.prototype, _init: function(text, iconName) { PopupMenu.PopupSubMenuMenuItem.prototype._init.call(this, text); // remove previous added actor in PopupSubMenuMenuItem _init // because the add order is important this.removeActor(this.label); this.removeActor(this._triangleBin); this._icon = CreateIcon(iconName); this.addActor(this._icon); this.addActor(this.label); this.addActor(this._triangleBin); } }; |
GLib
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); } |
Gio
GFile
const Gio = imports.gi.Gio; var gFile = Gio.file_new_for_path("chemin absolu vers un fichier"); if(gFile.query_exists(null)) { // test si le fichier existe } |
GFileIcon
const Gio = imports.gi.Gio; var gFile = Gio.file_new_for_path("chemin absolu vers un fichier"); var gFileIcon = new Gio.FileIcon({ file: gFile }); |
Gtk
IconTheme
const Gtk = imports.gi.Gtk; // récupère le thème courant let icon_theme = Gtk.IconTheme.get_default(); // test si l’icône mail-receive existe dans le thème courant if (icon_theme.has_icon("mail-receive")) ... |
Stock Items
Liste des icônes
Exemples :
- Gtk.STOCK_EDIT
- Gtk.STOCK_HELP
- Gtk.STOCK_ABOUT
St
StIcon
const St = imports.gi.St; const Gio = imports.gi.Gio; // Création d'une icone à partir d'une image du thème actuel // ici « user-home.png » (répertoire du thème par défaut: « /usr/share/icons/gnome ») var icon = new St.Icon({ icon_name: "user-home", icon_size: 22, icon_type: St.IconType.FULLCOLOR }); // Création d'une icone à partir d'un fichier image 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.
Goa
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 // GOOGLE accounts[i].get_account().id // account_1234567890 accounts[i].get_account().identity // user@gmail.com accounts[i].get_account().presentation_identity // user@gmail.com accounts[i].get_mail().email_address // user@gmail.com accounts[i].get_oauth_based().consumer_secret // anonymous accounts[i].get_oauth2_based() // null } |
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 (); |
GnomeKeyring
// lister toutes les entrées 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
Secret library
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, } ); // Storage 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); // Lookup var password = Secret.password_lookup_sync( EXAMPLE_SCHEMA, { "string": "test", "integer": 10, "bool": true }, null); |
Secret.SchemaFlags
- 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. |
Soup
Errors
Main.Util is undefined
JS ERROR: Exception in callback for signal: activate: TypeError: Main.Util is undefined
//const Main = imports.ui.main; const Util = imports.misc.util; // replace Main.Util.spawnCommandLine("...") by Util.spawnCommandLine("..."); |