Dans mon billet fondateur Darktable : dans le mur au ralenti, j’ai présenté le désastre qu’a été le nouveau « grand turducken MIDI ». Le but de ce turducken1 était de réécrire le système de raccourcis clavier pour l’étendre aux périphériques MIDI.

À ce jour, je suis encore furieux contre cette entreprise de destruction massive. Voici un récapitulatif des raisons :

  1. il a remplacé en 2021 un système de raccourcis clavier qui était plutôt bon, complet, bien testé, stable et codé en moins de 1 500 lignes (commentaires compris),
  2. …pour ajouter la prise en charge des périphériques MIDI et des manettes PlayStation (!?!)…
  3. …mais dans mon enquête Darktable de 2022, un an après cette nouvelle fonctionnalité, sur 1 251 utilisateurs participants :
    • 81 % des utilisateurs n’avaient pas de périphérique MIDI et n’en prévoyaient pas,
    • 2 % ne savaient même pas ce qu’était un périphérique MIDI,
    • 8 % des utilisateurs avaient un périphérique MIDI mais ne l’utilisaient pas avec Darktable,
    • 6 % envisageaient peut-être d’acheter un périphérique MIDI à l’avenir,
    • 2 % des utilisateurs avaient un périphérique MIDI qu’ils utilisaient réellement avec Darktable,
  4. le code était absolument terrible, en termes de :
    • qualité de code : des instructions if/switch-case illisibles imbriquées sur 4 niveaux, au milieu de fonctions de 1 000 lignes (j’ai publié des extraits d’exemple dans mon article),
    • volume de code :
      • 3 546 lignes de code pour Darktable 4.0,
      • 4 397 lignes de code pour Darktable 5.0,
      • l’augmentation du volume est une conséquence directe des tentatives de correction de bugs dans une architecture qu’on ne peut pas corriger parce que sa complexité produit davantage de complexité. Tout cela vient de la conception, mais résoudre des problèmes créés par la complexité en ajoutant encore plus de complexité n’est pas une solution.
    • complexité du code :
      • complexité cyclomatique  :
        • 1088 pour Darktable 4.0,
        • 1245 pour Darktable 5.0 (détails ),
      • complexité cognitive  :
        • 1885 pour Darktable 4.0,
        • 2098 pour Darktable 5.0 (détails ).
      • c’est de loin la fonctionnalité la plus complexe du logiciel, alors même qu’elle ne traite pas les images. À titre de comparaison, la deuxième fonctionnalité la plus complexe est le décodage des métadonnées EXIF, avec une complexité cognitive de 1348.
  5. il ne décode pas les modificateurs de touches par conception, mais ne traite que les frappes matérielles, ce qui signifie :
    • que l’entrée « 1 » du pavé numérique est décodée en Keypad End,
    • que l’entrée « 1 » d’un clavier AZERTY français est décodée en Shift+&, ou en Shift+" en BÉPO,
    • vous devez donc dupliquer tous vos raccourcis basés sur des chiffres pour chaque manière de saisir un nombre, et vous préparer à ce que la fenêtre de configuration des raccourcis ne contienne aucun chiffre réel dans les combinaisons de touches.
  6. la conception côté utilisateur est absolument affreuse, avec beaucoup trop d’actions et d’émulations à configurer (« effets »), qui ne sont même pas entièrement documentées 4 ans plus tard (qu’est-ce que « ctrl-toggle » ? « right-activate » ?), et la configuration des raccourcis utilise une étrange fenêtre scindée qui n’a aucun sens,
  7. l’implémentation est elle aussi terrible : la fonctionnalité connaît toute l’interface graphique du logiciel, et l’interface graphique du logiciel connaît le code des raccourcis. Il n’y a ici aucune modularité, et modifier quoi que ce soit dans le code des raccourcis peut avoir des effets inattendus et indésirables partout dans le logiciel.2 Il suffit de voir le graphe de dépendances ci-dessous,
  8. plusieurs « raccourcis » (ou assignations MIDI) peuvent être attachés à une même action, ce qui signifie que chaque interaction utilisateur doit parcourir toute la liste des actions disponibles, entraînant une gestion des raccourcis très inefficace, des lags dans l’interface dans certains cas et des faux positifs « combinaison de touches inconnue » dans des cas particuliers.
image
Touches numériques non décodées et étrange découpage de fenêtre entre « action » et « raccourci ».
image

Le graphe de dépendances de src/gui/accelerators.c (le grand turducken MIDI) avant la réécriture. Devinez pourquoi on appelle ça du « code spaghetti  »… Cela montre clairement qu’il existe une dépendance dans les deux sens entre le code des accélérateurs et le reste du code de l’interface. C’est un cauchemar à maintenir.

Autrement dit, un (très mauvais) programmeur a remplacé une fonctionnalité simple et opérationnelle par une monstruosité, avec l’approbation du mainteneur (qui n’écrirait jamais un code aussi mauvais lui-même, mais qui est déterminé à « ne pas casser l’élan » des contributions, quel qu’en soit le coût), pour faire plaisir à 2 % de la base d’utilisateurs. Tout ça pour une fonctionnalité secondaire (tertiaire ?).

J’appelle cela de mauvaises priorités. D’autant plus que cela a demandé plusieurs mois de travail acharné à un développeur et à de nombreux bêta-testeurs, uniquement pour empirer les choses, et que ce travail acharné est ensuite devenu une justification pour ne jamais revenir sur le changement, ce qu’on appelle le biais des coûts irrécupérables .

Je suis utilisateur, je me fiche du code

