Nautilus Terminal : L'histoire d'un projet compliqué

Nautilus Terminal est l'un de mes plus anciens projets, l'un des plus anciens rendu public en tout cas. En me repenchant dessus suite à des issues sur Github, je me suis rendu compte que je n'avais encore jamais pris le temps de réellement en parler : quelles motivations m'avaient poussé à commettre ce projet, comment ce truc a réussit à fonctionner, quelles ont été les difficultés rencontrées durant toutes ces années de maintenances, etc. Voici donc un article « retour d'expérience » sur ce projet.

Pour commencer, c'est quoi « Nautilus Terminal », ça sert à quoi ?

Avant de parler de Nautilus Terminal, on va commencer par parler du projet GNOME, qui est l'un des premiers environnements graphique que j'ai utilisé sous Linux et que j'utilise encore aujourd'hui. Un environnement graphique est un ensemble de programmes permettant d'afficher / gérer des fenêtres, lancer des applications, etc (on parle de shell, comme dans les terminaux), souvent accompagné d'un ensemble cohérent d'application graphique permettant de travailler au quotidien.

Nautilus, aussi appelé Files ou Fichiers, est l'explorateur de fichier de l'environnement GNOME. Il permet de naviguer dans l'arborescence du disque, d'ouvrir des fichiers, de les copier, déplacer, supprimer,... je vous fais pas un dessin vous avez compris. :)

Utilisant Nautilus tous les jours, j'avais de plus en plus souvent envie de juste taper rapidement une commande dans le dossier courant, parce que parfois certaines actions sont plus rapides ou plus pratiques à faire avec une ligne de commande... Alors il y avait bien une extension rajoutant une entrée « Ouvrir un terminal ici » dans le menu contextuel, mais c'était deux clics et une fenêtre de trop pour moi, sans compter que ce terminal ne suivait pas mes déplacements dans les dossiers.

Bref, c'est de ce manque qu'est née l'idée d'intégrer un terminal dans Nautilus. C'est ainsi qu'a commencé le projet Nautilus Terminal vers la fin de l'année 2010 (à défaut d'être original, le nom est plutôt explicite).

Nautilus Terminal est donc un terminal, intégré directement dans l'explorateur de fichier Nautilus. Sa principale fonctionnalité est de suivre automatiquement la navigation : si vous changez de dossier, il exécute automatiquement la commande « cd » dans le terminal pour qu'il navigue lui aussi jusqu'au même dossier.

Si à ce stade vous vous dites « c'est chouette, il faut que j'installe ça tout de suite ! » (vous changerez peut-être d'avis en lisant la suite de l'article), les indications se trouvent dans le dépôt du projet sur Github :

Au début, ça avait l'air simple

Intégrer un terminal dans Nautilus, ça avait l'air simple : il y avait un système d'extension, avec un point d'entrée (nommé NautilusLocationWidgetProvider) qui permettait justement d'insérer n'importe quel widget GTK (la bibliothèque d'interface graphique utilisée par le projet GNOME) juste à l'endroit où je le voulais. Ce point d'entrée sert normalement à afficher un bandeau au-dessus des fichiers afin de proposer des actions contextuelles dans certains dossiers spéciaux (vider la corbeille, importer des photos,...) :

Capture d'écran de l'utilisation de l'API NautilusLocationWidgetProvider dans la corbeille

Il y avait en plus le projet Nautilus Python qui permettait d'accéder à ces API en Python, mon langage préféré, c'était nickel... enfin ça c'est ce que je croyais.

La branche 0.x : la fougue de la jeunesse

Les premières versions de Nautilus Terminal (branche 0.x) exploitaient simplement cette API et insérait un terminal (et quelques boutons) dans l'emplacement prévu. Mais très vite un problème s'est fait remarquer : je n'avais aucune idée de la durée de vie de mes widgets, ni même aucun contrôle dessus : au changement de dossier, Nautilus les dégageait de son interface et je n'étais même jamais prévenu de leur destruction...

