In mijn stellingnemende bericht, Darktable: crashing into the wall in slow-motion, presenteerde ik de treinramp die de nieuwe „Great MIDI turducken" was. Het doel van deze turducken1 was om het sneltoetsensysteem te herschrijven om het uit te breiden voor MIDI-apparaten.

Tot op de dag van vandaag ben ik nog steeds boos over deze onderneming van massavernietiging, hier is een samenvatting van de redenen:

  1. het verving in 2021 een sneltoetsensysteem dat behoorlijk goed was, functioneel compleet, goed getest, stabiel en gecodeerd in minder dan 1500 regels (commentaar inbegrepen),
  2. …om ondersteuning toe te voegen voor MIDI-apparaten en PlayStation-gamepads (!?!)…
  3. …maar in mijn Darktable-enquête van 2022, één jaar na deze nieuwe functie, van de meer dan 1251 gebruikers die deelnamen:
    • had 81% van de gebruikers geen MIDI-apparaat en was niet van plan er een aan te schaffen,
    • wist 2% niet eens wat een MIDI-apparaat was.
    • had 8% van de gebruikers een MIDI-apparaat maar gebruikte het niet met Darktable,
    • overwoog 6% misschien in de toekomst een MIDI-apparaat aan te schaffen,
    • had 2% van de gebruikers een MIDI-apparaat dat ze daadwerkelijk in Darktable gebruikten,
  4. de code was absoluut verschrikkelijk, wat betreft:
    • codekwaliteit: onleesbare if/switch-case-statements genest op 4 niveaus, midden in functies van 1000 regels (ik plaatste voorbeeldfragmenten in mijn artikel),
    • codevolume:
      • 3546 regels code voor Darktable 4.0,
      • 4397 regels code voor Darktable 5.0,
      • de toename in volume is een direct gevolg van het proberen te repareren van bugs in een architectuur die niet gerepareerd kan worden omdat de complexiteit ervan meer complexiteit bevordert. Dat komt allemaal voort uit het ontwerp, maar problemen die door complexiteit veroorzaakt worden oplossen met het toevoegen van meer complexiteit is geen oplossing.
    • codecomplexiteit:
      • cyclomatische complexiteit :
        • 1088 voor Darktable 4.0,
        • 1245 voor Darktable 5.0 (details ),
      • cognitieve complexiteit :
        • 1885 voor Darktable 4.0,
        • 2098 voor Darktable 5.0 (details ).
      • het is verreweg de meest complexe functie van de software, ook al werkt het niet op afbeeldingen. Ter vergelijking: de op één na meest complexe functie is de EXIF-metadata-decodering, die een cognitieve complexiteit van 1348 heeft.
  5. het decodeert door ontwerp geen toetsmodificatoren, maar behandelt alleen hardware-toetsaanslagen, wat betekent:
    • „1"-invoer van het numerieke toetsenblok wordt gedecodeerd als Keypad End,
    • „1"-invoer van een Frans AZERTY-toetsenbord wordt gedecodeerd als Shift+&, of Shift+" op BÉPO,
    • je moet daarom al je op cijfers gebaseerde sneltoetsen dupliceren voor elke manier om een cijfer in te voeren, en erop voorbereid zijn dat het sneltoetsinstellingenvenster geen enkel echt cijfer in de toetscombinaties bevat.
  6. het ontwerp aan de gebruikerskant is absoluut verschrikkelijk, met veel te veel te configureren acties en emulaties („effecten"), die zelfs 4 jaar later niet volledig gedocumenteerd zijn (wat is „ctrl-toggle"? „right-activate"?), en de sneltoetsconfiguratie gebruikt een raar gesplitst venster dat nergens op slaat,
  7. de implementatie is ook verschrikkelijk: de functie is op de hoogte van de hele software-GUI, en de software-GUI is op de hoogte van de sneltoetscode. Er is hier geen modulariteit, en iets veranderen in de sneltoetscode kan overal in de software onverwachte en ongewenste effecten hebben.2 Kijk maar naar de afhankelijkheidsgraaf hieronder,
  8. meerdere „sneltoetsen" (of MIDI-bindingen) kunnen aan dezelfde actie gekoppeld worden, wat betekent dat elke gebruikersinteractie de hele lijst met beschikbare acties moet doorzoeken, wat zeer inefficiënte sneltoetsafhandeling veroorzaakt, GUI-vertragingen in sommige gevallen en „onbekende toetscombinatie"-fout-positieven in bijzondere gevallen.
image
Niet-gedecodeerde cijfertoetsen en rare venstersplitsing tussen „action" en „shortcut".
image

De afhankelijkheidsgraaf van src/gui/accelerators.c (Great MIDI turducken) vóór de herschrijving. Raad eens waarom we het „spaghetticode " noemen… Dit maakt duidelijk dat er een tweezijdige afhankelijkheid is tussen de accels-code en de rest van de GUI-code. Dit is een nachtmerrie om te onderhouden.

Dus, anders gezegd, één (zeer slechte) programmeur verving een werkende en eenvoudige functie door een monstruositeit, met de goedkeuring van de beheerder (die zelf nooit zulke slechte code zou schrijven, maar vastbesloten is om „geen momentum te verliezen" op bijdragen, wat het ook kost), om 2% van de gebruikersbasis te plezieren. Dat alles voor een secundaire (tertiaire?) functie.

Ik noem dat verkeerde prioriteiten. Zeker aangezien het meerdere maanden hard werk kostte van één ontwikkelaar en veel bètatesters, alleen maar om de dingen erger te maken, en vervolgens werd het harde werk een rechtvaardiging om de verandering nooit terug te draaien, wat bekend staat als de sunk cost-drogreden .

Ik ben een gebruiker, code interesseert me niet

Je bekommeren om de code van de applicaties die je gebruikt is net als je bekommeren om de vraag of de leidingen die water naar je huis brengen van lood gemaakt zijn. Het is niet jouw taak, leidingen liggen begraven buiten het zicht, dus je hebt alle reden om je er niet om te bekommeren — en de mensen die verantwoordelijk zijn voor het waternet hebben alle reden om je je er niet om te laten bekommeren — maar ze zullen effect hebben op je gezondheid, en die effecten zullen onzichtbaar zijn totdat het te laat is. Software heeft misschien geen direct effect op je gezondheid,3 maar de manier waarop het gemaakt is zal een langetermijnimpact op je hebben.

Ik bevind me in een ongemakkelijke positie – de Cassandra  — die aan gebruikers moet uitleggen dat problemen die ze niet kennen, niet zien en waar ze zich niet om bekommeren wel degelijk de dagelijkse bruikbaarheid en stabiliteit van de tools die ze gebruiken beïnvloeden, maar op niet voor de hand liggende manieren. In elk geval beïnvloeden deze problemen direct de waarschijnlijkheid dat een beheerder ooit de bugs oplost waarmee ze geconfronteerd worden.

Slecht nieuws brengen maakt jou het slechte nieuws, maar slecht nieuws brengen waar niemand zich om bekommert maakt je een klootzak, ook al verklaart dat slechte nieuws de rare en willekeurige problemen die sinds 2021 steeds vaker voorkomen in Darktable-bugtrackers, die jarenlang niet opgelost worden omdat op dat punt de code geen bugs bevat, hij is een bug. Maar hier moet je te maken krijgen met filterbubbels : afhankelijk van waar op het web je kijkt, zul je ofwel mensen vinden die diep gelukkig zijn met Darktable (prestaties, stabiliteit, ontwerp), en mensen die er diep ongelukkig mee zijn. Mijn empirische observatie is dat de gelukkige kampeerders gemiddeld krachtige computers bezitten en STEM-diploma’s hebben.4 De ongelukkige mensen hebben de neiging om zich af te wenden en de tijd die ze met de software verliezen te verminderen, wat betekent dat, als je niet actief naar hen op zoek bent, je alleen je overlevingsbias  zult voeden. En, hoewel ik het nut niet inzie van proberen gebruikers te bekeren tot Darktable (of tot welke open-source fotobewerker dan ook), omdat ieder zijn eigen, is het uitfilteren en afschrikken van gebruikers op basis van hun computergeletterdheid een grote mislukking voor elke fotobewerkingsapp.

Als een man met een YouTube-kanaal dat uitlegt hoe je Darktable gebruikt, en die 1-op-1-trainingssessies geeft, heb ik de neiging om meer gevarieerde feedback aan te trekken dan alleen de GitHub-issue-trackers (waar kritiek op het ontwerp toch vrij snel de kop wordt ingedrukt), en getuig ik uit de eerste hand op videochats van die rare, willekeurige en niet-reproduceerbare bugs. Elke bevoorrechte man heeft de neiging om getuigenissen over problemen waar hij zelf niet mee te maken heeft te ontkennen, te minimaliseren of te negeren, of zelfs de problemen op de persoon die ze meldt af te schuiven. Dus heb ik vaak deze indruk van twee parallelle universa die niet communiceren maar op elkaar neerkijken, als het gaat om feedback over Darktable. Uiteraard kozen de Darktable-ontwikkelaars ervoor om te kijken waar de zon schijnt.

En hoewel Darktable eruitziet als een actief project met veel nieuwe functies die regelmatig door een dozijn mannen worden toegevoegd, is het des te moeilijker uit te leggen dat wat er eigenlijk achter de schermen gebeurt vernietiging van de codebasis is, omdat de codekwaliteit naarmate de tijd verstrijkt sterk verslechtert, totdat het helemaal niet meer mogelijk zal zijn om te debuggen. Het volume aan veranderingen zegt niets over kwaliteit, betrouwbaarheid of onderhoudbaarheid. Maar 2 eeuwen kapitalisme hebben ons geconditioneerd om glimmende nieuwe functies te zoeken, wat het ook kost, en in dit opzicht voldoet Darktable aan de verwachtingen.

Code wordt gebroken geboren. Elke software heeft bugs. Erger nog, we gebruiken veel bibliotheken van derden om te voorkomen dat we „het wiel opnieuw uitvinden", maar die bibliotheken zullen hun API  in de toekomst veranderen, wat betekent dat code die vandaag werkt morgen zal stoppen met werken en in de toekomst herwerkt zal moeten worden, wanneer afhankelijkheden veranderen. Code is als een tuin: elk nieuw seizoen brengt zijn hoeveelheid klusjes. Het heet onderhoud. Zodra dit erkend is, ligt wijsheid in het plannen van manieren om onderhoud eerst mogelijk te maken, en vervolgens gemakkelijk.

Goede code is code die gemakkelijk te onderhouden is. Dat wil zeggen, goede code is geschreven op manieren die aangenaam zijn voor de mensen die hem moeten lezen, begrijpen en repareren. We schrijven geen code voor computers. We schrijven geen code opdat functies nu en de komende maanden „gewoon werken". Code is niet iets wat we onder een mooie motorkap verbergen in de hoop er nooit meer naar te hoeven kijken. Code is een levend organisme dat energie en tijd kost om in leven te houden.

Onderhoud heeft een kostprijs, een die vaak onderschat wordt. Het maakt niet uit of je product het beste van de markt is, als je onderhoudskosten prohibitief zijn, zullen (goed geïnformeerde) klanten het mijden. Onderhoud is saai, oninteressant, niet sexy. Onderhoud is het tegenovergestelde van het introduceren van glimmende nieuwe dingen: het is ervoor zorgen dat stoffige oude dingen stilletjes blijven werken. In je software-releasenotities komt het aan het einde. Gebruikers worden niet enthousiast over onderhouden functies, maar ze zullen boos worden over niet-onderhouden code en hebben de neiging om de verkeerde maatstaf te gebruiken om te beoordelen hoe goed een project onderhouden wordt (namelijk: agitatie en schijn van werk). Dus je zult niets winnen met het onderhouden van code, maar je zult verliezen door het niet te onderhouden, en het zal je kosten of je het nu doet of niet.

Code is ononderhoudbaar wanneer elke bugfix leidt tot een nieuwe bug ergens anders, op een whack a mole-manier, en de aard van de bugfixes eigenlijk contextuele patching en workarounds is die alleen complexiteit aan de software toevoegen. Wanneer je dat punt bereikt, is je enige optie de betreffende code vanaf nul te herschrijven. Wat betekent dat werk uit het verleden zojuist meer werk nu heeft gecreëerd. En wanneer dat specifieke werk uit het verleden al eerder werk overschreef, belanden we in idiocracy.

Meer code is geen succes of prestatie. Prestaties liggen in het oplossen van problemen van gebruikers. Ook hier treft de kapitalistische mindset ons hard: we zouden werk willen meten aan de hand van het volume aan toegevoegde code. Meer code is gewoon meer aansprakelijkheid, meer technische schuld, meer onderhoudskosten. Dat is een cognitieve dissonantie aangezien software juist gaat over het schrijven van code. Maar denk er zo over: een auto maken gaat niet over het toevoegen van meer staal over 4 wielen, want op een gegeven moment zal de auto moeten bewegen. Je hebt de juiste hoeveelheid staal nodig, op de juiste manier gevormd op de juiste plaatsen: je moet efficiënt en spaarzaam zijn. Software gaat over het oplossen van problemen van gebruikers met computers, niet over het opslaan van code.

Bij projecten waar bijdragers niet betaald worden, is saai onderhoud niet waar mensen hun zaterdagen willen doorbrengen, en het is een reële zorg wat projectmanagement betreft. Het is des te belangrijker om onderhoud gemakkelijk te maken wanneer mensen gratis werken. Gelukkig heeft Darktable nul projectmanagement, gewoon een zwerm willekeurige bijdragers die op willekeurige momenten hun jeuk krabben, zonder verenigd doel, strategie, schema of werkverdeling. Daarom zou ik willen dat iedereen stopt met het gebruiken van de term „Darktable-team". Er is geen Darktable-team, want er is geen werkverdeling, geen leiderschap, geen schema, geen roadmap, geen planning, geen doel, geen prioriteiten, geen visie, geen strategie, geen methode, geen ontwerp, geen communicatie vooraf voordat er dingen gedaan worden, geen specificatiedocumenten voor de op te lossen problemen. Dit is de middelbareschoolcomputerclub, bestaande uit losgekoppelde individuen.

Er zijn 3 dingen die een ontwikkelaar moet doen om ervoor te zorgen dat onderhoud zo gemakkelijk mogelijk is:

  • schrijf de minste hoeveelheid code: een bug opsporen tussen 300 regels zal veel sneller gaan dan tussen 3000 regels,
  • schrijf de eenvoudigste code: „eenvoudig" als in „de functionele logica heeft weinig stappen, weinig aannames, weinig randgevallen". Eenvoudiger betekent gemakkelijker te lezen, te begrijpen en te repareren, maar ook dat er minder gevallen te dekken zijn tijdens het testen. In de praktijk betekent het het vermijden van contextuele gevallen, gebruikersopties, varianten, if/else in de code.
  • schrijf zelfstandige code: verdeel de softwarefunctionaliteit tussen modules (subprogramma’s) die via de kern communiceren en van elkaar geïsoleerd zijn. Naarmate het codevolume groeit, garandeert dat dat veranderingen die binnen modules plaatsvinden geen onvoorziene effecten buiten zullen hebben.

Ten slotte moet je evalueren of de nieuwe functies die je toevoegt de extra onderhoudskosten waard zijn. In het geval van de sneltoetsafhandelaar bedienen we de „behoeften" (of liever, de luxe) van 2% van de gebruikersbasis met een implementatie die op zijn (allerminst) tweemaal zo gecompliceerd is als degene die het verving. Overpeins dat in je kapitalistische geest…5

Codecomplexiteit kan gemeten worden met grafentheorie, via cyclomatische complexiteit, cognitieve complexiteit, of N-path-complexiteit. Veel onderzoeksartikelen  correleren codevolume en/of codecomplexiteit met het aantal verborgen bugs, wat vrij intuïtief is: de regel code die je niet schrijft is de enige waarin je nooit bugs zult hebben. Eenvoud is het doel. En, zoals ik op de harde manier ontdekte, is GUI-complexiteit (front-end) vaak een direct gevolg van back-endcomplexiteit. Er zijn veel mannen die naar de maan huilen om de mystieke figuur van de UI/UX-designer op te roepen, in de hoop de verschrikkelijke GUI van Darktable (gecompliceerd én inconsistent) magisch op te lossen. De GUI bestaat niet parallel aan de back-end, hij bedraadt alleen de invoer die de back-end verwacht met grafische bedieningselementen. De front-end vereenvoudigen kan niet gedaan worden zonder de back-end te vereenvoudigen, dit is gewoon een dom dogma verzonnen door mensen die alleen soft skills hebben. Maar back-ends vereenvoudigen is een stuk gecompliceerder dan alleen maar mockups tekenen.

Zoals blijkt, faalde wat er gebeurde met de Great MIDI turducken op alle 3 de punten: codevolume, codecomplexiteit, code-zelfstandigheid. En Darktable-ontwikkelaars zullen nooit leren van hun fouten, zoals bewezen wordt door Darktable 5.0. Naarmate sneltoetsbugs zich in de loop van de tijd opstapelden in Ansel (bovenop functies die door ontwerp verschrikkelijk zijn), probeerde ik ze te repareren op manieren die het herschrijven van het geheel vermeden, totdat het vrij duidelijk was dat ik de herschrijving niet veel langer zou kunnen vermijden.

Dit wordt technische schuld  genoemd. De hele code van de Great MIDI turducken was bedoeld om te werken zodra hij geschreven was, niet om op de lange termijn onderhoudbaar te zijn. Het is in feite een proof of concept die het nooit in productie had mogen halen. En het bewijs zelf van de ononderhoudbaarheid ervan is hoezeer het codevolume en de complexiteit gegroeid zijn van bugfix tot bugfix, tussen Darktable 3.8 en 5.0, waardoor het nog ononderhoudbaarder wordt naarmate bugs gerepareerd worden. Dit is de definitie van jezelf vastlijmen in een spinnenweb: hoe meer je beweegt, hoe vaster je komt te zitten.

Mijn fout is misschien dat ik in 2022 begon met mijn kritiek op het (gebrek aan) management van Darktable, nadat ik het bewijs kreeg dat de ongeorganiseerde troep bijdragers hun fouten nooit zou erkennen en ervan zou leren. Hoewel het zelfopgelegde en onhoudbare werktempo meer geneigd was om burn-out te creëren dan reflectie. Sindsdien probeert Pascal Obry, de huidige zelfbenoemde beheerder van Darktable, iedereen ervan te overtuigen dat ik een onuitstaanbaar teamlid ben, niet in staat om samen te werken met andere personen die het met mij oneens zijn, die iedereen begon te beledigen. Uiteraard is er geen specifiek en technisch antwoord gegeven op de kwesties die ik aankaartte in Darktable: crashing into the wall in slow-motion, alleen vaag geruststellende algemene uitspraken in de trant van „Darktable is een actief project, gezond omdat het veel bijdragers heeft". Alsof meer ongeorganiseerde apen uiteindelijk aan een ingenieur gelijk zouden zijn als je er maar genoeg toevoegde.

Ik denk dat ik gedurende 4 jaar niets dan aardig ben geweest – veel te aardig, in feite — totdat ik me realiseerde hoe erg deze mannen het project schaadden. Ze leren niet, en zullen nooit leren van hun fouten, omdat ze ze niet eens zullen erkennen en de evolutie van Darktable 5.0 bevestigt alleen maar de trend. Maar steeds meer rare, willekeurige en niet-reproduceerbare bugs zich op de bugtrackers zien opstapelen is een vrij duidelijke aanwijzing dat er iets grondig mis is. Vooral omdat het onderzoeken van die bugs me leidde naar archeologie in verschrikkelijke shitstacks van code die nog geen 2 jaar oud was, en hoewel mijn kennis en ervaring met de Darktable-codebasis in de loop der jaren groeide, nam mijn vermogen om de grondoorzaak van bugs te repareren af en werd ik geconfronteerd met steeds verontrustender hooibergen van code-indirecties .6

Het is moeilijk om dat allemaal uit te leggen aan mensen die computers zien als magische dozen ontworpen door tech-tovenaars. Er zit geen magie in. Het is voornamelijk wiskunde en toepassingen. Veel ontwikkelaars zullen bekennen slecht in wiskunde te zijn, wat betekent dat ze ook slecht in programmeren zijn. Want wiskunde gaat niet alleen over het uitvoeren van rekenkundige bewerkingen, het is een hele discipline van de geest die het mogelijk maakt om complexe problemen te abstraheren om ze op te delen in eenvoudige problemen, die zullen leiden tot eenvoudige code en een eenvoudige GUI. Goede programmeurs besteden veel tijd aan het nadenken over hoe ze weinig code kunnen schrijven. Want code is een aansprakelijkheid, code is technische schuld, code is kostbaar om te onderhouden. Dus je betaalt de schuld vooraf, zonder rente, door veel over je code na te denken voordat je hem schrijft, in plaats van eerst te coderen en dan jaren branden te blussen.

Dit alles roept ook de vraag op wie eigenaar is van open-source code en open-source projecten. Sinds de oprichter van het Darktable-project, Hanatos, het schip verliet, evenals de hele eerste generatie ontwikkelaars, om verschillende redenen, benoemde de laatst overgebleven man van de eerste generatie zichzelf tot nieuwe beheerder. Hij is een zeer capabele en bekwame ontwikkelaar: zijn code is schoon, netjes, en ik heb tot nu toe geen enkele bug gevonden in een regel waar git blame  „Pascal Obry" zei. Maar zijn politiek wat betreft codemanagement is verschrikkelijk: hij gelooft dat elke bijdrage een goede bijdrage is, dat het bijdrage-„momentum" niet ontmoedigd moet worden, en is werkelijk niet in staat om „nee" te zeggen tegen bijdragen, wat betekent dat vrijwel alle pull requests worden samengevoegd. Het is Frankrijk in 1940: alles komt binnen, wordt met een brede glimlach verwelkomd en de Führer krijgt twee keer zoveel joden als gevraagd. Ondertussen worden de verzetsstrijders terroristen genoemd.

Maar er is een enorm verschil tussen de kwaliteit van de code die Pascal schrijft en de kwaliteit van de code die hij accepteert en samenvoegt. Dit is een zorgwekkende paradox die zijn wortels vindt tussen de angst om iets te missen  en radicaal techno-positivisme , dat neigt naar dogma en overtuigingen, met een volledige minachting voor ontwerp en gebruikersperspectief. En misschien een overdreven vertrouwen in het vermogen van de „Community" om bugs later te repareren.

Er zijn veel gevallen waarin niets doen beter is dan het verkeerd doen, vooral wanneer je bestaande functies vervangt. Aangezien Darktable een niet-destructieve code-editor is, investeer je erin wanneer je een database van beeldbewerkingen begint. Tenzij je van plan bent al je afbeeldingen te exporteren naar bestanden met hoge resolutie en hoge bitdiepte, zodra je klaar bent met bewerken, om ze nooit meer te veranderen. Dat creëert een legitieme verwachting van langetermijnstabiliteit en -consistentie, zodat je oude bewerkingen nog steeds geopend, bijgewerkt en opnieuw geëxporteerd kunnen worden, misschien naar nieuwe formaten, misschien naar hogere resoluties. Van koers veranderen in het applicatieontwerp is een soort contractbreuk, en ook al ontheft de GNU/GPL-licentie van alle wettelijke verantwoordelijkheid, het ontheft niet van de schade aan gebruikers. Noch geeft het de jaren van mijn leven terug die ik verloor aan het repareren van hun troep.

Dus, zelfs afgezien van ontwikkelaargerichte onderhoudsproblemen, is er ook een discussie te voeren over wie mag beslissen hoe oude en beproefde functies vervangen worden, vooral die basale en universele functies van desktopapplicaties zoals het afhandelen van bestanden (importeren, exporteren, bladeren) of muis- en toetsenbordinteractie, die zo lang zo alomtegenwoordig zijn geweest dat de jaren 2020 30 jaar te laat zijn om te doen alsof ze ze opnieuw uitvinden.

Veel werk en manuren werden geïnvesteerd om de sneltoetsen slechter te maken, omwille van gebrekkige politiek en schadelijke dogma’s, bovenop wishful thinking en sociaal lanterfanten  waarbij iedereen hoopt dat The Community® (oftewel iemand anders) hun eigen fouten zal repareren. Gebruikers moesten ook hun toetsenbordconfiguratie verliezen en waren verplicht om alles te resetten en alles ook opnieuw te leren. Maar het op de juiste manier doen zou eigenlijk minder werk en minder manuren hebben gekost. Dit is een zichzelf voedende lus van waanzin, die een toxische werkomgeving creëert waarin instabiliteit meer instabiliteit bevordert, waarin complexiteit meer complexiteit bevordert, opnieuw zonder enige vorm van functie-roadmap die aan iedereen die erbij betrokken is een algemene richting en zicht zou geven.

Een korte geschiedenis van slecht ontwerp

Het is pas nadat ik de sneltoetsfunctie vanaf nul reconstrueerde dat ik begreep wat er misging in de sneltoetsen/accelerators van Darktable.

Om te beginnen is er deze verlammende losbandigheid van functies, die het verleidelijk maakt om de GUI op te ruimen door simpelweg functies te verbergen, alleen om ze vanaf het toetsenbord te laten afhandelen. Het probleem is dan dat zulke functies niet altijd niche en optioneel zijn (zoals de sneltoets die maskerinteracties omzeilt bij het slepen en neerzetten van de hoofdafbeeldingsvoorbeeldweergave in de donkere kamer), maar zeker onvindbaar zijn voor gebruikers. Ansel loste dit probleem op met het globale menu.

Sommige functies waren verborgen via een basale vimkeys-ondersteuning, als je begint met het invoeren van :: :q zal de applicatie afsluiten, :set gevolgd door de naam van de schuifregelaar of keuzelijst zal de waarde veranderen. Dit is uiteraard nergens gedocumenteerd in de Darktable-handleiding, en als gebruiker van Darktable gedurende meer dan tien jaar had ik er nog nooit van gehoord voordat ik de code ervan verwijderde, want dit grapje luistert naar al je toetsaanslagen om te bepalen of het al dan niet op je typen moet reageren.

Maar dan is er ook nog het feit dat modules zelfgemaakte Gtk-widgets gebruiken (genaamd „Bauhaus", in src/bauhaus/bauhaus.c) die niet alles implementeren wat je zou verwachten van een GUI-widget die gebruikersgebeurtenissen vastlegt, vooral niet de toegankelijkheidsfuncties.

Een van de meest basale toegankelijkheidsfuncties is de mogelijkheid om door focusbare widgets te cyclen. In GUI- (en Gtk-)jargon is een focusbare widget er een die toetsaanslaggebeurtenissen kan vastleggen, zodra hij gefocust is. Widgets worden doorgaans gefocust zodra erop geklikt wordt, maar Gtk beheert intern ook een focusketen  waarin je navigeert met de Tab- en de pijltjestoetsen. Het eerste probleem is dat de Tab-toets, in Darktable, gekoppeld was aan de „preview"-modus (alle panelen in de weergave aan/uit schakelen). En in feite zou dit met native Gtk-accelerators helemaal niet mogelijk zijn geweest, aangezien Tab door Gtk toegewezen is en verboden is in door gebruikers gedefinieerde sneltoetsen, maar aangezien Darktable zijn eigen sneltoetsafhandelaar implementeerde, zelfs vóór de Great MIDI turducken, werd het overschreven. Dus het tabulatie-gebaseerde cyclen van de focusketen was uitgeschakeld simpelweg omdat de Tab-toets aan iets anders toegewezen was. Maar het tweede probleem was dat de zelfgemaakte Bauhaus-widgets ook alle pijltjestoetsaanslagen vastlegden. Zo was het sequentieel navigeren tussen bedieningselementen (volgende/vorige) ook volledig onmogelijk, door ontwerp. Net zoals Bauhaus-widgets alle muisscrollgebeurtenissen vastlegden, waardoor het scrollen van de zijpanelen verhinderd werd.

Omdat sequentiële/incrementele navigatie tussen focusbare widgets door ontwerp onmogelijk was, moesten alle toetsenbordinteracties noodzakelijkerwijs absoluut gemaakt worden: voor elke schuifregelaar, voor elke keuzelijst, zou je een sneltoets hebben die de waarde direct gekoppeld verhoogt/verlaagt/reset.

Maar er ontstonden meer problemen toen modules multi-instantieerbaar werden gemaakt, want bedieningselementen werden geïdentificeerd door een accelerator-pad zoals view/module/slider/increase of view/module/slider/decrease, maar alle module-instanties zouden hetzelfde pad erven. Dat leidde tot een behoefte om dat allemaal tijdens runtime te beheren, met gebruikersvoorkeuren om te beslissen of de beoogde module de eerste, laatste of laatst-mee-geïnteracteerde module zou zijn.

Dit allemaal generaliseren naar MIDI en gamepads maakte het alleen maar erger, want bovenop het hebben van één sneltoets per mogelijke actie per widget, was er dan een behoefte om typische desktopinteractie-emulatie van andere invoerapparaten te beheren. Maar in plaats van de emulatielaag hoog-niveau te beheren, op de interface tussen MIDI en gewone toetsenbord-/muissneltoetsen, over-engineerde een verschrikkelijke ontwikkelaar volledig een abstractielaag van acties, ingebed in modules, native Gtk-widgets (als een overlay), en zelfgemaakte Bauhaus-widgets (diep ingebed). Het probleem is dat die abstractielaag niet echt een laag was maar meer als een uitgezaaide tumor die zich overal verspreidde. Het verwijderen ervan en al zijn afhankelijkheden  leidde tot de verwijdering van 7674 regels verspreid over 163 bestanden, ook al werd het verondersteld geïmplementeerd te zijn in src/gui/accelerators.c (4412 regels code, commentaar en witregels).

Sneltoetsen/accels vanaf nul opnieuw ontwerpen

De nulde stap van het herontwerp zijn deze 2 eenvoudige vereisten:

  • Elke actie zou vindbaar moeten zijn in de GUI, sneltoetsen zijn niet bedoeld om de GUI op te ruimen; de GUI opruimen is een kwestie van de workflow op te breken in eenheidsstappen en alleen de bedieningselementen te presenteren die er voor de huidige stap toe doen.
  • De software zou volledig bruikbaar moeten zijn met alleen de muis en met alleen het toetsenbord. Gemengde interactie zou volledig optioneel moeten zijn.

De eerste beperking van het herontwerp is daarom om absolute sneltoetsen volledig optioneel te maken, dat wil zeggen de toetsenbord-workflow ontwerpen voor een sequentiële/relatieve toegang (cyclen tussen volgend/vorig bedieningselement).

Relatieve toetsenbordnavigatie

Het eerste stuk van de oplossing hier is het globale menu, dat veel knoppen opslaat die voorheen verborgen waren in inklapbare modules (en soms inklapbare secties van inklapbare modules, aangezien de Darktable-bijdragers hier sinds 2021 verliefd op zijn geworden). Het verwijderen van deze modules maakte het mogelijk om de rechterzijbalk in de lichttafel vrij te maken, waardoor meer schermruimte aan de miniaturen toegewezen wordt. Menu-items hebben toets-mnemonieken, dat wil zeggen dat wanneer je op Alt drukt, één letter in hun label onderstreept wordt en, indien ingedrukt terwijl je Alt ingedrukt houdt, het bijbehorende menu zal uitklappen. Zodra een menu is uitgeklapt, kunnen alle menu’s genavigeerd worden met pijltjestoetsen en kunnen de bijbehorende acties geactiveerd worden met Enter.

Dit is niet alleen volledig standaard, wat het GUI-paradigma betreft, het voorkomt ook dat je überhaupt een sneltoets moet instellen en onthouden. Kers op de taart, menu’s zijn native Gtk-objecten die zeer weinig code-overhead toevoegen en al bedraad zijn met alle typische toegankelijkheidsmethoden, kant-en-klaar.

Aangezien de Tab-sneltoets ook is losgekoppeld van de volledig-voorbeeld-functie, is hij dan beschikbaar om door de focusketen omlaag te gaan, binnen native Gtk-widgets, en ik heb de zelfgemaakte Bauhaus-widgets uitgebreid om focussen af te handelen zoals elke native Gtk-widget.

In de weergave van de donkere kamer zijn de beeldverwerkingsmodules georganiseerd in tabbladen die heen en weer gecycled kunnen worden met de typische Ctrl+Tab en Ctrl+Shift+Tab. Binnen een tabblad kunnen individuele modules omhoog en omlaag doorgebladerd worden (uitgeklapt en gefocust) met Page Up/Down. Zodra een module gefocust is, kan het cyclen door zijn interne bedieningselementen gedaan worden met Ctrl+Up/Down, wat tabbladen ondersteunt, zodat, wanneer je het laatste bedieningselement van het zichtbare tabblad bereikt en op Ctrl+Down drukt, het volgende tabblad automatisch weergegeven wordt en zijn eerste bedieningselement gefocust wordt. Zowel zelfgemaakte Bauhaus-widgets als native Gtk-widgets ondersteunen het native Gtk Up/Down-focussen (naar het vorige/volgende bedieningselement), maar dit werkt alleen zodra een van de bedieningselementen van de module gefocust is en voor de zichtbare widgets. Daarom werd Ctrl+Up/Down geïmplementeerd voor betere robuustheid.

Dit alles geeft focus aan bedieningselementen, wat betekent dat het hen toestaat verdere toetsaanslagen te registreren. Gefocuste bedieningselementen worden weergegeven in vet lettertype in de GUI. Gefocuste modules worden ook gemarkeerd.

Interactie met gefocuste bedieningselementen

Het gesplitste paradigma „focussen" dan „interacteren" (dat ik niet heb uitgevonden…) is zeer krachtig omdat het toestaat om toetsaanslagen in de juiste context te scopen, wat betekent dat dezelfde toetsaanslagen (vooral de Up/Down/Right/Left-pijltjestoetsen) meer dan één keer in de GUI gekoppeld kunnen worden, en verschillend afgehandeld kunnen worden afhankelijk van welke context de focus heeft. Dit is flexibeler en eigenlijk eenvoudiger7 dan de Darktable-obsessie om alles te bedraden aan globale, absolute sneltoetsen die dan zouden botsen op vaak-hergebruikte toetsen, en zou leiden tot het moeten toevoegen van steeds meer toetsmodificatoren als een workaround.

Tot nu toe gaf het navigeren door bedieningselementen ze alleen focus. Hoe zit het met daadwerkelijke interactie?

In de lichttafelweergave, zodra het miniaturenraster gefocust is, wordt navigatie door miniaturen gedaan met typische pijltjestoetsen, Page Up/Down, enz. (zie de documentatie voor alle details) en beeldselecties kunnen op verschillende manieren gedaan worden (batch, series, individueel) ook vanaf het toetsenbord.

In de weergave van de donkere kamer kan het veranderen van waarden op schuifregelaars en keuzelijsten gedaan worden met de pijltjestoetsen (eventueel met Shift voor een grove stap of Ctrl voor een fijne stap), het activeren van de kleurenpipet (op schuifregelaars die ze ondersteunen) wordt gedaan met Insert, enz. (lees, nogmaals, de documentatie voor details).

Dus, nogmaals, tot nu toe geen door de gebruiker gedefinieerde sneltoets, geen cryptische toetscombinatie om te onthouden, het zijn voornamelijk pijltjestoetsen en toch is alles toegankelijk.

Absoluut focussen van bedieningselementen

Dat is allemaal geweldig, maar het laat je achter op een soort Ikea-parcours wanneer je direct een bepaald gebied van de applicatie moet bereiken maar je door alle secties vanaf de ingangsdeur moet navigeren.

Om dat te verlichten was een zeer oude Darktable-functie het tabblad „favoriete modules", oftewel een speciaal tabblad dat de GUI van de meest gebruikte modules zou dupliceren, gebaseerd op gebruikerskeuze. Dat is in wezen bloat oplossen met meer bloat, en toch zijn gebruikers hier erg gehecht aan geraakt. Hoewel als we ons op het doel richten in plaats van op het middel, de vereiste een snelle toegang tot willekeurige modules is, wat volkomen begrijpelijk is.

Dus dit werd opnieuw geïmplementeerd als een manier om een absolute sneltoets te definiëren die onmiddellijk een beeldverwerkingsmodule of een van zijn interne bedieningselementen focust. Het focussen van een verborgen module of bedieningselement zal het automatisch in de GUI laten verschijnen. Van daaruit wordt verdere interactie precies als voorheen gedaan, met uniforme toetscombinaties. Dit vermindert het aantal te configureren sneltoetsen enorm, maakt het geheel generieker en het sneltoetsinstellingenvenster veel eenvoudiger.

Absolute sneltoetsen kunnen ook menu-items als doel hebben (oftewel globale acties), in welk geval ze in het menu opgeroepen zullen worden, naast het actielabel, wat wederom een native Gtk-functie is die met nul overhead komt.

MIDI-controllers vervangen

Als we alle hype van het hebben van speciale controllers en het gevoel als een vliegtuigpiloot terzijde laten, is het enige ding dat MIDI-controllers hebben dat toetsenbord en muis nooit zullen hebben de mogelijkheid om potentiometers (draaiknoppen) direct aan GUI-schuifregelaars te koppelen. Maar dan is de prijs daarvan het hebben van een extra apparaat dat ruimte (en stof) op je bureau inneemt, om nog maar te zwijgen van toekomstig elektronisch afval. Alle fotografen die ik ken die MIDI’s of Loupedecks kochten, borgen ze „tijdelijk" op om wat bureauruimte terug te winnen… en haalden ze nooit meer uit de opslag.

Beeldverwerkingsbedieningselementen kunnen bedraad worden aan sneltoetsen met één toets, omdat Ansel een aangepaste sneltoets-overlay bovenop native Gtk-accelerator-functies gebruikt. Ik heb de zelfgemaakte Bauhaus-widgets aangepast zodat, wanneer je op een van de bovenstaande absolute-focus-sneltoetsen drukt en die ingedrukt houdt, muiswielscrollen direct aan de widget toegewezen wordt, zelfs als de muis niet over de juiste schuifregelaar heen ligt.

Dus door één-letter-sneltoetsen te combineren om een bedieningselement te focussen, met muis- (of touchpad-)scrollen, krijg je alle gemakken van MIDI-draaiknoppen zonder het extra apparaat, extra bibliotheek voor ondersteuning en wonderen van over-geëngineerde emulatielagen.

Maar dat alles gebeurt nog steeds met de ene en enige absolute sneltoets, dus je hoeft niet meerdere sneltoetsen per bedieningselement te definiëren en te onthouden, om vervolgens te merken dat je te kort komt aan beschikbare toetscombinaties.

Sneltoetsloze acties, zoekmachine en vimkeys-achtige triggers

Mijn sneltoetsafhandelaar is een dunne wrapper over de native Gtk-accelerators-API. Als zodanig wordt een actie gedefinieerd door een tekstpad, zoals Ansel/Darkroom/Modules/Exposure/Black level, wat een unieke identificator is, leesbaar zowel door een computer als door een mens. De GUI declareert een functie gekoppeld aan elk van deze paden, die de uit te voeren code bevat om de bijbehorende actie toe te passen. Vervolgens koppelt de sneltoetsafhandelaar een toetscombinatie aan dit pad.

Dus, telkens wanneer een gebruiker op enkele toetsen drukt, kijkt de sneltoetsafhandelaar of we een bekend pad voor deze combinatie hebben, en als het er een vindt, activeert het de eraan gekoppelde functie. Dit is een idioot-bestendig ontwerp dat niets weet over de interne werking van Ansel-modules, zelfgemaakte widgets, enz. Dus het kan uitgebreid worden naar veel delen van de software zonder overhead.

Maar het is eigenlijk veel krachtiger dan alleen dat. Want door alle bekende paden op te sommen, kunnen we vervolgens paden teruggeven die overeenkomen met een tekst-zoekopdracht (zoeken naar een bedieningselement, module of globaal menu-item op naam), dat wil zeggen alle acties uit een lijst vinden, maar dan kunnen we ze ook activeren ook al zijn ze niet aan een sneltoets gekoppeld.

image
De globale acties-zoekmachine, die het mogelijk maakt om acties op te zoeken en te activeren, of ze nu een toetscombinatie toegewezen hebben gekregen of niet.

Aangezien beeldverwerkingsmodules ook op deze manier gevonden en weergegeven kunnen worden, vervangt dit ook de module-zoekmachine, waardoor wat verticale ruimte teruggewonnen wordt voor lange modules en hun maskering-/mengopties (en ongeveer 200 regels code verwijderd worden). Van daaruit breidde ik het uit naar alle gereedschapskisten en maakte ik het globaal, wat betekent dat de actie-zoekmachine in alle weergaven werkt maar alleen de acties zal opsommen die relevant zijn voor de huidige weergave.

In de donkere kamer ondersteunt het ook module-multi-instanties, waardoor je direct een specifieke instantie kunt targeten (meer daarover in de documentatie).

Standaard is de globale actie-zoekopdracht gekoppeld aan de Ctrl+P-sneltoets, en kan hij worden geopend vanuit het globale menu Help, of met een knop Acties zoeken in het midden van de kopbalk. Dit vervangt ook enigszins de vimkeys die een zeer gedeeltelijke ondersteuning van GUI-acties hadden (en die accelerators volledig hadden moeten dupliceren om het uit te breiden tot een bruikbare staat), zodat je, in plaats van : gevolgd door een commando te typen, Ctrl+P kunt typen en ofwel het pad van de actie kunt typen, of een zoekopdracht kunt starten en uit de lijst met overeenkomsten kunt kiezen (met pijltjestoetsen en vervolgens Enter).

Overeenkomsten worden gesorteerd op afnemende relevantie (van boven naar beneden), en relevantie wordt berekend op basis van de positie van de overeenkomst. De aanname hier is dat, aangezien actiepaden van generiek naar specifiek gaan van links naar rechts (view/module/control), tekstovereenkomsten die aan het einde van het pad plaatsvinden verondersteld worden overeen te komen met bedieningselementen in plaats van modules of weergaven, die we als specifieker en daarom relevanter beschouwen. Dit kan voorkomen dat je langs alle module-inhoud omlaag moet scrollen die zou kunnen overeenkomen met een tekst-zoekopdracht die naar een bedieningselement zoekt.

Sneltoetsen-bewerkingsvenster

Aangezien er nu slechts één door de gebruiker configureerbare sneltoets per bedieningselement is, is de GUI voor het opsommen en bewerken van sneltoetsen een eenvoudige boom:

image

Het volstaat om in de kolom Toetsen te dubbelklikken om een nieuwe toetsencombinatie op te nemen. Omdat het zo eenvoudig is, vervangt dit venster meteen ook de cheatsheet. Sneltoetsen kunnen worden opgezocht op naam van de actie of op de gebruikte toetsen, en de toetszoekfunctie heeft automatische aanvulling voor toetsmodificatoren (details in de documentatie). De pop-up met sneltoetsen kan worden weergegeven vanuit het globale menu BewerkenSneltoetsen…

Documentatie binnen de software

Dit is de kwestie: het bijhouden van een actuele documentatie over standaardsneltoetsen is een gedoe, omdat het te fijnmazig is. Dit is het soort document dat verouderd zal zijn tegen de tijd dat je het klaar hebt met schrijven. Aangezien standaardsneltoetsen sowieso in de applicatie geïmplementeerd moeten worden, is de beste plek om ze te documenteren regelrecht daarbinnen, zodat het bijwerken automatisch gebeurt.

Darktable had 2 redundante GUI’s voor sneltoetsen, één om sneltoetsen in te stellen, en een andere „cheatsheet"-achtige pop-up (die lange tijd alleen toegankelijk was via… een sneltoets. Zoveel over vindbaarheid). De reden was dat de instellingen-pop-up veel te rommelig was om als enige vorm van snelle geheugensteun te dienen. Vervolgens werden sneltoetsen toegevoegd aan de tooltips van sommige bedieningselementen… tooltips die alleen verschijnen wanneer je met de muis over de bedieningselementen zweeft. Hoe onzinnig is het dat je je muis zou moeten gebruiken om per geval te ontdekken hoe je het toetsenbord gebruikt?

Ansel heeft sneltoetsen (toetsencombinaties) die in de instellingen-pop-up staan, en ook worden opgeroepen in de globale actiezoekfunctie. Maar die sneltoetsen zijn niet beperkt tot de door de gebruiker instelbare, ik heb de sneltoetsafhandeling uitgebreid met „virtuele sneltoetsen", dat wil zeggen sneltoetsen die in principe aan geen enkele actie gekoppeld zijn maar net als de rest gedeclareerd worden, en tussen de rest verschijnen als „vergrendelde" sneltoetsen.

image

Die sneltoetsen die worden beschreven als contextuele interactie bij focus documenteren de generieke acties die gericht zijn op het gefocuste bedieningselement, of het bedieningselement nu op een relatieve of absolute manier gefocust werd.

Implementatiedetails

De broncode voor het hele sneltoetsafhandelingssysteem, inclusief de GUI-onderdelen (globale actiezoekfunctie en pop-up voor het bewerken van sneltoetsen), gebruikt 1144 regels code, waarvan de helft de GUI, voor een cyclomatische complexiteit van 216 . Dat is een vijfde van het codevolume voor een zesde van de complexiteit (vergeleken met Darktable 5.0), ook al biedt het aanvullende functies (toetszoekfunctie met automatische aanvulling, globale zoekfunctie, expliciete doelen voor module-instanties, enz.). MIDI-ondersteuning is geschrapt omdat ik, eerlijk gezegd, niet zie welk probleem het oplost dat niet al opgelost is door het huidige eenvoudigere ontwerp.

Het vinden van de actie die aan een toetsaanslag gekoppeld is duurt ongeveer een tiental nanoseconden, terwijl de Grote MIDI-turducken 10 tot 50 milliseconden per aanslag kostte.8

De afhankelijkheidsgraaf van de functie is te zien in de ontwikkelaarsdocumentatie, ze is veel schoner dan de vorige spaghettikom. De rest van de GUI-code van Ansel werkt samen met de sneltoetsafhandeling door nieuwe versnellerpaden te declareren, callbackfuncties op te nemen die eraan gekoppeld zijn, en eventueel standaardsneltoetsen, dat alles met een enkele methode uit de API. De sneltoetsafhandeling is zich niet bewust van en immuun voor de interne werking van Ansel, in het bijzonder weet ze niets over modules of zelfgemaakte Bauhaus-widgets, dus het ontwerp is volledig op zichzelf staand. Voorkeuren worden per taal opgeslagen in ~/.config/ansel/keyboardrc-LANG, met gebruik van een native Gtk-versnellerkaart . De API is volledig gedocumenteerd in de ontwikkelaarsdocumentatie van Ansel voor toekomstig onderhoud en uitbreiding, dus er zal geen reverse-engineering nodig zijn.

image
image

Zo werk ik omdat ik niet voor mijn plezier programmeer. Ik heb geen plezier in programmeren. Ik los problemen op, terwijl ik probeer er geen nieuwe te creëren.

Conclusie

Dit is het schoolvoorbeeld van alles wat er misging in Darktable op het gebied van meedogenloze over-engineering en hoe het opgelost had moeten worden. De hier voorgestelde oplossing is beter omdat:

  1. de hele GUI kan vanaf het toetsenbord genavigeerd worden zonder ook maar één sneltoets te hoeven onthouden,
  2. bedieningselementen, modules en andere acties hebben slechts één directe, absolute door de gebruiker instelbare sneltoets, die rechtstreeks de actie activeert of de widget van het bedieningselement focust indien aanwezig, wat de instellingen-GUI een stuk minder overweldigend maakt en de noodzaak van een extra cheatsheet-pop-up wegneemt,
  3. acties zijn globaal doorzoekbaar en activeerbaar, of ze nu aan een toetsencombinatie gekoppeld zijn of niet,
  4. Gemengde Toets+scroll-interacties kunnen zonder extra kosten geactiveerd worden, waarbij MIDI-draaiknoppen en schuifregelaars worden geëmuleerd zonder dat er extra hardware nodig is,
  5. De code is objectief 5 tot 6 keer eenvoudiger (afhankelijk van welke metriek je in aanmerking neemt),
  6. Het geheel is volledig gedocumenteerd en uitvoerig van commentaar voorzien in de code,
  7. Elke toekomstige hersendode idioot die C kan lezen zal dit ding kunnen onderhouden, wat sowieso niet veel onderhoud zou moeten vergen omdat het niets slims doet en buiten de kern van de software leeft.

Translated from English by : Claude. 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. ↩︎