Se soucier du code des applications que vous utilisez, c’est comme se soucier de savoir si les canalisations qui amènent l’eau chez vous sont en plomb. Ce n’est pas votre métier, les tuyaux sont enterrés hors de vue, vous avez donc toutes les raisons de ne pas vous en préoccuper, et les gens en charge du réseau d’eau ont toutes les raisons de préférer que vous ne vous en préoccupiez pas ; pourtant, ils auront un effet sur votre santé, et ces effets resteront invisibles jusqu’à ce qu’il soit trop tard. Le logiciel n’a peut-être pas d’effet direct sur votre santé,3 mais la manière dont il est fabriqué aura un impact à long terme sur vous.

Je me retrouve dans une position inconfortable, celle de Cassandre  : devoir expliquer aux utilisateurs que des problèmes qu’ils ne connaissent pas, ne voient pas et dont ils ne se soucient pas affectent pourtant très concrètement l’utilisabilité et la stabilité quotidiennes des outils qu’ils utilisent, mais de manière peu visible. Dans tous les cas, ces problèmes affectent directement la probabilité qu’un mainteneur, un jour, résolve les bugs auxquels ils sont confrontés.

Apporter de mauvaises nouvelles fait de vous la mauvaise nouvelle, mais apporter de mauvaises nouvelles dont personne ne se soucie fait de vous un connard, même si ces mauvaises nouvelles expliquent les problèmes bizarres et aléatoires qui deviennent de plus en plus courants dans les trackers de bugs de Darktable depuis 2021, et qui restent non résolus pendant des années parce qu’à ce stade, le code ne contient plus des bugs, il est un bug. Mais ici, vous devez aussi faire face aux bulles de filtres  : selon l’endroit du web où vous regardez, vous trouverez soit des gens profondément heureux de Darktable (performances, stabilité, conception), soit des gens profondément mécontents. Mon observation empirique est que les plus satisfaits possèdent en moyenne des ordinateurs puissants et ont des diplômes STEM.4 Les gens mécontents ont tendance à partir et à réduire le temps qu’ils perdent avec le logiciel, ce qui signifie que, si vous ne les cherchez pas activement, vous ne ferez qu’alimenter votre biais du survivant . Et même si je ne vois pas l’intérêt d’essayer de convertir les utilisateurs à Darktable, ou à n’importe quel éditeur photo open source, parce que chacun fait comme il veut, filtrer et décourager les utilisateurs en fonction de leur culture informatique est un énorme échec pour toute application de retouche photo.

Comme j’ai une chaîne YouTube expliquant comment utiliser Darktable et que je donne des cours particuliers, j’ai tendance à recevoir des retours plus variés que les seuls trackers de tickets GitHub, où les critiques sur la conception sont de toute façon vite étouffées, et j’observe en direct, en visioconférence, ces bugs bizarres, aléatoires et impossibles à reproduire. Tout type privilégié a tendance à nier, minimiser ou ignorer les témoignages sur des problèmes auxquels il n’est pas lui-même confronté, voire à en faire porter la faute à la personne qui les signale. J’ai donc souvent l’impression qu’il existe deux univers parallèles qui ne communiquent pas mais se regardent de haut lorsqu’il s’agit des retours sur Darktable. Bien sûr, les développeurs de Darktable choisissent de regarder là où le soleil brille.

Et si Darktable donne l’apparence d’un projet actif auquel une douzaine de gars ajoutent régulièrement de nouvelles fonctionnalités, il est d’autant plus difficile d’expliquer que ce qui se passe réellement en coulisses, c’est une destruction de la base de code, parce que la qualité du code se dégrade fortement avec le temps, jusqu’au moment où il ne sera plus possible de déboguer quoi que ce soit. Le volume des changements ne dit rien de la qualité, de la fiabilité ni de la maintenabilité. Mais deux siècles de capitalisme nous ont conditionnés à rechercher des fonctionnalités clinquantes, quel qu’en soit le coût, et à cet égard Darktable répond aux attentes.

Le code naît cassé. Tout logiciel a des bugs. Et pire encore, nous utilisons beaucoup de bibliothèques tierces pour éviter de « réinventer la roue », mais ces bibliothèques feront évoluer leur API  à l’avenir, ce qui signifie qu’un code qui fonctionne aujourd’hui cessera de fonctionner demain et devra être retravaillé plus tard, quand les dépendances changeront. Le code est comme un jardin : chaque nouvelle saison apporte son lot de corvées. Cela s’appelle la maintenance. Une fois ce constat admis, la sagesse consiste d’abord à rendre la maintenance possible, puis à la rendre simple.

Un bon code est un code facile à maintenir. Autrement dit, un bon code est écrit de manière à être agréable pour les personnes qui devront le lire, le comprendre et le corriger. On n’écrit pas du code pour les ordinateurs. On n’écrit pas du code pour que des fonctionnalités « marchent juste » maintenant et pendant les prochains mois. Le code n’est pas quelque chose qu’on cache sous un joli capot en espérant ne plus jamais avoir à le regarder. Le code est un organisme vivant qui demande de l’énergie et du temps pour rester en vie.

La maintenance a un coût, souvent sous-estimé. Peu importe que votre produit soit le meilleur du marché : si vos coûts de maintenance sont prohibitifs, les clients informés l’éviteront. La maintenance est ennuyeuse, peu intéressante, pas sexy. C’est l’opposé de l’introduction de nouveautés brillantes : c’est le fait de s’assurer que les vieilles choses poussiéreuses continuent à fonctionner tranquillement. Dans les notes de version d’un logiciel, cela arrive à la fin. Les utilisateurs ne s’enthousiasment pas pour des fonctionnalités entretenues, mais ils se mettront en colère face à du code non maintenu et utiliseront souvent la mauvaise métrique pour évaluer l’entretien d’un projet, à savoir l’agitation et l’apparence de travail. Vous ne gagnerez donc rien à maintenir le code, mais vous perdrez à ne pas le faire, et cela vous coûtera que vous le fassiez ou non.