La solution était donc de ne pas exécuter de commande « cd » pour changer le chemin du shell, mais de spawner un nouveau shell à chaque changement de dossier, ouvert directement sur le dossier courant. Ce fonctionnement a causé pas mal de problèmes. Le pire d'entre eux était que les processus des shells (bash, zsh,...) restaient ouverts en arrière plan : à chaque fois qu'on naviguait, un nouveau processus inutile restait en mémoire... pas très cool tout ça...

Pour essayer de corriger le problème, j'ai commencé à killer les shells quand un nouveau était lancé, mais ceci a également entraîné son propre lot de problèmes : Nautilus supporte les onglets, et possédait même une vue splittée à l'époque : il était donc normal d'avoir plusieurs shells actifs en même temps dans ces configurations-là, et étant incapable de savoir lequel terminer, il a fallu mettre en place un certain nombre de bidouilles pour limiter le massacre. Par exemple en ne conservant que deux shells actifs au maximum (à cause de la vue splittée) et en basculant les shells d'un terminal à l'autre au gré de la navigation et des changements d'onglets (en exécutant à chaque fois la commande « cd » pour remettre le shell dans le bon dossier)...

Le plus « drôle » étant qu'il n'y avait évidemment rien dans l'API de Nautilus pour obtenir les informations sur les changements d'onglets ou autre... Il fallait donc essayer de deviner en fonction des événements reçu par nos différents widget. Par exemple un évènement « draw » reçu par un terminal qui n'avait plus de shell attaché signifiait que l'utilisateur avait rebasculé sur un ancien onglet et qu'il fallait par conséquent aller piquer l'un des shell disponibles et le réattacher à ce terminal.

D'ailleurs, être obligé d'exécuter une commande dans le terminal pour changer de dossier, ça aussi ça pose des problèmes : si un processus est en cours d'exécution dans le terminal, eh bien c'est ce processus qui reçoit la commande. Si par exemple l'éditeur de texte vim est ouvert dans le terminal, ça écrit littéralement « cd /home/foo/bar/baz » dedans au lieu de changer le dossier courant du shell... Il a donc fallu trouver un moyen de détecter qu'un processus était en cours d'exécution dans le shell, et ne pas envoyer de commande lorsque c'était le cas.

Dit comme ça, on pourrait penser que ce logiciel n'a jamais fonctionné, mais malgré le nombre impressionnant de hacks sur lesquels reposait Nautilus Terminal (je ne vous les ai pas tous racontés, sinon on pourrait y passer la journée...) il fonctionnait plutôt bien : on était arrivés à un genre de point d'équilibre.

Les versions de cette branche étaient bien plus complètes que celles qui ont suivi, elles disposaient de nombreuses options accessibles via une interface de configuration et ont eu bien plus de succès que je n'aurais pu l'imaginer, de nombreux blogs (et pas seulement des blogs francophones) en avaient parlés à l'époque (il suffit de chercher sur les internets pour s'en rendre compte), tant et si bien que Nautilus Terminal s'est retrouvé traduit en 35 langues !

En fouillant dans de veilles sauvegardes, j'ai retrouvé des captures d'écran d'époque de la toute première version de Nautilus Terminal (0.1) :

Capture d'écran de Nautilus Terminal v0.1
Capture d'écran de la fenêtre de préférences de Nautilus Terminal 0.1 (1er onglet)
Capture d'écran de la fenêtre de préférences de Nautilus Terminal 0.1 (2ème onglet)
Capture d'écran de la fenêtre de préférences de Nautilus Terminal 0.1 (3ème onglet)
Capture d'écran de la fenêtre de préférences de Nautilus Terminal 0.1 (4ème onglet)

La branche 1.x : GNOME 3 débarque

