En mi artículo fundacional, Darktable: estrellándose contra el muro a cámara lenta, presenté el desastre que era el nuevo «gran turducken MIDI». El propósito de este turducken1 era reescribir el sistema de atajos de teclado para ampliarlo a los dispositivos MIDI.
A día de hoy, sigo furioso por esta empresa de destrucción masiva; aquí tienes un resumen de las razones:
- en 2021 reemplazó un sistema de atajos de teclado que era bastante bueno, funcionalmente completo, bien probado, estable y programado en menos de 1500 líneas (comentarios incluidos),
- …para añadir compatibilidad con dispositivos MIDI y mandos de PlayStation (!?!)…
- …pero en mi encuesta de Darktable de 2022, un año después de esta nueva función, de más de 1251 usuarios que participaron:
- el 81% de los usuarios no tenía un dispositivo MIDI ni pensaba conseguir uno,
- el 2% ni siquiera sabía lo que era un dispositivo MIDI.
- el 8% de los usuarios tenía un dispositivo MIDI pero no lo usaba con Darktable,
- el 6% se planteaba quizás conseguir un dispositivo MIDI en el futuro,
- el 2% de los usuarios tenía un dispositivo MIDI que realmente usaba en Darktable,
- el código era absolutamente terrible, en términos de:
- calidad del código: sentencias
if/switch-caseilegibles anidadas en 4 niveles, en medio de funciones de 1000 líneas (publiqué fragmentos de ejemplo en mi artículo), - volumen de código:
- 3546 líneas de código para Darktable 4.0,
- 4397 líneas de código para Darktable 5.0,
- el aumento de volumen es una consecuencia directa de intentar corregir errores en una arquitectura que no se puede arreglar porque su complejidad fomenta más complejidad. Todo eso surge del diseño, pero resolver los problemas creados por la complejidad añadiendo más complejidad no es una solución.
- complejidad del código:
- complejidad ciclomática :
- 1088 para Darktable 4.0,
- 1245 para Darktable 5.0 (detalles ),
- complejidad cognitiva :
- 1885 para Darktable 4.0,
- 2098 para Darktable 5.0 (detalles ).
- es con diferencia la función más compleja del software, aunque no opera sobre imágenes. A modo de comparación, la segunda función más compleja es la decodificación de metadatos EXIF, que tiene una complejidad cognitiva de 1348.
- complejidad ciclomática :
- calidad del código: sentencias
- no decodifica los modificadores de teclas por diseño, sino que solo trata las pulsaciones de teclas físicas, lo que significa que:
- la entrada «1» del teclado numérico se decodifica como
Keypad End, - la entrada «1» de un teclado francés AZERTY se decodifica como
Shift+&, oShift+"en BÉPO, - por lo tanto necesitas duplicar todos tus atajos basados en números para cada forma de introducir un número, y prepararte para que la ventana de configuración de atajos no contenga ningún número real en las combinaciones de teclas.
- la entrada «1» del teclado numérico se decodifica como
- el diseño de cara al usuario es absolutamente terrible, con demasiadas acciones y emulaciones que configurar («efectos»), que ni siquiera están completamente documentadas 4 años después (¿qué es «ctrl-toggle»? ¿«right-activate»?), y la configuración de atajos usa una extraña ventana dividida que no tiene ningún sentido,
- la implementación también es terrible: la función conoce toda la GUI del software, y la GUI del software conoce el código de los atajos. Aquí no hay modularidad, y cambiar cualquier cosa en el código de los atajos puede tener efectos inesperados e indeseados en cualquier parte del software.2 Basta con ver el grafo de dependencias de abajo,
- se pueden asociar varios «atajos» (o asignaciones MIDI) a la misma acción, lo que significa que cada interacción del usuario tiene que recorrer toda la lista de acciones disponibles, provocando una gestión de atajos muy ineficiente, retardos en la GUI en algunos casos y falsos positivos de «combinación de teclas desconocida» en casos peculiares.