Le code devient impossible à maintenir lorsque chaque correction de bug provoque un nouveau bug ailleurs, façon whack-a-mole, et que la nature même des corrections consiste à ajouter des rustines contextuelles et des contournements qui ne font qu’accroître la complexité du logiciel. Quand vous en arrivez là, votre seule option est de réécrire le code incriminé à partir de zéro. Ce qui signifie que le travail passé n’a fait que créer davantage de travail aujourd’hui. Et quand ce travail passé remplaçait déjà un travail antérieur, on entre dans l’idiocratie.

Plus de code n’est ni un succès ni une réussite. La réussite consiste à résoudre les problèmes des utilisateurs. Ici encore, le logiciel nous frappe de plein fouet avec l’état d’esprit capitaliste : nous aimerions mesurer le travail au volume de code ajouté. Or plus de code, c’est seulement plus de responsabilité, plus de dette technique et plus de coûts de maintenance. C’est une dissonance cognitive, puisque le logiciel consiste précisément à écrire du code. Mais pensez-y ainsi : construire une voiture, ce n’est pas ajouter toujours plus d’acier sur quatre roues, parce qu’à un moment la voiture doit rouler. Il faut la bonne quantité d’acier, façonnée correctement aux bons endroits : il faut être efficace et économe. Le logiciel consiste à résoudre des problèmes d’utilisateurs avec des ordinateurs, pas à entreposer du code.

Dans les projets où les contributeurs ne sont pas payés, la maintenance ennuyeuse n’est pas ce à quoi les gens veulent consacrer leurs samedis, et c’est un vrai sujet de gestion de projet. Il est encore plus important de rendre la maintenance facile quand les gens travaillent gratuitement. Heureusement, Darktable n’a aucune gestion de projet, juste une ruche de contributeurs aléatoires qui grattent leur démangeaison à des moments aléatoires, sans objectif commun, sans stratégie, sans calendrier ni répartition du travail. C’est pourquoi je voudrais que tout le monde cesse d’utiliser l’expression « équipe Darktable ». Il n’y a pas d’équipe Darktable, parce qu’il n’y a ni répartition du travail, ni direction, ni calendrier, ni feuille de route, ni planification, ni objectif, ni priorités, ni vision, ni stratégie, ni méthode, ni conception, ni communication en amont des choses, ni documents de spécification des problèmes à résoudre. C’est le club informatique du lycée, composé d’individus déconnectés les uns des autres.

Il y a 3 choses qu’un développeur doit faire pour rendre la maintenance aussi facile que possible :

  • écrire le moins de code possible : traquer un bug parmi 300 lignes sera bien plus rapide que parmi 3 000 lignes,
  • écrire le code le plus simple possible : « simple » au sens où « la logique fonctionnelle comporte peu d’étapes, peu d’hypothèses, peu de cas limites ». Plus c’est simple, plus c’est facile à lire, comprendre et corriger, mais cela signifie aussi moins de cas à couvrir dans les tests. En pratique, cela veut dire éviter les cas contextuels, les options utilisateur, les variantes et les if/else dans le code.
  • écrire un code auto-contenu : répartir les fonctionnalités du logiciel entre des modules (sous-programmes) qui communiquent via le cœur et sont isolés les uns des autres. À mesure que le volume de code augmente, cela garantit que les changements à l’intérieur des modules n’auront pas d’effets imprévus à l’extérieur.

Enfin, il faut évaluer si les nouvelles fonctionnalités que vous ajoutez valent le coût de maintenance supplémentaire. Dans le cas du gestionnaire de raccourcis, nous répondons aux « besoins » (ou plutôt au luxe) de 2 % de la base d’utilisateurs avec une implémentation qui est au moins deux fois plus compliquée que celle qu’elle remplace. Méditez cela avec votre esprit capitaliste…5

La complexité du code peut se mesurer à l’aide de la théorie des graphes, via la complexité cyclomatique, la complexité cognitive ou la complexité N-path. De nombreux articles de recherche  corrèlent le volume de code et/ou la complexité du code au nombre de bugs cachés, ce qui est assez intuitif : la ligne de code que vous n’écrivez pas est la seule dans laquelle vous n’aurez jamais de bug. La simplicité est l’objectif. Et, comme je l’ai appris à mes dépens, la complexité de l’interface graphique (front-end) est souvent une conséquence directe de la complexité du back-end. Il y a plein de types qui hurlent à la lune pour invoquer la figure mystique du designer UI/UX, en espérant résoudre magiquement l’interface épouvantable de Darktable, compliquée et incohérente. L’interface graphique n’existe pas en parallèle du back-end, elle ne fait que relier les entrées attendues par le back-end à des contrôles graphiques. Simplifier le front-end ne peut pas se faire sans simplifier le back-end ; c’est juste un dogme stupide inventé par des gens qui n’ont que des soft skills. Mais simplifier les back-ends est bien plus compliqué que dessiner des maquettes.

En fin de compte, ce qui s’est passé avec le grand turducken MIDI a échoué sur les 3 tableaux : volume de code, complexité du code et caractère auto-contenu du code. Et les développeurs de Darktable n’apprendront jamais de leurs erreurs, comme le prouve Darktable 5.0. À mesure que les bugs des raccourcis clavier s’accumulaient dans Ansel, en plus de fonctionnalités déjà mauvaises par conception, j’ai essayé de les corriger en évitant de tout réécrire, jusqu’à ce qu’il devienne évident que je ne pourrais plus repousser cette réécriture bien longtemps.