La branche 0.x dont le développement avait débuté fin 2010, ciblait l'environnement GNOME 2 et le toolkit graphique GTK 2. Mais en avril 2011, KABOOM ! (tagada tsouin tsouin ?) GNOME 3 sortait en fanfare : plein de nouveautés trop cool (j'étais grave hypé à l'époque), la plus visible étant certainement GNOME Shell : une toute nouvelle interface pour l'environnement de bureau, mais aussi une toute nouvelle version du toolkit graphique, GTK 3. Ceci est une révolution... faut tout redévelopper !

Clarifions un peu les choses : une application écrite en GTK 2 fonctionnait parfaitement dans GNOME 3, et c'est toujours le cas. Le problème de Nautilus Terminal, c'est que ce n'est pas vraiment une application, mais un plugin dans une autre application. Nautilus étant évidement passé à GTK 3, Nautilus Terminal était obligé de se convertir également.

Comme je l'ai dit dans la section précédente le fonctionnement de Nautilus Terminal reposait sur un subtil équilibre de hacks extrêmement dépendants de Nautilus et de GTK lui-même. Les changements entre GTK 2 et GTK 3 était suffisamment importants pour qu'il ne soit pas question de simplement modifier deux ou trois trucs en espérant que tout allait fonctionner. Le plus simple était de tout réécrire depuis zéro.

À ce moment-là, plus motivé que jamais face à ce nouveau défi, j'ai commencé à développer la nouvelle version de Nautilus Terminal (depuis zéro donc), en essayant de corriger ses problèmes de jeunesse au passage.

Cette version vu naître de nouveaux hacks, plus évolués. Il n'était en effet plus question de juste s'intégrer sagement dans l'emplacement prévu par l'API NautilusLocationWidgetProvider, où l'on avait de contrôle sur rien... On allait maintenant s'infiltrer bien plus profondément dans l'interface, en procédant un peu à la manière d'un cheval de Troie :

  • On entre toujours par le point d'entré NautilusLocationWidgetProvider de l'API d'extension de Nautilus, mais au lieu de lui donner directement notre terminal à afficher, on lui donne un autre widget, invisible pour l'utilisateur, que j'ai nommé Crowbar, soit pied de biche en français.
  • Une fois que l'API de Nautilus a inséré ce widget dans son interface (ce que l'on sait via un événement émit par GTK), on remonte de widget parent en widget parent jusqu'à arriver à la racine de la vue dossier (plus ou moins la racine de l'onglet).
  • Une fois là, CRACK ! On arrache toute l'interface... au pied de biche ! Je suis sûr que les développeurs de Nautilus ne l'avaient pas vu venir celle-là ! :)
  • Puis, maintenant qu'on a fait place nette, on insère un widget GTK nommé GtkPaned : il s'agit d'un conteneur séparant la vue en deux parties avec une poignée de redimensionnement au milieu.
  • Dans l'une des parties on place notre terminal, dans l'autre, on recolle tant bien que mal l'interface de Nautilus que l'on avait arrachée (promis, aucun widget n'a été blessé durant l'opération, enfin pas trop).

Tout ça peut sembler un peu violent, mais au final ça fonctionne plutôt bien, et Nautilus ne vient plus supprimer nos widgets de manière intempestive à chaque navigation : seul le widget Crowbar reste sous son contrôle... mais ça on s'en fiche, on a en plus besoin.

Mais comment cela se passe-t-il lorsque l'on navigue d'ailleurs ? On ne va pas recommencer toute l'opération à chaque fois, sinon ça serait un peu inutile, voici donc ce qu'il se passe :

  • Comme précédemment, le widget Crowbar est inséré,
  • comme précédemment, on remonte de parent en parent, et là, au lieu de tomber sur le widget racine de la vue dossier de Nautilus, on tombe sur notre GtkPaned... il n'y a plus qu'a redescendre dedans pour retrouver notre terminal et pouvoir lui demander de changer de dossier...