El grafo de dependencias de src/gui/accelerators.c (el gran turducken MIDI) antes de la reescritura. Adivina por qué lo llamamos «código espagueti »… Esto deja claro que hay una dependencia bidireccional entre el código de los aceleradores y el resto del código de la GUI. Esto es una pesadilla de mantener.
Así que, dicho de otro modo, un programador (muy malo) reemplazó una función que funcionaba y era simple por una monstruosidad, con la aprobación del mantenedor (que jamás escribiría un código tan malo él mismo, pero está decidido a «no perder impulso» en las contribuciones, cueste lo que cueste), para complacer al 2% de la base de usuarios. Todo eso por una función secundaria (¿terciaria?).
A eso lo llamo prioridades equivocadas. Sobre todo porque hicieron falta varios meses de arduo trabajo de un desarrollador y muchos beta-testers, solo para empeorar las cosas, y luego el arduo trabajo se convirtió en una justificación para no revertir nunca el cambio, lo que se conoce como la falacia del coste hundido .
Soy un usuario, el código me da igual
Preocuparte por el código de las aplicaciones que usas es igual que preocuparte de si las tuberías que llevan el agua a tu casa son de plomo. No es tu trabajo, las tuberías están enterradas fuera de la vista, así que tienes todas las razones para no preocuparte —y las personas a cargo de la red de agua tienen todas las razones para que no te preocupes— pero tendrán un efecto sobre tu salud, y estos efectos serán invisibles hasta que sea demasiado tarde. Puede que el software no tenga un efecto directo sobre tu salud,3 pero la forma en que está hecho tendrá un impacto a largo plazo sobre ti.
Me encuentro en una posición incómoda —la de Casandra — teniendo que explicar a los usuarios que problemas que no conocen, no ven y les dan igual afectan muchísimo a la usabilidad y estabilidad cotidianas de las herramientas que usan, pero de formas no evidentes. En cualquier caso, estos problemas afectan directamente a la probabilidad de que cualquier mantenedor, algún día, resuelva los errores a los que se enfrenta.
Traer malas noticias te convierte en la mala noticia, pero traer malas noticias que a nadie le importan te convierte en un imbécil, aunque esas malas noticias expliquen los problemas extraños y aleatorios que se vuelven cada vez más comunes en los rastreadores de errores de Darktable desde 2021, que no se resuelven a lo largo de los años porque, a estas alturas, el código no contiene errores, es un error. Pero aquí tienes que enfrentarte a las burbujas de filtro : dependiendo de dónde mires en la web, o bien encontrarás gente profundamente contenta con Darktable (rendimiento, estabilidad, diseño), y gente profundamente descontenta con él. Mi observación empírica es que los que están contentos, de media, tienen ordenadores potentes y títulos en STEM.4 Los descontentos tienden a alejarse y a reducir el tiempo que pierden con el software, lo que significa que, si no los buscas activamente, solo alimentarás tu sesgo del superviviente . Y, aunque no le veo sentido a intentar convertir usuarios a Darktable (ni a ningún editor de fotos de código abierto), porque cada uno a lo suyo, filtrar y disuadir usuarios en función de su alfabetización informática es un gran fracaso para cualquier aplicación de edición fotográfica.
Como tipo con un canal de Youtube que explica cómo usar Darktable, y que da sesiones de formación individuales, tiendo a atraer opiniones más variadas que solo los rastreadores de incidencias de Github (donde las críticas al diseño se acallan bastante rápido de todos modos), y presencio de primera mano en videollamadas esos errores extraños, aleatorios e irreproducibles. Cualquier tipo privilegiado tiene tendencia a negar, minimizar o desestimar los testimonios sobre problemas a los que él mismo no se enfrenta, o incluso a culpar de los problemas a la persona que los reporta. Así que a menudo tengo esta impresión de dos universos paralelos que no se comunican pero que se miran por encima del hombro, cuando se trata de opiniones sobre Darktable. Por supuesto, los desarrolladores de Darktable eligieron mirar donde brilla el sol.
Y aunque Darktable parece un proyecto activo con un montón de funciones nuevas añadidas con regularidad por una docena de tipos, resulta aún más difícil explicar que lo que en realidad está ocurriendo entre bastidores es la destrucción de la base de código, porque la calidad del código se degrada mucho con el paso del tiempo, hasta que no será posible depurar en absoluto. El volumen de cambios no dice nada sobre la calidad, la fiabilidad o la mantenibilidad. Pero 2 siglos de capitalismo nos han condicionado a buscar funciones nuevas y relucientes, cueste lo que cueste, y en este sentido, Darktable cumple las expectativas.
El código nace roto. Cualquier software tiene errores. Peor aún, usamos un montón de bibliotecas de terceros para evitar «reinventar la rueda», pero esas bibliotecas cambiarán su API en el futuro, lo que significa que el código que funciona hoy dejará de funcionar mañana y tendrá que reelaborarse en el futuro, cuando cambien las dependencias. El código es como un jardín: cada nueva estación trae su cantidad de tareas. Se llama mantenimiento. Una vez reconocido esto, la sabiduría está en planificar formas de hacer el mantenimiento posible, primero, y luego fácil.
El buen código es código fácil de mantener. Es decir, el buen código está escrito de formas que son amables con las personas que tienen que leerlo, entenderlo y arreglarlo. No escribimos código para los ordenadores. No escribimos código para que las funciones «simplemente funcionen» ahora y en los próximos meses. El código no es algo que escondamos bajo un bonito capó con la esperanza de no volver a mirarlo jamás. El código es un organismo vivo que cuesta energía y tiempo mantener con vida.
El mantenimiento tiene un coste, uno que a menudo se subestima. No importa si tu producto es el mejor del mercado, si tus costes de mantenimiento son prohibitivos, los clientes (informados) lo evitarán. El mantenimiento es aburrido, poco interesante, nada sexy. El mantenimiento es lo opuesto a introducir cosas nuevas y relucientes: es asegurarse de que las viejas cosas polvorientas sigan funcionando en silencio. En las notas de versión de tu software, va al final. Los usuarios no se emocionan con las funciones mantenidas, pero se enfadarán por el código sin mantener y tienden a usar la métrica equivocada para evaluar cómo de bien se mantiene un proyecto (a saber: agitación y apariencia de trabajo). Así que no ganarás nada manteniendo el código, pero perderás por no mantenerlo, y te costará tanto si lo haces como si no.
El código es inmantenible cuando cada corrección de un error conduce a un nuevo error en otra parte, a la manera de un juego del topo, y la naturaleza de las correcciones es en realidad parcheo contextual y soluciones improvisadas que solo añaden complejidad al software. Cuando llegas a ese punto, tu única opción es reescribir desde cero el código incriminado. Lo que significa que el trabajo pasado acaba de crear más trabajo ahora. Y cuando ese trabajo pasado concreto ya sobrescribía trabajo anterior, nos embarcamos en la idiocracia.
Más código no es un éxito ni un logro. Los logros están en resolver los problemas del usuario. Aquí de nuevo, la mentalidad capitalista nos golpea con fuerza: querríamos medir el trabajo por el volumen de código añadido. Más código es solo más responsabilidad, más deuda técnica, más costes de mantenimiento. Esto es una disonancia cognitiva, ya que el software consiste precisamente en escribir código. Pero piénsalo así: hacer un coche no consiste en añadir más acero sobre 4 ruedas, porque en algún momento el coche tendrá que moverse. Necesitas la cantidad justa de acero, con la forma adecuada en los lugares adecuados: necesitas ser eficiente y parsimonioso. El software consiste en resolver los problemas del usuario con ordenadores, no en acumular código.
En los proyectos donde los colaboradores no cobran, el aburrido mantenimiento no es donde la gente quiere pasar sus sábados, y es una preocupación real en lo que respecta a la gestión de proyectos. Es aún más importante hacer que el mantenimiento sea fácil cuando la gente trabaja gratis. Por suerte, Darktable tiene cero gestión de proyectos, solo un enjambre de colaboradores aleatorios rascándose donde les pica a horas aleatorias, sin objetivo unificado, estrategia, calendario o división del trabajo. Por eso me gustaría que todo el mundo dejara de usar el término «equipo de Darktable». No hay ningún equipo de Darktable, porque no hay división del trabajo, ni liderazgo, ni calendario, ni hoja de ruta, ni planificación, ni objetivo, ni prioridades, ni visión, ni estrategia, ni método, ni diseño, ni comunicación antes de hacer las cosas, ni documentos de especificaciones para los problemas a resolver. Esto es el club de informática del instituto, formado por individuos desconectados.
Hay 3 cosas que un desarrollador necesita hacer para garantizar que el mantenimiento sea lo más fácil posible:
- escribir la menor cantidad de código: rastrear un error entre 300 líneas será mucho más rápido que entre 3000 líneas,
- escribir el código más simple: «simple» en el sentido de «la lógica funcional tiene pocos pasos, pocas suposiciones, pocos casos límite». Más simple significa más fácil de leer, entender y arreglar, pero también que habrá menos casos que cubrir al hacer pruebas. En la práctica, significa evitar casos contextuales, opciones de usuario, variantes,
if/elseen el código. - escribir código autocontenido: dividir la funcionalidad del software entre módulos (subprogramas) que se comunican a través del núcleo y están aislados unos de otros. A medida que crece el volumen de código, eso garantiza que los cambios que ocurran dentro de los módulos no tengan efectos imprevistos fuera.
Por último, tienes que evaluar si las nuevas funciones que añades merecen el coste adicional de mantenimiento. En el caso del gestor de atajos, estamos atendiendo las «necesidades» (o más bien, el lujo) del 2% de la base de usuarios con una implementación que es (como mínimo) el doble de complicada que la que reemplazó. Reflexiona sobre eso en tu mente capitalista…5
La complejidad del código puede medirse mediante la teoría de grafos, a través de la complejidad ciclomática, la complejidad cognitiva o la complejidad de N-caminos. Muchos artículos de investigación correlacionan el volumen de código y/o la complejidad del código con el número de errores ocultos, lo cual es bastante intuitivo: la línea de código que no escribes es la única en la que nunca tendrás errores. La simplicidad es el objetivo. Y, como descubrí por las malas, la complejidad de la GUI (front-end) suele ser una consecuencia directa de la complejidad del back-end. Hay muchos tipos que le aúllan a la luna para invocar la figura mística del diseñador de UI/UX, con la esperanza de resolver mágicamente la terrible GUI de Darktable (complicada e inconsistente). La GUI no existe en paralelo al back-end, solo conecta la entrada esperada por el back-end a controles gráficos. Simplificar el front-end no puede hacerse sin simplificar el back-end, esto es solo un dogma estúpido inventado por gente que solo tiene habilidades blandas. Pero simplificar back-ends es mucho más complicado que simplemente dibujar maquetas.
Resulta que lo que ocurrió con el gran turducken MIDI fracasó en los 3 aspectos: volumen de código, complejidad del código, autocontención del código. Y los desarrolladores de Darktable nunca aprenderán de sus errores, como demuestra Darktable 5.0. A medida que los errores de los atajos de teclado se fueron acumulando con el tiempo en Ansel (además de funciones terribles por diseño), intenté corregirlos de formas que evitaran reescribir todo el asunto, hasta que quedó bastante claro que no podría evitar la reescritura por mucho más tiempo.
Esto se llama deuda técnica . Todo el código del gran turducken MIDI estaba pensado para funcionar una vez escrito, no para ser mantenible a largo plazo. Es básicamente una prueba de concepto que nunca debería haber llegado a producción. Y la mismísima prueba de su inmantenibilidad es cuánto ha crecido el volumen y la complejidad del código de corrección en corrección, entre Darktable 3.8 y 5.0, volviéndolo aún más inmantenible a medida que se corrigen errores. Esta es la definición de quedarte pegado en una telaraña: cuanto más te mueves, más te quedas atascado.
Mi error quizás sea que empecé a plantear mis críticas a la (falta de) gestión de Darktable en 2022, después de tener la prueba de que la desorganizada manada de colaboradores nunca reconocería ni aprendería de sus errores. Aunque el ritmo de trabajo autoinfligido e insostenible se parecía más a crear un burn-out que a la reflexión. Desde entonces, Pascal Obry, el actual mantenedor autoproclamado de Darktable, ha estado intentando convencer a todo el mundo de que soy un miembro insoportable del equipo, incapaz de trabajar con otras personas que no están de acuerdo conmigo, que empezó a insultar a todo el mundo. Por supuesto, no se ha dado ninguna respuesta específica y técnica a los problemas que planteé en Darktable: estrellándose contra el muro a cámara lenta, solo vagas declaraciones tranquilizadoras y genéricas del tipo «Darktable es un proyecto activo, sano porque tiene muchos colaboradores». Como si más monos desorganizados por fin equivalieran a un ingeniero si añades los suficientes.
Creo que no he sido más que amable durante 4 años —demasiado amable, de hecho— hasta que me di cuenta de lo mucho que estos tipos han dañado el proyecto. No aprenden, y nunca aprenderán de sus errores, porque ni siquiera los reconocerán, y la evolución de Darktable 5.0 no hace más que confirmar la tendencia. Pero que se acumulen cada vez más errores extraños, aleatorios e irreproducibles en los rastreadores de errores es una pista bastante clara de que algo está profundamente mal. Sobre todo porque investigar esos errores me llevó a hacer arqueología en terribles montones de mierda de código que ni siquiera tenía 2 años, y aunque mi conocimiento y experiencia sobre la base de código de Darktable crecieron con los años, mi capacidad para corregir la causa raíz de los errores disminuyó y me enfrentaba a laberintos cada vez más desconcertantes de indirecciones de código .6
Es difícil explicar todo eso a personas que ven los ordenadores como cajas mágicas diseñadas por magos de la tecnología. Ahí no hay ninguna magia. Es sobre todo matemáticas y aplicaciones. Muchos desarrolladores confesarán que son malos en matemáticas, lo que significa que también son malos programando. Porque las matemáticas no consisten solo en ejecutar operaciones aritméticas, son toda una disciplina de la mente que permite abstraer problemas complejos para descomponerlos en problemas simples, que conducirán a un código simple y a una GUI simple. Los buenos programadores dedican mucho tiempo a pensar cómo escribir poco código. Porque el código es una responsabilidad, el código es deuda técnica, el código es costoso de mantener. Así que pagas la deuda por adelantado, sin intereses, pensando mucho tu código antes de escribirlo, en lugar de programar primero y luego pasar años apagando incendios.
Todo esto plantea también la cuestión de quién es dueño del código de código abierto y de los proyectos de código abierto. Desde que el fundador del proyecto Darktable, Hanatos, abandonó el barco, así como toda la primera generación de desarrolladores, por diversas razones, el último hombre en pie de la primera generación se autonombró nuevo mantenedor. Es un desarrollador muy capaz y hábil: su código es limpio, ordenado, y hasta ahora no he encontrado ningún error en una línea donde git blame dijera «Pascal Obry». Pero su política respecto a la gestión del código es terrible: cree que toda contribución es una buena contribución, que no se debe disuadir el «impulso» de las contribuciones, y es propiamente incapaz de decir «no» a las contribuciones, lo que significa que casi todos los pull requests se fusionan. Es la Francia de 1940: todo entra, se recibe con una gran sonrisa y el Führer obtiene el doble de judíos de los que pidió. Mientras tanto, a los resistentes se les llama terroristas.
Pero hay una enorme diferencia entre la calidad del código que escribe Pascal y la calidad del código que acepta y fusiona. Esta es una paradoja preocupante que encuentra sus raíces entre el miedo a perderse algo y el tecnopositivismo radical , que peca de dogma y creencias, con un desprecio total por el diseño y la perspectiva del usuario. Y quizás una confianza exagerada en la capacidad de la «Comunidad» para corregir errores más adelante.
Hay muchos casos en los que no hacer nada es mejor que hacerlo mal, especialmente cuando estás reemplazando funciones existentes. Al ser Darktable un editor de código no destructivo, inviertes en él cuando empiezas una base de datos de ediciones de imágenes. A menos que planees exportar todas tus imágenes a archivos de alta resolución y alta profundidad de bits, en cuanto termines de editarlas, para no cambiarlas nunca más. Eso crea una expectativa legítima de estabilidad y consistencia a largo plazo, para que tus viejas ediciones aún se puedan abrir, retocar y exportar de nuevo, quizás a nuevos formatos, quizás a resoluciones más altas. Cambiar de rumbo en el diseño de la aplicación es una especie de incumplimiento de contrato, y aunque la licencia GNU/GPL anula cualquier responsabilidad legal, no anula los daños a los usuarios. Ni tampoco reembolsa los años de mi vida que perdí arreglando su mierda.
Así que, incluso dejando de lado los problemas de mantenimiento centrados en el desarrollador, también hay una discusión que tener respecto a quién decide cómo se reemplazan las funciones viejas y probadas, especialmente esas funciones básicas y universales de las aplicaciones de escritorio como el manejo de archivos (importar, exportar, navegar) o la interacción con el ratón y el teclado, que han sido tan omnipresentes durante tanto tiempo que la década de 2020 llega 30 años tarde para pretender reinventarlas.
Se invirtió mucho trabajo y muchas horas-hombre en empeorar los atajos de teclado, en aras de políticas defectuosas y dogmas dañinos, además de ilusiones y holgazanería social donde todo el mundo espera que La Comunidad® (alias otra persona) se encargue de corregir sus propios errores. Los usuarios también tuvieron que perder su configuración de teclado y se les exigió reiniciar todo y volver a aprenderlo todo. Pero hacerlo de la forma correcta en realidad habría costado menos trabajo y menos horas-hombre. Este es un bucle de locura que se autoalimenta, creando un entorno de trabajo tóxico donde la inestabilidad fomenta más inestabilidad, donde la complejidad fomenta más complejidad, de nuevo sin ningún tipo de hoja de ruta de funciones que diera una dirección general y visibilidad a todos los implicados.
Una breve historia de mal diseño
Solo después de reconstruir la función de atajos desde cero entendí qué había salido mal en los atajos/aceleradores de Darktable.
Al principio, está este desenfreno incapacitante de funciones, que hace tentador despejar la GUI simplemente ocultando funciones, solo para manejarlas desde el teclado. El problema es entonces que tales funciones no siempre son de nicho y opcionales (como el atajo que evita las interacciones con máscaras al arrastrar y soltar la vista previa de la imagen principal en el cuarto oscuro), sino que son definitivamente no descubribles por los usuarios. Ansel resolvió este problema con el menú global.
Algunas funciones estaban ocultas mediante una compatibilidad básica con vimkeys, si empiezas a introducir :: :q cerrará la aplicación, :set seguido del nombre del deslizador o del cuadro combinado cambiará el valor. Esto por supuesto no está documentado en ninguna parte del manual de Darktable, y como usuario de Darktable durante más de una década, nunca había oído hablar de ello antes de borrar su código, porque esta pequeña broma escucha todas tus pulsaciones de teclas para determinar si debe actuar sobre lo que escribes o no.
Pero luego, también está el hecho de que los módulos usan widgets Gtk caseros (llamados «Bauhaus», en src/bauhaus/bauhaus.c) que no implementan todo lo que esperarías de un widget de GUI que captura eventos del usuario, especialmente no las funciones de accesibilidad.
Una de las funciones de accesibilidad más básicas es la capacidad de recorrer los widgets enfocables. En la jerga de la GUI (y de Gtk), un widget enfocable es aquel que puede capturar eventos de pulsación de teclas una vez enfocado. Los widgets se enfocan normalmente al hacer clic en ellos, pero Gtk también gestiona internamente una cadena de foco por la que navegas con la tecla Tab y las teclas de flecha. El primer problema es que la tecla Tab, en Darktable, estaba asignada al modo «vista previa» (activar/desactivar todos los paneles de la vista). Y de hecho, usando aceleradores nativos de Gtk, esto no habría sido posible en absoluto, ya que Tab está asignada por Gtk y prohibida en los atajos definidos por el usuario, pero como Darktable implementó su propio gestor de atajos, incluso antes del gran turducken MIDI, se sobrescribió. Así que el recorrido de la cadena de foco basado en la tabulación se deshabilitó simplemente porque la tecla Tab estaba asignada a otra cosa. Pero el segundo problema era que los widgets Bauhaus caseros también capturaban todas las pulsaciones de las teclas de flecha. Por lo tanto, navegar entre controles de forma secuencial (siguiente/anterior) también era completamente imposible, por diseño. Igual que los widgets Bauhaus capturaban todos los eventos de desplazamiento del ratón, impidiendo desplazar los paneles laterales.
Como la navegación secuencial/incremental entre widgets enfocables era imposible por diseño, todas las interacciones de teclado tenían necesariamente que hacerse absolutas: para cada deslizador, para cada cuadro combinado, tendrías un atajo que aumenta/disminuye/reinicia el valor asignado directamente.
Pero surgieron más problemas cuando los módulos se hicieron multi-instanciables, porque los controles se identificaban mediante una ruta de acelerador como view/module/slider/increase o view/module/slider/decrease, pero todas las instancias de un módulo heredaban la misma ruta. Eso desencadenó la necesidad de gestionar todo eso en tiempo de ejecución, con preferencias de usuario para decidir si el módulo objetivo sería el primero, el último o el último con el que se interactuó.
Generalizar todo eso a MIDI y mandos de juego solo lo empeoró, porque además de tener un atajo por cada acción posible por cada widget, luego hacía falta gestionar la emulación de la interacción típica de escritorio desde otros dispositivos de entrada. Pero en lugar de gestionar la capa de emulación a alto nivel, en la interfaz entre MIDI y los atajos normales de teclado/ratón, un desarrollador terrible sobreingenió por completo una capa de abstracción de acciones, incrustada en los módulos, en los widgets Gtk nativos (como una superposición) y en los widgets Bauhaus caseros (profundamente incrustada). El problema es que esa capa de abstracción no era realmente una capa sino más bien un tumor metastásico que se extendía por todas partes. Eliminarla y todas sus dependencias condujo a la eliminación de 7674 líneas repartidas por 163 archivos, aunque se suponía que estaba implementada en src/gui/accelerators.c (4412 líneas de código, comentarios y líneas en blanco).
Rediseñar desde cero los atajos de teclado/aceleradores
El paso cero del rediseño son estos 2 requisitos simples:
- Toda acción debería ser descubrible en la GUI; los atajos de teclado no están pensados para despejar la GUI; despejar la GUI es cuestión de dividir el flujo de trabajo en pasos unitarios y presentar solo los controles que importan al paso actual.
- El software debería ser completamente utilizable solo con el ratón y solo con el teclado. La interacción mixta debería ser completamente opcional.
La primera restricción del rediseño es por lo tanto hacer que los atajos absolutos sean completamente opcionales, es decir, diseñar el flujo de trabajo de teclado para un acceso secuencial/relativo (recorriendo el control siguiente/anterior).
Interacción con los controles enfocados
El paradigma dividido «enfocar» y luego «interactuar» (que no inventé yo…) es muy potente porque permite acotar las pulsaciones de teclas al contexto adecuado, lo que significa que las mismas pulsaciones (especialmente las teclas de flecha Up/Down/Right/Left) se pueden asociar más de una vez en la GUI, y manejarse de forma diferente según qué contexto tenga el foco. Esto es más flexible y en realidad más simple7 que la obsesión de Darktable de conectar todo a atajos globales y absolutos que luego colisionarían en teclas reutilizadas a menudo, y llevaría a tener que añadir cada vez más modificadores de teclas como solución improvisada.
Hasta ahora, navegar por los controles solo les daba foco. ¿Y la interacción real?
En la vista de mesa de luz, una vez enfocada la cuadrícula de miniaturas, la navegación por las miniaturas se hace usando las típicas teclas de flecha, Page Up/Down, etc. (consulta la documentación para todos los detalles) y las selecciones de imágenes se pueden hacer de varias formas (por lotes, en serie, individualmente) también desde el teclado.
En la vista de cuarto oscuro, cambiar valores en deslizadores y cuadros combinados se puede hacer usando las teclas de flecha (posiblemente usando Shift para un paso grueso o Ctrl para un paso fino), activar el selector de color (en los deslizadores que lo admiten) se hace con Insert, etc. (de nuevo, lee la documentación para más detalles).
Así que, de nuevo, hasta ahora ningún atajo definido por el usuario, ninguna combinación de teclas críptica que recordar, son sobre todo las teclas de flecha y aun así se puede acceder a todo.
Enfoque absoluto de controles
Todo eso está muy bien, pero te deja en una especie de recorrido de Ikea cuando necesitas acceder directamente a alguna zona de la aplicación pero tienes que navegar por todas las secciones desde la puerta de entrada.
Para aliviar eso, una función muy antigua de Darktable era la pestaña de «módulos favoritos», alias una pestaña especial que duplicaba la GUI de los módulos más usados, según la elección del usuario. Eso es básicamente resolver el exceso con más exceso, y aun así los usuarios le han cogido mucho cariño. Aunque si nos centramos en el objetivo, en lugar del medio, el requisito es un acceso rápido a módulos arbitrarios, lo cual es perfectamente comprensible.
Así que esto se reimplementó como una forma de definir un atajo absoluto que enfoca inmediatamente un módulo de procesamiento de imágenes o cualquiera de sus controles internos. Enfocar un módulo o control oculto hará que aparezca automáticamente en la GUI. A partir de ahí, la interacción posterior se hace exactamente como antes, usando combinaciones de teclas uniformes. Esto reduce muchísimo el número de atajos que configurar, hace que todo el asunto sea más genérico y que la ventana de configuración de atajos sea mucho más simple.
Los atajos absolutos también pueden apuntar a entradas de menú (alias acciones globales), en cuyo caso se recordarán en el menú, junto a la etiqueta de la acción, lo cual es de nuevo una función nativa de Gtk que viene con cero sobrecarga.
Reemplazar los controladores MIDI
Si dejamos de lado todo el bombo de tener controladores dedicados y sentirse como un piloto de avión, lo único que los controladores MIDI tienen que el teclado y el ratón nunca tendrán es la capacidad de asociar potenciómetros (perillas giratorias) directamente a los deslizadores de la GUI. Pero entonces, el coste de eso es tener un dispositivo adicional ocupando espacio (y acumulando polvo) en tu escritorio, por no mencionar la futura basura electrónica. Todos los fotógrafos que conozco que compraron MIDIs o Loupedecks los guardaron «temporalmente» para recuperar algo de espacio en el escritorio… y nunca los sacaron del almacén.
Los controles de procesamiento de imágenes se pueden conectar a atajos de una sola tecla, porque Ansel usa una superposición de atajos personalizada sobre las funciones de aceleradores nativas de Gtk. He modificado los widgets Bauhaus caseros de modo que, al pulsar uno de los atajos de enfoque absoluto de arriba y mantenerlo presionado, el desplazamiento de la rueda del ratón se asigne directamente al widget aunque el ratón no esté sobre el deslizador adecuado.
Así que combinando atajos de una sola letra para enfocar un control, con el desplazamiento del ratón (o del panel táctil), obtienes todas las bondades de las perillas giratorias MIDI sin el dispositivo adicional, la biblioteca adicional para darle compatibilidad y las maravillas de las capas de emulación sobreingeniadas.
Pero todo eso sigue ocurriendo con el único y solo atajo absoluto, así que no tienes que definir y recordar varios atajos por control, solo para encontrarte sin combinaciones de teclas disponibles.
Acciones sin atajo, motor de búsqueda y disparadores tipo vimkeys
Mi gestor de atajos es un envoltorio ligero sobre la API de aceleradores nativa de Gtk. Como tal, una acción se define mediante una ruta de texto, como Ansel/Darkroom/Modules/Exposure/Black level, que es un identificador único, legible tanto por un ordenador como por un humano. La GUI declara una función asociada a cada una de estas rutas, que contiene el código que se ejecuta para aplicar la acción correspondiente. Luego el gestor de atajos asigna una combinación de teclas a esta ruta.
Así que, cada vez que un usuario pulsa algunas teclas, el gestor de atajos comprueba si tenemos una ruta conocida para esta combinación, y si encuentra una, activa la función asociada a ella. Este es un diseño a prueba de tontos que no sabe nada sobre el interior de los módulos de Ansel, los widgets caseros, etc. Así que se puede ampliar a muchas partes del software sin sobrecarga.
Pero en realidad es mucho más potente que solo eso. Porque al listar todas las rutas conocidas, podemos entonces devolver las rutas que coinciden con una consulta de búsqueda de texto (buscando un control, módulo o entrada de menú global por nombre), es decir, encontrar todas las acciones de una lista, pero entonces también podemos activarlas aunque no estén asociadas a ningún atajo.