Cela s’appelle de la dette technique . Tout le code du grand turducken MIDI était pensé pour fonctionner une fois écrit, pas pour être maintenable à long terme. C’est fondamentalement une preuve de concept qui n’aurait jamais dû arriver en production. Et la preuve même de son absence de maintenabilité est l’ampleur de la croissance du volume et de la complexité du code, correctif après correctif, entre Darktable 3.8 et 5.0, le rendant encore moins maintenable à mesure que les bugs sont corrigés. C’est exactement la définition du fait de se coller soi-même dans une toile d’araignée : plus on bouge, plus on reste coincé.

Mon erreur est peut-être d’avoir commencé à formuler mes critiques sur la prétendue gestion de Darktable en 2022, après avoir obtenu la preuve que cette meute désorganisée de contributeurs ne reconnaîtrait jamais ses erreurs et n’en tirerait jamais de leçon. D’autant que ce rythme de travail auto-infligé et intenable ressemblait davantage à une fabrique à burn-out qu’à un contexte propice à la réflexion. Depuis, Pascal Obry, l’actuel mainteneur autoproclamé de Darktable, essaie de convaincre tout le monde que je suis un coéquipier insupportable, incapable de travailler avec des personnes en désaccord avec moi, et qui se serait mis à insulter tout le monde. Bien sûr, aucune réponse spécifique et technique n’a été apportée aux problèmes que j’ai soulevés dans Darktable : dans le mur au ralenti, seulement de vagues déclarations rassurantes du genre « Darktable est un projet actif, en bonne santé parce qu’il a beaucoup de contributeurs ». Comme si davantage de singes désorganisés finissaient par faire un ingénieur si vous en ajoutiez suffisamment.

Je pense n’avoir été que gentil pendant 4 ans, bien trop gentil en réalité, jusqu’à ce que je réalise à quel point ces types avaient nui au projet. Ils n’apprennent pas, et n’apprendront jamais de leurs erreurs, parce qu’ils ne les reconnaîtront même jamais, et l’évolution de Darktable 5.0 ne fait que confirmer la tendance. Pourtant, voir s’empiler de plus en plus de bugs bizarres, aléatoires et impossibles à reproduire dans les trackers de bugs est un indice assez clair qu’il y a quelque chose de profondément détraqué. D’autant plus que l’enquête sur ces bugs m’a conduit à faire de l’archéologie dans d’horribles empilements de code qui n’avaient même pas 2 ans, et que, bien que mes connaissances et mon expérience de la base de code Darktable aient grandi au fil des ans, ma capacité à corriger la cause racine des bugs a diminué, tandis que je me retrouvais face à des labyrinthes de redirections de code  toujours plus déconcertants.6

Il est difficile d’expliquer tout cela à des gens qui voient les ordinateurs comme des boîtes magiques conçues par des sorciers de la tech. Il n’y a rien de magique là-dedans. C’est surtout des maths et des applications. Beaucoup de développeurs admettent être mauvais en maths, ce qui signifie qu’ils sont mauvais en programmation aussi. Parce que les maths ne consistent pas seulement à faire des opérations arithmétiques, c’est une discipline entière de l’esprit qui permet d’abstraire des problèmes complexes pour les décomposer en problèmes simples, menant à un code simple et à une interface simple. Les bons programmeurs passent beaucoup de temps à réfléchir à la manière d’écrire peu de code. Parce que le code est un passif, une dette technique, et qu’il coûte cher à maintenir. On paie donc la dette tout de suite, sans intérêts, en réfléchissant beaucoup au code avant de l’écrire, au lieu de coder d’abord puis de passer des années à éteindre les incendies.

Tout cela soulève aussi la question de savoir à qui appartiennent le code open source et les projets open source. Depuis que le fondateur du projet Darktable, Hanatos, a quitté le navire, de même que tous les développeurs de première génération, pour diverses raisons, le dernier survivant de cette première génération s’est autoproclamé nouveau mainteneur. C’est un développeur très compétent et très doué : son code est propre, soigné, et je n’ai jusque-là trouvé aucun bug sur une ligne pour laquelle git blame  indiquait « Pascal Obry ». Mais sa politique de gestion du code est désastreuse : il croit que toute contribution est une bonne contribution, que l’« élan » des contributions ne doit pas être freiné, et il est tout bonnement incapable de dire « non » aux contributions, ce qui fait que presque toutes les pull requests sont fusionnées. C’est la France de 1940 : tout entre, tout est accueilli avec un grand sourire et le Führer reçoit deux fois plus de Juifs que demandé. Pendant ce temps, les résistants sont appelés terroristes.

Mais il existe un énorme écart entre la qualité du code que Pascal écrit et la qualité du code qu’il accepte et fusionne. C’est un paradoxe inquiétant, enraciné quelque part entre la peur de manquer quelque chose  et le techno-positivisme radical , qui penche du côté du dogme et des croyances, avec un mépris total pour la conception et pour le point de vue des utilisateurs. Et peut-être aussi une confiance exagérée dans la capacité de la « Communauté » à corriger les bugs plus tard.

Il existe de nombreux cas où ne rien faire vaut mieux que faire les choses de travers, en particulier quand on remplace des fonctionnalités existantes. Darktable étant un éditeur non destructif, on y investit dès qu’on commence une base de données de modifications d’images. Sauf si vous prévoyez d’exporter toutes vos images dans des fichiers haute résolution à haute profondeur de bits dès que vous avez fini de les éditer, pour ne plus jamais y revenir. Cela crée une attente légitime de stabilité et de cohérence à long terme, afin que vos anciennes retouches puissent encore être rouvertes, reprises et exportées, peut-être dans de nouveaux formats, peut-être à de plus hautes résolutions. Changer la direction de la conception de l’application revient à une sorte de rupture de contrat, et même si la licence GNU/GPL annule toute responsabilité légale, elle n’efface pas les dommages causés aux utilisateurs. Et elle ne me rembourse pas non plus les années de ma vie perdues à réparer leur merde.

