lunedì 2 gennaio 2017

Primi esperimenti di desklet in Cinnamon


Dopo qualche settimana di smanettamenti (e smadonnamenti!) vari sono riuscito ad ottenere qualche prototipo funzionante di desklet per cinnamon.

La prima cosa che mi viene da dire è che è veramente difficile riuscire a raccapezzarsi tra tutte le librerie usabili, quali sono le funzioni e come si usano!

Questo perchè il linguaggio usato per l'implementazione dei desklet è il JavaScript, mentre la documentazione delle librerie richiamabili è generata dal codice C, con la possibilità di essere richiamate da codice python o altri linguaggi. Come si immagina la situazione è veramente complessa.

La storia è che sono state inventate delle interfacce per chiamare queste librerie scritte in C dal JavaScript (o da altri linguaggi), raccolte sotto il nome di GObject Introspection.

La documentazione di queste librerie segue lo standard C, per cui ad esempio la funzione
ClutterActor *gtk_clutter_embed_get_stage (GtkClutterEmbed *embed);
va invocata in JavaScript come
const GtkClutter = imports.gi.GtkClutter;
this._clutterEmbed = new GtkClutter.Embed();
this._gtkEmbedActor = this._clutterEmbed.get_stage();
per cui la prima parte del nome della funzione gtk_clutter rappresenta la libreria da importare: GtkClutter; mentre l'oggetto GtkClutterEmbed diventa GtkClutter.Embed; e l'ultima parte del nome della funzione rappresenta come va chiamata la funzione (get_stage); tra l'altro in C non ci sono gli oggetti mentre in JavaScript la funzione viene invocata sull'istanza (this._clutterEmbed).


Documentazione


Un ottimo post che spiega una desklet di tipo hello world è disponibile qui.

Una fonte preziosa di informazioni è quella ufficiale di linuxmint (i creatori di Cinnamon).
 
Un'altra ottima fonte di informazioni è il pacchetto devhelp, che contiene offline un sacco di reference utili; si installa con il comando
 sudo apt install devhelp cinnamon-doc

 

Un esempio


Andando al sodo, ecco un codice di esempio che stampa un'immagine:

il file comune metadata.json:
{
 "uuid": "videoDesk@zac",
 "name": "Video Desklet",
 "description": "Show video streams on your desktop",
 "icon": "",
 "prevent-decorations": false,
 "max-instances": "100",
 "dangerous": false
}

il file desklet.js:
const Desklet = imports.ui.desklet;
const St = imports.gi.St;
const Clutter = imports.gi.Clutter;

function VideoDesk(metadata, desklet_id)
{
 this._init(metadata, desklet_id);
}

VideoDesk.prototype =
{
 __proto__: Desklet.Desklet.prototype,

 _init: function(metadata, desklet_id)
 {
  Desklet.Desklet.prototype._init.call(this, metadata, desklet_id);

  this.window = new St.Bin();

  let imgFilename = '/home/zac/Progetti/videoDesk/image.png';
  this._clutterTexture = new Clutter.Texture({keep_aspect_ratio: true});
  this._clutterTexture.set_from_file(imgFilename)

  this._clutterBox = new Clutter.Box();
  this._binLayout = new Clutter.BinLayout();
  this._clutterBox.set_layout_manager(this._binLayout);
  this._clutterBox.set_width(this.metadata["width"]);
  this._clutterBox.add_actor(this._clutterTexture);
  this.window.add_actor(this._clutterBox);

  this.setContent(this.window);
 }
}

function main(metadata, desklet_id)
{
 return new VideoDesk(metadata, desklet_id);
}



Un trucco pratico


Durante l'implementaizone lancio spesso il desklet per verificare che la sintassi dei comandi che sto scrivendo sia corretta. Un'opzione è quella di aggiungere e rimuovere in continuazione il nostro desklet dal desktop. Ma ho trovato molto più comodo e veloce usare il compilatore di JavaScript di Cinnamon (il cjs). Tra l'altro ho impostato l'editor Scite a lanciarlo ogni volta che premo F5 per cui l'esecuzione e visualizzazione degli errori è ancora più veloce.

In scite la configurazione per la compilazione automatica si ottiene aggiungendo al file di configurazione la linea
command.go.*.js=cjs $(FileNameExt)
il codice del file di test (che mostra un filmato):

#!/usr/bin/cjs

const Lang = imports.lang;
const Gtk = imports.gi.Gtk;
const Gst = imports.gi.Gst;
const Clutter = imports.gi.Clutter;
const ClutterGst = imports.gi.ClutterGst;
const GtkClutter = imports.gi.GtkClutter;

ClutterGst.init(null, null);

const Application = new Lang.Class({
 //A Class requires an explicit Name parameter. This is the Class Name.
 Name: 'Application',

 //create the application
 _init: function() {
  this.application = new Gtk.Application();

   //connect to 'activate' and 'startup' signals to handlers.
   this.application.connect('activate', Lang.bind(this, this._onActivate));
   this.application.connect('startup', Lang.bind(this, this._onStartup));
 },

 //create the UI
 _buildUI: function() {
  this._window = new Gtk.ApplicationWindow({ application: this.application, title: "Hello World!" });
  this._window.set_default_size(200, 200);
  this.label = new Gtk.Label({ label: "Hello World" });

  this.player = new ClutterGst.Playback();
  this.player.set_filename("/home/zac/Scrivania/test.mov");

  this._content = new ClutterGst.Aspectratio();
  this._content.set_player(this.player);

  this._clutterBox = new Clutter.Box();
  this._binLayout = new Clutter.BinLayout();
  this._clutterBox.set_layout_manager(this._binLayout);
  this._clutterBox.set_width(300);
  this._clutterBox.set_height(100);
  this._clutterBox.set_content(this._content);

  this._clutterEmbed = new GtkClutter.Embed();
  this._gtkEmbedActor = this._clutterEmbed.get_stage();
  this._gtkEmbedActor.add_actor(this._clutterBox);

  this._window.add(this._clutterEmbed);

  this.player.set_playing(true);
 },

 //handler for 'activate' signal
 _onActivate: function() {
  //show the window and all child widgetsq
  this._window.show_all();
 },

 //handler for 'startup' signal
 _onStartup: function() {
  this._buildUI();
 },

});

//run the application
let app = new Application();
app.application.run(ARGV);

Il prossimo passo è aprire un repository su GitHub dove piazzare il codice dei miei esperimenti, nel frattempo, buona programmazione!

Nessun commento:

Posta un commento