Como los módulos de procesamiento de imágenes también se pueden encontrar y mostrar de esta forma, esto también reemplaza el motor de búsqueda de módulos, recuperando algo de espacio vertical para los módulos largos y sus opciones de enmascarado/fusión (y eliminando alrededor de 200 líneas de código). A partir de ahí, lo amplié a todas las cajas de herramientas y lo hice global, lo que significa que el motor de búsqueda de acciones funciona en todas las vistas pero solo listará las acciones relevantes para la vista actual.
En el cuarto oscuro, también admite las multi-instancias de módulos, permitiendo apuntar directamente a una instancia específica (más sobre eso en la documentación).
Por defecto, la búsqueda global de acciones está asignada al atajo Ctrl+P, y se puede acceder desde el menú global Ayuda, o con un botón Buscar acciones en el centro de la barra de encabezado. Esto también reemplaza en cierto modo las vimkeys que tenían una compatibilidad muy parcial con las acciones de la GUI (y habrían necesitado duplicar por completo los aceleradores para ampliarlas a un estado útil), de modo que, en lugar de escribir : seguido de un comando, puedes pulsar Ctrl+P y o bien escribir la ruta de la acción, o bien empezar una consulta y elegir de la lista de coincidencias (usando las teclas de flecha y luego Enter).
Las coincidencias se ordenan por relevancia decreciente (de arriba abajo), y la relevancia se calcula a partir de la posición de la coincidencia. La suposición aquí es que, siendo las rutas de acción de genéricas a específicas de izquierda a derecha (view/module/control), se supone que las coincidencias de texto que ocurren al final de la ruta coinciden con controles más que con módulos o vistas, que consideramos más específicos y por tanto más relevantes. Esto puede evitar tener que desplazarse hacia abajo pasando todo el contenido de los módulos que podría coincidir con una consulta de texto que busca un control.
Ventana de edición de atajos
Como ahora solo hay un atajo configurable por el usuario por control, la GUI para listar y editar atajos es un simple árbol:

Basta con hacer doble clic en la columna Teclas para empezar a grabar una nueva combinación de teclas. Como es tan simple, esta ventana también reemplaza la chuleta de una vez. Los atajos se pueden buscar por nombre de la acción o por teclas usadas, y la búsqueda de teclas tiene autocompletado para los modificadores de teclas (detalles en la documentación). La ventana emergente de atajos se puede mostrar desde el menú global Editar → Atajos de teclado…
Documentación dentro del software
La cuestión es esta: mantener una documentación actualizada sobre los atajos predeterminados es un fastidio, porque es demasiado detallada. Es el tipo de documento que quedará obsoleto para cuando termines de escribirlo. Dado que los atajos predeterminados hay que implementarlos en la aplicación de todos modos, el mejor lugar para documentarlos es directamente dentro de ella, así la actualización es automática.
Darktable tenía 2 interfaces de atajos redundantes, una para configurar los atajos, y otra que era una especie de ventana emergente “chuleta” (la cual, durante mucho tiempo, solo era accesible mediante… un atajo. Vaya manera de facilitar su descubrimiento). El motivo era que la ventana emergente de ajustes estaba demasiado abarrotada como para usarla a modo de recordatorio rápido. Luego, se añadieron atajos en las descripciones emergentes (tooltips) de algunos controles… descripciones que solo aparecen al pasar el ratón por encima de los controles. ¿Qué sentido tiene que debas usar el ratón para descubrir cómo usar el teclado caso por caso?
Ansel tiene atajos (combinaciones de teclas) escritos en la ventana emergente de ajustes, y también recordados en la búsqueda global de acciones. Pero esos atajos no se limitan a los que son configurables por el usuario; extendí el gestor de atajos con “atajos virtuales”, es decir, atajos básicamente asociados a ninguna acción pero declarados igual que el resto, y que aparecen entre los demás como atajos “bloqueados”.

