Construisons des interfaces, pas des fonctionnalités

et les fonctionnalités émergeront d'elles-même

Le besoin

Cela fait plusieurs années que j’utilise vim slides pour faire mes présentations et feh pour visionner des images.

J’aime beaucoup vim slides. La capacité d’éditer le contenu en direct et d’exécuter du code en fait l’outil idoine pour présenter des choses à propos de systèmes Unix. Mais vim slides a une limite évidente. Puisque c’est vim portant un costume de powerpoint textuel, il n’est pas possible d’intégrer des images. Qu’à cela ne tienne, vim slides permet d’exécuter automatiquement une commande arbitraire, présente dans le texte de la présentation mais masquée, au passage d’une slide. Il suffit donc d’ouvrir feh, ou tout autre visionneur d’image et hop on a une présentation visuelle.

Cette technique m’a suffit pour tout un tas de conférence mais pour ma dernière je voulais quelque chose d’autre. J’avais envie de pouvoir afficher des images au vidéo projecteur et d’avoir, en simultané sur l’écran de mon portable, le script correspondant à cette image. Au passage d’une slide dans vim slides il faudrait que feh passe à l’image suivante. Le tout reste synchronisé. Dit autrement, l’idée est de s’approcher la fonctionnalité de vue de présentateurice présente dans la plupart des logiciels de présentation, avec une vision sur la slide en cours et les notes associées à côté.

Le diaporama

Rien de plus simple que de faire un diaporama avec feh. Il faut lui donner la liste des images en argument. Si l’on veut que le tout s’affiche en plein écran on y ajoute l’argument qui va bien :

feh --full-screen img1.jpg img2.jpg img3.jpg

Si l’on a beaucoup d’images ça va être embêtant de maintenir la liste comme ça. On peut à la place écrire la liste des images dans un fichier texte :

img1.jpg
img2.png
img3.webp
...

Et lancer le diaporama avec :

cat liste-images.txt | xargs feh --full-screen

Il est possible de passer d’une image à l’autre avec les flèches. Reste un problème, si feh est sur une autre fenêtre que celle des notes il faudra alt-tab à chaque passage de slide pour passer “manuellement” à l’image suivante. Heureusement, on peut lire dans le manuel :

SIGNALS

In slideshow and multiwindow mode, feh handles the following signals:

SIGUSR1       Slideshow  mode:  switch  to next image; reload current image if       the slideshow consists of a single file.  Multiwindow mode:       reload all images.

SIGUSR2       Slideshow mode:  switch  to  previous  image;  reload  current       image  if  the slideshow consists of a single file.  Multiwindow       mode: reload all images.

En logiciel connaissant et honorant les traditions unixiennes, feh est pilotable avec des signaux. Dans le manuel sur les signaux, (man signal.7) on trouve :

Standard signals

 SIGUSR1      P1990      Term    User-defined signal 1  SIGUSR2      P1990      Term    User-defined signal 2

Ces signaux, standardisés depuis 1990, ne sont pas réservés pour un usage figé et commun à tous les processus, comme SIGKILL par exemple. Il revient à chaque logiciel de les utiliser ou non si besoin. En l’occurrence feh les utilise pour passer à l’image suivante ou précédente. Ainsi, après avoir lancé feh on peut écrire :

kill -s SIGUSR1 pid

avec pid étant l’identifiant du processus de feh. Il existe pleins de façons différentes de l’obtenir, plus ou moins robustes, plus ou moins portables. Sans trop y avoir réfléchi j’utilise pidof :

kill -s SIGUSR1 $(pidof feh)

Si tout fonctionne bien on voit feh passer à l’image suivante comme par magie. Superbe.

Vim slides comme pilote

On peut piloter feh à l’aide d’une commande. Plus tôt j’écrivais que vim slides permettait de lancer, au passage d’une slide donnée une commande arbitraire donnée. Pourtant ce n’est pas cette mécanique que l’on va utiliser. En effet, il est possible que l’on veuille revenir en arrière dans les slides. La synchronisation doit être possible dans les deux sens. Écrire une slide comme ceci :

commande> Titre de la slide

blabla
blabla

Ne satisfait donc pas notre besoin puisque lorsque l’on va de l’avant commande doit envoyer SIGUSR1 et SIGUSR2 quand on va en arrière. On va donc à la place modifier, au moins temporairement, le plugin.

Le plugin est assez simple. Le gros se passe dans le fichier ftplugin/slides.vim. On y trouve notamment

nnoremap <buffer> <PageUp> zkzt
nnoremap <buffer> <PageDown> zjzt

Ces lignes permettent le passage d’une slide à une autre. zkzt et zjzt sont les raccourcis claviers permettant de passer au “fold” précédent ou suivant, les folds étant ici nos slides. Il nous suffit alors d’y ajouter nos commandes1 :