Donc, même en laissant de côté les problèmes de maintenance vus du point de vue des développeurs, il reste aussi une discussion à avoir sur qui décide de remplacer des fonctionnalités anciennes et éprouvées, en particulier ces fonctionnalités de base et universelles des applications de bureau comme la gestion des fichiers, import, export, navigation, ou l’interaction souris et clavier, qui sont si omniprésentes depuis si longtemps que les années 2020 arrivent avec 30 ans de retard pour prétendre les réinventer.

Beaucoup de travail et d’heures humaines ont été investis pour rendre les raccourcis clavier pires, au nom de politiques défaillantes et de dogmes nuisibles, en plus du vœu pieux et de la paresse sociale  où chacun espère que La Communauté®, autrement dit quelqu’un d’autre, prendra en charge la correction de ses erreurs. Les utilisateurs ont aussi dû perdre leur configuration clavier, tout réinitialiser et tout réapprendre. Pourtant, faire les choses correctement aurait en réalité coûté moins de travail et moins d’heures-homme. C’est une boucle autoalimentée de folie, qui crée un environnement de travail toxique où l’instabilité produit plus d’instabilité, où la complexité produit plus de complexité, encore et toujours sans la moindre feuille de route fonctionnelle qui donnerait une direction générale et de la visibilité à toutes les personnes impliquées.

Un bref historique de la mauvaise conception

Ce n’est qu’après avoir reconstruit la fonctionnalité de raccourcis à partir de zéro que j’ai compris ce qui n’allait pas dans les raccourcis et accélérateurs de Darktable.

Au départ, il y a cette débauche paralysante de fonctionnalités, qui donne envie de désencombrer l’interface graphique en cachant simplement des fonctions pour qu’elles ne soient pilotées qu’au clavier. Le problème, c’est que ces fonctions ne sont pas toujours de niche ni optionnelles, par exemple le raccourci qui contourne les interactions de masque lorsqu’on glisse-dépose l’aperçu principal en chambre noire, mais elles deviennent totalement impossibles à découvrir pour les utilisateurs. Ansel a résolu ce problème avec le menu global.

Certaines fonctionnalités étaient cachées derrière un support rudimentaire de vimkeys : si vous commenciez à saisir :, alors :q quittait l’application, et :set suivi du nom du curseur ou de la boîte de sélection changeait la valeur. Bien sûr, cela n’est documenté nulle part dans le manuel de Darktable et, en tant qu’utilisateur de Darktable depuis plus de dix ans, je n’en avais jamais entendu parler avant de supprimer son code, parce que cette petite blague écoute toutes vos frappes au clavier pour déterminer si elle doit agir sur votre saisie ou non.

Mais il y a aussi le fait que les modules utilisent des widgets Gtk faits maison, appelés « Bauhaus » dans src/bauhaus/bauhaus.c, qui n’implémentent pas tout ce qu’on attend normalement d’un widget d’interface capturant des événements utilisateur, et certainement pas les fonctions d’accessibilité.

L’une des fonctions d’accessibilité les plus élémentaires est la possibilité de faire défiler les widgets pouvant recevoir le focus. Dans le jargon des interfaces graphiques et de Gtk, un widget focalisable est un widget qui peut capter des événements clavier une fois focalisé. Les widgets reçoivent généralement le focus après un clic, mais Gtk gère aussi en interne une chaîne de focus  dans laquelle on navigue avec la touche Tab et les flèches. Le premier problème, c’est que la touche Tab, dans Darktable, était associée au mode « aperçu », qui active ou désactive tous les panneaux de la vue. En fait, avec les accélérateurs natifs de Gtk, cela n’aurait pas été possible du tout, puisque Tab est gérée par Gtk et interdite dans les raccourcis définis par l’utilisateur, mais comme Darktable avait implémenté son propre gestionnaire de raccourcis, bien avant le grand turducken MIDI, cela a été écrasé. Le défilement du focus basé sur la tabulation a donc été désactivé simplement parce que la touche Tab était assignée à autre chose. Mais le second problème, c’était que les widgets Bauhaus faits maison captaient aussi toutes les frappes des touches fléchées. Naviguer entre les contrôles de manière séquentielle, précédent ou suivant, était donc complètement impossible, par conception. De la même manière, les widgets Bauhaus captaient tous les événements de molette, empêchant le défilement des panneaux latéraux.

Comme la navigation séquentielle et incrémentale entre widgets focalisables était impossible par conception, toutes les interactions clavier devaient nécessairement être absolues : pour chaque curseur, pour chaque boîte de sélection, il fallait un raccourci qui augmente, diminue ou réinitialise directement la valeur.

Mais d’autres problèmes sont apparus quand les modules sont devenus multi-instanciables, parce que les contrôles étaient identifiés par un chemin d’accélérateur du type view/module/slider/increase ou view/module/slider/decrease, alors que toutes les instances du module héritaient du même chemin. Cela a créé un besoin de gérer tout cela à l’exécution, avec des préférences utilisateur permettant de décider si le module ciblé devait être le premier, le dernier ou celui avec lequel on avait interagi le plus récemment.

Généraliser tout cela au MIDI et aux manettes n’a fait qu’aggraver la situation, parce qu’en plus d’avoir un raccourci par action possible et par widget, il fallait ensuite gérer l’émulation d’interactions typiques du bureau depuis d’autres périphériques d’entrée. Mais au lieu de gérer cette couche d’émulation à haut niveau, à l’interface entre le MIDI et les raccourcis clavier ou souris ordinaires, un développeur catastrophique a complètement sur-ingéniéré une couche d’abstraction d’actions, incrustée dans les modules, dans les widgets Gtk natifs sous forme de surcouche, et dans les widgets Bauhaus faits maison en profondeur. Le problème, c’est que cette couche d’abstraction n’était pas vraiment une couche, mais plutôt une tumeur métastatique qui se répandait partout. La supprimer, ainsi que toutes ses dépendances , a conduit à retirer 7 674 lignes réparties sur 163 fichiers, alors qu’elle était censée être implémentée dans src/gui/accelerators.c, avec 4 412 lignes de code, commentaires et lignes blanches compris.

