Next: , Previous: Utiliser adesklets, Up: Top


5 Programmation d'adesklets

5.1 Une introduction à adesklets

Comme expliqué ci-dessus (See A propos d'adesklets.), adesklets est principalement une console interactive d'Imlib2, avec une ou deux fonctions supplémentaires:

Commençons une session interactive typique pour voir ce qui se passe. Sous X, ouvrez un terminal et tapez:

     adesklets :

Comme dans la section précédente (See 'Utilisation d'adesklets comme un éditeur graphique en ligne de commande'.), vous obtenez une invite. Maintenant entrez la commande 'images_info'. Vous devriez obtenir sur la sortie standard :

     2 images
     id 0 width 1 height 1 alpha 1 filename (null)
     id 1 width 1 height 1 alpha 1 filename (null)
     command 0 ok: images_info

Cela signifie que vous avez deux images : l'image 0 et l'image 1, et qu'elles ont une taille de 1x1 pixel. Ce sont les seules images que vous ne pourrez jamais détruire ou modifier (redimensionner indépendamment, retourner diagonalement, etc.) comme vous le souhaitez. Qu'est-ce que c'est que cela?

Mais où est cette fenêtre? Eh bien, pour l'instant, ce sont seulement des images de 1x1 pixel, et elles ne sont pas affichées1 à l'écran. Ainsi, redimensionnons les à 100x100 pixels: tapez 'window_resize 100 100'. Maintenant, si vous retapez la commande 'images_info', vous obtiendrez:

     2 images
     id 0 width 100 height 100 alpha 1 filename (null)
     id 1 width 100 height 100 alpha 1 filename (null)
     command 2 ok: images_info

Vous voyez? Les images d'avant-plan et d'arrière-plan ont été redimensionées à 100x100 pixels: de plus l'arrière-plan a été mis à jour pour contenir la nouvelle image de fond. Maintenant, rendons cette fenêtre visible. Tapez les deux commandes: 'window_reset managed' et enfin 'window_show'.

La première commande demande à adesklets de laisser votre gestionnaire de fenêtres "gérer" votre fenêtre (ce qui n'est pas fait par défaut): cela signifie que la fenêtre va être décorée, et que vous pourrez changer sa visibilité2 de façon interactive. La seconde montre votre fenêtre de 100x100. Rien d'impressionnant: un carré noir de 100x100 avec une barre de titre et des bordures fixes.

Comme dit plus haut, seule l'image 0 (l'avant-plan) est affichée par défaut. Maintenant, tapons : 'window_set_transparency 1'. Wouah! Nous voyons la fenêtre racine du dessous! Avec "window transparency" activé, l'image 1 (arrière-plan) est d'abord copiée dans la fenêtre, puis l'image 0 (avant-plan) qui est entièrement noire et transparente, est mise par-dessus3.

Il est temps de parler des couleurs: souvenez-vous, adesklets est une console d'Imlib2. Par conséquent, comme Imlib24, les couleurs sont toujours de véritables couleurs 32 bits codées en RVBA - huit bits par canal (de 0 à 255), incluant un canal alpha pour la transparence. La conversion de palette se fera automatiquement si la définition de votre écran est inférieure, vous n'avez pas à vous en occuper. Si vous tapez: 'context_get_color', vous obtiendrez:

     command 0 ok: context color 255 255 255 255

Imlib2 est une sorte de machine par états; en tant que tel, il utilise un "contexte" qui garde en mémoire une série d'attributs qui seront utilisés dans ses opérations futures. Par exemple, nous savons maintenant que la couleur qui sera utilisée, aussi longtemps que nous ne la modifions pas, sera un blanc opaque (rouge=255, bleu=255, vert=255, alpha=255). Si nous tapons: 'context_get_image', il retourne:

     command 0 ok: context image 0

Cela signifie que toute opération que nous effectuons sur une fenêtre sera faite directement sur l'image d'avant-plan. Dessinons un rectangle plein semi-transparent de couleur magenta sur l'avant-plan. Les commandes seront: 'context_set_color 255 0 0 200' et 'image_fill_rectangle 25 25 50 50'. Élégant, n'est-ce pas?

Si vous avez l'habitude de programmer avec Imlib2, vous avez du remarquer que ces dernières commandes ont l'air familières. C'est assez normal; la plupart des fonctions C d'Imlib2 sont présentées de la même façon dans les 'commandes' d'adesklets. Ainsi, la commande d'adesklets 'image_fill_rectangle' suit la même sémantique que la fonction 'imlib_image_fill_rectangle()' d'Imlib2, la commande 'context_set_color' suit celle de la fonction 'imlib_context_set_color()', etc. Les deux seules différences significatives (et systématiques) sont premièrement que partout où vous utiliserez un objet 'Imlib_Quelquechose' en C, vous utiliserez un ID entier avec adesklets, et deuxièmement dans la sémantique des fonctions 'imlib_free_Quelquechose()' où on doit préciser l'ID de l'objet auquel s'applique la fonction au lieu de libérer l'objet de ce type sélectionné dans le contexte.

Tout ceci est facile à illustrer. Chargeons la police "Vera" incluse dans adesklets à la taille 20pt, mettons la en police par défaut et écrivons 'Hello' en blanc opaque en diagonale sur l'image d'avant-plan en partant du coin en haut à gauche, avant de décharger la police. Cela devrait ressembler à:

     6 >>> load_font Vera/20
     command 6 ok: new font 0
     7 >>> context_set_font 0
     command 7 ok: context_set_font 0
     8 >>> context_set_direction text_to_angle
     command 8 ok: context_set_direction text_to_angle
     9 >>> context_set_angle 45
     command 9 ok: context_set_angle 45
     10 >>> context_set_color 255 255 255 255
     command 10 ok: context_set_color 255 255 255 255
     11 >>> text_draw 0 0 Hello
     command 11 ok: text_draw 0 0 Hello
     12 >>> free_font 0
     command 12 ok: free_font 0

Si vous regardez votre documentation d'Imlib2, vous remarquerez immédiatement les deux différences que l'on vient d'évoquer: toutes les références à la police Vera/20 sont faites par un ID entier (0 ici), et la commande 'free_font', contrairement à la fonction correspondante 'imlib_free_font()', est appliquée à la police dont l'ID est donné en argument plutôt qu'à la police du contexte.

A partir de là, imaginons que vous voulez sauvegarder l'image résultante; aucun problème! Tapez: 'save_image out.png', et l'avant-plan sera sauvé dans un fichier "out.png" dans le répertoire courant5. Maintenant, juste pour le plaisir, quittons adesklets, et lançons une autre session interactive pour voir si nous pouvons recharger cette image. Tapez juste la commande "quit" ou pressez ^D (Control D: fin de fichier), votre nouvelle session devrait ressembler à ceci :

     0 >>> window_resize 100 100
     command 0 ok: window_resize 100 100
     1 >>> window_reset managed
     command 1 ok: window_reset managed
     2 >>> window_set_transparency 1
     command 2 ok: window_set_transparency 1
     3 >>> load_image out.png
     command 3 ok: new image 2
     4 >>> blend_image_onto_image 2 1 0 0 100 100 0 0 100 100
     command 4 ok: blend_image_onto_image 2 1 0 0 100 100 0 0 100 100
     5 >>> window_show
     command 5 ok: window_show
     6 >>> free_image 2
     command 6 ok: free_image 2

Bien sur, nous voulions visualiser le résultat - c'est pourquoi nous avons utilisé les commandes 'window_reset', 'window_set_transparency' et 'window_show': elles n'ont pas d'autre utilité. La commande 'blend_image_onto_image' vient elle aussi tout droit d'Imlib2; elle prend la nouvelle image 2 que l'on vient de créer avec la troisième commande, et la met dans l'image du contexte (image 0 - avant-plan par défaut), à partir de ses coordonnées (0,0) et d'une taille de 100x100 pixels, et la met dans un rectangle partant de (0,0) et de taille 100x100 pixels dans l'image 0. Il est finalement bon de noter que, avec adesklets, il y a toujours une image dans le contexte d'Imlib2: dès que vous essayez de libérer l'image courante, le contexte d'image est réinitialisé à l'image d'avant-plan.

Voila, c'est presque la fin de cette introduction... A partir de là vous devriez pouvoir jouer un peu avec adesklets. Si vous êtes déjà familier avec Imlib2 vous vous y ferez très rapidement. Sinon vous devriez garder la documentation d'Imlib2 sous la main. :-)

Quelques dernières astuces:

Voici un exemple:

     #!/usr/bin/env adesklets -f
     
     # Rendre la fenêtre 'gérée'
     #
     window_reset managed
     
     # Redimmensioner la fenêtre à 100x100 pixels
     #
     window_resize 100 100
     
     # Montrer la fenêtre, puis la geler pendant 10 secondes avant de sortir
     #
     window_show
     pause 10

Vous aurez juste besoin de rendre ce script exécutable et de le lancer. Comme d'habitude, les résultats seront affichées sur la sortie standard.

5.2 Véritable programmation avec adesklets: écriture d'un script de desklet

Lorsque vous serez prêt pour un vrai travail7, vous voudrez surement utiliser un véritable langage de script plutôt que l'interpréteur rudimentaire d'adesklets que nous avons utilisé dans l'introduction; ne serait-ce que pour pouvoir utiliser des variables.

Au moment où j'écris ces lignes (31 mars 2006), le support pour Python et Perl 5 est inclus dans le paquetage d'adesklets8.

5.2.1 Quelques astuces pour les programmeurs

Une fonction très utile de l'interpréteur d'adesklets est sa capacité à produire un trace complète d'une de ses sessions (commandes, évènements et messages spéciaux), que ce soit sur stderr ou dans un fichier journal, et ce indépendemment de ce qui le contrôle. Pour se servir de cette fonctionalité, il faut:

  1. Configurer le paquetage au niveau des sources en activant la trappe --enable-debug de configure.
  2. Exporter la variable ADESKLETS_LOG dans l'environnement si la sortie de la session doit être enregistrée dans un fichier plutôt qu'affichée sur stderr9. Ce doit être un chemin absolu qui servira de préfixe à l'interpréteur qui, l'ayant hérité de l'environnement, s'en servira pour créer les noms complets des fichiers dans lesquels il écrira.
A partir de là, il est possible de lire des journaux de session complets et chronologiques; utilisés correctement, ils peuvent être utiles pour débugger. Vous pouvez vous en servir conjointement avec la commande echo pour placer des marqueurs faciles à trouver10.

5.2.2 adesklets et Python

Un exemple d'utilisation de Python

Avec Python, les choses ne sont pas très différentes de l'interpréteur. Les commandes ont été encapsulées dans des fonctions Python, et une classe publique Events_handler a été construite pour manipuler les retours asynchrones de l'interpréteur. Jetons un coup d'oeil sur le script test/test.py de l'archive source de la version 0.6.1:

"""
test_fr.py - S.Fourmanoit <syfou@users.sourceforge.net>,
             Guillaume Boitel <g.boitel@wanadoo.fr>

Petit script de test non-exhaustif pour adesklets :

      - Redimensionne la fenêtre adesklets à 100x100 pixels
      - La met sous le contrôle du gestionnaire de fenêtre
      - La rend pseudo-transparente
      - L'affiche à l'écran
      - Attends ensuite que l'utilisateur la quitte,
        génère une alarme toutes les 10 secondes
        et attrape les événements motion_notify dès qu'ils apparaissent.

Pour l'essayer :
      - Installer adesklets avec le support python activé (par défaut)
      - Lancer python test.py à partir de ce répertoire.
"""
import adesklets

class My_Events(adesklets.Events_handler):
    def __init__(self):
        adesklets.Events_handler.__init__(self)
        
    def __del__(self):
        adesklets.Events_handler.__del__(self)
        
    def ready(self):
        adesklets.window_resize(100,100)
        adesklets.window_reset(adesklets.WINDOW_MANAGED)
        adesklets.window_set_transparency(True)
        adesklets.window_show()

    def quit(self):
        print 'Quitting...'
        
    def alarm(self):
        print 'Alarm. Next in 10 seconds.'
        return 10
    
    def motion_notify(self, delayed, x, y):
        print 'Motion notify:', x, y, delayed

My_Events().pause()
Voilà! 26 lignes de code Python, et nous avons un desklet parfaitement fonctionnel. Je pense qu'il s'explique de lui-même; jetez un coup d'oeil à l'aide intégrée de python à propos de adesklets, adesklets.functions et adesklets.Events_handler pour de bonnes et complètes explications (See Documentation du paquetage Python.) . Tout ce que nous avons fait ici c'est:
  1. Importer le paquetage adesklets: cela instancie automatiquement un processus fils adesklets et configure toutes les communications. Si l'initialisation du paquetage termine sans provoquer d'exception, cela signifie que l'interpréteur tourne et qu'il attend des commandes.
  2. Faire une sous-classe d'adesklets.Events_handler et redéfinir les méthodes invoquées pour les événements qui nous intéressent (tous les autres événements ne sont pas simplement ignorés, ils ne sont pas générés).
  3. Instancier un objet de cette classe, et le mettre en boucle infinie 11.
Attention, piège: quelques détails sur les ID d'objets

Tous les objets d'adesklets (polices, images, palettes de couleurs, polygones, etc), sont stockés dans des piles (une pile par type d'objet). Les ID de Python ne sont rien d'autre que la position initale d'objets donnés dans la pile correspondante, et deviendront donc invalides si un objet du même type situé en-dessous est libéré.