Esos atajos descritos como interacción contextual al enfocar documentan las acciones genéricas dirigidas al control enfocado, tanto si el control se enfocó de manera relativa como absoluta.
Detalles de implementación
El código fuente de todo el sistema del gestor de atajos, incluidas las partes de la interfaz gráfica (búsqueda global de acciones y ventana emergente de edición de atajos), usa 1144 líneas de código, la mitad de ellas correspondientes a la interfaz gráfica, para una complejidad ciclomática de 216 . Eso es una quinta parte del volumen de código para una sexta parte de la complejidad (en comparación con Darktable 5.0), a pesar de que ofrece funciones adicionales (búsqueda de teclas con autocompletado, búsqueda global, objetivos explícitos de instancias de módulos, etc.). Se ha eliminado la compatibilidad con MIDI porque, francamente, no veo qué problema resuelve que no esté ya resuelto por el diseño más simple actual.
Encontrar la acción asociada a una pulsación de tecla lleva alrededor de una docena de nanosegundos, mientras que el Gran turducken MIDI tardaba de 10 a 50 milisegundos en cada pulsación.8
El grafo de dependencias de la función puede verse en la documentación para desarrolladores; es mucho más limpio que el anterior plato de espaguetis. El resto del código de la interfaz gráfica de Ansel interactúa con el gestor de atajos declarando nuevas rutas de aceleradores, registrando funciones de retorno (callback) asociadas a ellas, y posiblemente atajos predeterminados, todo ello usando un único método de la API. El manejo de atajos desconoce por completo los aspectos internos de Ansel y es inmune a ellos; en particular, no sabe nada sobre módulos ni sobre los widgets Bauhaus caseros, así que el diseño es totalmente autónomo. Las preferencias se guardan por idioma en ~/.config/ansel/keyboardrc-LANG, usando un mapa de aceleradores nativo de Gtk . La API está completamente documentada en la documentación para desarrolladores de Ansel para su futuro mantenimiento y ampliación, así que no hará falta ninguna ingeniería inversa.