Reconcevoir les raccourcis clavier et accélérateurs à partir de zéro

L’étape zéro de cette refonte repose sur ces 2 exigences simples :

  • Chaque action doit être découvrable dans l’interface graphique. Les raccourcis clavier ne sont pas là pour désencombrer l’interface ; désencombrer l’interface, c’est découper le flux de travail en étapes unitaires et ne présenter que les contrôles pertinents pour l’étape en cours.
  • Le logiciel doit être totalement utilisable à la souris seule comme au clavier seul. L’interaction mixte doit rester entièrement optionnelle.

La première contrainte de cette refonte est donc de rendre les raccourcis absolus totalement optionnels, c’est-à-dire de concevoir le flux de travail clavier pour un accès séquentiel et relatif, en faisant défiler le contrôle précédent ou suivant.

Interaction avec les contrôles focalisés

Le paradigme en deux temps « focaliser » puis « interagir », que je n’ai pas inventé, est très puissant parce qu’il permet de limiter les frappes au bon contexte, ce qui signifie que les mêmes touches, en particulier les flèches Haut/Bas/Droite/Gauche, peuvent être associées plusieurs fois dans l’interface, puis traitées différemment selon le contexte qui a le focus. C’est plus flexible et en réalité plus simple7 que l’obsession de Darktable de tout relier à des raccourcis globaux et absolus, qui finissent par entrer en collision sur des touches souvent réutilisées et conduisent à devoir ajouter toujours plus de modificateurs comme rustine.

Jusqu’ici, naviguer entre les contrôles ne faisait que leur donner le focus. Qu’en est-il de l’interaction proprement dite ?

Dans la vue table lumineuse, une fois la grille de vignettes focalisée, la navigation entre les vignettes se fait avec les flèches habituelles, Page préc./suiv., etc., voir la documentation pour tous les détails, et la sélection des images peut elle aussi se faire de différentes manières au clavier, en lot, par série ou individuellement.

Dans la vue chambre noire, changer les valeurs des curseurs et des boîtes de sélection peut se faire avec les flèches, éventuellement avec Maj pour un pas grossier ou Ctrl pour un pas fin, le déclenchement de la pipette sur les curseurs qui la prennent en charge se fait avec Insert, etc. Là encore, il faut lire la documentation pour les détails.

Donc, encore une fois, à ce stade aucun raccourci défini par l’utilisateur, aucune combinaison cryptique à mémoriser, essentiellement des touches fléchées, et pourtant tout reste accessible.

Focalisation absolue des contrôles

Tout cela est très bien, mais cela vous laisse sur une sorte de parcours Ikea quand vous avez besoin d’accéder directement à une zone de l’application et que vous devez pourtant traverser toutes les sections depuis l’entrée.

Pour atténuer cela, une très vieille fonctionnalité de Darktable était l’onglet des « modules favoris », autrement dit un onglet spécial qui dupliquait l’interface des modules les plus utilisés selon le choix de l’utilisateur. C’est essentiellement résoudre de l’encombrement par plus d’encombrement, et pourtant les utilisateurs y sont devenus très attachés. Mais si l’on se concentre sur le but plutôt que sur le moyen, le besoin réel est un accès rapide à des modules arbitraires, ce qui est parfaitement compréhensible.

Cela a donc été réimplémenté comme un moyen de définir un raccourci absolu focalisant immédiatement un module de traitement d’image ou l’un de ses contrôles internes. Focaliser un module ou un contrôle caché le fera automatiquement apparaître dans l’interface. À partir de là, les interactions ultérieures se déroulent exactement comme auparavant, avec des combinaisons de touches uniformes. Cela réduit fortement le nombre de raccourcis à configurer, rend l’ensemble plus générique et simplifie beaucoup la fenêtre de réglage des raccourcis.

Les raccourcis absolus peuvent aussi cibler des entrées de menu, autrement dit des actions globales. Dans ce cas, ils sont rappelés dans le menu, à côté du libellé de l’action, ce qui est là encore une fonctionnalité Gtk native qui ne coûte aucune surcharge.

Remplacer les contrôleurs MIDI

Si l’on met de côté tout l’effet de mode des contrôleurs dédiés et la sensation de se prendre pour un pilote d’avion, la seule chose que les contrôleurs MIDI ont et que le clavier et la souris n’auront jamais, c’est la possibilité d’associer des potentiomètres, des molettes rotatives, directement aux curseurs de l’interface. Mais le prix à payer, c’est d’avoir un périphérique de plus qui prend de la place, et de la poussière, sur le bureau, sans parler du futur déchet électronique. Tous les photographes que je connais et qui ont acheté des MIDI ou des Loupedeck les ont rangés « temporairement » pour récupérer un peu d’espace sur leur bureau… et ne les ont jamais ressortis du placard.

Les contrôles de traitement d’image peuvent être reliés à des raccourcis à touche unique, parce qu’Ansel utilise une surcouche de raccourcis personnalisée au-dessus des accélérateurs Gtk natifs. J’ai modifié les widgets Bauhaus faits maison de sorte que, lorsqu’on active l’un des raccourcis de focalisation absolue ci-dessus et qu’on le maintient enfoncé, le défilement de la molette soit directement transmis au widget, même si le pointeur n’est pas placé au-dessus du bon curseur.