Par exemple, partant d'un état vierge, il n'y a d'autre dans la pile des images que l'avant-plan (ID O) et l'arrière-plan (ID 1) de la fenêtre. Lorsque quelqu'un créé deux autres images depuis Python comme ceci:

     im_1 = adesklets.create_image(10,10)
     im_2 = adesklets.create_image(10,10)

on a im_1==2, et im_2==3. Dès qu'on fera:

     adesklets.free_image(im_1)

La pile commence à s'écraser, et ce qui était l'image 3 (im_2) est maintenant l'image 2, et im_2 est une réference invalide. Cela signifie clairement qu'invoquer:

     adesklets.free_image(im2)

génerera le message d'erreur image out of range.

Un sujet spécial: les animations

adesklets inclut maintenant aussi bien un mode d'exécution indirect que les variables textuelles (pour être précis, le remplacement textuel non-récursif). Cela signifie que les auteurs de desklets ont ce qu'il faut pour créer des animations précisément temporisées. Vous trouverez un exemple simple à suivre dans test/fading.py. Une utilisation plus poussée de ces fonctionalités est faite dans le desklet yab. Pour réaliser un animation en Python, il faut:

  1. Enregistrer une séquence de commandes (que nous appelerons une commande macro, ou macro). Avec Python, il faut passer en mode indirect avec la fonction adesklets.start_recording(), émettre un nombre indéterminé de commandes adesklets, puis repasser en mode normal (execution directe) avec adesklets.stop_recording(). Bien sur, aucune commande n'est évaluée quand l'interpréteur est en mode indirect; les commandes sont seulement conservées dans l'historique pour être rejouées plus tard.
  2. Choisir si la séquence de commandes à rejouer peut être interrompue par des évènements ou pas, en utilisant la fonction adesklets.play_set_abort_on_events(). La méthode de haut niveau adesklets.Events_handler::set_events() peut aussi être utilisée pour choisir dynamiquement quels évènements seront rattrapés. Pour un exemple, voyez test/test_events.py.
  3. Initialiser les variables utilisées dans la macro enregistrée avec la fonction adesklets.set(), qui est très similaire à son homonyme du shell Bourne.
  4. Lire la macro avec la fonction adesklets.play().

