Extension d'adesklets - adesklets 0.6.1
Description: adesklets 0.6.1

Next: Recherche d'aide!, Previous: Programmation d'adesklets, Up: Top

## 6 Utiliser adesklets à partir d'autres languages

adeskets a été fait pour être facilement utilisable depuis toutes sortes d'environnements de langages1. Ce chapitre explique aux développeurs comment l'interpréteur d'adesklets s'intègre dans le système d'une manière indépendante du langage.

Si vous n'êtes pas un minimum habitués aux systèmes POSIX ou que vous n'avez pas les notions de base sur la programmation de systèmes d'exploitations, ce chapitre n'est surement pas d'un grand interêt pour vous.

Veillez noter que le paquetage Python du répertoire `scripting/pyhton/adesklets` est un bon complément de ce que vous allez lire.

### 6.1 Fonctionalités requises

Si vous voulez utiliser l'interpréteur d'adesklets depuis votre langage favori mais qu'il n'est pas supporté à la base2, vous pouvez toujours ajouter ce support, pourvu que votre langage comporte une de ces fonctionalités:

- Lire et écrire dans des tuyaux (pipes); ou
- Lire et écrire depuis ou dans des fichiers sans mémoire tampon ('unbuffered files')

Il doit aussi pouvoir3:

- Rattraper des signaux IPC; ou
- Lire des fichiers en mode non-bloquant; ou
- Utiliser poll/select sur des fichiers

Enfin, il doit aussi être capable de lancer un processus fils.

Ces pré-requis sont bien minces du point de vue d'un système POSIX, mais précisons par souci d'exhaustivité que si votre environnement de langage ne remplit pas ces critères, vous pouvez quand même vous en servir pourvu que vous arriviez à faire en sorte que votre application communique de manière fiable avec un autre programme 4 qui, lui, les remplirait. Bien sur, ce n'est pas la situation idéale et cela doit être évité autant que possible.

### 6.2 Mécanisme d'initialisation

Comme vous le savez probablement (See Utiliser adesklets.), `adesklets` possède deux modes de fonctionnement, si l'on peut les appeler ainsi:

1. en tant que lanceur de desklets, si vous appelez `adesklets` sans arguments.
2. en tant qu'interpréteur, dans tous les autres cas (utilisation en ligne de commande intéractive ou en processus fils d'un desklet).

#### 6.2.1 adeskelts appelé comme un lanceur de desklets - redémarrer tous les desklets

Dans une nouvelle session X, la séquence de démarrage classique est à peu près la suivante:

1. `adesklets` est appelé sans arguments à partir d'un script d'initialisation de la session X.
2. A partir de là, la nouvelle instance d'adesklets se comporte comme un lanceur de desklets. Elle parcoure le fichier `$HOME/.adesklets`, puis se copie (fork) autant de fois qu'il y a de desklets à démarrer. Ensuite le processus lanceur termine puisqu'il n'y a pas besoin de démon ici.
3. Tous les processus copiés initialisent la variable d'environnement `ADESKLETS_ID` contenant leur propre ID entier en tant que chaine de caractères, puis exécutent5 le script associé.
4. Chaque desklet se lance une instance d'`adesklets` en tant que processus fils, donnant en passant la variable `ADESKLETS_ID` sans l'alterer. Il est du devoir du desklet de s'assurer que:
   - Les flux standards de l'interpréteur d'adesklets, `stdin`, `stdout` et `stderr`, soient redirigés séparément, de sorte que le processus parent puisse facilement écrire dans le `stdin` d'adesklets, et lire `stdout` et `stderr`. L'utilisation de tuyaux, FIFOs ou éventuellement de fichiers réguliers sont possibles.
   - les écritures vers `stdin` et les lectures depuis les deux autres flux se font toutes sans tampon.
   - le premier argument de la commande `adesklets` est le nom absolu du script du desklet.
5. Chaque interpréteur `adesklets` nouvellement créé s'initialise en utilisant la variable d'environnement `ADESKLETS_ID` et le nom absolu donné en premier argument de ligne de commande pour déterminer ses caracteristiques non-scriptables6 à partir de `$HOME/.adesklets`. Une fois cette opération terminée, l'évènement `ready!` est géneré (See Evènements.), l'interpréteur est alors prêt à traiter des commandes.

#### 6.2.2 adesklets appelé en tant qu'interpréteur - enregistrement d'un nouveau desklet

Quand un nouveau desklet est appelé directement, la séquence de démarrage décrite dans la section précedente est la même, sauf que les trois premières étapes n'ont tout simplement pas lieu. Cela signifie que l'environnement `ADESKLETS_ID` n'est pas défini; cela permet au nouvel interpréteur d'`adesklets` de détecter qu'il s'agit d'un nouveau desklet, il va parcourir le fichier de configuration `$HOME/.adesklets` et allouer au desklet le premier ID disponible.