Ainsi, en combinant des raccourcis d’une seule lettre pour focaliser un contrôle avec le défilement de la souris ou du pavé tactile, on obtient tous les avantages des molettes MIDI sans périphérique supplémentaire, sans bibliothèque de prise en charge supplémentaire et sans les merveilles des couches d’émulation sur-ingéniérées.

Mais tout cela se produit toujours à partir d’un seul et unique raccourci absolu, de sorte qu’il n’est pas nécessaire de définir et mémoriser plusieurs raccourcis par contrôle pour finir à court de combinaisons de touches disponibles.

Actions sans raccourci, moteur de recherche et déclencheurs de type vimkeys

Mon gestionnaire de raccourcis est une fine enveloppe autour de l’API d’accélérateurs natifs de Gtk. Une action y est définie par un chemin textuel, par exemple Ansel/Darkroom/Modules/Exposure/Black level, qui constitue un identifiant unique, lisible à la fois par un ordinateur et par un humain. L’interface déclare une fonction attachée à chacun de ces chemins, contenant le code à exécuter pour appliquer l’action correspondante. Ensuite, le gestionnaire de raccourcis associe une combinaison de touches à ce chemin.

Ainsi, chaque fois qu’un utilisateur appuie sur des touches, le gestionnaire de raccourcis vérifie s’il connaît un chemin correspondant à cette combinaison et, s’il en trouve un, déclenche la fonction qui lui est attachée. C’est une conception à l’épreuve des imbéciles qui ne sait rien de l’intérieur des modules Ansel, des widgets faits maison, etc. Elle peut donc être étendue à de nombreuses parties du logiciel sans surcharge.

Mais c’est en réalité bien plus puissant que cela. En listant tous les chemins connus, on peut ensuite renvoyer ceux qui correspondent à une recherche textuelle, pour chercher un contrôle, un module ou une entrée de menu globale par son nom, c’est-à-dire retrouver toutes les actions dans une liste, puis les déclencher même si elles ne sont attachées à aucun raccourci.

image
Le moteur de recherche global d'actions, qui permet de retrouver et déclencher des actions qu'elles soient ou non associées à une combinaison de touches.

Comme les modules de traitement d’image peuvent eux aussi être retrouvés et affichés de cette manière, cela remplace également le moteur de recherche de modules, récupérant un peu d’espace vertical pour les longs modules et leurs options de masquage ou de fusion, tout en supprimant au passage environ 200 lignes de code. À partir de là, je l’ai étendu à toutes les boîtes à outils et rendu global, ce qui signifie que le moteur de recherche d’actions fonctionne dans toutes les vues mais n’affiche que les actions pertinentes pour la vue courante.

En chambre noire, cela prend aussi en charge les modules multi-instances, permettant de cibler directement une instance précise. Plus de détails à ce sujet dans la documentation.

Par défaut, la recherche globale d’actions est associée au raccourci Ctrl+P et peut être ouverte depuis le menu global Aide, ou via un bouton Rechercher des actions au centre de la barre d’en-tête. Cela remplace aussi en partie les vimkeys, qui ne prenaient en charge qu’une fraction des actions de l’interface et auraient nécessité de dupliquer entièrement les accélérateurs pour devenir réellement utiles. Ainsi, au lieu de taper : suivi d’une commande, vous pouvez taper Ctrl+P, puis soit saisir le chemin de l’action, soit lancer une requête et choisir dans la liste des correspondances avec les flèches puis Entrée.

Les correspondances sont triées par pertinence décroissante, de haut en bas, et la pertinence est calculée à partir de la position de la correspondance. L’hypothèse ici est que, les chemins d’actions allant du générique au spécifique de gauche à droite, vue/module/contrôle, les correspondances textuelles apparaissant à la fin du chemin sont censées pointer plutôt vers des contrôles que vers des modules ou des vues, que nous considérons comme plus spécifiques et donc plus pertinents. Cela peut éviter d’avoir à faire défiler tout le contenu des modules susceptible de correspondre à une recherche textuelle portant sur un contrôle.

Fenêtre d’édition des raccourcis

Comme il n’existe désormais qu’un seul raccourci configurable par l’utilisateur pour chaque contrôle, l’interface listant et modifiant les raccourcis prend la forme d’un simple arbre :

image

Il suffit de double-cliquer dans la colonne Touches pour commencer à enregistrer une nouvelle combinaison de touches. Comme c’est extrêmement simple, cette fenêtre remplace aussi d’un seul coup le pense-bête. Les raccourcis peuvent être recherchés par nom d’action ou par touches utilisées, et la recherche sur les touches dispose d’une auto-complétion pour les modificateurs, voir les détails dans la documentation. La fenêtre contextuelle des raccourcis peut être affichée depuis le menu global ÉditionRaccourcis clavier…

Documentation intégrée au logiciel

Le problème est le suivant : maintenir une documentation à jour sur les raccourcis par défaut est une corvée, parce que c’est trop granulaire. C’est typiquement le genre de document déjà obsolète au moment où l’on finit de l’écrire. Puisque les raccourcis par défaut doivent de toute façon être implémentés dans l’application, le meilleur endroit pour les documenter est directement à l’intérieur, de sorte que la mise à jour soit automatique.

Darktable avait deux interfaces redondantes pour les raccourcis : l’une pour les configurer, l’autre sous forme de fenêtre « aide-mémoire », qui pendant longtemps n’était accessible que… par un raccourci. Question découvrabilité, on a vu mieux. La raison en était que la fenêtre de réglages était beaucoup trop encombrée pour servir de rappel rapide. Ensuite, des raccourcis ont été ajoutés dans les infobulles de certains contrôles, des infobulles qui n’apparaissent que lorsqu’on survole les contrôles à la souris. En quoi est-il logique qu’il faille utiliser sa souris pour découvrir comment utiliser le clavier, contrôle par contrôle ?