Quelques remarques supplémentaires:

Configuration et internationalisation

Mentionnons jute ici que depuis adesklets 0.4.0, le module adesklets.utils inclut maintenant une classe optionelle ConfigFile qui peut être facilement réutilisée par les auteurs de desklets pour ajouter un système de configuration facilement extensible à leur code12 (See Documentation du paquetage Python.) . Par défaut, la classe prend aussi en charge l'internationalisation automatiquement, en sélectionnant les jeux de caractères selon les besoins de l'utilisateur. L'utilisation des jeux peut bien sûr être déterminée ou changée à tout moment en utilisant les fonctions adesklets.get_charset() et adesklets.set_charset(). La classe adesklets.utils.ConfigFile a aussi un attribut charset qu'on peut examiner pour connaitre les préferences de l'utilisateur. En utilisant cette classe on notera que le jeu de caractères 'ASCII' est considéré comme choix par défaut, et qu'il ne désactivera pas une éventuelle conversion. Ainsi, les utilisateurs de plate-formes ne supportant pas iconv pourront toujours utiliser adesklets sans le support de l'internationalisation.

Derniers mots

Enfin, signalons que vous pourriez avoir envie de jeter un oeil au code source du desklet weather disponible sur le site web du projet SourceForge pour voir un plus gros morceau de code Python utilisant adesklets. Il y a aussi quelques autres desklets de test sous le répertoire test/ qui pourraient vous donner des idées. Vous pourriez aussi jeter un oeil au paquetage d'aide autogéneré d'adesklets par pydoc, inclus dans ce document (See Documentation du paquetage Python.). Dès que vous êtes suffisement content de votre desklet, n'hésitez pas à le partager avec le reste d'entre nous (ce qui serait très apprécié). Faites-en un paquetage (See GNU Makefile pour empaqueter les desklets.), puis soumettez-le (See Soumettre un desklet.).