Il faut noter que si le nom de fichier du desklet donné comme premier argument à l'instance fille d'`adesklets` est relatif ou si'l n'est pas lisible et exécutable par l'utilisateur courant, le desklet ne sera pas enregistré dans `$HOME/.adesklets`, et par conséquent ne sera pas automatiquement redémarré par le lanceur dans les futures nouvelles sessions X. Dans le cas où `stdin` est un terminal7, l'enregistrement ne se fera pas non plus.

#### 6.2.3 Conséquences

Ainsi, du point de vue du script de desklet, les deux cas sont identiques, ce qui ne nécessite aucun traitement conditionel. Tout le travail d'initialisation du desklet se fait dans la quatrième étape de la sous-partie ci-dessus.

Si besoin, le desklet pourra utiliser la commande `get_id` pour déterminer son ID.

### 6.3 Utilisation des flux

Comme expliqué précedemment, un desklet communique avec son propre interpréteur adesklets via des flux sans tampons redirigés. Voyons ici quelles informations ils transportent.

#### 6.3.1 stdin: les commandes

Sur le `stdin` de l'interpréteur, le desklet envoie les commande qu'il veut exécuter dans l'ordre dans lequel il veut les exécuter, une commande par ligne8. Etant donné que la lecture de ce flux est suspendue quand une commande est évaluée, un desklet devra géneralement attendre qu'une commande soit exécutée avant d'en envoyer une autre 9 (voir la section suivante).

Le format des commandes est assez simple: on a le nom d'une commande suivi d'arguments séparés par des espaces, tous représentés par du texte pur, y compris les nombres. adesklets ne se soucie pas du nombre d'espaces entre les arguments, pourvu qu'il y en ait au moins un. Dans le cas particulier des commandes où le dernier argument est une chaine de caractères (`text_draw`, par exemple), adesklets considèrera les premiers caractères n'étant pas des espaces comme le début de la chaine; le reste de la ligne, espaces inclus, sera considéré comme faisant partie de la chaine.

Deux fichiers, automatiquement tenus à jour avec la source (`scripting/prototypes` et `scripting/enums`) à partir de la distribution d'origine, fournissent au format texte brut (avec une tabulation comme séparateur) une description de toutes les commandes et constantes mises à disposition des desklets10. Un bon portage pour un langage particulier devrait être fourni avec quelques routines permettant de génerer automatiquement les parties pertinentes de sa propre bibliothèque à partir de ces fichiers; par exemple, le portage de Python comporte une fonction `protoize` dans le script `setup.py` de `distutils` pour le faire. Idéalement, cette routine devrait être écrite avec ce langage et un ensemble raisonnable de librairies11; si'l n'est pas bien adapté à cette tâche, vous devrez vous limitez à:

- Des scripts portables pour le shell Bourne, conforme aux standards POSIX 1003.2 et 1003.2a
- Un sed (Streaming Editor) récent, compatible avec GNU sed 12
- Une utilisation portable de n'importe quel bc pour l'arithmetique

si vous voulez que votre travail soit intégré au paquetage officiel.

#### 6.3.2 stdout: les résultats des commandes

L'interpréteur d'`adesklets` cherche fréquemment si de nouveaux caractères sont arrivés sur son flux `stdin`, et le lit si besoin. Dès qu'il reçoit le caractère de fin de ligne ('\n'), il arrête de lire et essaye d'exécuter la commande. Une fois la commande terminée (elle aura peut-être envoyé quelques lignes sur `stdout`), il envoie en tant que dernière entrée un message d'état de la forme suivante:

         command RANG ok: MESSAGE

si la commande s'est terminée avec succès, ou:

         command RANG error: MESSAGE_DERREUR

`RANG` est l'indentifiant numérique de la commande (un entier non signé) qui part de zéro lorsque l'interpréteur est créé, et qui est automatiquement incrémenté à chaque ligne qu'il reçoit ensuite. Il est garanti que toutes les commandes seront exécutées dans l'ordre dans lequel elles ont étées données. `MESSAGE_DERREUR` est un message compréhensible, sans format particulier, expliquant d'où vient l'erreur. `MESSAGE` est également compréhensible, mais sera formaté de sorte qu'il peut être utilisé pour récupérer les valeurs de retour utiles algorithmiquement de n'importe quelles commandes. Ces valeurs de retour peuvent être:

1. un entier–l'ID de la valeur de retour (`create_image` et autres)
2. un couple d'entiers–le résultat numérique de la commande (pour `context_get_color` et consorts)
3. une liste de chaines–la liste ordonnée des chaines de caractères retournées par les commandes retournant plusieurs lignes sur `stdout` (`images_info` et ses semblables), la dernière ligne étant un message d'état
4. une chaine de description–une réponse textuelle en une ligne de la commande
5. rien du tout

Algorithmiquement, votre 'routine de récupération des valeurs de retour' devrait d'abord essayer de récupérer les entiers de `MESSAGE`13. Si elle trouve un entier ou plus, elle devra vérifier que le message n'est pas une simple copie d'une commande donnée; alors la valeur de retour correspond au cinquième cas (cf ci-dessus). Autrement, si elle ne trouve qu'un entier, on est dans le premier cas; et si'l y en a plus d'un, dans le deuxième. Si aucun entier n'est trouvé et si'l y avait une liste de lignes envoyées à `stdout` avant le message d'état de la commande, on est dans le troisième cas. Pour le reste: si le message d'état diffère d'une commande donnée, cela correspond au quatrième cas. Sinon il faut considérer que la sortie correspond au cinquième cas. Cet algorithme de récupération fonctionne avec toutes les commandes listées dans `scripting/prototypes`.

#### 6.3.3 stderr: les évènements

Tous les autres rapports asynchrones d'évènements sont envoyés au flux `stderr` de l'interpréteur. Ils sont asynchrones par rapport au traitement des commandes; ils ne se produiront jamais pendant le traitement d'une commande (dans l'intervalle de temps entre la soumission de la commande complète sur `stdin` et la sortie de son message d'état sur `stdout`), ils peuvent être envoyés à n'importe quel autre moment 14.

Les rapports d'évènements tiennent sur une ligne, de la forme 15:

         event: MESSAGE_DEVENEMENT

Sous la forme de Backus Naur16, la grammaire employée pour MESSAGE_DEVENEMENT serait:

    MESSAGE_DEVENEMENT :: ready! |
                          backgroundgrab |
                          menufire MENU_ID MENU_STR |
                          motionnotify X Y |
                          enternotify X Y |
                          leavenotify X Y |
                          buttonpress X Y BUTTON_ID |
                          buttonrelease X Y BUTTON_ID
    ;;

où `MENU_ID`, `X`, `Y`, et `BUTTON_ID` sont tous des entiers supérieurs ou égaux à zéro, et `MENU_STR` est une chaine de caractères, pouvant contenir des espaces. Détaillons un peu plus tout cela:

- Evènement Ready: envoyé une fois seulement, dès que l'interpréteur d'`adesklets` est prêt à traiter des commandes.
- Evènement BackgroundGrab: envoyé à chaque fois que l'image d'arrière-plan (image 1) est regénerée.
- Evènement MenuFire: envoyé à chaque fois qu'un utilisateur sélectionne un élément d'un menu qui n'est pas géré en interne par adesklets.
- Evènement MotionNotify: envoyé dès que le pointeur bouge dans la fenêtre d'un desklet.
- EnterNotify, LeaveNotify: envoyé dès que le curseur passe sur une bordure visible de la fenêtre de l'interpréteur, qu'il aille dedans ou dehors.
- ButtonPress, ButtonRelease: envoyé quand un bouton de la souris est cliqué dans la fenêtre de l'interpréteur.

Tous les évènements sauf 'Ready!' (nominativement: MotionNotify, EnterNotify, LeaveNotify, ButtonPress, ButtonRelease, BackgroundGrab et MenuFile) peuvent êtres masqués par le desklet. En fait, `adesklets` fournit au desklet des commandes permettant de gérer spécifiquement la création et la production des évènements, qui sont: `event_catch`, `events_get_send_sigusr1`, `events_reset_all`, `event_uncatch`, `events_info`, `events_set_echo`, `events_get_echo`, `events_purge` et `events_set_send_sigusr1`. Il faut noter que ces fonctions ne sont pas listées dans `scripting/prototypes` puisque vous ne voulez probablement pas que vos utilisateurs y aient accès. Vous devrez probablement démarrer une session interactive et jouer avec pour être sur que vous les avez bien comprises. Il est aussi temps de mentionner le script `src/adesklets_debug.sh`, qui se rend utile pour les expériences intéractives avec les évènements.

Il faut encore mentionner deux choses à propos des évènements:

1. Par défaut, les évènements ne sont pas affichés, mais stockés en interne, en attendant d'être purgés avec `event_purge`. Vous pouvez modifier cela avec `events_set_echo`.
2. Vous pouvez paramètrer un interpréteur adesklets pour qu'il envoie le signal SIGUSR1 à chaque fois qu'un évènement est envoyé à son processus père (le desklet). Cette communication entre processus est très utile puisque le signal peut être ratrappé, ce qui permet à la routine de gestion des évènements de ne fonctionner que lorsque c'est nécessaire. C'est ce qui est utilisé dans le paquetage Pyhton dans la classe `Events_handler`.