nnoremap <buffer> <PageUp> zkzt:!kill -s SIGUSR2 $(pidof feh)<cr><cr>
nnoremap <buffer> <PageDown> zjzt:!kill -s SIGUSR1 $(pidof feh)<cr><cr>

Superbe ! Nos notes et le diaporama sont maintenant synchronisées.

Pourquoi c’est cool

Vim slides étant, évidemment, basé sur vim, il profite de son excellente intégration avec le reste du système. Cette intégration fait dire à certain·e que Vim est un éditeur de texte créé avec l’idée que le système Unix dans son ensemble est un IDE, par opposition à d’autres éditeurs qui deviennent des IDE en étendant un maximum de fonctionnalités en leur sein2. Lorsque l’on utilise vim slides on est toujours à quelques touches du clavier prêt d’interagir avec l’interface universelle des systèmes Unix : la ligne de commande. De son côté feh se rend accessible depuis la ligne de commande via les signaux.

De ces deux idées très simples, celles de pouvoir se rendre au point de rendez-vous standard d’Unix et celle de s’ouvrir à ce qui en vient, émerge une nouvelle fonctionnalité à moindre coût. Il aura suffit de modifier deux lignes dans vim slides. feh intègre les signaux en seulement une dizaine de lignes de C3. Dans signals.c :

(sigaddset(&feh_ss, SIGUSR1) == -1) ||
(sigaddset(&feh_ss, SIGUSR2) == -1) ||
[...]
(sigaction(SIGUSR1, &feh_sh, NULL) == -1) ||
(sigaction(SIGUSR2, &feh_sh, NULL) == -1) ||

Et dans main.c :

[...]
if (filelist_len > 1) {
    if (signo == SIGUSR1)
        slideshow_change_image(winwid, SLIDE_NEXT, 1);
    else if (signo == SIGUSR2)
        slideshow_change_image(winwid, SLIDE_PREV, 1);
} else {
    feh_reload_image(winwid, 0, 0);
}
[...]

Pas de code qui soit spécifique aux notions de slides, de notes, de synchronisation entre les deux. Les deux logiciels ne se connaissent pas, ne se comprennent pas mais peuvent coopérer. Aucun des deux ne doit grossir pour faire advenir notre nouvelle fonctionnalité.

J’intuite que cette façon de développer, consistant à créer des interfaces et laisser aux utilisateurices le soin de satisfaire leurs besoins en combinant les logiciels plutôt qu’en étendant leurs fonctionnalités intrinsèques est bon pour la durabilité. Alors que la réutilisation de code, longtemps un objectif à atteindre pour faciliter et fiabiliser l’ingénieurie logicielle, pose aujourd’hui de gros problèmes de robustesse et de complexité4, la combinaison de deux logiciels via un couplage très faible permis par l’OS semble être une méthode de réutilisation de code qui, bien que n’étant pas équivalente, est moins sensible aux chocs que la première.

Je n’invente rien, cet article est simplement un bon exemple de ce que les créateurices d’Unix envisageaient comme étant de l’ingénieurie capable de contrôler la complexité des logiciels. Rob Pike et Brian Kernighan écrivaient dans The Unix Programming Environment :

Even though the UNIX system introduces a number of innovative programs and techniques, no single program or idea makes it work well. Instead, what makes it effective is an approach to programming, a philosophy of using the computer. Although that philosophy can’t be written down in single sentence, at its heart is the idea that the power of a system comes more from the relationships among programs than from the programs themselves. Many UNIX programs do quite trivial tasks in isolation, but, combined with other programs, become general and useful tools.

Cet argument me semble très proche de l’idée populaire qu’il est préférable de spécifier et d’implémenter des protocoles plutôt que de construire des monolithes logiciels enfermés sur eux même. On pourrait dire qu’ici le “protocole” est l’ouverture sur la ligne de commande.

Alors la prochaine fois que vous avez besoin d’une nouvelle fonctionnalité dans un logiciel, plutôt que de la développer5 demandez-vous s’il ne serait pas préférable de créer une interface générique pour l’utiliser en combinaison avec un autre logiciel.


  1. On peut forcément faire plus malin, une jolie fonction, exécuter la commande que si l’on trouve effectivement un processus feh etc, mais ça fait le taf. 

  2. exemple : emacs 

  3. Astuce : si vous êtes sur un système utilisant apt vous pouvez récupérer le code source compilé pour créer le paquet avec apt source paquet. Plutôt que d’aller chercher sur internet les sources puis de fouiller pour trouver la version packagée par debian etc, j’ai fait apt source feh

  4. Cox, Russ. « Surviving Software Dependencies ». Communications of the ACM 62, nᵒ 9 (2019): 36‑43. https://doi.org/10.1145/3347446

  5. ou de demander à la faire développer