5.2.3 adesklets et Perl

Depuis adesklets 0.6.0, adesklets peut par défaut être commandé par Perl, grâce au travail de Lucas Brutschy . Bien sur, les liens avec Perl sont complètement perpendiculaires à Python, et n'ont besoin que de l'environnement Perl 5 en plus de l'interpréteur de base d'adesklets, écrit en C.

Exemple d'utilisation de Perl

Comme avec le paquetage Python, les commandes sont encapsulées dans des routines Perl. Une boucle de gestion des évènements peut alors être lancée, enregistrant et appelant automatiquement les routines données (BackgroundGrab, ButtonRelease, LeaveNotify, MotionNotify, ButtonPress, EnterNotify, MenuFire) selon le cas approprié. adesklets::event_loop ne termine que lors de la fin de l'interpréteur adesklets commandé. Voici le script test/test.pl du paquetage source de la version 0.6.1:

# test.pl - Lucas Brutschy <lbrutschy@users.sourceforge.net>, 2006
#
# Small, non-exhaustive adesklets test script:
#        - Resize adesklets window to 100x100 pixels
#        - Set it to be pseudo-transparent
#        - Draw a translucent light grey rectangle on it
#        - Map it on screen
#        - Mark with a red dot the pointer position every time 
#          a user click
#
# To try it:
#        - Install adesklets with perl support enabled (default)
#        - Run perl test.py from this directory