### 6.4 Récuperation du signal SIGCHLD

Pourvu que votre langage le permette, vous devriez installer une sorte de récuperateur de SIGCHILD, ainsi vous pourrez au moins être averti si jamais l'interpréteur d'adesklets se termine sans raison (sans parler du cas des processus fils à l'état zombie)17.

### 6.5 Variables textuelles

`adesklets` supporte aussi l'exécution differée (mode indirect) et le remplacement textuel des variables. Voici comment fonctionnent les variables en mode indirect:

1. Une fois qu'une commande est entrée, elle est stockée telle quelle dans la liste de l'historique des commandes si on est bien dans le mode non-interactif de l'interpréteur et si la commande réside entre les appels à `start_recording` et `stop_recording`)
2. Quand une macro est éxecutée (avec la commande `play`, See Programmation d'adesklets.), les variables sont substituées en une seule passe, et aucune réference indirecte n'est possible. La substitution est assez directe. On recherche dans la ligne de commande stockée toute séquence non-vide de caractères distincts de l'espace commançant par un seul '$'. Chaque séquence trouvée est remplacée par la valeur `$variable` correspondante, ou supprimée si aucune variable n'est trouvée.
3. La commande completée est exécutée.

Le même mécanisme de substitution s'applique en mode d'exécution direct. Tout ceci implique que, avec le paquetage Python par exemple, ces deux morceaux de code sont équivalents:

         adesklets.window_resize(100,100)

et:

         adesklets.set('dim',100)
         adesklets.window_resize('$dim','$dim')

Python étant un langage typé dynamiquement, aucune modification de notre code n'est nécessaire, mais d'autres langages pourraient demander plus de réflexion.

### 6.6 Derniers mots

Maintenant vous devriez savoir quasiment tout ce qu'il faut pour intégrer adesklets à votre environnement de langage. Cette tache peut avoir l'air intimidante, mais un traducteur très propre peut être fait par un seul développeur en seulement quelques jours. Encore une fois, jetez un oeil à `src/adesklets_debug.sh`; c'est un traducteur (simplet) du Bourne Shell pour adesklets qui tient dans seulement cinquante lignes, commentaires compris.

Si jamais vous avez besoin d'aide, vous êtes encouragés à écrire (en anglais) à la mailing list adesklets-devel ...

Bonne programmation!

#### Footnotes

[1] On parle d'environnement de langage parceque le problème n'est pas la syntaxe ou le paradigme utilisé (qu'il soit impératif, fonctionnel ou n'importe quoi d'autre), mais la manière dont vous pouvez gérer des opérations de base de POSIX sur les fichiers, les signaux, etc. (See Fonctionalités requises.). Ainsi un programmeur BASH devrait utiliser adesklets facilement, alors qu'un programmeur Linux JDK devrait probablement recontrer plus de difficultés (sans vouloir critiquer).

[2] Pour adesklets 0.6.1, Python et Perl sont tous deux supportés à la base.

[3] Ceci dit, disposer du support d'IPC et de poll/select rendra les choses à la fois plus simples et plus claires. Il est aussi très utile de pouvoir bloquer des signaux.

[4] Qui serait en fait une interface.

[5] Avec un appel de la famille execve*; voir `man 2 execve`.

[6] Qui sont, nominativement, son écran et ses coordonnées.

[7] Ce qui signifie que la session est interactive

[8] Le caractère '\n' doit être placé après chaque commande.

[9] Ceci permet d'éviter de bloquer le processus en surchargeant le flux avec des commandes arbitrairement longues; bien sur, envoyer trois commandes de quelques centaines d'octets d'un coup n'est pas un problème!

[10] Pour plus de détails sur le format de ces deux fichiers, cf `scripting/protoize.sh.in` et `scripting/enums.sh`.

[11] L'idée étant que la plupart des utilisateurs de votre portage doivent être plus ou moins capables de s'en servir avec l'installation de base du langage.

[12] De nos jours éxecuter GNU sed n'est pas un problème sur pratiquement n'importe quel système POSIX.

[13] Tous les paramètres de `MESSAGE` sont séparés par un esapce.

[14] De toute façon aucun évènement n'est jamais perdu; son apparition est simplement retardée.

[15] Toutes les autres informations sur `stderr` peuvent être ignorées sans risque.

[16] http://fr.wikipedia.org/wiki/Forme_de_Backus_Naur

[17] `man 2 signal` est une très bonne réference sur les signaux.