Así es como trabajo, porque no programo por diversión. No me divierto programando. Resuelvo problemas, intentando no crear otros nuevos.
Conclusión
Este es el caso emblemático de todo lo que salió mal en Darktable en términos de sobreingeniería incesante y de cómo debería haberse arreglado. La solución que se propone aquí es mejor porque:
- toda la interfaz gráfica puede navegarse desde el teclado sin tener que recordar un solo atajo,
- los controles, módulos y otras acciones tienen solo un atajo directo, absoluto y configurable por el usuario, que activa directamente la acción o enfoca el widget de control si lo hay, lo que hace que la interfaz de ajustes sea mucho menos abrumadora y elimina la necesidad de una ventana emergente de chuleta adicional,
- las acciones pueden buscarse y activarse globalmente, estén o no vinculadas a una combinación de teclas,
- las interacciones mixtas de tecla+desplazamiento pueden activarse sin coste adicional, emulando las ruedas y deslizadores giratorios MIDI sin necesidad de hardware extra,
- el código es objetivamente de 5 a 6 veces más simple (según la métrica que consideres),
- todo el sistema está completamente documentado y ampliamente comentado en el código,
- cualquier futuro idiota descerebrado capaz de leer C podrá mantener esto, que de todos modos no debería necesitar mucho mantenimiento porque no hace nada ingenioso y vive fuera del núcleo del software.
Translated from English by : ChatGPT, Claude. In case of conflict, inconsistency or error, the English version shall prevail.
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. ↩︎
Developers call that “whack-a-mole bug fixing” . ↩︎
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. ↩︎
STEM: Science Technology Engineering Mathematics ↩︎
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… ↩︎
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
ifto 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. ↩︎Flexibility is usually the opposite of simplicity, so you have to enjoy when you can win on both fronts. ↩︎
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. ↩︎