use strict;
use adesklets;

adesklets::open_streams();

# these are just normal adesklet commands
adesklets::window_resize(100,100);
adesklets::window_reset(adesklets::WINDOW_UNMANAGED);
adesklets::window_set_transparency(1);
adesklets::context_set_color(255,255,255,64);
adesklets::image_fill_rectangle(5,5,90,90);
adesklets::window_show();

adesklets::event_loop(ButtonPress=>\&onbutton); # supply a hash of callbacks
adesklets::close_streams();

sub onbutton
{
    my($x,$y) = @_;
    adesklets::context_set_color(255,0,0,255);
    adesklets::image_fill_rectangle($x,$y,3,3);    
}
Simplement vingt lignes de code, et comme précedemment, nous avons déjà un desklet fonctionnel. Il doit être assez simple à comprendre, mais voici quelques indications génerales sur ce qu'il fait:
  1. D'abord, il importe de module adesklets de Perl (use adesklets), et instancie l'interpréteur d'adesklets en appelant adesklets::open_stream(). Lorsque la sous-routine termine, la ligne de commande est déjà traitée et l'interpréteur est prêt à accepter des commandes.
  2. A partir de là, nous avons simplement initialisé une fenêtre non-gerée de 100 pixels sur 100, que nous avons remplie avec un fond transluicide blanc, et l'avons affichée.
  3. Enfin, nous allumons une boucle des évènements, enregistrant une sous-routine de retour qui agit quand l'utilisateur clique dessus (ici, l'action consiste à dessiner un carré rouge de 3 pixels de coté sous le pointeur). Cette boucle des évènements tourne tant que l'interpréteur d'adesklets n'est pas tué (que ce soit manuellement ou à cause de la fin d'une session X11). Quand elle termine, la sous-routine de nettoyage adesklets::close_streams() est appelée.