Elle est cool la vie... MAIS, par ce qu'il y a un gros MAIS, on est à présent bien plus sensible au moindre changement d'interface de Nautilus : un rien peut casser Nautilus Terminal (s'il ne retrouve plus les widgets qui lui servaient de repère, etc).

Autre problème majeur apparu avec Nautilus 3.x : les développeurs de Nautilus se sont mis à ajouter des raccourcis clavier mono-caractères comme "/" pour mettre le focus dans la barre de navigation par exemple... Le problème, c'est que lorsque le terminal a le focus, ces raccourcis sont toujours actifs et nous empêchent d'utiliser certains caractère ou combinaisons de touches importantes... Ces raccourcis rendaient l'utilisation de du terminal au mieux pénible au pire impossible.

Étant dans l'incapacité de trouver une solution à ce problème notamment (et à quelques autres, essentiellement dû à la jeunesse de GTK 3 et de ses nouveaux bindings Python), j'ai lâchement abandonné le développement du logiciel fin 2011, n'ayant plus la motivation nécessaire pour continuer.

Il y a bien eu trois commits en 2015, issues de correction proposées par des utilisateurs, mais rien de fou fou... J'ai toutefois été très surpris d'apprendre que des gens utilisaient encore ce plugin que je pensais totalement oublié.

Niveau fonctionnalité, on était loin de la branche 0.x : pas d'interface de configuration, à peine un fichier texte pour configurer deux ou trois trucs... Je vous mets une petite capture d'écran de Nautilus Terminal 1.0 pour la nostalgie :

Capture d'écran de Nautilus Terminal v1.0

La branche 3.x : Résurection

Début 2017, je ne sais plus ce qui m'a motivé, mais j'ai déterré le projet : peut-être n'avais-je pas envie de rester sur un échec, car oui, j'ai vécu la branche 1.x comme un échec : j'avais abandonné Nautilus Terminal (et ses utilisateurs) face à mon incapacité à arriver au résultat que je désirais...

Je me suis donc lancé dans l'écriture de Nautilus Terminal 3.0. Oui 3.0 et pas 2.0, il ne s'agit pas d'une erreur, mais d'une volonté d'indiquer qu'il s'agissait d'une version pour GNOME 3 (j'ai toutefois retrouvé un dossier "2.x/" daté d'avril 2016 dans mon dossier de développement mais celui-ci était vide... Probablement une tentative avortée que j'avais préféré effacer de ma mémoire). Quoi qu'il en soit, j'étais reparti une fois encore pour une réécriture complète.

Avant, pour développer Nautilus Terminal, il fallait que je désinstalle la version « stable » que j'utilisais au quotidien, que je copie la version en cours de développement dans le bon dossier pour qu'elle soit prise en compte, que je ferme complètement Nautilus (il tourne normalement en tache de fond, prêt à ouvrir des fenêtres à la moindre demande), puis que je le relance pour qu'il démarre avec mon extension. À chaque modification du code il fallait recopier le fichier au bon endroit et redémarrer Nautilus. Puis une fois le développement terminé, réinstaller la version stable...

Tout ceci étant bien ennuyeux, la première chose que j'ai fait a donc été de complètement séparé la partie plugin pour Nautilus du reste du programme. La partie plugin est vraiment minimal et peu sujette au changement : elle ne fait qu'aller chercher le reste du programme dans les modules Python installés, et c'est tout. Cela m'a permis de ne plus avoir à désinstaller la version que j'utilise au quotidien à chaque fois que je voulais développer sur le logiciel : j'ai juste à redémarrer Nautilus en ayant préalablement ajouté une variable dans l'environnement et il prend ma version de développement au lieu de celle installée sur le système.

Pour ce qui est de l'intégration dans l'interface de Nautilus, j'ai conservé la technique du pied de biche de la branche 1.x, en l'ayant toutefois amélioré pour la rendre moins sensible aux changements internes de Nautilus (si vous regardez le code source vous verrez qu'il y a quand même des embranchements pour prendre en compte certains changements effectués dans les différentes versions de Nautilus 3.x).

Pour ce qui est des problèmes causés par les raccourcis claviers de nautilus, c'est la première chose sur laquelle je me suis penché : ce n'était même pas la peine de continuer si je n'arrivais pas à résoudre ce problème. J'ai fini par réussir à me frayer un chemin jusqu'à l'élément qui contrôle ces raccourcis clavier dans Nautilus, et à partir de là c'est assez simple : lorsque le terminal prend le focus, je supprime tous les raccourcis claviers de Nautilus (en les ayant préalablement sauvegardés hein, je suis pas un sauvage non plus !), puis je les restaure lorsque le terminal perd le focus.

L'essentiel du travail de cette branche fut dirigé sur ce problème de raccourcis clavier, et sur la stabilité. En effet, s'incruster aussi profondément dans Nautilus n'est pas sans danger, et on a vite fait de provoquer des crashs du logiciel entier ! Il a donc fallu travailler à garder aussi peu de référence à des éléments internes de Nautilus que possible, à éviter que certaines références d'objets Python injectés dans Nautilus ne soient collectés par le garbage collector,... On a atteint des sommets niveau hack-de-précision dans cette version...

Niveau fonctionnalité, on est encore en dessous même de la branche 1.x, mais il y a quand mêmes quelques configurations. Cette fois-ci j'ai fait les choses bien (du point de vue de GNOME en tout cas), les configurations sont stockées dans GSettings et plus dans un fichier texte. Comme je n'ai toujours pas implémenté d'interface graphique de configuration, il faut passer par le logiciel dconf editor pour modifier les options :

Configuration de Nautilus Terminal v3 via dconf editor

Et voici une capture d'écran de Nautilus Terminal 3.x pour comparaison avec les versions précédentes (oui, il y a pas de grands changements avec la 1.x...) :

Capture d'écran de Nautilus Terminal v3

Quel avenir pour Nautilus Terminal ?

Ce projet a fait un sacré bout de chemin depuis le jour où l'avait fièrement annoncé sur les forums Ubuntu-FR. Aujourd'hui il fonctionne et est plutôt stable. J'envisage même de réintégrer quelques options de personnalisations (au moins les plus basiques comme la taille du texte et les couleurs du terminal), et peut être même de redévelopper une interface de configuration, soyons fous...

Mais son avenir est toujours incertain. Premièrement on est pas à l'abri que l'équipe de développement de Nautilus n'introduise des changements qui casse le fonctionnement de Nautilus Terminal (et suivant les changements ça sera plus ou moins difficile à réparer). Ensuite, le système d'extension de Nautilus tel qu'il existe aujourd'hui est voué à disparaitre (enfin on nous annonce ça depuis aussi longtemps que je développe ce logiciel, donc ça n'arrivera probablement pas demain). Et enfin GTK 4 commence à pointer le bout de son museau, et bien que le gap entre GTK 3 et GTK 4 semble bien moins grand que celui entre GTK 2 et GTK 3, je n'ai aucune idée des implications que cela aura pour Nautilus Terminal : une seule chose est sûr, c'est que le jour où Nautilus passe à GTK 4, Nautilus Terminal devra faire de même pour continuer d'exister !

Quand j'ai commencé cet article, je ne pensais pas que j'aurai autant de chose à dire sur ce projet, et j'avoue que ça m'a fait un peu bizarre de me replonger dans son histoire, une espèce de mélange entre de la nostalgie et des regrets de l'avoir abandonné pendant plusieurs années, le tout saupoudré de « pourquoi diable suis-je aller refourrer mes doigts là-dedans ? ».

Je referais peut-être des articles de ce style à l'avenir, si je ressens le besoin de parler des vieux projets qui hantent mes placards. Mais ne vous attendez pas à ce que cela arrive tout de suite ni trop souvent, sinon je serais obligé de renommer ce blog « Blog d'un développeur Open Source aigri » ou quelque chose dans le genre. ;)