Ansel affiche les raccourcis, c’est-à-dire les combinaisons de touches, dans la fenêtre de réglages, et les rappelle aussi dans la recherche globale d’actions. Mais ces raccourcis ne se limitent pas à ceux qui sont configurables par l’utilisateur : j’ai étendu le gestionnaire de raccourcis avec des « raccourcis virtuels », autrement dit des raccourcis qui ne déclenchent aucune action mais sont déclarés exactement comme les autres et apparaissent parmi eux comme des raccourcis « verrouillés ».

image

Ces raccourcis décrits comme interaction contextuelle sur le focus documentent les actions génériques ciblant le contrôle focalisé, que ce contrôle ait reçu le focus de manière relative ou absolue.

Détails d’implémentation

Le code source de tout le système de gestion des raccourcis, y compris les morceaux d’interface, recherche globale d’actions et fenêtre d’édition des raccourcis, utilise 1 144 lignes de code, dont la moitié pour l’interface, pour une complexité cyclomatique de 216 . C’est un cinquième du volume de code pour un sixième de la complexité par rapport à Darktable 5.0, alors même que cela fournit des fonctionnalités supplémentaires, comme la recherche de touches avec auto-complétion, la recherche globale, le ciblage explicite d’instances de modules, etc. Le support MIDI a été abandonné parce que, franchement, je ne vois pas quel problème il résout qui ne soit pas déjà résolu par la conception actuelle, bien plus simple.

Trouver l’action attachée à une frappe prend environ une douzaine de nanosecondes, là où le grand turducken MIDI mettait 10 à 50 millisecondes à chaque frappe.8

Le graphe de dépendances de cette fonctionnalité peut être consulté dans la documentation développeur ; il est bien plus propre que l’ancien plat de spaghetti. Le reste du code d’interface d’Ansel interagit avec le gestionnaire de raccourcis en déclarant de nouveaux chemins d’accélérateurs, en enregistrant les fonctions de rappel qui leur sont attachées et, éventuellement, des raccourcis par défaut, le tout via une seule méthode de l’API. La gestion des raccourcis est totalement ignorante et immunisée vis-à-vis des internals d’Ansel ; en particulier, elle ne sait rien des modules ni des widgets Bauhaus faits maison, si bien que la conception est entièrement auto-contenue. Les préférences sont stockées par langue dans ~/.config/ansel/keyboardrc-LANG, à l’aide d’une table native d’accélérateurs Gtk . L’API est entièrement documentée dans la documentation développeur d’Ansel pour faciliter la maintenance et les extensions futures, de sorte qu’aucune rétro-ingénierie ne sera nécessaire.

image
image

C’est ainsi que je travaille parce que je ne programme pas pour m’amuser. Je ne prends aucun plaisir à programmer. Je résous des problèmes, en essayant de ne pas en créer de nouveaux.

Conclusion

C’est le cas d’école de tout ce qui a mal tourné dans Darktable en matière de sur-ingénierie frénétique, et de la manière dont cela aurait dû être corrigé. La solution proposée ici est meilleure parce que :

  1. toute l’interface peut être parcourue au clavier sans avoir à mémoriser un seul raccourci,
  2. les contrôles, modules et autres actions n’ont chacun qu’un seul raccourci direct, absolu et configurable par l’utilisateur, qui déclenche directement l’action ou focalise le widget de contrôle le cas échéant, ce qui rend l’interface de réglage bien moins écrasante et supprime le besoin d’une fenêtre aide-mémoire supplémentaire,
  3. les actions sont globalement recherchables et déclenchables, qu’elles soient ou non liées à une combinaison de touches,
  4. les interactions mixtes touche + défilement peuvent être déclenchées sans coût supplémentaire, en émulant les molettes et curseurs MIDI sans matériel additionnel,
  5. le code est objectivement 5 à 6 fois plus simple, selon la métrique considérée,
  6. l’ensemble est entièrement documenté et largement commenté dans le code,
  7. n’importe quel futur abruti décérébré capable de lire du C pourra maintenir cette chose, qui de toute façon ne devrait pas demander beaucoup de maintenance puisqu’elle ne fait rien d’intelligent et vit en dehors du cœur du logiciel.

Translated from English by : Aurélien Pierre, ChatGPT. In case of conflict, inconsistency or error, the English version shall prevail.

  1. A turducken is a chicken stuffing a duck stuffing a turkey. That’s a decadent amount of meat that will likely go to waste, unless you have 20 persons to feed. Anyway, it will take forever to cook, and the turkey will likely be dry by the time the chicken is well done. ↩︎

  2. Developers call that “whack-a-mole bug fixing” ↩︎

  3.  Although the stress and anxiety inflicted upon workers by ever-changing software stacks is largely underrated, since innovation is deemed to increase productivity by definition, regardless of user feedback. ↩︎

  4. STEM: Science Technology Engineering Mathematics ↩︎

  5. I don’t see why the capitalistic mindset is ok when it comes to getting excited about new products, but gets boring when it reaches return on investment and hidden/sunk costs… ↩︎

  6.  I’m not talking here about the Darktable way of “fixing bugs” that consists into rushing on the visible manifestation of the bug, and adding a fourth level of nested if to take care of the pathological corner case. That’s working around the cause of the bug by creating more technical debt, not actually fixing the root cause of the bug. And since that root cause will generally stem many visible manifestations, patching all the manifestations is actually more complicated on the long run, and leads to brittle code. ↩︎

  7. Flexibility is usually the opposite of simplicity, so you have to enjoy when you can win on both fronts. ↩︎

  8. Remember that the shortcut handler has to listen to all keystrokes before deciding if it’s supposed to do something with them or discard them, so this part of the software runs all the time for all users, whether or not they actually use shortcuts. ↩︎