Rattraper les erreurs

Lorsqu'une commande adesklets retourne une erreur, le script Perl meurt. Vous pouvez récuperer le message d'erreur d'origine comme ceci:

     eval { my $image = adesklets::load_image("aaarrhg"); };
     if($@) { print $@; }

See Quelques astuces pour les programmeurs, pour une alternative.

Configuration

Vous pouvez très bien conserver de multiples instances du même desklet en Perl configurées differemment; il s'agit simplement d'utiliser la valeur de retour de adesklets::get_id() pour identifier de manière unique l'instance en cours d'exécution; la manière dont vous utilisez cet unique identifiant entier pour sélectionner les paramètres de configuration dépends entièrement de vous.

Derniers mots

Vous désirez certainement jeter un oeil aux sections précedentes concernant les ID d'objets (See Attention.) et les animations (See Un sujet spécial: les animations.), qui sont toujours valables pour Perl.

Vous pouvez aussi voir la vieille documentation (plain old documentation – POD) du paquetage Perl, incluse dans ce document (See Documentation du paquetage Perl.). Comme signalé precedemment, dès que vous êtes suffisemment satisfait de votre desklet, n'hésitez pas à le partage avec le reste d'entre nous (ce qui serait très apprecié) – faites-en un paquetage (See GNU Makefile pour empaqueter les desklets.), puis soumettez-le (See Soumettre un desklet.).


Footnotes

[1] "Mappé" dans le jargon d'X Window

[2] Une fenêtre "non-gérée" est très utile pour les desklets: c'est comme si c'était une partie de la fenêtre racine puisqu'elle peut être faite pour ne jamais être en avant-plan et pour rester affichée (mapped) lorsque l'on change d'espace de travail.

[3] Enfin... Pas tout à fait : il y a un buffer qui intervient pour la performance, mais l'opération est logiquement équivalente à cette description.

[4] Gardez votre documentation d'Imlib2 à proximité: c'est très utile! See Documentation d'Imlib2.

[5] Pour que ça marche, il faut que votre installation d'Imlib2 soit correctement configurée pour utiliser libpng, sinon vous risquez de recevoir l'erreur "no loader for file format".

[6] Pour que ça marche, GNU history doit être installé lors de la compilation d'adesklets.

[7] :-)

[8] adesklets a été écrit pour être facilement utilisé depuis toutes sortes de langages interprétés; n'hésitez pas à proposer votre aide si vous voulez que votre langage favori soit supporté!

[9] L'afficher sur stderr risque fortement de perturber les liens avec les langages externes, il est donc préferable de ne le faire que lors d'une invocation de l'interpréteur directement sur la console.

[10] Certains systèmes de gestion des paquetages, comme les ebuilds de Portage avec le drapeau USE debug, utilisent cette fonctionalité automatiquement.

[11] Cette dernière étape n'est pas obligatoire; le script peut très bien continuer, mais devra alors avoir été écrit pour supporter les interruptions des signaux. Lisez `help(adesklets.Events_handler)' pour de plus amples informations.

[12] Pour en voir un exemple voyez n'importe quel desklet non issu de contributions.