Autor: Joseph Lee

Basado en el complemento StationPlaylist para NVDA 21.10

Prólogo y notas de 2021

Esta guía ha pasado por muchas revisiones, cambios de estilo, y se ha actualizado para incluir características de las últimas versiones del complemento. Cuando se publicó por primera vez en 2015, se elaboró como una serie de entradas de blog. Ahora en 2021, se está editando para eliminar las huellas del estilo anterior y actualizándose para reflejar las últimas características del 2021 y más allá.

En 2018 también se expandió el ámbito del complemento para cubrir StationPlaylist Creator y la herramienta de pista. En su mayor parte, esta guía todavía cubrirá StationPlaylist Studio únicamente, pero hay cambios importantes que se han hecho en las versiones más recientes que nos harán plantearnos añadir otros programas de la suite StationPlaylist. En particular, la jerarquía de herencia de la clase de elemento de pista ha cambiado, por lo que muchas órdenes de navegación por columnas están disponibles al tratar con pistas en las aplicaciones de SPL. Como resultado, el propio complemento se renombró en 2019 a «StationPlaylist».

Más tarde en 2018, la función de búsqueda de actualizaciones se eliminó en favor del complemento Add-on Updater. Aunque el código fuente de esta función ya se ha ido, la información relacionada con él permanecerá aquí con motivos de completitud (a fin de cuentas, el complemento Add-on Updater tiene sus raíces en el complemento de Studio).

Después vino 2019, y con él llegaron Python 3, las clases abstractas, y nuevos tipos de codificadores. Aquellos días de tratar sólo con los codificadores SAM y SPL se acabaron, y se ha rediseñado el soporte de codificadores de arriba a abajo en 2020. En comparación con versiones antiguas del complemento, el ámbito de la extensión global de utilidades de SPL se ha reducido en favor de una mayor autonomía para los módulos de aplicación de soporte del codificador (mayoritariamente el motor de SPL). Aparte de esto, se ha actualizado el complemento para apoyarse estrictamente en Python 3.

En 2020, el complemento ha sufrido otro cambio importante: eliminación de características innecesarias y separación de la gestión de perfiles de emisión y las opciones del complemento. Antes, la gestión de ajustes del complemento se encontraba íntimamente relacionada con los perfiles de emisión, y esa era la razón por la que el panel de perfiles de emisión era parte integral del diálogo de opciones del complemento. A principios de 2020, salieron a la luz varios fallos causados por decisiones de diseño de hace años, tales como la aplicación de los ajustes en el perfil equivocado. Junto a la necesidad de hacer los paneles de opciones del complemento independientes entre sí para permitir a los usuarios abrir el panel de alarmas desde cualquier parte (descrito más adelante), se decidió separar el panel de perfiles de emisión y ponerlo en su propio diálogo.

Otro gran cambio de 2020 fue la eliminación de características innecesarias y problemáticas. Durante años, los usuarios de Window Eyes recibían soporte mediante un diseño de órdenes dedicado en la capa del asistente de SPL. Según ha ido decayendo el uso de Window Eyes, el diseño de órdenes dedicadas se ha eliminado. Otra función eliminada es la de perfiles de emisión basados en tiempo (disparados), ya que quedó claro que definir perfiles de cambio instantáneo es suficiente, y la característica en sí tiene problemas de diseño que se han descubierto en los últimos años. Al igual que la función de actualización del complemento, la función de perfiles basados en tiempo se describirá para proporcionar una visión general histórica.

2021 puede convertirse en un punto de inflexión para el complemento. Yo (Joseph Lee) dejaré de mantener este complemento. Tengo la esperanza de que los nuevos encargados de su mantenimiento (quienes quiera que sean) sigan adelante y hagan fantásticas mejoras a este complemento.

Introducción

Si eres locutor de radio, podrías estar acostumbrado a las actividades involucradas al producir un programa. Aquí se pueden incluir la selección de la lista de reproducción, programación de notas de interrupción, respuesta a peticiones, monitorización del número de oyentes y estado de la codificación, entre otras. Para asistir a un locutor, se usa un programa de automatización de la emisión, y uno de los más populares se llama StationPlaylist Studio.

En Complementos de NVDA vistos por dentro: StationPlaylist, aprenderemos qué es Studio (y la suite de aplicaciones de SPL) y cómo funciona este complemento de NVDA. No tienes que instalar o utilizar este potente complemento para entender sus peculiaridades, (usar el complemento podría ayudarte a apreciar la importancia de este material; para una experiencia más completa, es útil que tengas el código fuente del complemento ante ti mientras navegas por este artículo). Ahora, empecemos aprendiendo más sobre la suite de aplicaciones SPL.

Nota: a lo largo de esta guía, a menos que se especifique lo contrario, los términos «StationPlaylist», «SPL» y «Studio» hacen referencia a la misma cosa.

Presentación de la suite de aplicaciones de StationPlaylist y el complemento para NVDA

La suite de StationPlaylist es una colección de programas para ayudar a los locutores a planear, ejecutar y hacer actividades relacionadas con la emisión. Las aplicaciones constan de Studio, Creator, la herramienta de pista, Streamer y otras.

StationPlaylist Studio es un software de automatización de emisiones que ayuda a los locutores a planificar pistas, reproducir jingles y más. Incluye soporte para notas de interrupción, listas de reproducción por cada hora, etiquetado de pistas y viene con herramientas para gestionar la reproducción de pistas, como establecer la introducción de pista. En Studio 5.00 y posteriores, se incluye un codificador de flujos propio.

StationPlaylist Creator y Remote Voice Track (VT) se usan mayoritariamente para planificar un programa y diseñar listas de reproducción para que las use Studio. Pueden usarse para definir grupos de spots, categorías personalizadas de pista y más. Mientras que Creator se limita sólo a listas de reproducción locales, Remote VT se usa para gestionar listas de reproducción almacenadas en un ordenador remoto.

La herramienta de pista de StationPlaylist se usa principalmente para gestionar pistas. Con frecuencia se emplea para definir introducciones, puntos clave y otras propiedades de las pistas.

StationPlaylist Streamer es útil para emitir un programa con algo diferente a Studio. Por tanto, viene con soporte para diversos codificadores y módulos procesadores de señal digital (DSP).

Además de los componentes anteriores, la suite de StationPlaylist incluye herramientas adicionales como el grabador de pistas de voz, y un conjunto de módulos internos de soporte como el motor SPL para procesar DSP y otras tareas.

¿Es la suite de Studio accesible? Sorprendentemente, sí. Es posible utilizar las funciones de Studio sin usar scripts para lectores de pantalla y complementos. Sin embargo, hay veces en las que un locutor usaría scripts, como el anunciado de cambios de estado, monitorización de inicio y fin de pistas, soporte mejorado para codificadores y demás, y el complemento de NVDA para StationPlaylist (al que nos referiremos normalmente como SPL) resuelve todo esto bien.

Complemento StationPlaylist: una historia

En 2011, Geoff Shang, un locutor ciego experimentado, comenzó a trabajar en el complemento para SPL Studio. Esta versión temprana (numerada como 0.01) fue desarrollada para hacer que NVDA verbalizara diversos cambios de estado, como la alternancia de automatización y así. Esta versión inicial, co-desarrollada con James Teh (uno de los principales desarrolladores del lector de pantalla NVDA) se consideró como un proyecto rápido, y el desarrollo cesó hasta 2013.

En 2013, yo (Joseph Lee) recibí varios correos relacionados con el soporte de NVDA para SPL Studio con una petición de que alguien escribiera un complemento para él. Como todavía era nuevo en el desarrollo de complementos (esto vino después de que desarrollara Control Usage Assistant y GoldWave), decidí aceptar este reto para aprender más sobre Python y practicar lo aprendido en los laboratorios de ciencias de la computación en UC Riverside. Primero descargué el complemento existente (0.01) e instalé Studio 5.01 en mi ordenador para aprender más sobre este programa y recopilar sugerencias de otros usuarios de SPL. Después de un mes de desarrollo y versiones de prueba, liberé el complemento para Studio 1.0 en enero de 2014.

La mayoría de las primeras versiones (1.x, 2.x, 3.x, publicadas a lo largo de 2014) fueron principalmente proyectos rápidos que reducían la brecha entre NVDA y otros lectores de pantalla (los scripts para JAWS de Brian Hartgen fueron mi fuente de inspiración y he estudiado la documentación de los scripts para Window Eyes creados por Jeff Bishop). Estas primeras versiones, que daban soporte a Studio 4.33 y versiones posteriores, se utilizaron también para solucionar fallos encontrados por usuarios de Studio – por ejemplo, un locutor subió un vídeo a YouTube explicando que NVDA no estaba leyendo los cuadros de edición, cosa que se solucionó rápidamente. Las siguientes versiones (4.x, 5.x, 6.x, publicadas a lo largo de 2015), profundizaron en reducir las diferencias con otros lectores de pantalla e introdujeron características únicas (por ejemplo, el complemento 5.0 vino con un diálogo de configuración, y el 6.0 introdujo el concepto de perfil de emisión). Ya avanzado 2016, viendo que algunos de mis complementos estaban adoptando el esquema año.mes para las versiones, decidí cambiar SPL para que siguiera este modelo tras recibir comentarios de la comunidad de NVDA. En el momento en que escribo este artículo, se está llevando a cabo otra transformación significativa en las versiones 20.x y 21.x.

Lo reseñable de las versiones pasadas principales y de mantenimiento es lo siguiente:

  • 1.x: versión inicial, añadida alarma de fin de pista y otras características.
  • 2.x: buscador de pista y procedimientos mejores para reconocer versiones de Studio.
  • 3.x: primera versión de soporte extendido (lts), explorador de Cart, soporte para el codificador SAM y eliminación de la necesidad de permanecer en la ventana del codificador durante los intentos de conexión. Esta fue la última versión que dio soporte a Studio 4.33.
  • 4.x: escaneo de biblioteca, soporte para el codificador de SPL y Studio 5.10.
  • 5.x: dial de pista, diálogo de configuración dedicado.
  • 6.x: perfiles de emisión, flujo de metadatos, búsqueda de columnas y reordenado de verbalizaciones.
  • 7.x: segunda versión lts, actualizaciones del complemento, cambio de perfiles basado en tiempo, explorador de columnas de pista y otros. Esta es la última versión que da soporte a Studio 5.01. Se renombró a 15.x ya entrado el 2016.
  • 16.10 (también conocida como 8.0): explorador de columnas para la herramienta de pista, restablecimientos selectivos de datos.
  • 17.04 (conocida como 9.0): navegación vertical por columnas, instantáneas de listas de reproducción, soporte para Studio 5.20.
  • 17.08 (10.0): notificación de solicitud de oyente, supresión de la verbalización de cabeceras de columna. Esta es la última versión principal, con las siguientes versiones usando entrega continua.
  • 17.12: fin de soporte para versiones antiguas de Windows, reorganización de los ajustes del complemento, puntos de extensión.
  • 18.06: respuesta ante nuevas características de NVDA, transcripción de listas de reproducción, soporte para WXPython 4, instantáneas parciales de la lista de reproducción, expansión del ámbito del complemento.
  • 18.09: tercera versión LTS, paneles de configuración del complemento, lista con casillas de verificación, WXPython 4.
  • 19.01: función de actualización del complemento eliminada, indicadores de compatibilidad para versiones futuras de NVDA.
  • 19.07: se renombra el complemento, recarga / restablecimiento de configuración.
  • 20.02: Python 3, soporte reestructurado de codificadores y nuevos codificadores, soporte del editor de listas de reproducción de Creator.
  • 20.06: se eliminaron el soporte para Window Eyes y la función de perfiles de emisión basados en tiempo, soporte para el cliente Remote VT.
  • 20.09: cuarta versión de soporte extendido, se eliminan las características piloto, conexión individual a codificadores en los codificadores SPL, registro de monitorización del codificador en segundo plano.
  • 21.01: cambios en el anuncio de la propiedad de la pista, más correcciones de estilo.
  • 21.06: compatibilidad con versiones recientes de NVDA, anotaciones de tipos y código fuente más robusto. Esta es mi última versión planeada con nuevas funciones y fallos corregidos.
  • 22.01: refactorización de controlTypes, refinamiento interno de gestión de la configuración, se elimina el mecanismo de caché de perfiles al encontrarse más madura la tecnología SSD. Esta es la última versión por mi parte.

A lo largo de este artículo, tendrás una oportunidad para ver cómo funciona el complemento, la filosofía de diseño y cómo se desarrolla el complemento, con vistas al pasado y al futuro. Espero que este artículo de complementos vistos por dentro sea una referencia valiosa para usuarios y desarrolladores – para los usuarios, que vean los trabajos internos de este complemento, y para los desarrolladores, que usen este complemento como un ejemplo a la hora de planear, implementar, probar, publicar y mantener un complemento.

Para descargar el complemento, ve a la sección de complementos que se encuentra en nuestra web.

Diseño, estructura del código, conjuntos de capas e importancia de la api de Studio y el manejador de ventana de Studio

Diseño general y del código fuente

El complemento de NVDA para StationPlaylist consta de siete módulos de aplicación (incluyendo dos de ellos en forma de paquete) y una extensión global. Ya que Studio y Creator vienen con la herramienta de pista para gestionar pistas, el complemento incluye un módulo de aplicación para la herramienta de pista además del módulo de aplicación principal para Studio, así como un módulo de aplicación para StationPlaylist Creator. Se encuentra presente un cuarto módulo de aplicación para la grabadora de pistas de voz, usado con propósitos de seguimiento de eventos. El cliente Remote VT es el quinto módulo de aplicación, y se usa principalmente para dar soporte al editor remoto de listas de reproducción. Los otros dos módulos de aplicación tratan con el motor DSP de SPL y Streamer, siendo el módulo de aplicación del motor SPL un paquete debido a la inclusión del módulo de soporte de codificadores, que también utiliza Streamer.

El diseño general es una relación entre el módulo de aplicación principal de Studio y la extensión global de utilidades de Studio (SPLUtils). El módulo de aplicación de Studio hace cosas que se esperan de los scripts, como reaccionar a pulsaciones de teclado, anunciar información de estado, gestión de configuración y todo eso, mientras que la extensión global es responsable de ejecutar órdenes de Studio desde cualquier sitio y, en versiones anteriores del complemento, del soporte de los codificadores (el complemento soporta los codificadores SAM, SPL y AltaCast). En realidad, la extensión global es subordinada del módulo de aplicación, ya que el módulo de aplicación controla la funcionalidad general del complemento y la extensión global necesita que Studio esté en funcionamiento para desbloquear algunas funciones (aquí, desbloquear significa usar la capa de órdenes y partes del soporte de codificadoresÇ).

Si nos centramos en la jerarquía de los módulos de aplicación, el paquete del módulo de aplicación de Studio se encuentra en lo más alto. Esto se debe a que el módulo de aplicación de Studio constituye la parte más antigua del complemento, y proporciona servicios básicos y utilidades para otros módulos. Por ejemplo, Creator y la herramienta de pista se apoyan en las funciones de configuración proporcionadas por el paquete de módulo de aplicación de Studio para el explorador de columnas (explicado más tarde), y el módulo de aplicación de la grabadora de pistas de voz (vt) no puede funcionar adecuadamente si el módulo de aplicación de Studio no está en ejecución. Incluso aunque el motor de SPL y Streamer son independientes del módulo de aplicación de Studio, aún necesitan que el módulo de aplicación de Studio funcione (este es especialmente el caso del motor de SPL, ya que Studio carga splengine.exe, el ejecutable del motor DSP).

En resumen, todos los módulos del complemento de StationPlaylist enfatizan el módulo de aplicación de Studio – aunque muchos componentes son independientes de Studio, aún hacen referencia a él por diversas razones. Por tanto, Studio sirve como el puente que conecta diversas características del complemento entre sí.

El código fuente consta de:

  • appModules: esta carpeta contiene el paquete (módulo de aplicación) principal splstudio y los módulos de aplicación para la herramienta de pista, Creator, la grabadora de pistas de voz, el cliente Remote VT, el motor DSP (paquete) y Streamer.
  • El paquete SPL Studio consta de varios módulos, en los que se incluyen init (módulo de aplicación principal y clases de elemento de pista), el gestor de configuraciones y las interfaces de usuario (splconfig y splconfui) y servicios misceláneos (splmisc), así como módulos de soporte y diversos archivos wav usados por el complemento.
  • El paquete del motor de SPL consta del módulo del motor principal y el módulo de soporte del codificador.
  • El archivo del módulo de aplicación principal está dividido en secciones. Primero, se definen las clases de superposición para elementos de pista, después viene el módulo de aplicación, dividido en cuatro secciones: métodos fundamentales (constructor, eventos y otros), órdenes de tiempo (fin de pista, tiempo del locutor, etc.), otras órdenes (buscador de pista, explorador Cart y otros) y la capa de SPL Assistant. Esto me permite identificar de dónde viene un fallo y añadir características en las secciones apropiadas.
  • globalPlugins: esta carpeta contiene el paquete SPLUtils, que consta del código de la extensión principal y la capa del controlador de SPL.

Nota: hasta 2019, el soporte de codificadores era parte de las utilidades de SPL. En 2020, es parte del paquete del módulo de aplicación del motor DSP de SPL.

Filosofía del diseño

Cuando me puse a escribir el complemento en 2013, Puse en marcha ciertas cosas que el complemento debe cumplir, incluyendo:

  • Consistencia: el complemento debería tener una interfaz y estructura de órdenes consistente. La interfaz incluye varias GUI’s como el diálogo de configuración del complemento. Para las órdenes de capa, intenté usar la asignación de órdenes nativa de Studio.
  • Extensibilidad: el complemento debería estar organizado y escrito de tal manera que permita una fácil extensibilidad, y por esto el módulo de aplicación y la extensión global se dividieron en submódulos, cada uno de ellos especializado en una cosa concreta (como la gestión de la configuración).
  • Separación de conceptos: junto con la extensibilidad, esto me permitió proporcionar las órdenes necesarias en el momento adecuado, lo que resultó en dos capas de conjuntos de órdenes (se explica más abajo).
  • Código fuente fácil de seguir: aunque algunos puedan decir que la documentación excesiva es ruido, creo que para un desarrollador es importante entender cómo y por qué vino una función o módulo. También he usado y leído guías de usuario de scripts para otros lectores de pantalla para entender mejor cómo funcionaba una característica y hacerle algunas mejoras en puntos donde encontraba fallos graves, como con los scripts para JAWS (uno de ellos, que espero que Bryan haya parcheado por ahora, es la alarma de micrófono, donde la alarma se apagaría a pesar del hecho que el micrófono ya estaba apagado antes de que acabara la cuenta atrás de la alarma).
  • Etiquetas únicas de características: una manera de destacar el complemento fue dar nombres interesantes a las características. Por ejemplo, durante el desarrollo del complemento 3.0, decidí dar al modo de aprendizaje de cart un nombre que reflejara mejor lo que hace esa función: explorador Cart para explorar las asignaciones de Cart. Lo mismo podría hacerse con la implementación de NVDA de las teclas de flecha mejoradas (llamada dial de pista, ya que esta función es similar a girar una ruleta en un mando a distancia).
  • Extenso ciclo de colaboración y retroalimentación entre usuarios y desarrolladores: yo creía que las auténticas estrellas del espectáculo no eran los archivos de código del complemento, sino los locutores que usarían diversas características del complemento. Por ello, trabajé con los usuarios desde bien temprano, y sus comentarios continuos dan forma a las versiones futuras del complemento. Este ciclo de colaboración y retroalimentación también me ayudó a mí (el autor del complemento) a entender cómo se usaba el complemento y a planear futuras características para satisfacer las necesidades de los locutores que pueden usar este complemento en diversos escenarios (un buen ejemplo son los perfiles de emisión, como verás en la sección de configuración del complemento).

¿Por qué dos capas de conjuntos?

Cuando al principio me senté a diseñar el complemento, ya sabía que tendría que escribir un módulo de aplicación y una extensión global (para ejecutar órdenes de Studio desde cualquier sitio), lo que llevó a definir dos capas de conjuntos de órdenes con propósitos específicos:

  • Asistente de SPL: esta capa de conjunto de órdenes está disponible en el módulo de aplicación y está pensada para obtener información de estado y para gestionar las características del módulo de aplicación. La llamé asistente porque esta carpa sirve como el asistente de un locutor al leer diversa información de estado. Se pueden encontrar más detalles más adelante en este artículo.
  • Controlador de SPL: esta capa es para la extensión global y ejecuta órdenes de Studio desde cualquier parte. La llamé «controlador» porque controla varias funciones de Studio desde otros programas. Más abajo se darán más detalles.

En los primeros días reforcé esta separación, pero en el complemento 6.0 es posible invocar la capa del asistente de SPL pulsando la orden que se usa para invocar al controlador de SPL. En el complemento 7.0, es posible para el asistente de SPL emular órdenes de scripts para otros lectores de pantalla, y los mecanismos que lo permiten se cubren más adelante en este artículo.

La «magia» tras las órdenes de capa

Para que las órdenes de capa funcionaran, tomé prestado código de otro complemento: Toggle y ToggleX, de Tyler Spivey. Toggle / ToggleX permite alternar varios ajustes de verbalización de formato a través de un conjunto de capa de órdenes. Trabaja de la siguiente manera:

  • Orden dinámica: asociación y eliminación de script: es posible asociar gestos dinámicamente a través del método bindGesture / bindGestures para un módulo de aplicación o una extensión global (bindGesture asocia una única orden a un script, mientras que bindGestures asocia órdenes a scripts desde un mapa de gestos u otro contenedor). Para eliminar el mapa de gestos dinámicamente, se limpiaba la combinación de gestos principales / de capa, y después se asociaban los gestos principales.
  • Definición de mapas extra de gestos en el módulo de aplicación / extensión global: normalmente, un módulo de aplicación o extensión global que acepta entrada por teclado usa un mapa de gestos único (llamado __gestures; un mapa es otro término para diccionarios o arrays asociativos donde hay un valor asociado a una clave). Pero para que las capas funcionen, se ofrecía un segundo mapa de gestos para almacenar las órdenes de capa (la orden y el script asociado de la forma «orden»:»script»). En versiones recientes de NVDA, se usa el decorador script para las órdenes principales, mientras que se usa un mapa de gestos para las órdenes de capa.
  • Funciones envueltas: Tyler usaba el decorador «wraps» de functools para encapsular cómo se llama a la función «finalmente» en el conjunto de capa (esto era necesario para eliminar asociaciones de órdenes de capa después de ejecutarse). Además, se usó una implementación personalizada de la función getScript (módulo de aplicación / extensión global) para devolver el script principal de la versión de la capa dependiendo del contexto.

La ejecución típica de las órdenes de capa es como sigue:

  1. Primero, se asigna una orden a una capa (entrada) orden (complemento 2.0 y posterior; el complemento 1.x usaba NVDA+para el controlador de SPL y ctrl+NVDA+ para la capa del asistente; se eliminó en la 2.0 para prevenir conflictos con gestos específicos del idioma).
  2. Pulsas la orden de entrada de la capa. Esto causa que el módulo de aplicación o extensión global haga lo siguiente:
    1. Se comprueban las condiciones de capa. Hasta el complemento 6.x, el módulo de aplicación quería ver si estabas en el visor de listas de reproducción (se relajó en el complemento 7.0), y para la extensión global, comprueba si Studio está en ejecución.
    2. Se establece un indicador para decirle a NVDA que la capa del asistente / controlador está activa.
    3. Se añaden los gestos del conjunto de la capa al mapa principal de gestos a través de la función bindGestures. En el caso de la emulación de lector de pantalla en el asistente de SPL, se selecciona el mapa apropiado de gestos.
  3. Pulsas una orden en el conjunto de la capa (como la A de asistente para oír el estado de la automatización, o la A para activar la automatización si se usa la capa del controlador de SPL). Dependiendo de cómo esté implementado el script de capa, llama a la api de Studio (para la capa de controlador de SPL y algunas órdenes del asistente) o bien simula navegación de objetos para recuperar la información necesaria (capa del asistente). En el módulo de aplicación, por razones de rendimiento, el objeto se mete en caché. Hay más detalles de los mecanismos de este procedimiento en las siguientes secciones.
  4. Después de que se haya hecho la orden de capa, se llama a la función «finish» (módulo de aplicación / extensión global) para realizar acciones de limpieza tales como:
    • Limpiar los indicadores de capa.
    • eliminar los gestos «actuales» (gestos principales y gestos de capa) y reasignarlos al mapa principal de gestos (esto se llama eliminación dinámica de asociaciones).
    • Realizar acciones adicionales dependiendo del contexto (por ejemplo, si el explorador cart estaba en uso).

La importancia del manejador de ventana de Studio y la api de Studio

Para usar los servicios que ofrece Studio, se debe utilizar la api de Studio, que básicamente necesita que tengamos vigilado el manejador de ventana de Studio (en la api de Windows, un manejador de ventana (simplemente llamado manejador) es una referencia a algo, como a una ventana, un archivo, procedimientos de conexión y así sucesivamente). Es importante si se desean ejecutar órdenes de Studio desde otros programas (Studio usa mensajes para comunicarse con el programa externo en cuestión mediante la función SendMessage de user32.dll).

Desde el complemento 7.0, una de las actividades que realiza el módulo de aplicación cuando se inicia (además de verbalizar la versión de Studio que estás usando) es buscar el manejador de la ventana principal de Studio hasta que lo encuentra (esto se hace mediante un hilo que llama a la función FindWindowW (FindWindowA hasta finales de 2018 como se explica debajo) de user32.dll cada segundo), y una vez que lo encuentra, el módulo de aplicación guarda esta información para uso futuro. El controlador SPL realiza una comprobación similar, ya que sin el manejador, el controlador de SPL es inútil (como se ha explicado antes). Debido a la importancia de la api de Studio y el manejador de ventana, una de las primeras cosas que hago cada vez que se publica una nueva versión de Studio es pedir la última api de Studio y modificar el módulo de aplicación y / o la extensión global apropiadamente.

FindWindowA frente a FindWindowW

En los viejos tiempos de Windows (años 90), los programas no estaban listos para dar soporte a Unicode cuando el propio Windows lo hizo. Para dar soporte a programas que no están preparados para Unicode, Microsoft definió dos versiones de una función dada de la API de Windows. Por ejemplo, había dos versiones de la función FindWindow, siendo la diferencia su carácter final, como se explica a continuación:

  • A: versión ANSI hecha para programas antiguos (por ejemplo, FindWindowA).
  • W: versión para caracteres Wide (Unicode) (por ejemplo, FindWindowW).

En realidad, los programas llaman a la función FindWindow, y se elige la «versión» apropiada basada en la macro de representación de caracteres general como la especifica el programa. Por ejemplo, si el programa estuviese preparado para Unicode, al llamar a FindWindow, Windows llamaría internamente a FindWindowW.

Hasta el 2018, el módulo de aplicación de Studio y otros componentes del complemento llamaban a FindWindowA debido al hecho de que, en Python 2, una cadena es un array de sólo lectura de caracteres ANSI. Python 3 (y si se crea una cadena en Python 2 prefijada con la «u») usa un array inmutable de caracteres Unicode para las cadenas. Internamente, NVDA espera cadenas Unicode para la función que envuelve a FindWindow (ubicada en el módulo winUser), imitando por tanto al comportamiento en Python 3. El complemento de StationPlaylist adoptó el comportamiento de FindWindowW a finales de 2018, pero no se usa la envoltura proporcionada por NVDA debido al comportamiento incorrecto al comprobar errores en NVDA (si el manejador de ventana es 0 (NULL), se lanza un código de error que indica éxito, que va en contra de las especificaciones de la API de Windows; ya no se da el caso en NVDA 2019.3 / Python 3).

Vida del módulo de aplicación de SPL

Nota: durante el resto de este artículo, verás algunas porciones del código fuente para hacerte entender mejor cómo funciona algo (mayoritariamente se proporcionará pseudocódigo). Además, para algunas cosas será necesario explicar cómo funciona el núcleo de NVDA (el lector de pantalla en sí) (por lo que aprenderás muchas cosas de una vez).

Módulo de aplicación de SPL Studio y amigos: descripción del diseño y del código

Como se ha indicado antes, el módulo de aplicación de SPL Studio (splstudio/init.py) consta de varias secciones. Estas incluyen (de arriba a abajo):

  • Importaciones: aquí se importan muchos módulos de diversos paquetes Python y del lector de pantalla NVDA, incluyendo soporte para controles de IAccessible, gestor de configuración y así sucesivamente.
  • Envoltura de orden de capa: ya dije en un capítulo anterior cómo funcionaban las órdenes de capa, y la función «finally» al principio es la que hace esto posible.
  • Unas pocas comprobaciones y funciones auxiliares: aquí se incluye un indicador que especifica la mínima versión de Studio requerida, el valor almacenado para el manejador de ventana de Studio (SPLWin), y referencias para hilos como el temporizador de alarma del micrófono (más sobre esto en la sección de hilos). Esta sección también incluye funciones auxiliares como «messageSound» (muestra un mensaje en una pantalla Braille y reproduce un archivo wav) y otras funciones auxiliares.
  • Clases de superposición del elemento de pista: se definen tres clases con diversos propósitos. La primera es una clase base que proporciona órdenes y servicios a través de Studio y otras aplicaciones, mientras que las otras dos clases proporcionan soporte para las pistas que se encuentran a lo largo de Studio (una es una clase general de elementos de pista, la otra es específica del visor de listas de reproducción). Volveremos a estos objetos más tarde.
  • Clase del módulo de aplicación: este es el núcleo no sólo del módulo de aplicación, sino del paquete del complemento entero. La clase del módulo de aplicación (appModules.splstudio.AppModule) está a su vez dividida en secciones como se describe en el capítulo de diseño del complemento.

Los compañeros de Studio (Creator, herramienta de pista, cliente Remote VT) se explican en las secciones listadas arriba excepto la capa de envoltura de órdenes, y se han simplificado las clases de elemento de pista. Ya que la grabadora de pistas de voz controla ciertos aspectos internos del módulo de aplicación de Studio al arrancar, sólo se proporcionan el constructor y el método terminate (mira más abajo). Para Streamer y el motor DSP, se encuentran presentes soluciones específicas del codificador.

Recorramos ahora el ciclo de vida del objeto del módulo de aplicación en cuestión: antes y durante la inicialización del módulo de aplicación, actividades realizadas mientras el módulo de aplicación está activo, muerte y (hasta 2018) actualizaciones del complemento.

Nota: aunque la función independiente de búsqueda de actualizaciones se ha ido, el mecanismo detrás de la actualización de complementos se documentará para dar completitud.

Antes de nacer: procedimientos de importación del módulo de aplicación de NVDA

Antes de que profundicemos más, es importante que entiendas cómo carga NVDA distintos módulos de aplicación. Este procedimiento, disponible en source/appModuleHandler.py (núcleo de NVDA) puede resumirse de la siguiente manera:

  1. Si se ejecuta un nuevo proceso (programa), NVDA intentará obtener el identificador de proceso (PID) para el proceso recién cargado.
  2. A continuación, NVDA buscará un módulo de aplicación que encaje con el nombre del ejecutable del proceso recién creado. Mira en varios lugares, entre ellos source/appModules, directorio de configuración de usuario/appModules y complemento/appModules, luego vuelve al módulo de aplicación por defecto si no encuentra ningún módulo de aplicación con el nombre especificado.
  3. Después, NVDA intentará usar la función import integrada en Python para cargar el módulo de aplicación, lanzando errores si es necesario. Si no hay errores significa que el módulo de aplicación está listo para usarse.
  4. Una vez esté listo el módulo recién cargado, NVDA instanciará la clase appModule.AppModule (la pondrá disponible). Si se define un constructor (método init), Python (y no NVDA) llamará al constructor del módulo de aplicación (más sobre esto debajo).

En caso de que la clase AppModule del módulo de aplicación tenga un constructor definido, Python seguirá las órdenes especificadas en ese constructor. Antes de hacer los procedimientos de inicialización específicos del módulo de aplicación, es importante llamar primero al constructor del módulo de aplicación por defecto como en el siguiente código:

def __init__(self, *args, **kwargs):
    super(AppModule, self).__init__(*args, **kwargs)

Esto es obligatorio porque el constructor del módulo de aplicación por defecto realiza actividades importantes, incluyendo:

  1. El constructor del módulo de aplicación por defecto llamará a otro constructor base (en esta ocasión, se trata de baseObject.ScriptableObject, que contiene soporte de gestos entre otras propiedades importantes).
  2. Inicializa diversas propiedades, como PID (identificador de proceso), nombre del módulo de aplicación (si está definido), nombre de la aplicación y manejador hacia la aplicación en cuestión mediante la función OpenProcess de kernel32.dll (XP/Server 2003 y Vista/Server 2008 y posterior necesitan argumentos distintos).
  3. Por último, el constructor inicializa el manejador de inyección de proceso y el manejador de asociación auxiliar en caso de que se necesiten estos procedimientos.

Nacimiento: construcción del módulo de aplicación

Ciertos complementos con módulos de aplicación vienen con un módulo de aplicación con un constructor definido, y SPL Studio es uno de ellos; en 2018, se añadieron constructores a Creator y a la herramienta de pista con diversos propósitos, y el cliente Remote VT viene con un constructor similar al del módulo de aplicación de Creator. Después de llamar al constructor base como se describe arriba, el constructor del módulo de aplicación de SPL (método init que se ejecuta cuando se inicia el módulo de aplicación) hace lo siguiente:

  1. Comprueba si se está ejecutando una versión soportada de Studio, y si no es así, se lanza una excepción RuntimeError, impidiendo que uses el módulo de aplicación mientras esté en uso una versión no soportada de Studio (a partir del complemento 17.04, es necesario usar Studio 5.10 o posterior).
  2. A menos que se silencie poniendo globalVars.appArgs.minimal a True, NVDA verbaliza «Usando SPL Studio versión 5.01» si Studio 5.01 está en uso (por supuesto, NVDA dirá 5.10 cuando Studio 5.10 esté en uso). Esto se hace mediante la función ui.message (parte del núcleo de NVDA), que te permite oír mensajes hablados o leer el mensaje en una pantalla braille. En realidad, la función ui.message llama en serie a dos funciones (una después de la otra): speech.speakMessage (hablando algo mediante un sintetizador) y braille.handler.message (mostrando mensajes en una pantalla braille si hay alguna conectada).
  3. A continuación, se inicializan los ajustes del complemento y los subsistemas relacionados llamando a splconfig.initialize(). Para los complementos 6.x y 7.x, se hacen los cuatro primeros pasos en la misma función init (conocida como initConfig), mientras que en el 8.0 se encarga de esto el constructor de la clase ConfigHub de SPLConfig. El complemento 17.10 cambia esto de forma significativa, y en la versión 18.07 y posteriores, se saltan algunos pasos si otra aplicación de Studio está en uso (mira las siguientes secciones). Esto se hace de la siguiente manera:
    1. Para los complementos 6.x y 7.x, se carga un archivo de configuración predefinido llamado rutaAjustesDeUsuario/splconfig.ini. En el complemento 6.0 y versiones posteriores, esto se conoce como «perfil normal». En los complementos 6.x y 7.x, esto se hace llamando a la función splconfig.unlockConfig(), que se encarga de la validación de la configuración mediante ConfigObj y Validator, y en el 8.0 y posteriores, esto es parte del constructor de SPLConfig. En el complemento 17.10 y posteriores, este paso no tendrá lugar si decimos a NVDA que utilice configuración en memoria, y en la versión 18.07 y posteriores, cualquier módulo de aplicación de SPL que abra SPLConfig (splconfig.openConfig) registrará el nombre de la aplicación para indicar qué aplicación se está iniciando.
    2. Para el complemento 6.0 y posteriores, se cargan los perfiles de emisión desde la carpeta rutaDelComplemento/profiles. Son archivos .ini y se procesan como se hace con el perfil normal salvo porque los ajustes globales se toman desde el perfil normal. En el complemento 8.0, al igual que con el perfil normal, esto se hace al construir el objeto SPLConfig. En el complemento 17.10 y posteriores, si decimos al complemento que use solamente el perfil normal, no se hará este paso.
    3. A continuación, cada perfil se anexa a un contenedor encargado de los registros (splconfig.SPLConfigPool para 6.x y 7.x, splconfig.SPLConfig.profiles en 8.0 y posteriores). Después se establece el perfil activo y splconfig.SPLConfig (mapa de configuración del usuario) se establece al primer perfil en el contenedor de configuración (perfil normal; para el complemento 5.x y anteriores o si sólo va a usarse el perfil normal (17.10 y posteriores), hay (o habrá) simplemente un perfil, por lo que el paso de anexar se omite).
    4. Entre los complementos 7.0 y 21.10 (mejorado en 17.10 y relajado en 20.09), a menos que se solicite la configuración en memoria, el diccionario del perfil normal (y no otros) se guarda en caché. Esto es útil para conservar un registro de ajustes cargados de disco contra la configuración en tiempo de ejecución, y se emplea al comparar valores al guardar perfiles. Mira la sección de guardado de perfiles en perfiles de emisión para más detalles y razones, documentada por motivos históricos.
    5. Comenzando con el complemento 18.08, si NVDA lo soporta, SPLConfig escuchará la acción de guardar configuración para guardar los ajustes del complemento cuando se invoque la orden para guardar la configuración (control+NVDA+c). En el complemento 19.03 se añade soporte para la acción de recarga / restablecimiento de la configuración, de tal forma que las opciones del complemento se recargan o restablecen a valores por defecto si se pulsa NVDA+CTRL+r una o tres veces, respectivamente.
    6. Si se define un perfil instantáneo (hay presente un nombre de perfil instantáneo en la caché) se establece de forma apropiada la variable de perfil instantáneo.
    7. Si se encuentran errores, NVDA muestra un diálogo de error (5.x y anteriores) o bien un diálogo de estado (6.0 y posteriores) detallando el error en cuestión y lo que ha hecho NVDA a los perfiles fallidos. Esto puede pasar por aplicar valores por defecto a algunos ajustes para restablecer los ajustes por defecto (lo último ocurrirá si el validador informa de que todos los ajustes en el perfil normal son inválidos o configObj lanzó errores de interpretación, vistos comúnmente cuando el contenido del archivo no tiene sentido).
    8. Entre los complementos 7.0 y 18.12, se inicializa la característica de actualizaciones (splupdate.initialize). Entre otras cosas, el procedimiento de inicialización carga los metadatos de búsqueda de actualizaciones. En 2018, la inicialización de las actualizaciones se hace como parte del constructor del módulo de aplicación. Encontraremos los procedimientos de actualización del complemento (alojados en splstudio/splupdate.py) más adelante en este artículo.
    9. En el complemento 8.0, se cargan los comentarios de pista (si los hay). Mira la sección de elementos de pista para más detalles.
    10. Aunque no es parte del procedimiento de inicialización, empezando con el complemento 17.12, diversos módulos registran una o más funciones para notificaciones de acción. Mira la sección de puntos de extensión para más detalles.
  4. Empezando con NVDA 2015.3, es posible para un módulo de aplicación pedirle a NVDA que monitorice ciertos eventos para ciertos controles incluso si no se está usando la app. Esto se hace llamando a la función eventHandler.requestEvents con tres argumentos: identificador del proceso, clase de ventana para el control en cuestión y el evento a monitorizar. Para versiones más antiguas de NVDA (comprobadas mediante la función incorporada hasattr), se salta este paso, y el indicador de monitorización del estado en segundo plano se establece de forma apropiada. Hablaremos del procesamiento de eventos a lo largo de este artículo.
  5. Después, se inicializa el subsistema gráfico (NVDA usa WXPython). Este procedimiento añade una entrada en el menú de preferencias de NVDA, titulada «Ajustes de SPL Studio», el diálogo de configuración del complemento.
  6. Como se ha descrito antes, el módulo de aplicación buscará el manejador de ventana de la aplicación Studio. Para evitar que este procedimiento consuma recursos y reduzca el rendimiento de NVDA, esto se hace en un hilo separado. El hilo hace lo siguiente:
    1. Se busca el manejador de ventana de Studio con un bucle. Si Studio se cierra por la razón que sea, el módulo de aplicación activa un indicador de evento, causando que este hilo finalice.
    2. Si se encuentra el manejador, su valor se registra en un indicador situado en el módulo de servicios básicos (splbase).
    3. Si le decimos al módulo de aplicación que verbalice el estado del flujo de metadatos y conexión a URLs predefinidas, NVDA hará todo eso en este punto, asumiendo que el visor de listas de reproducción de Studio (explicado más tarde) está cargado. Para verbalizar mensajes de estado en último lugar después de conectar a los servidores de metadatos, el módulo de aplicación de Studio sitúa ui.message en la cola de eventos para que NVDA la procese (queueHandler.queueFunction). Hay más información sobre el interior del anuncio de metadatos y sus componentes relacionados en el capítulo sobre el asistente de SPL.
  7. Entre los complementos 7.0 y 18.12, si está activada la búsqueda automática de actualizaciones, se inicia el temporizador asociado.

Cambios introducidos en 17.10 debido a indicadores de configuración volátil

En el complemento 17.10, se introducen varios indicadores internos y argumentos de línea de órdenes asociados para controlar cómo se cargan y se tratan los ajustes del complemento. La presencia de estos indicadores alterará significativamente la forma en que se cargan y guardan los ajustes. Estos indicadores deberían usarse cuando lo indique el desarrollador del complemento, ya que su propósito es experimentar o solucionar problemas.

Los indicadores son los siguientes:

  1. No guardar cambios a disco (configVolatile/–spl-configvolatile, eliminado en la versión 20.09): todos los perfiles (incluyendo los perfiles de emisión) se cargarán desde disco, pero sus cambios no se guardarán. Con este indicador activado, la conservación de perfiles en caché no ocurrirá si está soportada, incluso para el perfil normal.
  2. Cargar solamente el perfil normal (normalProfileOnly/–spl-normalprofileonly): no se usarán perfiles de emisión, no se podrán crear estos perfiles ni pasar de uno a otro. Combinando este indicador con la opción de NVDA para no guardar la configuración al salir, el perfil normal se convierte de forma efectiva en un almacén de configuración de sólo lectura.
  3. Usar configuración en memoria (configInMemory/–spl-configinmemory): sólo se usará el perfil normal, pero en lugar de cargar los ajustes desde disco, se usará una versión en memoria con los ajustes por defecto aplicados (y sin caché de perfiles si se soporta).

Usar indicadores que especifiquen la utilización solamente del perfil normal restringirá la habilidad de crear nuevos perfiles de emisión.

Cambios introducidos en 18.07 para procesar opciones del complemento desde aplicaciones distintas a Studio

Como el explorador de columnas se usa en Studio, Creator y la herramienta de pista (lee la sección correspondiente a continuación), se hizo necesario cambiar la forma de cargar y gestionar la configuración del complemento fuera de Studio. En vez de llamar directamente al constructor de SPLConfig en splconfig.initialize, se llama a la función splconfig.openConfig, que llamará al constructor (si es necesario) y añadirá el nombre de la aplicación a una lista de componentes de SPL. Si esto se hace con los ajustes del complemento ya cargados, no tendrá lugar ninguna actividad de construcción.

En los módulos de aplicación de Creator y la herramienta de pista, el constructor llamará a splconfig.openConfig para realizar la actividad explicada arriba. Cuando el módulo de aplicación finaliza (mira más abajo), se llamará a splconfig.closeConfig para eliminar del registro el componente que finaliza, y si no hay componentes de SPL activos, las opciones del complemento se irán de la memoria.

Vida del módulo de aplicación: eventos, órdenes y salida

Una vez el módulo de Studio está listo, puedes moverte a la ventana de Studio y realizar actividades como estas:

  • Pulsar órdenes, y NVDA responderá abriendo un diálogo o bien diciendo lo que ha hecho.
  • Verbalizar cambios de estado, como estado del micrófono. La longitud y formato de estos mensajes (y otros mensajes del complemento) están controlados por el indicador de verbosidad de mensajes (en el caso de que la verbosidad se establezca en avanzada, NVDA acortará estos anuncios, que vienen de un contenedor de mensajes). Un caso especial es la verbalización automática de título y artista de la pista que se está reproduciendo actualmente, para el que se emplea navegación por objetos. Esto es necesario por culpa de una opción rota en el propio Studio.
  • Buscar pistas.
  • Examinar la información en columnas mediante el dial de pista y / o el explorador de columnas (el dial de pista quedó obsoleto en 2017).
  • Escuchar el progreso de escaneado de una biblioteca en segundo plano.
  • Realizar gestos del asistente de SPL.
  • Para 6.0 y posteriores, gestionar perfiles de emisión (hablaremos de los perfiles de emisión en la sección de gestión de la configuración).
  • Para 17.12 y posteriores, responder ante acciones como cambios de perfil de emisión.

En Creator y la herramienta de pista, te permitirá revisar los datos de una columna.

Puntos de extensión

Introducido en NVDA 2017.4, un punto de extensión es un sistema de notificaciones de NVDA que dice a los módulos y funciones que realicen tareas cuando algo ocurra. Por ejemplo, el subsistema braille cargará una pantalla braille diferente si los perfiles de configuración especifican este cambio, o un sintetizador de voz puede filtrar cierto texto de los mensajes hablados antes de verbalizarlos a los usuarios.

Hay tres tipos de puntos de extensión:

  • Acción: una función puede esperar a que ocurra algo, como un cambio de perfiles, que se cargue o guarde la configuración y otros, y actuar en consecuencia cuando una de estas acciones tiene lugar.
  • Decisión: un procesador de datos dentro de un script o una función puede decirle a NVDA que continúe procesando datos, tales como enviar una pulsación de teclas a un sistema remoto.
  • Filtro: un procesador de voz puede añadir, cambiar o eliminar textos antes de dejar que los sintetizadores verbalicen el texto que debería ser verbalizado.

En el complemento de SPL, las acciones se usan para notificar a los módulos de alguna acción, como cuando salimos de Studio, cambia el perfil de emisión y así sucesivamente. Hay dos acciones definidas (en el módulo splactions):

  • Cambio de perfil de emisión: notifica al hilo de alarma del micrófono y a la configuración del flujo de metadatos para que cambie los flujos o apague la alarma (mira más abajo para más detalles).
  • Saliendo de Studio: indica a los diálogos del complemento que se cierren sin guardar los ajustes.

Muerte: procedimientos de finalización

Mientras se usa el complemento de Studio, puedes dejar de usar este complemento de varias maneras, que incluyen salir o reiniciar NVDA, apagar tu ordenador o cerrar sesión, o cerrar Studio o una aplicación de SPL. Al igual que pasa con los procedimientos de inicialización, el módulo de aplicación de Studio tiene instrucciones que seguir cuando el complemento se cierra.

Aquí hay una lista de pasos que sigue el módulo de aplicación de Studio cuando está a punto de abandonar este mundo:

  1. Se llama al método «terminate». Al igual que el procedimiento de inicialización (constructor), este método primero llama al método terminate definido en el módulo de aplicación por defecto, que cierra manejadores y realiza otros procedimientos de cierre.
  2. Se llama a la función splconfig.terminate() para guardar los ajustes del complemento y realizar tareas de apagado de algunas características. Esta función sigue los siguientes pasos:
    1. En el complemento 7.0, si el temporizador de búsqueda de actualizaciones está funcionando, recibe la orden de detenerse, y se copian los metadatos de actualización de vuelta en el perfil normal. Este paso se elimina en la versión 19.01.
    2. Empezando con el complemento 18.07, se elimina el componente activo del registro mediante la función splconfig.closeConfig. Si hay otros componentes en ejecución, los siguientes pasos no ocurrirán, y en cualquier otro caso las opciones del complemento se cerrarán.
    3. A menos que se desactive con indicadores en 17.10, se guardan los perfiles (empezando con el perfil normal) en disco si y sólo si se cambian ajustes específicos del perfil (con este propósito se conserva una caché en línea usada para almacenar ajustes de los perfiles cuando estos se cargan). Este paso no ocurrirá si está en uso una versión en memoria del perfil normal o hay otros componentes de SPL activos. La caché de perfiles en línea ya no está en 21.10.
    4. Si hay un perfil de cambio instantáneo definido, este se registra en el perfil normal, de cualquier otro modo se elimina de la base de datos de perfiles.
    5. Una vez se guardan todos los perfiles, se limpian diversos indicadores, el perfil activo y el contenedor de configuración.
    6. Para el complemento 5.x y anteriores, hay sólo un perfil de emisión del que preocuparse, y este perfil se guarda en este punto.
    7. Estos pasos son parte del método splconfig.SPLConfig.save en el complemento 8.0 y posteriores.
  3. En 17.12 y posteriores, NVDA notifica a los manejadores registrados la acción de finalización de aplicación. Como se explica arriba, esto causará que los diálogos del complemento se cierren sin guardar los ajustes.
  4. A continuación NVDA intenta eliminar la entrada de ajustes del complemento de SPL del menú de preferencias de NVDA, después se limpian diversos mapas usados por la aplicación Studio (como el mapa del explorador cart).
  5. Una tarea importante es limpiar las cachés de objetos de la aplicación usada por algunas órdenes del asistente de SPL, ya que la información en caché (concretamente objetos) apuntará a un lugar distinto la próxima vez que se inicie Studio (sin reiniciar NVDA), lo que supone un peligro.
  6. Como el módulo de aplicación se sienta a descansar, el valor del manejador de ventana para la ventana de Studio se limpia. Esto es obligatorio, ya que el manejador será diferente la próxima vez que se inicie Studio. En este punto, NVDA elimina splstudio (el módulo de aplicación de Studio) de la lista de módulos de aplicación en uso.

Actualizaciones del complemento: actualizar a la última y más fantástica versión

Nota: esta función, reciemtemente llamada «actualización independiente del complemento», se ha ido en la versión 19.01, aunque el mecanismo que hay tras ella se documenta aquí para dar completitud. La actualización independiente hace referencia a usar el asistente de SPL para buscar actualizaciones desde Studio.

En el complemento 7.0 y posteriores (hasta la versión 18.12), es posible actualizar a la última versión del complemento usando la característica de comprobación de actualizaciones del complemento. Esto se hace conectando a un servidor donde se alojan los archivos de actualización del complemento.

El complemento SPL usa una combinación de la biblioteca urllib y canales de actualización (explicados más tarde) para recuperar los metadatos necesarios de actualización. El usuario puede decirle al complemento que busque actualizaciones automáticamente o puede comprobarlas de forma manual.

La búsqueda de actualizaciones se hace de la siguiente manera:

  1. Si le decimos al complemento que busque actualizaciones, el constructor del módulo de aplicación de Studio iniciará un temporizador cuyo propósito es llamar a una función cuando sea hora de buscar una actualización.
  2. Si está activada la búsqueda automática de actualizaciones, el gestor de actualizaciones (splconfig.updateInit) determinará cuando fue la última vez que se buscaron actualizaciones. Esto se hace para realizar búsquedas de actualizaciones cada 24 horas.
  3. Una vez el temporizador llega a su fin (con la búsqueda automática de actualizaciones activada), se llamará a la función de búsqueda de actualizaciones (splupdate.updateChecker). Esta función usa dos parámetros para determinar si debería reproducirse un tono de estado de progreso y para programar la próxima búsqueda de actualizaciones.
  4. La función de búsqueda de actualizaciones primero se conecta a la URL del canal de actualizaciones actual (más sobre canales al final de este artículo) y compara el nombre de archivo devuelto por el servidor. Si los nombres de archivo no encajan, el complemento interpretará esto como que hay una actualización disponible y devolverá un diccionario que contiene la versión actual del complemento, la nueva versión (extraída de una expresión regular) y la URL del archivo, y si no, no devuelve nada.
  5. Si hay una nueva versión disponible y el usuario ha dicho «Sí» a la pregunta de actualización, los metadatos de la actualización (sello de actualización) se guardarán para que el módulo de aplicación los consulte más tarde.
  6. Este proceso se repetirá si la búsqueda automática está activada (se creará un temporizador para llamar otra vez a esta función en 24 horas).

En el caso de una búsqueda manual de actualizaciones (descrita en la sección de la capa del asistente de SPL), aparte de no usarse un temporizador y detener el temporizador de búsqueda de actualizaciones automática temporalmente (si es necesario), se harán los pasos del 3 al 5 explicados arriba.

Anuncios de tiempo, alarmas y modificación de ajustes básicos

Ahora que sabemos cómo nace y muere el módulo de aplicación de Studio, es el momento para que miremos qué pasa mientras Studio está en uso, y empezaremos por ver cómo anuncia la hora este complemento, cómo trabaja con las alarmas y usa ajustes básicos.

Procedimientos de anunciado de tiempo: un cuento de cuatro hermanos

El módulo de aplicación de SPL Studio para NVDA viene con cuatro órdenes de verbalización de tiempo. Estas son tiempo transcurrido, tiempo restante, hora del locutor y hora completa incluyendo segundos. Los dos primeros usan la api de Studio para obtener la información necesaria, mientras que los otros usan una combinación de procedimientos Python y funciones de la api de Windows.

Tres de estos procedimientos tienen órdenes asignadas (a veces llamadas gestos). Estas son:

  • Ctrl+alt+t: tiempo restante
  • Alt+shift+t: tiempo transcurrido
  • NVDA+shift+f12: tiempo del locutor

Se puede usar el diálogo de gestos de entrada (parte del lector de pantalla NVDA) para cambiarlas o asignar una orden a la función de hora completa.

Un paso a un lado con la función StudioApi: un manejador y procesador central de la api de Studio

Antes de continuar, es importante mencionar una función que no sólo usan los dos primeros procedimientos de tiempo, sino que también es útil en el asistente de SPL y otros métodos. Esta función, llamada studioAPI (parte del módulo de aplicación principal y definida como una función a nivel de módulo), envía mensajes a la ventana de Studio y recupera el valor devuelto. Su cabecera es:

studioAPI(arg, command)

Siendo los argumentos el mensaje que se envía a la ventana de Studio (arg y command). A primera vista, puede parecer similar a la función SendMessage de user32 (de hecho, es a la que llamará la función studioAPI), pero a diferencia del procedimiento típico de la función SendMessage, el manejador de Studio y el tipo de mensaje se rellenan automáticamente, por lo que sólo el argumento (WParam) y la orden (LParam) son necesarios.

En versiones más antiguas del complemento, studioAPI hacía más que devolver resultados. Llamaba a una función de retorno con o sin desplazamiento, o no devolvía nada. Sin embargo, la única función de retorno que se le pasaba era la de verbalización de tiempo (próxima sección), y por lo tanto en 2018, la función studioAPI ha sido relegada a una delgada envoltura alrededor de la función SendMessage con el manejador de la ventana de Studio y el tipo de mensaje rellenados.

Primeras aplicaciones de la función studioAPI: verbalización de tiempo transcurrido y restante

Cuando pulses ctrl+alt+t o alt+shift+t para escuchar el tiempo transcurrido o el restante, el script primero comprobará si estás en el visor principal de listas de reproducción, y si es así, llamará a la función studioAPI con los argumentos correctos y órdenes, devolviendo el resultado a la función announceTime. De hecho, las únicas diferencias son el argumento que se usa y el mensaje de error.

Hora del locutor: simulando el reloj del locutor de Studio

Cuando escuchas programas de radio, puedes oír mensajes como, «Cinco minutos para las dos» o «Son las cinco y diez». Este anuncio se llama hora del locutor.

Studio muestra el reloj del locutor. Sin embargo, como está en medio de la pantalla, hay que usar órdenes de navegación de objetos para ubicarlo, y esto se hacía en versiones antiguas del complemento. Esto implicaba ubicar la ventana en primer plano (api.getForegroundObject()) y navegar en una dirección prestablecida para llegar al objeto del reloj, y esto todavía se usa en algunos lugares. Sin embargo, esto era propenso a un problema crítico: en ocasiones, el objeto en el que estamos interesados cambiaba su posición (un buen ejemplo era cuando se publicaban diferentes versiones de Studio 5.10).

Recientemente, este método se ha abandonado en favor del uso del módulo time de Python para obtener la hora actual y convertirla a un formato que sea familiar para los locutores, por lo tanto eliminando la necesidad de usar navegación por objetos. Cuando pulsas NVDA+shift+f12, NVDA primero recupera la hora local (time.localtime), después la convierte a un formato adecuado para la salida. A lo largo del camino, NVDA intenta emular cómo muestra Studio el reloj del locutor. Recientemente se ha hecho una ligera modificación para que este proceso pueda usarse para obtener el tiempo restante hasta la hora en punto cuando NVDA+shift+f12 se pulsa dos veces, con la diferencia de que la hora local se extrae de la siguiente hora. Cuando el proceso se completa para ambos casos, NVDA verbaliza el texto de salida.

Hora completa: la api de Windows al rescate

Aquí, hora completa se refiere a la hora incluyendo los segundos. Normalmente, cuando pulsas NVDA+f12, NVDA excluye los segundos al verbalizar la hora. Lo único que hace falta para verbalizar los segundos es cambiar el argumento de formato en la función GetTimeFormatEx (antes GetTimeFormat) de kernel32.dll. Con este cambio, NVDA puede verbalizar la hora incluyendo segundos, pero para usarla, debes asignar una orden para esta función (algunas órdenes del módulo de aplicación no vienen asignadas por defecto).

Establecimiento de alarmas

El módulo de aplicación de Studio viene con tres alarmas: fin de canción (desenlace), introducción y alarma de micrófono activo. Ya que tenemos que hablar de cosas importantes al explicar la alarma del micrófono, recorreremos el procedimiento usado al establecer las alarmas de introducción y fin.

Antes de 2020, se usaban diálogos independientes para configurar las opciones de sonido de alarma de intro y salida. En 2020, se han combinado en una orden que abre la categoría Alarmas en la configuración del complemento de SPL. El mecanismo detrás del primer método se documenta por motivos de completitud.

Los siguientes dos controles se usan para configurar los ajustes de los sonidos de introducción y salida en las opciones de la alarma (Alt+NVDA+1):

  • Ajuste de alarma: un botón giratorio (un control giratorio) se usa para ajustar los valores de la alarma. Puedes teclear el valor de la alarma o usar flechas arriba y abajo para cambiarlo. Si se introduce un valor incorrecto, se usará el valor máximo (59 para fin de pista, 9 para introducción de canción).
  • Casilla de verificación de notificación: establece si la alarma se reproducirá o no.

Aprenderemos cómo se almacenan y recuperan los valores de la alarma en la sección de gestión de configuración, y sabrás cómo funcionan las alarmas de introducción y fin de pista en el próoximo capítulo.

Centro de alarmas

Antes de la introducción de la categoría de alarmas en las opciones del complemento SPL, se utilizaban diálogos separados para configurar diversas opciones de la alarma. En realidad, se trataba de una única clase de diálogo, llamada AlarmsCenter, que presentaba opciones diferentes en función de un indicador interno.

Debido a su nombre y función, el centro de alarmas no sólo alojaba las alarmas de fin de pista e introducción de pista, incluía controles para la alarma del micrófono, y todo lo que hacía falta para modificar su apariencia era un único entero que especificaba qué partes del diálogo se mostraban. En el caso del diálogo del centro de alarmas presentado desde una versión antigua de las opciones del complemento (mira un capítulo posterior para más información sobre la configuración del complemento), se mostraban todos los controles de alarma (level=0), habiendo diferentes niveles que decidían que controles se mostraban de la siguiente forma:

  • 0: centro de alarmas, muestra todos los controles.
  • 1: alarma de fin de pista.
  • 2: alarma de introducción de pista.
  • 3: controles de la alarma del micrófono.

El centro de alarmas se eliminó en 2020 en favor de una estandarización alrededor de la categoría de opciones de alarma.

Conmutar ajustes

El complemento StationPlaylist viene con algunos ajustes conmutables que afectan a la forma de operar del módulo de aplicación. Estos incluyen anuncio de escaneo de biblioteca (alt+NVDA+r) y temporizador braille (ctrl+shift+x). Para cada script de configuración, NVDA primero comprobará el valor actual, cambiará el valor y anunciará el nuevo valor.

Procesamiento de eventos: verbalización de cambios de estado, activación de alarmas y más

Cuando produces un programa en vivo, es importante que tu lector de pantalla verbalice diversos cambios de estado y sucesos como número de oyentes, notificación de fin de pista, estado de la reproducción, alertas sobre peticiones de oyentes y demás. NVDA es un experto cuando se trata de manejar cambios de estado, activar alarmas y más. En esta sección, conoceremos la magia que hay tras esta profesionalidad: el procesamiento de eventos.

NVDA está dirigido por eventos

Las aplicaciones de Windows (especialmente aquellas que usan la api de Windows) son programas dirigidos por eventos. En algún lugar de la aplicación hay un bucle que responde a diversos eventos, tales como cuando se hace clic en una casilla de verificación, el ordenador se está apagando y demás. NVDA, al ser un lector de pantalla, usa los eventos con diversos propósitos, desde verbalizar una notificación de nuevo chat en Skype hasta ignorarlo completamente.

Para manejar distintos eventos, NVDA usa una cola de eventos (queueHandler) y un manejador de eventos para procesarlos. Cuando un evento se dispara desde NVDA o desde otros programas, NVDA primero comprueba si dicho evento es digno de su atención. A continuación realiza acciones asociadas con el evento, como verbalizar los cambios de un control (nombre, valor, etc.), reproducir pitidos y sonidos (actualizaciones de una barra de progreso) y así sucesivamente. Para los complementos (mayoritariamente extensiones globales y módulos de aplicación), NVDA tiene ganas de escuchar ciertos eventos y dejar que los controles los disparen.

Normalmente, un procedimiento para manejar un evento se declara de la siguiente manera:

event_nombredeevento(módulo, objeto en cuestión, siguiente manejador)

Por ejemplo, para el módulo de aplicación de Studio, un manejador de eventos declarado es gainFocus (te has movido a un control diferente), y está escrito así:

event_gainFocus(self, obj, nextHandler)

El procedimiento es como cualquier otra función (llamable) salvo porque se llama al procedimiento nextHandler al final para permitir a otros controles responder a eventos.

¿Qué tienen que hacer los eventos con el módulo de aplicación de Studio?

Los eventos y los manejadores de eventos son cruciales en el funcionamiento del módulo de aplicación de Studio, descrita como «los latidos del corazón» del complemento. Los eventos y sus manejadores se usan para hacer la mayoría del trabajo necesario para que el módulo de aplicación funcione. Entre ellos se incluyen:

  • Verbalización de cambios de estado tales como cuando el micrófono se enciende o se apaga.
  • Notificación de alarma, como la que hay cuando se acerca el final de una pista.
  • Activación de ciertas verbalizaciones y tareas en segundo plano como la alarma del micrófono, el anunciado del progreso de escaneado de la biblioteca y más.
  • Aplicar solución a algunos problemas como los que da el foco al eliminar una pista.

De todos los manejadores de eventos declarados, el más importante es el evento de cambio de nombre. Se declara de la siguiente manera:

event_nameChange(self, obj, nextHandler)

Cuando Studio dispara este evento, NVDA hace las siguientes operaciones:

  1. Se realizan diversas comprobaciones. Estas incluyen, pero no se limitan a:
    1. Asegurarse de que hay algo que verbalizar.
    2. Si se está usando otra aplicación, NVDA se asegurará de que el indicador de monitorización en segundo plano esté establecido (mira la exposición en el constructor del módulo de aplicación en artículos anteriores para más detalles).
    3. Si el estado a ser verbalizado es un estado común, como número de oyentes, estado de la programación y reproducción del cart, NVDA comprobará si tiene permitido verbalizarlo.
  2. Dependiendo del tipo de control (mayoritariamente nombre de la clase de ventana), NVDA realiza distintas operaciones (mira más abajo).
  3. Finalmente, NVDA llama a nextHandler() para dejar que otros controles respondan al evento de cambio de nombre.

Anuncios de estado comparados con notificaciones de alarma

Hay dos grupos de controles en los que NVDA está interesado: la barra de estado y el texto de estado, con nombres de clase de ventana TStatusBar y TStaticText, respectivamente (Studio es una aplicación Delphy). Dependiendo del control que dispare el evento, NVDA reaccionará de forma diferente.

Para anuncios de estado (TStatusBar), NVDA:

  1. Comprueba el ID del hijo de IAccessible (posición del control relativa al control padre tal y como expone MSAA (Microsoft Active Accessibility) / IAccessible).
  2. Si el identificador del hijo de IAccessible es 1, se verbaliza el progreso del escaneo de la biblioteca o bien el estado de la reproducción.
  3. Para otros objetos de la barra de estado (controles o ventanas), NVDA primero comprueba si es un cambio de conmutación (acaba en «on» o «off»), y si es así, hace lo siguiente:
    1. Si estableces la verbalización de estado en palabras, NVDA verbaliza la notificación de cambio de conmutación mediante voz.
    2. Si la verbalización de estado se ha establecido en pitidos, se selecciona un archivo wav adecuado y se reproduce mediante el módulo NVWave (nvwave.playWaveFile; esto se hace mediante la función messageSound).
    3. La salida braille no se ve afectada, se anunciarán los cambios de conmutación.
    4. Para el modo de edición de cart o la conmutación del micrófono, se realizan pasos extra, (llamando al método extraAction con la cadena de estado como argumento), como activar la alarma del micrófono o indicar que estás usando el explorador cart (si es el caso). Volveremos a explicar cómo funciona esto en futuras entregas.
  4. Para todos los demás anuncios, NVDA los verbaliza. En el caso del anuncio de programación, para evitar que se repita el mismo mensaje, NVDA comprueba si el nombre cambiado es igual que un nombre guardado que ya tenía.

Para controles de texto estático (mayormente usados para notificaciones de alarma):

  1. Comprueba si NVDA está mirando el tiempo restante para la introducción o para la pista entera.
  2. En ambos casos, comprueba si NVDA puede emitirlo por braille (el temporizador braille no está apagado).
  3. Si NVDA está configurado para verbalizar o reproducir alarmas (para introducción y fin), NVDA reproduce los tonos apropiados (la mitad de (440 hz) para fin de pista, y una octava por encima de la mitad (512 hz) para la introducción), o si se le pide hacerlo, te avisa de que el fin de pista o la introducción está llegando (complemento 6.0 y posteriores).

La estructura de la función event_nameChange definida en el módulo de aplicación de Studio está hecha de tal manera que puede extenderse para manejar el evento de cambio de nombre para otros controles (es una estructura en árbol, siendo la raíz el evento y dos subárboles, uno para la barra de estado y otro para el texto estático). Al igual que en otros complementos que definen procedimientos para manejar eventos, el evento de cambio de nombre llama a nextHandler().

Otros eventos definidos en el módulo de aplicación de Studio

Hay cinco eventos más definidos en el módulo de aplicación de Studio. Son los siguientes:

  • gainFocus: realiza procedimientos relacionados con el foco, tales como comprobar si estás en el diálogo de inserción de pistas para apagar el escaneo en segundo plano de la biblioteca (más sobre el escaneo de la biblioteca en segundo plano en una entrega futura).
  • gainFocus del módulo de aplicación: usado para procesar pantallas táctiles (sí, el módulo de aplicación de Studio tiene un modo táctil dedicado para SPL) que permite añadir órdenes adicionales.
  • loseFocus (pérdida de foco) del módulo de aplicación: el opuesto al evento anterior.
  • Show (mostrar): este evento está diseñado específicamente para reaccionar a peticiones de oyentes, de las que hablaremos más abajo.
  • Foreground (primer plano): se usa para coordinar el anuncio del contenido de la barra de estado cuando se inicia Studio.

Peticiones de los oyentes

Un componente raro del que se habla en StationPlaylist Studio es la capacidad de alojar un sitio web con un script php para permitir a los oyentes pedir pistas para que el locutor las reproduzca. Cuando se hace una petición, Studio muestra un diálogo de petición del oyente que lista las pistas solicitadas.

Debido a las limitaciones que había en antiguas versiones de NVDA, el módulo de aplicación de Studio no lo tenía fácil para detectar la aparición de este diálogo y notificarte las peticiones de los oyentes. Este ya no será más el caso, ya que NVDA permite registrar eventos de mostrar para rastrearlos en segundo plano. Sin embargo, ya que cualquier cosa puede disparar eventos de mostrar, rastrearlos no se recomienda. Sabiendo esto, el módulo de aplicación de Studio vigila los eventos de mostrar de una y sólo una ventana: peticiones de los oyentes, representada por el nombre de clase de ventana TRequests, siendo el único trabajo asignado para este manejador de eventos reproducir un sonido cuando las peticiones llegan, asumiendo que la opción de mostrar un globo cuando lleguen solicitudes esté activada en Studio.

Hasta ahora, hemos cubierto cosas que realiza el módulo de aplicación independientemente de dónde estés en Studio. Las siguientes secciones cubrirán cómo el módulo de aplicación maneja situaciones específicas tales como trabajar con elementos en el visor de listas de reproducción, buscar pistas, escanear la biblioteca y mucho más.

Elementos de pista, clases de superposición y buscador de pistas

Al usar el complemento, te habrás dado cuenta de que puedes ejecutar ciertas órdenes al tener el foco en los elementos de pista, y que la orden para buscar pistas es la misma que la de buscar en navegadores web. Si tienes curiosidad sobre todo esto, esta sección te permitirá ver cómo funciona. Pero primero, debemos hablar de algunos hechos más sobre el lector de pantalla NVDA, en esta ocasión hablaremos de objetos.

Consideraciones importantes sobre el uso que hace NVDA de los objetos

Una de las preguntas que hemos recibido tanto yo como otros programadores de complementos y desarrolladores de NVDA es, «¿Qué son exactamente los objetos y cómo se usan en NVDA?». En programación, un objeto es una instancia de la definición del objeto en tiempo de ejecución (esta definición, que se conoce como «clase», define cómo se comportan ciertas cosas y cómo recuperan la información de este objeto otras partes del programa; el paradigma de programación que usa clases y otros conceptos se llama «programación orientada a objetos»). Por ejemplo, alguien puede decir «Constrúyeme una guía telefónica», y un programador pensará cómo se almacenan las entradas de una guía de teléfonos y entregará una «guía telefónica» (un array de entradas de teléfono), toda ella hecha con objetos.

En las interfaces gráficas de usuario (GUIs), un objeto es un término más técnico para los controles (a veces llamados widgets). Esto incluye ventanas, campos de formulario, enlaces, documentos y demás. Un control (objeto) puede contener información como estado del control, ubicación, color y demás (el control contiene atributos visibles e internos que otros programas pueden usar).

En el mundo de NVDA, todos los elementos de la pantalla (controles) son objetos. Por tanto, al trabajar con objetos, NVDA usa las apis de accesibilidad para obtener la información necesaria. Para proporcionar una experiencia de usuario consistente, se abstraen las diferencias entre las distintas apis de accesibilidad (IAccessible, UI Automation, Java Access Bridge y otras), y se ofrece un mecanismo para verbalizar la misma información a través de controles implementados en distintos marcos de trabajo. Por ejemplo, cuando se marca una casilla de verificación, NVDA dice «marcado» – NVDA sabrá que has marcado esta casilla porque la api de accesibilidad subyacente informa a NVDA de este cambio, y la misma información se verbaliza sin importar que esté tratando con IAccessible, UIA o cualquier otro.

Actualmente, NVDA puede trabajar con IAccessible, User Interface Automation (UIA), Java Access Bridge (JAB) y otras (se soporta WAI Aria). Los módulos de soporte para estas apis se encuentran en el directorio source/NVDAObjects del código fuente principal de NVDA.

Clases superpuestas: personalización de objetos incorporados

Si NVDA estuviera limitado a usar sus propios manejadores de objetos, estaríamos limitados a la información que exponen correctamente las apis de accesibilidad (no habría módulos de aplicación). ¿Pero por qué NVDA puede verbalizar información extra de algunos controles y viene con diversos módulos de aplicación para diferentes aplicaciones? Esto se hace mediante clases de superposición – objetos personalizados y sus manejadores construidos sobre clases de la api (objetos incorporados).

En esencia, las clases de superposición son subclases de clases estables de la api (las subclases son clases especializadas que derivan (heredan) de una o más clases padre). Esto permite a los objetos personalizados (superpuestos) proporcionar propiedades extra, desde órdenes específicas para un control a eliminación de ciertas propiedades. Por ejemplo, así es como funciona NVDA al verbalizar las notificaciones emergentes (Windows 8.x y 10):

  1. Las notificaciones emergentes vienen de las aplicaciones, y son objetos UIA (NVDAObjects.UIA.Toast).
  2. Cuando NVDA recibe los eventos disparados por las notificaciones emergentes, este se asegurará de que está trabajando exactamente con este tipo de notificaciones.
  3. Cuando NVDA trate con notificaciones emergentes, hará lo que le hayan dicho que haga los objetos de notificación emergente (verbalizar las notificaciones sólo si está marcada la casilla «Anunciar globos de ayuda» en el diálogo Presentación de objetos).

¿Por qué son importantes los objetos y las clases de superposición en el módulo de aplicación de Studio?

Algunos lectores podrían hacerse esta pregunta después de leer la sección de arriba sobre las clases de superposición. Tenía que introducir las clases de superposición porque son importantes en el módulo de aplicación de Studio: los elementos de pista en el visor de listas de reproducción son clases de superposición. De hecho, hay al menos tres de ellas: una clase base abstracta que representa elementos de pista para Studio y otras aplicaciones (appModules.splstudio.SPLTrackItem), una representación genérica de un elemento de pista de Studio (appModules.splstudio.SPLStudioTrackItem), y un elemento de pista del visor de listas de reproducción (ventana principal) (appModules.splstudio.StudioPlaylistViewerItem; en el caso de la clase de elemento de pista del visor de listas de reproducción, su poder deriva de la clase de elemento de pista de Studio, que a su vez hereda del elemento de pista abstracto de SPL proporcionado por IAccessible).

Estas clases nacieron cuando empecé a trabajar en el soporte para Studio 5.10 en 2014. Ya que Studio 5.10 usa una manera diferente para mostrar propiedades de la pista, tuve que trabajar en una manera de procesarlas. A la urgencia se añadía el hecho de que Studio 5.10 usa marcas de verificación para indicar si una pista está seleccionada para reproducción (Studio 5.0x y anteriores usan casillas de verificación), y cuando estas marcas de verificación se marcan en Studio 5.10, NVDA no anuncia el nuevo estado de verificación, esto se arregló creando un procedimiento usado al pulsar espacio (mediante una clase de superposición). Además, la primera versión del buscador de pistas (mira más abajo) era sensible a cambios de descripción de los objetos, la modifiqué para que tuviera en cuenta las diferencias entre versiones de Studio.

Después, en 2015, cuando estaba diseñando el dial de pista (siguiente sección), pensé en el ámbito de esta característica. Pensé, «Si dejo que se invoque desde cualquier parte, podría crear problemas como errores e intentos de usar el dial de pista desde un lugar distinto a los elementos de pista». Después pensé, «Tal vez debería limitar esta característica al visor de listas de reproducción principal con el coste de identificar los elementos de pista correctamente». Dado que tenía experiencia con clases de superposición y ya que había una clase de superposición para los elementos de pista de Studio 5.10, decidí seguir adelante con la última opción, que desembocó en la definición de una nueva clase de superposición para los elementos de pista de Studio 5.0x y dejando que la clase de los elementos de pista de Studio 5.10 heredara de esta nueva clase.

En 2018, mientras se estaba trabajando en el soporte del explorador de columnas para Creator y la herramienta de pista, decidí revisar la jerarquía completa de clases de elemento de pista. Ya que los elementos de pista usaban los mismos procedimientos de navegación por columnas, decidí dividir la clase de elemento de pista de SPL 5.0x en dos clases: la vieja clase de elemento de pista, y una nueva clase abstracta que proporciona servicios básicos para los elementos de pista de Studio, Creator y la herramienta de pista. Además, para dar soporte al reordenado de columnas, se empleó la clase SysListView32, ya que proporciona un procedimiento útil para recuperar el contenido de una columna en la columna correcta cuando estas se reordenan en pantalla.

En 2020, se reconstruyeron las clases de superposición del elemento de pista de SPL. Ya que la clase base del elemento de pista fue concebida para actuar como plantilla, se convirtió en una clase abstracta en 2019. Lo que primero era la clase de elemento de pista de Studio 5.10 se convirtió en una clase especializada para los elementos del visor de listas de reproducción, y la antigua clase para elementos de Studio 5.0x revivió para representar elementos de pista disponibles en otras partes de Studio, como en el diálogo Insertar pistas. Al mismo tiempo, los servicios de navegación por columnas proporcionados por la clase base de elemento de pista de SPL se eliminaron en favor de las funciones proporcionadas por la clase del elemento de lista SysListView32 para proporcionar consistencia con el propio NVDA y añadir órdenes de navegación por columnas en el editor de listas de reproducción de Creator y el cliente Remote VT (antes, la clase base de elemento de pista de SPL proporcionaba órdenes de navegación por columnas personalizada). Posteriormente, se revisaron los procedimientos de nombre de pista y descripción para usar procedimientos de SysListView32 (consulta la sección sobre el anuncio de columnas en orden personalizado), lo que que resolvió un problema de larga duración por el que no se anunciaba la información de la pista a menos que se activase la opción «Anunciar descripciones de objeto» en NVDA.

Descripción de los elementos de pista

Cada elemento de pista de Studio (incluyendo el visor de listas de reproducción) y, en extensión, las pistas que se encuentran en Creator, la herramienta de pista y el cliente Remote VT, consta de una fila de columnas (6 para Studio 5.0x y anteriores, 18 para el visor de lista de reproducción de Studio 5.10 y posteriores, puede variar en otras listas de pistas). Tal y como está concebido NVDA, es una clase de superposición que proporciona un número de servicios, incluyendo:

  • Procedimientos para anunciar y navegar por diversas columnas (la mayoría se eliminaron en favor del uso de los procedimientos de la clase SysListView32 directamente en 2020).
  • Verbalizar las columnas en un orden específico (mira el próximo capítulo para conocer la importancia de la navegación por columnas).
  • En Studio, Obtener comentarios de pista si están definidos (mira la sección de comentarios de pista más abajo).
  • En Studio 5.10 y posteriores, un procedimiento para procesar la marcación de las marcas de verificación (cuando marques una pista pulsando espacio, NVDA verbalizará el nuevo estado de verificación y actualizará la pantalla braille apropiadamente).

Volveremos a los elementos de pista cuando hablemos de columnas en el próximo artículo. Por ahora, movámonos a dos características relacionadas en el módulo de aplicación de Studio que funcionan con los elementos de pista: buscador de pistas y comentarios de pista. Hay una segunda característica que trata con el buscador de pistas, la encontraremos en la siguiente sección.

Buscador de pistas: ubicación de pistas dada una cadena de búsqueda

El buscador de pistas permite buscar pistas dado su autor o el título de la canción. Esto se hace mediante una «búsqueda lineal» – examinando un elemento de pista y el siguiente hasta que se encuentra el término de búsqueda. Esta característica está inspirada en parte por características similares en otros lectores de pantalla y la propia función de búsqueda de NVDA (cursorManager.FindDialog y sus amigos).

El buscador de pistas no está limitado a buscar por artista o título: una variante de este diálogo (llamada búsqueda por columnas) permite buscar texto en columnas específicas como duración, nombre de archivo y demás. Otra variante de este diálogo, llamada buscador de rango de tiempo (que es un diálogo separado del otro (splmisc.SPLTimeRangeDialog) pero modelado después del buscador de pista) usa la api de Studio para ubicar pistas con duración entre el mínimo y el máximo especificados por el usuario.

En realidad, el buscador de pistas y el buscador por columnas son un único diálogo (splmisc.SPLFindDialog) que presenta dos diálogos (¿Te suena de algo?). Por ahora, hablaremos de cómo el buscador de pistas original (complementos del 2.x al 5.x) funciona (estáte atento a la próxima sección para aprender más sobre la búsqueda de columnas y la refactorización completa del buscador de pistas y sus aplicaciones).

Buscador de pistas original: órdenes, procedimientos y controles

Para usar el buscador de pistas, pulsa ctrl+NVDA+f (espera, yo ya he visto antes esta orden). Para cualquiera que esté acostumbrado al modo exploración de NVDA, esta orden debería ser familiar: buscar texto en páginas web. Esta orden realiza actividades similares a los diálogos de alarma (mira los artículos anteriores): después de comprobar las condiciones (asegurándose de que estás en el visor de lista de reproducción y que has añadido al menos una pista) y establecer los indicadores necesarios, NVDA abre el diálogo del buscador de pistas, donde puedes introducir un término de búsqueda y pulsar intro. NVDA llamará a la función del buscador de pistas (trackFinder) para encontrar la pista con el término de búsqueda dado, y dependiendo de los resultados de búsqueda, NVDA moverá el foco a la pista o abrirá un diálogo diciendo que no se encontraron resultados.

Otras dos órdenes se usan como parte del buscador de pistas: buscar siguiente y anterior, asignadas to NVDA+f3 y NVDA+shift+f3, respectivamente (también vienen del modo exploración). Cuando se invocan estas órdenes, se comprueba si has introducido previamente un término de búsqueda, y si no es así, se abrirá el diálogo del buscador de pistas. Si previamente ya has buscado un término, NVDA hará una búsqueda lineal en la dirección de búsqueda especificada (el método trackFinder en el módulo de aplicación acepta varios parámetros, y uno de ellos es la dirección de búsqueda).

Comparación del buscador de pistas 1.0 y el 2.0

En las versiones del complemento 2.0 a 5.x, al pedirle que busque pistas, NVDA buscará el término de búsqueda en las descripciones de pista (en caso de que estés buscando por artista en Studio 5.0x, NVDA también comprobará el nombre de la casilla de verificación, ya que contiene el nombre del artista). Aunque esto fue fácil de implementar, tenía varios problemas:

  1. A causa de los cambios del elemento de pista en Studio 5.10, tuve que dedicar algún tiempo a ajustar la fórmula del buscador de pistas.
  2. Al buscar una pista en una lista de reproducción con cientos de pistas cargadas, encontrar una pista al final de la lista llevaba varios segundos, y esto no era aceptable para los usuarios.

En el complemento 6.0, gracias a la búsqueda por columnas, el rendimiento del buscador de pistas mejoró. Además, el buscador se dividió en dos funciones: trackFinder todavía gestiona el movimiento del foco o muestra el diálogo de error, mientras que la búsqueda lineal ahora reside en una función privada. Este diseño permite que el procedimiento de búsqueda de pistas pueda ser usado por más de una característica (en este caso, el marcador de posiciones lo usa, como verás en la próxima sección).

En ambas versiones, la cabecera del método trackFinder (procedimiento de búsqueda lineal en 1.0, gestor de resultados de búsqueda en 2.0) en el módulo de aplicación de Studio es:

trackFinder(self, text, obj, directionForward=True, column=None)

Text es el término de búsqueda, obj indica dónde debería comenzar la búsqueda, direction especifica la dirección de búsqueda y column se usa si se emplea la búsqueda por columnas (buscar texto en columnas específicas). En el buscador de pistas 2.0 (complemento 6.0), se añade una «s» a la palabra clave column, y desde 2021, el argumento de columnas espera una lista de enteros de posición de columna.

Orden de resolución de métodos e importancia de la navegación por columnas en los elementos de pista, la herramienta de pista y otras características

En la sección anterior vimos cómo funcionaban las clases de superposición, y cómo se definen y usan los elementos de pista en Studio. Vamos a continuar con nuestro recorrido por los elementos de pista mirando la característica de navegación por columnas y viendo cómo se usa en diversos lugares. Pero antes de eso, debemos hablar sobre cómo sabe NVDA cómo aplicar ciertas órdenes en situaciones específicas mediante el orden de resolución de métodos.

Orden de resolución de métodos: ubicación de órdenes y definición de su ámbito

Si se definen múltiples clases (objetos), especialmente si la herencia está implicada, es duro determinar qué método es cuál y dónde se definen diversos métodos. Esto se hace más complicado cuando dos o más clases heredan del mismo padre, o se emplea herencia múltiple (Python soporta ambos escenarios).

Una forma que tiene Python de resolver esto es a través del orden de resolución de métodos (ORM). Resumiendo, cuando tiene que usarse un método, primero se mira si el método está definido en el objeto con el que estamos tratando, y si no, se consultará al padre de este objeto.

Por ejemplo, supongamos que tenemos un cuadro de lista que define un método scroll, y un widget de cuadro de lista personalizado que hereda de este cuadro de lista (en efecto, el cuadro de lista personalizado es un cuadro de lista). Para complicar un poco más las cosas, digamos que el método scroll no está definido (no está realmente definido) en el cuadro de lista personalizado. Cuando el usuario se desplace por el cuadro de lista personalizado, Python verá que el cuadro de lista personalizado no tiene el método scroll, por lo que mirará en el padre (cuadro de lista original) y empleará su método scroll (en este caso sí, Python usará el método scroll del padre).

En términos de NVDA, el orden de resolución de métodos es útil al tratar con clases de superposición. Esto tiene un extenso rango de consecuencias, incluyendo la habilidad de limitar dónde se pueden emplear ciertas órdenes o no definir una orden (configurando el script para que esté asociado al gesto None), forzando a NVDA de forma efectiva a que busque un gesto en una clase base (padre). Si NVDA no puede hallar la orden en cuestión, la pasará a Windows, que a continuación envía la orden al programa activo.

Terminando el puzle: MRO y los elementos de pista de Studio

Como se describía en la sección anterior, el módulo de aplicación de Studio define tres clases para los elementos de pista: una clase base abstracta para pistas encontradas en Studio y otras aplicaciones, una clase representando elementos de pista que se encuentran a lo largo de Studio, y otra que representa elementos de pista del visor de listas de reproducción. En realidad, todo lo que importa está en las dos primeras, y la clase para el visor de listas de reproducción proporciona procedimientos personalizados sobre la clase de elemento de pista de Studio. En caso de MRO, se consultará al módulo del visor de listas de reproducción si este está en uso, y en cualquier otro caso NVDA consultará al elemento principal de pista de Studio, lo que a efectos prácticos es consultar la clase base de elemento de pista de SPL. Otras aplicaciones, como la herramienta de pista o el cliente Remote VT, tienen su propio MRO, que desemboca también en una consulta a la clase base de elemento de pista de SPL definida en el módulo de aplicación de Studio.

El trabajo más importante de la clase appModules.splstudio.SPLTrackItem es anunciar datos de columnas. Antes de 2020, alojaba también procedimientos y órdenes de navegación por columnas tales como columna siguiente o anterior, el manejador del dial de pista (histórico), y un procedimiento dedicado para anunciar la información de una columna dados su número y una cabecera opcional. En 2020, sólo permanece el explorador de columnas.

Por encima de la clase base de elemento de pista de SPL se encuentra la clase general de elemento de pista de Studio, que no hace nada, ya que está diseñada para representar pistas en lugares como el diálogo para insertar pistas. Sin embargo, la clase que representa los elementos del visor de lista de reproducción (appModules.splstudio.StudioPlaylistViewerItem) añade procedimientos y scripts para usar desde el visor de lista de reproducción. Entre ellos se incluyen:

  • reportFocus: se llama a esta función al informarte a ti (locutor) sobre los elementos de pista. Su función principal es ver si se ha definido un orden personalizado para las columnas (mira más abajo) y construir las piezas necesarias si se especifica un orden de columnas. Posteriormente en 2020, el manejo del orden de columnas se movió al getter del nombre de pista.
  • Comentarios de pista: procedimientos relacionados con el trabajo con comentarios de pista (mira la sección anterior para más detalles).
  • Anunciar el estado de conmutación de las pistas al pulsar la barra espaciadora.
  • Conmutar la inclusión y el orden de los anuncios de columna entre orden en pantalla y orden personalizado (consulta la secci´ón sobre el orden de anuncio de las columnas para más detalles).

Nacimiento del dial de pista: de la duda a las posibilidades

Nota: la información sobre el dial de pista se conserva con propósitos de referencia y consulta. El dial de pista quedó obsoleto en 2017 con la publicación del complemento 17.04.

Según estaba programando el complemento, una de las sugerencias que más recibía era la capacidad de usar la característica de flechas mejoradas para revisar columnas. Esta característica permite a los locutores que usan scripts para lectores de pantalla usar las flechas para revisar información de columnas como artista, duración y demás. En el momento en que escribo esto, los tres scripts para lectores de pantalla soportan esta característica.

Al principio, dije a los locutores que esto no era posible. Mi impresión por entonces (verano de 2014) era que tendría que manipular el texto de la descripción de pista (obj.description) para activar esta posibilidad. Pero viendo cómo implementaban esto otros lectores de pantalla, me convencí de que podría ser posible implementarlo para usuarios de NVDA, y por tanto comencé a investigar sobre ello a finales de 2014.

Comencé buscando patrones en el texto de la descripción que pudieran ser usados para dar a los usuarios la impresión de que la navegación por columnas estaba activa (cuando no lo estaba). Estudié cómo trabajaba Python con las expresiones regulares y cómo manipulaba subcadenas (str.find y división) sin resultados satisfactorios. Después, en 2015, mientras trabajaba para mejorar el soporte para el codificador propio de Studio, me di cuenta de que las entradas del codificador eran objetos SysListView32 (NVDAObjects.IAccessible.SysListView32). Estudié cuidadosamente este objeto, especialmente un método para recuperar el contenido de una columna, y esto me dio una idea sobre cómo traer el dial de pista a la vida.

La magia tras el dial de pista: los controles SysListView32

Los controles SysListView32 son listas con elementos organizados en columnas. Por ejemplo, ciertas aplicaciones usan estos controles para ordenar entradas en columnas, tales como ciertas aplicaciones basadas en tablas, y en el caso de Studio, mostrar diversos estados de los codificadores.

Cuando se encuentran estos controles, NVDA permite usar órdenes de navegación de tablas (ctrl+alt+flechas) para navegar entre columnas, asumiendo que hay objetos hijo (columnas) expuestos por la implementación de la api de accesibilidad en uso. Las órdenes de navegación de tabla vienen ofrecidas por otra clase (behaviors.RowWithFakeNavigation; el paquete behaviors incluye cosas que NVDA debería hacer en diversos escenarios, incluyendo entrada y salida de una terminal, procesamiento de texto editable (mediante un módulo dedicado) y demás). Todo esto es posible gracias a un método en la clase SysListView32 (núcleo de NVDA) que permite recuperar el texto de una columna, y este se convirtió en el motor para el dial de pista, el explorador de columnas, y otras características de navegación por columnas en el complemento (digo «complemento» porque tanto Studio como la herramienta de pista usan la navegación por columnas). Esto se solidificó más en 2018 y más tarde, cuando todas las clases de elementos de pista (incluyendo los elementos de pista de Creator y las entradas del codificador SAM) fueron potenciadas por la clase SysListView32.ListItem.

Hasta el 2018, El procedimiento de recuperación del texto de la columna (que reside en SysListView32 y cuya copia reside en el módulo splstudio.splmisc) era el siguiente:

  1. Las características que necesiten texto de una columna específica llamarán a una función privada en el módulo splstudio.splmisc, que recibirá el objeto y el índice de columna como parámetros.
  2. El recuperador de columnas primero obtiene el manejador del proceso donde reside el control, luego crea un marcador de posición para el buffer que capturará el contenido de la columna.
  3. A continuación, mira el tamaño del control SysListView32 subyacente (ctypes.sizeof) y pide a Windows que reserve almacenamiento para un control SysListView32 interno mediante la función VirtualAllocEx de kernel32.dll. Esto es necesario para almacenar el texto de la columna resultante. Lo mismo se hace con otro marcador de posición para almacenar el texto real de la columna llamando a VirtualAllocEx.
  4. El recuperador crea a continuación un control interno SysListView32 usado como marcador de posición para almacenar el texto de la columna, después le pide a Windows que le diga al proceso donde reside el texto de la columna que revele el texto de columna de la columna especificada (primero se llama a WriteProcessMemory, se envía un mensaje mediante la función SendMessage de user32.dll para recuperar la longitud del texto, luego se usa ReadProcessMemory para recuperar el texto real de la columna si hay algo que leer). Una vez se ha revelado el texto de la columna, el recuperador almacena el valor dentro del buffer de texto (ctypes.create_unicode_buffer, reservado para almacenar el texto resultante).
  5. Por último, el recuperador libera recursos (VirtualFreeEx) y devuelve el texto de la columna recién recuperado, que puede ser usado por los procedimientos que lo habían solicitado.

En 2018, esto se simplificó usando directamente los procedimientos de SysListView32. En 2020, la clase base de elemento SysListView32 realiza casi todos los procedimientos de navegación por tablas (siendo la única excepción la navegación por filas en el visor de listas de reproducción).

Recuperación de columnas y procedimientos de navegación: de la duda a la piedra angular

El procedimiento de recuperación del contenido de las columnas ha llegado a ser una de las piedras angulares de este complemento. Además del dial de pista, otros diez procedimientos lo usan: búsqueda por columnas, marcador de posición de pista, verbalización del orden de columnas, explorador de columnas de pista, navegación vertical por columnas, instantáneas de listas de reproducción, transcripción de listas de reproducción, los módulos de aplicación de la herramienta de pista y de Creator, el editor de listas de reproducción que se encuentra en Creator y en el cliente Remote VT, y las entradas del codificador SAM. Veamos cómo funcionan siete de ellos con más detalle (las instantáneas de lista de reproducción y la transcripción de listas se describen en la sección del asistente de SPL, aunque se merecen secciones propias).

Dial de pista: navegación por columnas en elementos de pista

Nota: ya no es aplicable desde la versión 17.04, se incluye aquí por completitud.

El procedimiento de recuperación de columna es una de las actividades realizadas durante el dial de pista, y para ver la belleza de esta característica, asigna una orden para conmutar el dial de pista (debes poner el foco en el elemento de pista antes de abrir el diálogo de gestos de entrada, ya que el dial de pista sólo es usado por los elementos de pista). Una vez asignes la orden para conmutar el dial de pista y lo actives, Studio establecerá un indicador indicando que el dial de pista está activo, lo que causa que las flechas izquierda y derecha se asignen a órdenes de navegación de columna (este indicador se almacena como parte de la base de datos de configuración del complemento). Si configuras NVDA para que reproduzca pitidos para anunciar los cambios de estado (mira el capítulo anterior), NVDA reproducirá un pitido agudo. Una vez acabes con el dial de pista, pulsa la orden asignada para apagarlo. En este punto las flechas izquierda y derecha volverán a ejercer sus funciones originales, se oirá un pitido grave (si se configura NVDA para ello) y se eliminará el indicador del dial de pista.

Al navegar por columnas, NVDA comprobará si te encuentras en el borde de la fila de la pista, y si es así, reproducirá un pitido y repetirá el texto de la última columna. Si no, NVDA mirará la columna a la que quieres navegar (almacenada en el módulo de aplicación), después llamará al recuperador de texto de columna para recuperar el texto de esa columna.

Para procesar las diferencias entre Studio 5.0x y 5.1x, cada clase de elemento de pista informa a NVDA cómo debería procesarse la columna que hay a la izquierda del todo. En Studio 5.0x, la columna más a la izquierda es el campo artista (se comprobará obj.name), mientras que el estado de verificación de la pista es el que se «muestra» en Studio 5.10 (se verbalizará obj.name).

Orden personalizado de verbalización de columnas: qué anunciar y cómo

El procedimiento del dial de pista también permitió dar vida a otra solicitud: orden de verbalización de columnas. Esto te permite (locutor) oír las columnas en un orden específico y excluir ciertas columnas para que no sean verbalizadas.

Hasta 2020, el manejador del orden personalizado del anuncio de columnas residía en el método reportFocus de la clase principal del elemento de pista. A finales de 2020, se dividió en un método getter personalizado del nombre de pista, ya que se revisó el anuncio de pista para usar directamente los procedimientos de SysListView32 (antes de esto, la clase del elemento de pista se apoyaba en la implementación predeterminada de IAccessible).

Para usarlo, hay que indicar a NVDA que no utilice el orden en pantalla (diálogo de ajustes del complemento, panel de anuncio de columnas, o simplemente NVDA+v teniendo el foco en un elemento del visualizador de lista de reproducción para conmutarlo en la versión 21.01 o posterior). A continuación, en el mismo panel de opciones (anuncio de columnas), marca las columnas que quieras oír y usa la lista de columnas para configurar el orden de verbalización. El orden de verbalización de columnas es un cuadro de lista con dos botones: mover arriba y abajo.

Una vez se definen las columnas incluidas y el orden de las mismas, NVDA usará esta información para construir el texto de descripción del elemento de pista. Antes de 2021, esta era la descripción de la pista, cambiando después a la construcción del texto de nombre de la pista. Esto se hace llamando repetidamente al procedimiento recuperador de columna en las columnas que quieres oír, y usando después el orden de columnas que definiste para construir partes del texto de la descripción (se usa una combinación de lista y str.join).

Por ejemplo, si hacemos que NVDA anuncie el título y el artista (en ese orden específico), NVDA primero ubicará el título, luego añadirá la información del artista. Esto después se presentará como «Título: algún título, artista: algún artista».

También es posible suprimir el anuncio de las cabeceras de columna. Hasta 2020, las opciones del complemento venían con una casilla de verificación dedicada a conmutar el anuncio de cabeceras. Desde 2021 en adelante, se usa la opción de NVDA de cabeceras de fila y columna para configurar el anuncio de cabeceras de columna en las columnas de pista de Studio.

¿Nombre de pista versus descripción?

Hasta 2020, y ya que la clase de elemento de pista de Studio se apoyaba en la implementación predeterminada de IAccessible, la descripción de la pista registraba las propiedades de la misma. Por esta razón, siempre que se definía un orden o inclusión personalizados de columnas, el método reportFocus construía un texto de descripción de pista personalizado basado en el orden de columnas personalizado. Esto cambió en el complemento 20.11, cuando los procedimientos de SysListView32 adquirieron un rol más importante en la definición de elementos de pista, y ya que NVDA construye texto personalizado para los nombres de elementos basándose en la información de las columnas, la clase de elemento de pista de Studio también construirá nombres de elementos personalizados basándose en el orden personalizado de columnas e inclusión, si se han definido. A causa de esto, desde finales de 2020, el constructor de columnas está dividido en un método diferente, un getter personalizado del nombre del elemento que se usa para que NVDA recupere el texto del nombre de un control.

Explorador de columnas de pista: recuperar información de columnas específicas

En el complemento 7.0, apareció la posibilidad de hacer que NVDA verbalice información de columnas específicas. Esto se hace dejando a NVDA que asigne al asistente de SPL, del 1 al 0 (6 para Studio 5.0x) una función para obtener información de una columna específica (slot); el complemento 8.0 cambia estas órdenes para usar ctrl+NVDA+fila de números, y se eliminó Asistente de SPL, órdenes de la fila numérica en 2020. Esto se llama explorador de columnas de pista (generalmente conocido como explorador de columnas).

Además de usar el procedimiento de recuperación de columna en el dial de pista, el explorador de columnas necesita saber la versión de Studio en uso, ya que Studio 5.1x muestra columnas que no están en Studio 5.0x (esto se comprueba al entrar en el asistente de SPL, como se expondrá más tarde). Además, como no todos los elementos de pista exponen más de diez columnas o no se pueden configurar los slots de columna en algunos elementos (perceptible en el editor de listas de reproducción de Creator), el explorador de columnas necesita saber cuántas columnas se pueden recuperar y tomar medidas si no se puede definir un slot de columna.

Una vez se definen los slots de columna (en elementos que lo permitan, que son el visor de listas de reproducción de Studio, la herramienta de pista y la lista principal de pistas de Creator), el explorador de columnas hace lo siguiente:

  1. Comprueba si el foco se encuentra en un elemento de pista y, si no, dirá «No es una pista». Con la eliminación de las órdenes Asistente de SPL, fila numérica en 2020, esto ya no se comprueba, ya que los propios elementos de pista definirán por sí mismos órdenes del explorador de columnas.
  2. Consulta una lista de slots de columna y ubica el índice de columna correspondiente del slot en cuestión.
  3. Usa el procedimiento de recuperación de columna para verbalizar la cabecera de la columna y el contenido de la columna del slot seleccionado.
Bonus de optimización: he reordenado las columnas en Studio 5.10…

Debido a que hay una estructura de control de datos diferente en uso, se pueden reordenar columnas en Studio 5.10 y posteriores. ¿Pero cómo sabe NVDA exactamente qué columna es cuál? Lo hace gracias al hecho de que la posición interna de las columnas no cambia. Cuando reordenas columnas, estás cambiando la forma en que estas se presentan en pantalla. Cuando se invoca a la función de recuperación de columna (descrita arriba), Studio devuelve el contenido de la columna dado un índice sin importar dónde se encuentra esa columna en pantalla. Esto no sólo hace que la implementación del explorador de columnas sea más simple, sino que permite al dial de pista rastrear (después de una intervención manual) los cambios de presentación de las columnas en pantalla.

¿Qué pasa con los elementos donde no se pueden configurar slots de columna?

Para elementos distintos al visor de listas de reproducción de Studio, la herramienta de pista y la lista principal de pistas de Creator, los slots del explorador de columnas no pueden configurarse (de ahí la ausencia de la propiedad «exploreColumns», perceptible en el editor de listas de reproducción de Creator en el complemento 20.02). Si es así, el explorador de columnas las reordenará para mostrar sus datos en el orden que se muestran (como se ven en pantalla). Como extra, si un elemento de pista no muestra más de diez columnas, el explorador de columnas anunciará un mensaje de error si la posición de la columna actual se sale de los límites (más allá de la cantidad de columnas del elemento de pista). Estos se hicieron para dar soporte al editor de listas de reproducción adecuadamente, ya que sus elementos de pista sólo muestran ocho columnas.

Búsqueda por columnas: encontrar texto en columnas específicas

En la sección anterior, mencioné que un único diálogo hace una doble función cuando hablamos del buscador de pistas. Ahora recorreremos el otro lado de la moneda: la búsqueda por columnas.

El diálogo de búsqueda por columnas añade un segundo control al buscador de pistas: una lista de columnas. Una vez se introduce el texto a buscar en una columna, NVDA usará el procedimiento trackFinder (expuesto antes) para ubicar texto en columnas específicas (he mencionado que el procedimiento trackFinder recibe la(s) columnas(s) como un argumento, y aquí es donde el argumento resulta útil). De hecho, tanto el buscador de pistas normal como la búsqueda por columnas usan el procedimiento de recuperación de columnas de arriba para ubicar el texto de la columna (la función privada de búsqueda lineal introducida en el buscador de pistas 2.0 localiza texto de columnas específicas, y en el buscador de pistas normal, se examinan las columnas artista y título). Al igual que con el buscador de pistas normal, una vez se ha hecho la búsqueda, se moverá el foco a un elemento de pista o bien se presentará un diálogo de error.

Entonces, ¿qué causa que un diálogo presente tanto el buscador de pistas como el buscador por columnas? Esto se hace gracias a los argumentos pasados al constructor del diálogo de búsqueda. La cabecera es:

splmisc.SPLFindDialog(parent, obj, text, title, columnSearch=False)

El último argumento (columnSearch) determina la versión del diálogo que se debe presentar. El objeto (obj) es necesario para decirle a NVDA dónde empezar la búsqueda y para llamar al procedimiento del buscador de pistas definido en su módulo de aplicación.

Marcador de pista: una variante de la búsqueda por columnas para encontrar nombres de archivo

Otra característica que usa los procedimientos de columna es el marcador de pista. Tú pondrías un marcador en la pista actual (asistente de SPL, ctrl+k), te moverías por la lista de reproducción, y después te moverías a la pista que tiene el marcador (asistente de SPL, k).

Una vez sitúes un marcador, el módulo de aplicación de Studio registrará el nombre del archivo de la pista que tenga actualmente el foco, y cuando quieras moverte a la pista marcada, NVDA usará el procedimiento de búsqueda en columnas para encontrarla. Al contrario que pasa con las búsquedas por columna típicas, NVDA llamará a la función privada de búsqueda lineal directamente y seleccionará la columna donde se encuentre el nombre del archivo (en efecto, le estás diciendo a NVDA que haga una búsqueda por columnas después de elegir nombre de archivo como dato a buscar).

Desde que se implementó el dial de pista, algunos locutores solicitaron añadir soporte para moverse verticalmente por las pistas (como leer columnas específicas pero moviéndose a una fila diferente como en una tabla). Esto también resolvió un problema por el que al pulsar las teclas ctrl+alt+flechas arriba y abajo causaba que el monitor diera vueltas. Esto se hace pidiéndole a SPLTrackItem.reportFocus que verbalice la columna que el usuario quiere al pulsar ctrl+alt+flechas arriba y abajo, todo controlado por una variable de clase oculta. Esta característica no sólo funciona con la navegación vertical de columnas – también se usa cuando un locutor solicita que sólo se anuncie una columna, y la columna a ser verbalizada puede personalizarse (no hay que confundirse con el procedimiento de orden de verbalización de columnas expuesto arriba).

La navegación vertical por columnas se simplificó en 2020 utilizando procedimientos localizados en la clase de elemento de lista SysListView32 directamente. Antes de 2020 se usaban procedimientos personalizados para la navegación vertical por columnas, y en lugares distintos al visor de listas de reproducción la navegación vertical por columnas era imposible. En 2020, se usan los procedimientos de navegación vertical por columnas de SysListView32, lo que hizo posible que la navegación vertical por columnas llegara a lugares como la herramienta de pista.

Herramienta de pista y Creator: un procedimiento, múltiples módulos de aplicación

El procedimiento de recuperación de columnas no sólo se emplea en el módulo de aplicación de Studio, sino que también se usa en los módulos de aplicación de la herramienta de pista y de Creator (parte de este complemento). Estos módulos de aplicación (específicamente, las clases de elemento de pista) usan el recuperador de columnas para revisar los datos de columna mediante órdenes de navegación por tablas y anunciar la información de columna (ctrl+NVDA+números del 1 al 0, ahora llamado explorador de columnas en la herramienta de pista y en SPL Creator).

Editor de listas de reproducción: la navegación por columnas continúa expandiéndose

Creator viene con el editor de listas de reproducción, una herramienta para gestionar listas de reproducción generadas localmente. Remote VT también viene con un editor de listas de reproducción dedicado, pero está diseñado para editar listas de reproducción almacenadas en remoto. Los elementos de pista mostrados en la ventana del editor de listas de reproducción son elementos de pista de SPL, y por tanto soportan la navegación por columnas. Ya que sólo vienen con hasta 8 columnas, no hay necesidad de proporcionar la función personalizada de explorador de columnas en estos elementos. Aparte de eso, soporta otras características de anuncio y navegación por columnas disponibles en los demás elementos de pista.

Unas pocas notas

De todas las características del complemento de StationPlaylist, la navegación por columnas es una de mis favoritas (además del explorador cart, y el soporte para el codificador, y otras). Disfruté trabajando con este procedimiento y aprendí unas pocas cosas sobre la api de Windows, así como abrí posibilidades que no se habían explorado antes, como la herramienta de pista o la búsqueda por columnas. Espero que encuentres útiles las órdenes de navegación por columnas en tus emisiones.

Me gustaría aprovechar para responder una pregunta realizada por algunos usuarios y desarrolladores: ¿puede llevarse NVDA a otros sistemas operativos? No. El procedimiento de recuperación de columnas de arriba es un buen ejemplo de por qué no puede hacerse esto fácilmente: los diferentes sistemas operativos usan apis distintas, y llevar NVDA a otros sistemas operativos implicaría cambios significativos de la arquitectura para usar las nuevas apis. En el caso de ReactOS, no es posible, ya que no hay una base estable sobre la que NVDA pueda ejercer completamente todos sus derechos: hacen falta apis de accesibilidad, hace falta un marco de trabajo estable para el desarrollo de controladores, debe estar lista la capacidad de ejecutar un programa como servicio y muchas otras cosas. A eso añadimos el hecho de que tenemos varios complementos que se apoyan en la api de Windows (incluyendo este). Te imaginarás el enorme trabajo que implica intentar llevar NVDA a otros sistemas operativos.

Alarma del micrófono y escaneo de biblioteca: hilos, hilos y más hilos

De todos los trabajos hechos en este complemento, uno de ellos destaca sobre los demás: las tareas en segundo plano. Invertí muchas horas y meses perfeccionando este concepto, leí documentación sobre esta característica y aprendí un montón a través de esta experiencia. Hoy, se emplea este trabajo en diversas partes del módulo de aplicación de Studio y más allá, y echaremos un vistazo a dos resultados de los más importantes de este trabajo: el escaneo de biblioteca y la alarma del micrófono.

Breve descripción de las características

Cuando estás haciendo un programa en directo, puedes olvidarte de que el micrófono está activo. La característica de alarma de micrófono hace que NVDA pueda notificarte si el micrófono ha estado activado durante un rato. Esto sucede incluso si estás utilizando otro programa.

El escaneo de biblioteca viene bien cuando quieres ver el progreso de un escaneo de biblioteca en segundo plano. Normalmente, iniciarías los escaneos de biblioteca desde el diálogo de inserción de pistas (pulsa ctrl+shift+r). NVDA te dirá entonces cómo va el escaneo, y si cierras el diálogo de inserción de pistas, NVDA continuará monitorizando el escaneo de la biblioteca en segundo plano.

Pero hay mucho más que una simple descripción cuando llega el momento de mirar en el interior de estas características. Como verás, estas funciones usan un concepto que está ganando fuerza: ejecutar múltiples tareas a la vez, o al menos intentar emularlo. Visitaremos este concepto primero antes de volver a nuestro programa original de describir el interior de las dos características de arriba.

Tendencias recientes en computación: más y más procesadores en un único ordenador

Hace años, la gente pensaba que una CPU de un único núcleo era suficiente para ejecutar múltiples programas. Esto implicaba que el procesador invertía una pequeña fracción de segundo exclusivamente a cada programa. Hoy en día, es común ver ordenadores de sobremesa, portátiles, smartphones y otros pequeños dispositivos que tienen al menos dos núcleos (llamados multi-core; con dos núcleos se llama «dual core»). En 2020, muchos ordenadores disponen de procesadores con cuatro núcleos (llamados «quad core»), mientras que los entusiastas prefieren más núcleos (es común hoy en día ver ordenadores y servidores que tienen diez núcleos o más).

A pesar del hecho de que muchos ordenadores vienen equipados con procesadores de múltiples núcleos, no todos los programas aprovechan las ventajas de esto. El intérprete Python es uno de esos programas, y ya que NVDA es un lector de pantalla basado en Python y debido a su arquitectura operativa, muchas operaciones no pueden aprovechar la ventaja de tener múltiples procesadores. Por suerte, Python proporciona una manera de simular esto – ejecutar ciertas tareas en segundo plano, que tanto NVDA como algunos complementos utilizan como podrás comprobar en este artículo sobre el escaneo de la biblioteca y la alarma del micrófono.

Una introducción suave a los hilos: varias tareas a la vez

Durante las horas laborables, un programa se ejecutará de principio a fin con algunas interrupciones (entrada por teclado, cambiar a una zona diferente del programa y demás). Sin embargo, hay veces en las que el programa necesitará trabajar con muchas cosas simultáneamente, como calcular la distancia entre muchos puntos, sumar varios números a la vez, comparar muchos pares de cadenas y demás. Por suerte, un mecanismo llamado hilos permite que un programa haga muchas cosas simultáneamente.

Un hilo es un procedimiento independiente de otras tareas. Si un hilo está ocupado con algo, otros hilos pueden trabajar en otras tareas. La mejor analogía es varios banqueros en un banco: los clientes pueden hablar con diferentes banqueros, con un banquero actualizando los registros de un cliente mientras otro cliente discute sobre cargos en la tarjeta de crédito fraudulentos con otro banquero.

Un hilo puede implicarse en partes de una tarea, dedicarse a una tarea única o varias tareas. Por ejemplo, un programa antivirus podría tener varios hilos (trabajadores) trabajando independientemente unos de otros. Un trabajador puede mostrar el progreso general de un análisis, mientras que otros hilos pueden analizar varias unidades a la vez, donde cada hilo se dedica a analizar exclusivamente los archivos y carpetas de una unidad. En el mundo de NVDA, varios trabajadores están implicados en llevar a cabo diversas tareas, incluyendo asegurarse de que NVDA está adaptado, procesar el modo exploración en distintos navegadores web y demás.

Hilos: una introducción más jeek

Un hilo (a veces llamado «hilo de ejecución») es un camino independiente de ejecución. Un único proceso (aplicación) puede tener tantos hilos como desee (el mínimo es uno para el hilo principal). Se puede pedir a cada hilo que haga ciertas operaciones con otros hilos en paralelo, que pueden variar desde una única tarea repetitiva (parte de una función) hasta ser responsables de un módulo entero o una parte significativa del programa. En el ejemplo anterior del antivirus, cada hilo del analizador es responsable de analizar una unidad entera, y cada uno informa su progreso a un hilo administrador que muestra el progreso total del análisis de virus.

Usar hilos significa que cada hilo puede ejecutarse en un núcleo del procesador en un sistema multinúcleo. Por este motivo, mucha gente querría que muchos programas aprovecharan las ventajas de esta tecnología y finalizaran sus trabajos más rápido. Sin embargo, los hilos presentan desventajas, normalmente muchos días invertidos en diseñar cuidadosamente procedimientos de coordinación entre hilos, evitar intentos de múltiples hilos para que no intenten cambiar un valor crítico del que depende un hilo gestor (fallo conocido como condición de carrera) y demás.

El módulo threading y la forma que tiene Python de gestionar los hilos

El intérprete Python (y los programas que lo usan, incluido NVDA) no es exactamente multihilo. Debido a problemas internos, Python usa algo llamado bloqueo global del intérprete para evitar que varios hilos tengan conflictos entre sí. Una forma de traer el paralelismo real a Python es usar el módulo multiprocessing (varios intérpretes Python, cada uno dedicado a una tarea), que tiene sus propias ventajas y desventajas (la primera, que NVDA no viene con el módulo multiprocessing integrado).

Para gestionar hilos, los programas en Python (incluido NVDA) usan el módulo threading de Python. Esta biblioteca incluye varias formas de gestionar hilos, incluida la de definir qué función se ejecutará en un hilo separado, coordinación de la información compartida entre hilos (cerrojos, semáforos (contador de accesos al recurso) y demás), y dejar que un hilo ejecute su tarea tras esperar un rato (temporizadores). Incluso con varios hilos definidos, NVDA es mayoritariamente de un solo hilo (ejecución en serie).

Para usar hilos, un programador definirá el tipo de hilo (hilo regular, temporizador y demás), definirá algunas propiedades e indicará al hilo qué procedimiento debe ejecutar. Una vez el hilo está definido, se llama al método de inicio (thread.start) para hacer que el hilo haga su trabajo.

Hilos en el módulo de aplicación de Studio

La mayoría del módulo de aplicación de Studio usa sólo un hilo (el hilo principal de NVDA) para hacer su trabajo. Sin embargo, hay veces en que se usan varios hilos – puede haber hasta tres hilos activos al mismo tiempo: el hilo principal de NVDA (anunciando cambios de estado, alarmas, explorador cart y otras funciones), la alarma del micrófono (un temporizador) y el escaneo de biblioteca (un hilo en segundo plano). Otra situación en la que se usan hilos es cuando está activada la monitorización en segundo plano del codificador (mira el artículo de procedimientos del codificador para más detalles y uso de hilos allí).

El motivo principal para usar hilos es evitar que las tareas en segundo plano bloqueen la entrada de usuario (las órdenes no funcionarán cuando una tarea que tarda mucho en ejecutarse lo haga desde el hilo principal de NVDA). Esto es más perceptible cuando el escaneo de biblioteca está activo, como podrás comprobar pronto. Por ahora, echemos un vistazo a la alarma del micrófono.

Alarma del micrófono: un dúo de temporizadores esperando para hacer su trabajo

En pocas palabras, la alarma de micrófono es un temporizador (un temporizador de cuenta atrás). Cuando el micrófono se pone activo, el módulo de aplicación de Studio hará que un hilo temporizador venga a la vida. La única misión de este temporizador es reproducir el sonido de alarma y mostrar un mensaje de aviso, y esperará un rato (valor de la alarma del micrófono en segundos; por ejemplo, cinco segundos).

El interruptor maestro que hace girar al temporizador de la alarma del micrófono es:

alarm = threading.Timer(micAlarm, messageSound, args=[micAlarmWav, micAlarmMessage])

Donde «micAlarm» indica cuánto tiempo esperará el temporizador y el segundo argumento es la tarea a realizar (función messageSound). Si la alarma del micrófono está apagada (su valor es 0), este interruptor quedará solo para siempre (apagado hasta que actives la alarma especificando un valor superior a 0).

Sin embargo, la alarma del micrófono es más que un temporizador: una función única de los temporizadores es responder a eventos (un evento de cancelación). Cuando el micrófono se activa, el temporizador de la alarma del micrófono se activará con él. Si apagas el micrófono antes de que la alarma del micrófono salte, NVDA ordena a la alarma del micrófono que salga (se cancela el temporizador). En otras palabras, el interruptor maestro «real» es el cambio de estado, y una de las actividades que el manejador de evento de cambio de nombre (función event_nameChange descrita anteriormente) realiza es la de gestionar el temporizador de la alarma del micrófono mediante el método doExtraAction (de hecho, la alarma del micrófono y el explorador cart se gestionan desde esta función).

En algunos casos, podemos decirle a NVDA que nos avise periódicamente de que el micrófono está activo. Si este es el caso, NVDA iniciará un nuevo temporizador (esta vez, wx.PyTimer) que ejecutará una función para hacerlo periódicamente después de que suene la primera alarma de micrófono. Al igual que con el temporizador maestro de alarma, este temporizador también se detendrá si el micrófono se apaga.

Escaneo de biblioteca: una combinación única de la api de Studio y un hilo en segundo plano

Al decirle a NVDA que mantenga vigilado el escaneo de biblioteca en segundo plano, este llama a otro hilo para hacer esta tarea. Este hilo le preguntará a Studio por el número de elementos escaneados hasta ahora y tomará las medidas adecuadas en cuanto el escaneo se complete (de hecho, se usan varias funciones auxiliares).

El procedimiento de escaneo de biblioteca es el siguiente:

  1. NVDA se asegurará de que no estés en el diálogo de inserción de pistas (si estás ahí, no se invocará el procedimiento de escaneo de la biblioteca en segundo plano, ya que event_nameChange hará lo mismo en su lugar).
  2. Si cierras el diálogo de insertar pistas mientras hay un escaneo en progreso, o invocas el escaneo de biblioteca desde el asistente de SPL (shift+r), NVDA ordenará que un hilo vigile el progreso del escaneo en segundo plano (mira abajo la cabecera) para permitirte usar órdenes de Studio y dejarte oír el progreso del escaneo desde otros programas.
  3. El hilo de escaneo de biblioteca pedirá a Studio que le devuelva el número de elementos escaneados (lo hace cada segundo) y almacenará el resultado para llevar un registro.
  4. Después de obtener el resultado del escaneo, el hilo comprobará en qué parte de Studio estás, y si te encuentras de vuelta en el diálogo de inserción de pistas, el hilo finalizará (mira el paso 1).
  5. Cada cinco segundos, el hilo de escaneo de biblioteca llamará a una función privada (que quiere ver cuántos elementos se han escaneado y el ajuste actual de verbalización de escaneo de biblioteca) para anunciar los resultados del escaneo de la biblioteca de la siguiente manera:
    • Si le pides a NVDA que verbalice el progreso del escaneo, este dirá «Escaneando» y / o reproducirá un pitido (si así lo tiene indicado).
    • Si se pide a NVDA que verbalice la cantidad de elementos escaneados, se verbalizarán los que se han analizado hasta ahora (otra vez con o sin pitido).
    • No se invocará a esta función informadora si le dices a NVDA que ignore el escaneo de biblioteca completamente o le pides que te interrumpa sólo cuando el escaneo general se haya completado (puedes pulsar alt+NVDA+r para cambiar rápidamente entre los diferentes ajustes de verbalización de escaneo de biblioteca).
  6. Una vez el escaneo de la biblioteca se completa (después de comprobar el valor del resultado cada segundo y ver que el resultado del escaneo anterior y el actual tienen el mismo valor), NVDA verbalizará los resultados del escaneo (en algunos casos, se verbalizará el número de elementos escaneados). En Studio 5.10 y versiones posteriores, el contador del escaneo de la biblioteca no estará definido cuando el escaneo se complete, lo que es más eficiente que ir comparando.

Te puedes imaginar qué habría pasado si la operación anterior no fuese una tarea en segundo plano: no se podrían ejecutar otras órdenes de NVDA hasta que se completara el escaneo de biblioteca y no se podría cancelar la operación. Esta es la cabecera del hilo que realiza la operación de arriba:

libraryScanner = threading.Thread(target=self.libraryScanReporter, args=(_SPLWin, countA, countB, param))

Hay argumentos importantes en uso: la función (tarea) a realizar y los argumentos para esta función. El argumento más importante es el último: Studio 5.0x y 5.10 esperan argumentos diferentes cuando se les pide que informen de los elementos escaneados hasta el momento.

A pesar de las limitaciones de los procedimientos de Python para hilos, si se usan adecuadamente, pueden abrir nuevas posibilidades como las que has visto arriba: la alarma del micrófono y el escaneo de la biblioteca en segundo plano. El uso de hilos en el módulo de aplicación de Studio también permite que NVDA sea rápido mientras se utilice Studio y permite que las tareas en segundo plano sean fieles a la tarea que se desempeña a mano. Volveremos a los hilos cuando hablemos de los procedimientos de conexión del codificador. Hay una característica más «mágica» que visitaremos, y es nuestra siguiente parada en el recorrido por el interior del complemento: el explorador cart.

La magia que hay tras el explorador cart

Una emisión de radio en directo no estaría completa sin jingles. Estos pueden ir desde promociones de radio (a veces llamadas «promos»), anuncios, jingles para transmitir el estado de ánimo en un programa, jingles de segmento y más. Muchos programas de automatización de emisoras, incluido StationPlaylist Studio, incluyen formas de gestionar jingles (a veces llamados carts), incluyendo la definición de un cart para que sea reproducido al pulsar las teclas de cart, verbalizar el nombre del cart en reproducción y guardar nuestros carts específicos en una ubicación segura.

Una de las cosas que preocupa a los locutores ciegos es pulsar una tecla de jingle incorrecta por accidente, por lo que se pidió a los desarrolladores de scripts que implementaran una forma para que los locutores aprendieran qué carts están asignados a las teclas cart. En el momento en que escribo este artículo, los tres lectores de pantalla (JAWS for Windows (autor de los scripts: Brian Hartgen), Window Eyes (autor de los scripts: Jeff Bishop) y NVDA (autor de los scripts: Joseph Lee (yo, el autor de este artículo)) incluyen una función para aprender las asignaciones de jingles. Como esta es una serie de artículos sobre el interior de un complemento de NVDA, te daré una descripción de lo que pasa cuando activas y exploras las asignaciones cart (de hecho, esta sección fue la porción más interesante y dirigida por comentarios del complemento). A lo largo del camino aprenderás de dónde coge su poder el explorador cart (la versión de NVDA del modo de aprendizaje de cart) y por qué es muy importante.

Carts en StationPlaylist Studio

Studio viene en tres ediciones: Demo (igual que la Pro pero funcionando durante un tiempo limitado de prueba), Standard y Pro. La primera diferencia visible para el usuario entre Standard y Pro es la cantidad de asignaciones de cart: la Standard puede almacenar 48 jingles, mientras que la Pro puede trabajar con 96 de ellos.

Para reproducir jingles, un locutor usaría el modo de edición de cart (ctrl+t), y a continuación asignaría una tecla rápida a un archivo. En Studio Standard, puedes asignar de f1 a f12 por sí mismas o en combinación con ctrl, shift o alt. En Demo y Pro, puede asignarse la fila de números (del 1 al 9, el 0, el guión (-), y el igual (=) bien por sí solos o en combinación con ctrl, shift o alt, para llegar al total de 96 jingles). Una vez los jingles están asignados, aparecerán en los menús de cart (hay cuatro menús de cart, uno para teclas independientes (llamado principal) y uno para control, otro para shift y otro para alt).

¿Dónde almacena Studio los carts?

Los «carts» de Studio se alojan en la carpeta de instalación de Studio. Hay cuatro archivos de cart (llamados bancos) en uso: un archivo .cart por cada uno de los bancos de carts (principal, shift, control, alt). Durante las horas laborables, Studio funcionará con estos cuatro bancos a menos que un locutor cargue otros carts desde un archivo de banco de carts diferente.

Explorador cart: mi propio verano de código

Era un día caluroso en junio de 2014 cuando me senté a diseñar una forma de que los locutores aprendieran las asignaciones cart. Ya que estaba desarrollando el complemento 3.0 por entonces, decidí que esta característica debería tener la prioridad más alta para incluirla en la liberación que estaba por llegar.

Cuando empecé a escribir esta característica, la primera cosa en la que pensé fue su nombre. Yo sentía que «Modo de aprendizaje de cart» no representaba adecuadamente el cuadro – después de todo, razoné que los locutores usarán esta característica para explorar las asignaciones de cart. Por tanto, elegí el nombre «Explorador cart» – en efecto, cuando usas esta característica, estás explorando las asignaciones de jingles para prepararte para un programa.

Después, leí la documentación de los scripts de Jaws para hacerme una idea de cómo implementó Bryan el modo de aprendizaje de cart. En los scripts de Jaws, los ajustes de cada script se alojan en la carpeta de configuración del usuario (normalmente esta es %systemdrive%\Users\%username%\AppDate\Roaming\Freedom Scientific\JAWS\%JAWSVersion%\Settings\Esn; Bryan, corrígeme por favor si me equivoco). Una sección de este archivo de configuración del script está dedicada a los carts, y Jaws usa un mapa de nombres de clave y valores de cart para anunciar la información del cart mientras el modo de aprendizaje de carts esté activo.

Basándome en esta información, comencé a escribir un intérprete de archivos ini, viendo que los locutores almacenarían las asignaciones de cart en una base de datos de configuración. Este estaba sujeto a un gran número de errores, incluidos el formato equivocado del nombre del cart, asignación de clave de cart no existente, formato de configuración no válido y otros. Una vez escribí una entrada de blog (en mi blog personal) explicando cómo funcionaba (los tiempos han cambiado, como verás muy pronto).

Entonces me entró la curiosidad de cómo guarda Studio sus propios bancos cart y, naturalmente, abrí la carpeta donde se almacenaban los carts y abrí cada archivo .cart en Notepad++ (un editor de texto muy útil). Desde que leí el formato del banco cart (explicado debajo), pensé que podría ser útil escribir un intérprete de archivos cart. Por tanto continué escribiendo los procedimientos del explorador cart, en esta ocasión incorporando el formato del banco cart, sin olvidarme de tener en cuenta los errores, y este es el procedimiento que se usa en versiones del complemento posteriores a la 5.x (la 6.0 usa un procedimiento relacionado pero totalmente diferente, como verás).

Mientras escribía la primera versión del explorador cart, me di cuenta de que esta característica necesitaba algunas pruebas en la vida real, por lo que pedí a un locutor ciego profesional que la probara. Invertimos la mejor parte del día de la independencia escribiendo, depurando y reescribiendo este procedimiento hasta que nos sentimos satisfechos. Al final, nuestro duro trabajo fue recompensado, como puedes ver en los siguientes párrafos.

Presentación del explorador cart versión 1

La versión 1 del explorador cart, incorporada en el complemento 3.0, funcionaba de la siguiente manera:

  1. Se pulsa NVDA+ctrl+3 para activar el explorador cart. Cuando esto ocurre, NVDA se asegura de que estés en el visor principal de listas de reproducción, y después establece un indicador para informar de que el explorador cart está activo.
  2. NVDA abre e interpreta los archivos de los bancos cart, almacenando las asignaciones de carts en un diccionario con teclas de cart y nombres de cart. Este intérprete también tiene en cuenta algunos casos excepcionales, incluyendo saltarse los carts no asignados y determinar la edición de Studio en uso. Una vez se interpretan los carts, NVDA dice «Entrando en el explorador cart», y si ocurren errores, NVDA te informará de que no puede entrar en el explorador cart (esto pasa si el archivo del banco de carts no existe).
  3. Mientras usas el explorador cart, si pulsas una tecla cart, NVDA buscará el nombre de la tecla en el diccionario de carts, y anunciará el nombre del cart asociado con ella (si se encuentra, en cualquier otro caso NVDA dice «Cart sin asignar»).
  4. A veces algunas personas activan el modo de edición de cart para modificar sus asignaciones mientras explora carts. Si esto pasa, NVDA te recordará (mediante la función doExtraAction usada en el evento de cambio de nombre) que el explorador cart está activo, y cuando el modo de edición de cart se desactive, NVDA te preguntará si quieres volver a entrar en el explorador cart (esto se hizo para interpretar los archivos actualizados de bancos de carts).
  5. Se pulsa ctrl+NVDA+3, y NVDA limpiará el diccionario de carts, dejando el explorador cart después.

Pero había un gran problema con este enfoque: ¿qué pasa si una versión futura de Studio utiliza un formato diferente de bancos de cart? Por lo tanto, volví a visitar los archivos de banco de cart de nuevo en julio de 2015, y esta vez me di cuenta de que había una estructura familiar: valores separados por comas, y pensé en la posibilidad de que una aplicación de hojas de cálculo los procesaría adecuadamente. Para probar mi hipótesis, abrí los archivos .cart en Excel, y efectivamente, se mostraron como archivos csv. Por lo tanto, trabajé en modificar el procedimiento de interpretación de carts, en esta ocasión usando el módulo csv de Python para interpretar los archivos «cart» (los archivos de bancos de cart realmente son archivos csv en esencia). Este nuevo procedimiento (descrito debajo) hizo su aparición como parte del complemento 6.0.

La magia tras el explorador cart versión 2: trabajar con archivos csv

Ya que Python viene con una biblioteca para trabajar con archivos csv y ya que los bancos de carts son archivos csv, reescribí el procedimiento del explorador cart (una función en el módulo splmisc que devuelve el diccionario de carts) de la siguiente manera:

  1. Al entrar en el explorador cart, el procedimiento de preparación del explorador cart (splmisc.cartExplorerInit) tomará una instantánea de tu nombre de usuario y la edición de Studio (la barra de título de Studio indica qué versión se está usando). Después inicializa el diccionario de carts y almacena la edición de Studio en uso.
  2. A continuación, la función de preparación escribirá nombres y rutas en los bancos cart. En caso de que un usuario distinto al que hay por defecto esté utilizando Studio, se modificarán los nombres de archivo de los carts para que encajen con el nombre que probablemente usará Studio para presentar los bancos de carts específicos del usuario. Estos nombres de carts forman una parte de la ruta al banco de carts (la otra parte es la ruta a la carpeta en la que residen los carts, obtenida mediante una variable de entorno).
  3. En cada banco de carts, NVDA pedirá a Python que interprete el banco como un archivo csv (csv.reader; al acabar, devuelve una lista de listas, siendo cada lista una fila de la tabla csv).
  4. Una vez está lista la versión csv del banco de carts correspondiente, la fila que contiene las teclas de cart y sus nombres, junto con el modificador del banco de carts y el diccionario de carts, se envían a una función auxiliar (parte del módulo splmisc) que hará lo siguiente:
    1. Dependiendo de la edición de Studio, esta función auxiliar trabajará con la primera mitad solamente (la Standard sólo trabajará con las teclas de función, que están en las primeras doce columnas de esta fila) o se procesará la fila entera (el resto son las teclas numéricas).
    2. Dependiendo de la posición de la columna (elementos en la lista de la fila), se verá si deberían asignarse las teclas de función o las de la fila numérica a la entrada seleccionada del cart. Este procedimiento también comprueba el tipo del banco de carts (modificadores o nada (principal)) y modifica el nombre de la tecla cart según corresponda.
    3. A continuación, el procedimiento auxiliar intentará ubicar el nombre del jingle asignado a la tecla cart en cuestión, y si hay uno, añadirá el par de la tecla del cart y el nombre del jingle al diccionario de carts.
  5. De vuelta en la función cartExplorerInit, si no se encontraron errores mientras se interpretaba un banco de carts, esta se moverá al siguiente, en cualquier otro caso informará al módulo de aplicación de Studio modificando un valor indicador en el diccionario de carts (almacenado como un entero que representa el número de bancos cart con errores).
  6. Ahora cartExplorerInit está desesperada por pasarle el diccionario de carts a alguien, y ese alguien resulta ser el módulo de aplicación de Studio – una vez es recogido por el módulo de aplicación, tú lo utilizas para mirar los nombres de carts que se corresponden con las teclas cart mientras usas el explorador cart (para echar al fuego el diccionario de carts, simplemente desactiva el explorador cart pulsando ctrl+NVDA+3).

En efecto, el procedimiento anterior (la «magia» tras el explorador cart) reemplazó un intérprete de bancos de carts escrito a mano y simplificó el código del complemento (lamento no haber investigado el módulo csv en 2014). En términos de experiencia de usuario, no cambia nada con respecto al explorador cart versión 1, siendo la diferencia el procedimiento de interpretación. Al añadir la función splmisc.cartExplorerInit, la mayoría del módulo splmisc (servicios misceláneos entre los que están el diálogo combinado del buscador de columnas y el buscador de pistas, el recuperador de columnas y la herramienta de preparación del explorador cart) estaba completo. Pero continuaron las innovaciones.

Introducción al explorador cart versión 3: fechas de modificación de los archivos

Había dos problemas con la versión 2 del explorador cart: frases confusas cuando el modo de inserción de cart estaba activo, e incapacidad de detectar que había finalizado la edición de carts. El modo de inserción de carts permite a los locutores pulsar la orden cart para insertar el archivo dentro de la lista de reproducción como si fuera una pista normal. La incapacidad de detectar la finalización de la edición de carts significaba que había que entrar en el explorador cart para ver las asignaciones cart actualizadas.

El primer problema se solucionó indicando a los usuarios que el explorador cart estaba activo mientras el modo de inserción de carts lo estaba. La resolución del segundo problema necesitó un poco de trabajo, e implicó reescribir partes del explorador cart (ahora versión 3), que viene incluido a partir del complemento 17.01 (optimizado en 17.04). La gran diferencia es que se registran las fechas de modificación de los archivos cart cuando se construye el diccionario de carts (mira arriba), y si se desactiva la edición de carts, se comprueba la fecha de modificación de los archivos de banco de carts con las fechas guardadas (cuando la edición de carts se apaga, cuando cambian las asignaciones de carts, se escriben los archivos de carts de vuelta al disco) ignorando los archivos de cart no modificados. En la primavera de 2017, esto se optimizó aún más permitiendo que el diccionario de carts fuera modificado al vuelo (sólo se cambiarán los bits modificados, incluidas las posibles nuevas asignaciones, cambios y eliminaciones). Esto significa que desaparece la necesidad de volver a entrar al explorador cart cuando las asignaciones de los carts cambian, un gran alivio para los locutores que necesitan cambiar las asignaciones de carts para las fiestas u otras ocasiones especiales.

Unas pocas notas

El explorador cart ha recorrido un largo camino; desde una sugerencia simple hasta el procedimiento para interpretar csv que se ha descrito antes y la comprobación de las fechas de modificación de los archivos para buscar cambios de asignación de carts. El explorador cart ha cambiado hasta satisfacer las necesidades de los locutores que usan Studio y NVDA. Me gustaría mejorarlo más en versiones futuras (otra sugerencia que recibí fue la capacidad de especificar los nombres de archivo con carts de bancos individuales, y estoy pensando en implementarla en el futuro cercano).

Una de las cosas de las que puedes haberte dado cuenta según lees este artículo hasta ahora es cómo tanto yo como otros desarrolladores seguimos investigando mejores maneras de resolver algo. También echaste un vistazo a cómo desarrolladores y usuarios dan forma a una característica y cuánto trabajo implica hacer realidad una sugerencia. Estas actividades (investigación y colaboración en el desarrollo de características) son dos de los pilares que dan soporte a este complemento, y resaltan cómo afectan la filosofía del diseño y el enfoque de desarrollo del producto al curso futuro de su evolución.

Con esto termina nuestro recorrido detallado por el interior de las características más importantes del módulo de aplicación de Studio. Cuando volvamos, visitaremos a nuestro amigo del pasado: la capa del asistente de SPL y el funcionamiento interno de diversas órdenes de capa.

Todo sobre la capa del asistente de SPL

Como recordarás, en un artículo anterior hablamos de dos conjuntos de órdenes de capa: el controlador de SPL y el asistente de SPL, el primero usado para realizar funciones de Studio desde cualquier programa y el otro para anuncios de estado. He mencionado a lo largo de este artículo que visitaríamos estos conjuntos de capa, así que empezaremos con la capa del asistente de SPL.

Charla sobre las órdenes de capa

Una de las características que tienen en común los scripts de Jaws, Window Eyes y NVDA es el uso extenso de órdenes de capa. Jaws popularizó este concepto y su capa de Studio (la tecla grave). Algunos beneficios de este enfoque incluyen ahorrar órdenes de teclado, recordar a los usuarios las órdenes de Studio y así sucesivamente.

Nacimiento de la capa del asistente de SPL

Como mencioné antes, desde la versión 1.0 en enero de 2014, el complemento StationPlaylist viene con dos órdenes de capa para representar a la extensión global y al módulo de aplicación de Studio. En el caso del módulo de aplicación de Studio y su conjunto de capa (asistente de SPL), tomé prestadas algunas órdenes de los scripts de Jaws y Window Eyes con algunas diferencias destacables, entre ellas algunas órdenes y cómo se verbalizaban las cosas.

Cuando me senté a diseñar este conjunto de capa, sentí que sería útil para los locutores que la mayoría de las órdenes de la capa del asistente vinieran de las órdenes asignadas a Studio. Por ejemplo, un locutor pulsará la m para encender o apagar el micrófono, y en la capa del asistente del SPL, pulsar la m hace que se verbalice el estado del micrófono. Otro ejemplo fue el modo de edición de cart – pulsar ctrl+t en Studio lo conmutará, y pulsar asistente de SPL, t indicará si este modo está encendido o apagado (más tarde se discutirá la razón por la que se asignó la t al estado del modo de edición de carts).

Originalmente se podía invocar la capa del asistente de SPL pulsando ctrl+NVDA+grave desde Studio. Sin embargo, algunos traductores de NVDA me dijeron que esta combinación de teclas se usa para algunas órdenes de NVDA en su idioma. Por tanto, en el complemento 2.0 (a finales de la primavera de 2014), decidí eliminar esta orden, lo que se traduce en que para que podáis invocar la capa del asistente de SPL (locutores), debéis ir al diálogo gestos de entrada mientras tengáis el foco en Studio, expandir la categoría StationPlaylist y buscar la entrada del asistente (personalmente uso ctrl+NVDA+grave, y en compilaciones recientes de desarrollo del complemento, hice que el complemento dejara a la orden de capa del controlador de SPL (explicada más tarde) invocara la capa del asistente).

Otro extra añadido a la capa del asistente de SPL es la capacidad de emular órdenes de capa proporcionadas por otros lectores de pantalla. Esto se consigue usando un mapa de gestos para cada lector de pantalla (incluido NVDA), con el mapa de gestos correcto elegido al entrar en la capa del asistente de SPL. Actualmente, además de la distribución por defecto de NVDA, el complemento tiene soporte para las órdenes de capa de Jaws for Windows (la capa de Window Eyes se eliminó en 2020).

Categorización de las órdenes del asistente de SPL

Una vez que invoques a la capa del asistente de SPL (se escuchará un pitido), podrás hacer una de las siguientes operaciones:

  • Indicaciones de estado (automatización, micrófono, etc.).
  • Herramientas (escaneo de biblioteca, análisis de tiempo de pista, obtención de instantáneas y transcripciones de listas de reproducción y demás).
  • Configuración (alternar perfiles de emisión).
  • Pedir ayuda (abriendo el documento de ayuda del asistente de Spl o la guía de usuario en línea).
  • Hasta la versión 18.12, buscar actualizaciones del complemento (manualmente).

Las primeras dos categorías pueden subdividirse en órdenes que usan la api de Studio (mediante la función studioAPI de la que hablamos en un artículo anterior), las que usan la api de Windows (explorador de columnas) y las que se apoyan en la navegación por objetos (varios componentes están implicados y son sensibles a los cambios en la interfaz de usuario). Vamos a ir recorriendo estas categorías de una en una.

Asistente de SPL 1: indicaciones de estado

Estas órdenes te permiten obtener diversa información de estado como el título y duración de la próxima pista, el estado del modo de edición de carts y demás. Pueden dividirse en aquellos que usan la navegación por objetos (el viejo estilo) y órdenes que usan la api de Studio (el nuevo estilo). En algunos casos ambos métodos están disponibles, y se elige la versión apropiada basándose en la versión de Studio en uso.

Las siguientes órdenes (ordenadas alfabéticamente) utilizan la api de Studio para llevar a cabo las funciones necesarias:

  • E: indica si se ha definido alguna URL de flujo de metadatos.
  • H: duración de pistas en la hora seleccionada.
  • Shift+h: duración de las pistas restantes en la hora.
  • P: estado de la reproducción.
  • Shift+1 a shift+4 y shift+0: comprueba el estado de los metadatos del flujo de cada URL (0 es el codificador DSP). Mira debajo para más detalles.

Ten en cuenta que la indicación del recordatorio de lista de reproducción (asistente de SPL, D) era parte de esta categoría hasta el complemento 6.x. En el complemento 7.0, debido a los trabajos de refactorización que afectaban a esta orden, se ha movido a la categoría de herramientas.

Un paso a un lado: flujo de metadatos

Se puede pedir a Studio que emita en tiempo real los metadatos de una pista a cinco URLS: la dirección del codificador DSP y cuatro URLS adicionales (Opciones / Reproducción en curso). Por defecto, se usa la dirección del codificador DSP para enviar información de metadatos.

Una de las actividades que realiza el módulo de aplicación de Studio al iniciar es comprobar si la emisión de metadatos está activada en alguna URL. Además, NVDA te puede informar del estado del flujo de metadatos para todas las URLS (asistente de SPL, E) o para direcciones individuales (asistente de SPL, shift+1 a shift+4 y shift+0). Esto se hace mediante una función de indicación y administración de metadatos (metadataAnnouncer) que recopila indicadores de flujo de las URLS y presenta mensajes de estado y se conecta a los servidores por defecto si le pides a Studio que se conecte a servidores en tiempo real.

Cuando se llama al indicador de metadatos, NVDA hace lo siguiente:

  1. NVDA mirará el indicador de conexión (reminder=True, pero en realidad, esto se usa mayoritariamente como el indicador de conexión), y si el indicador está activo, se conecta a un conjunto predefinido de URLS de flujo.
  2. Recopila indicadores de emisión de metadatos, empezando por el codificador DSP.
  3. El anunciador contará el número de URLS de metadatos activas usadas para dar formato al mensaje de estado.
  4. Se verbaliza el mensaje de estado. Este mensaje no se verbaliza en el momento si se llama a la función cuando el módulo de aplicación se inicia – se verbalizará el mensaje y se reproducirá un sonido de alarma tras una breve pausa. Esto se hace para asegurarse de que este mensaje es el último que se verbaliza cuando arranca Studio.
Volviendo al pasado: navegación por objetos

Antes de escribir los procedimientos del nuevo estilo, todas las órdenes usaban navegación por objetos. Normalmente, el comando usará una función auxiliar y un mapa de objetos para ubicar el objeto que necesita y verbalizará lo que estás buscando (normalmente obj.name o un mensaje de error). El proceso era como se describe a continuación:

  1. El módulo de aplicación de Studio contiene un mapa de índices donde el objeto reside en relación con la ventana en primer plano. Por ejemplo, para un objeto dado, si el índice es 0, NVDA sabe que el objeto es el primer hijo del objeto en primer plano. Técnicamente, es un diccionario de listas, correspondiendo cada elemento de lista (índice) con cada versión de Studio a la que el complemento da soporte.
  2. Para recuperar los objetos necesarios y registrar el tipo de orden, se definen una serie de constantes en el módulo de aplicación (todas enteras, indicando lo que quieres oír). Estas constantes sirven como claves al mapa de índices de objeto.
  3. Después de entrar en la capa del asistente de SPL y una vez que pulses una de las siguientes órdenes, NVDA hará lo siguiente:
    1. Cada orden obtendrá el objeto en cuestión llamando al recuperador de objetos (función de estado) pasando como parámetro el tipo de anuncio (estado (SPLConstant; por ejemplo, para el modo de edición de carts, la llamada sería self.status(self.SPLPlayStatus), con la constante indicando una barra de estado).
    2. El recuperador de objetos (función de estado) consultará primero una caché de objetos (parte del módulo de aplicación de Studio) esperando que el objeto necesario esté listo para usarse (por razones de rendimiento; esto se hace también en el editor de listas de reproducción como se explica más adelante).
    3. Si el objeto no está en la caché, el recuperador anotará primero la ventana en primer plano, luego usará las directrices especificadas en el mapa de índices de objeto (la constante pasada a la función de estado es la clave de este mapa y se devuelven valores diferentes según la versión de Studio) para ubicar, meter en caché y devolver el objeto en cuestión (en ese orden).
    4. De vuelta en el procedimiento para esa orden, se hará una cosa u otra con el objeto en función del procedimiento elegido (a veces, el objeto real es hijo del objeto devuelto).

Las órdenes que utilizan los pasos anteriores con navegación por objetos incluyen:

  • A. Automatización.
  • C: título de la pista actualmente en reproducción.
  • I: número de oyentes (he intentado usar la api de Studio para obtener esta información, pero después de experimentar con ella, el procedimiento de navegación por objetos resultó más estable).
  • L: línea de entrada.
  • M: micrófono.
  • N: título y duración de la siguiente pista.
  • Shift+p: tono de pista.
  • R: grabar a archivo.
  • S: pista programada.
  • Shift+s: duración de las pistas seleccionadas en la hora actual.
  • T: modo de edición de carts (asigné la t a esta orden por razones de eficiencia).
  • U: tiempo de funcionamiento de Studio.
  • W: tiempo atmosférico y temperatura (si se han configurado).
  • Y: modificación de lista de reproducción.

Ten en cuenta que en Studio 5.20 y versiones posteriores, algunas de ellas (como la automatización) usan la api de Studio.

Por ejemplo, si pulsas la A para obtener el estado de la automatización en Studio 5.10:

  1. Se invoca al asistente de SPL, y luego se pulsa la A.
  2. Se llama a la función de estado (recuperador de objetos), tomando la constante de la barra de estado (SPLPlayStatus, que es 0) como clave para el mapa de índices.
  3. El recuperador de objetos comprobará si el objeto de la barra de estado (diccionario de la caché, clave 0) está cacheado. En este ejemplo, no lo está.
  4. Al ver que la barra de estado no está en caché, el recuperador de objetos mirará ahora el mapa de índices y decidirá qué columna va a leer (en este ejemplo, es la columna 2 (índice 1)). La columna registra la posición de la barra de estado hija relativa a la ventana en primer plano (en nuestro caso, se trata del séptimo hijo con índice 6).
  5. Una vez se obtiene el índice de la posición del objeto hijo, el recuperador de objetos ubicará el objeto real y lo meterá en caché (self._cachedStatusObjs[infoIndex] = fg.children[statusObj]), y luego devolverá este objeto a la función de verbalización de automatización.
  6. De vuelta en la función del script, se le recordará a NVDA que debe mirar uno de los objetos hijo (las barras de estado pueden contener objetos hijos si las apis de accesibilidad lo exponen así), y luego verbalizará uno de sus contenidos (segundo objeto hijo, que registra el estado de la automatización).

En Studio 5.20 y posteriores:

  1. Se invoca al asistente de SPL, y luego se pulsa la A.
  2. Se llama a la api de Studio para recuperar el indicador de estado dado (en este caso es el 1, que registra el estado de automatización).
  3. Después, se consulta una tabla de búsqueda con posibles mensajes de la barra de estado, y se recupera y verbaliza el mensaje apropiado basándose en el indicador de estado y el valor.

No todos los mensajes de la barra de estado usarán la api de Studio y la tabla de mensajes de estado. Una excepción es el estado del modo de carts, para el que es necesario consultar dos valores devueltos por Studio para construir el mensaje verbalizado finalmente (modo de edición de carts activado o desactivado y si el modo de edición de carts está activado).

Otra visita complementaria: el editor de listas de reproducción y la caché de objetos

Como se ha observado antes, parte de las órdenes de navegación por objetos en el asistente de SPL involucran a la caché de objetos. Es necesaria por motivos de rendimiento, ya que recuperar los objetos y navegar por ellos es lento comparado con recuperar el objeto necesario de una caché, que es más rápido que una recuperación de objeto manual. Esta técnica se conoce frecuentemente como «memorización», y se usa para mejorar el rendimiento si hay datos importantes presentes en algún lugar y hay que recuperarlos deprisa.

De forma similar a las órdenes del asistente de SPL que implican navegación por objetos, los objetos de estado del editor de listas de reproducción de Creator requieren una caché debido a la lentitud de la navegación por objetos. Sin caché, recuperar la información necesaria tomaría segundos en vez de fracciones de segundo. Al igual que en Studio, la información en caché se limpia cuando finaliza la ejecución del módulo de aplicación de Creator.

Asistente de SPL 2: herramientas

Aquí hay órdenes varias del asistente de SPL, tres de ellas usan la api de Studio:

  • D: tiempo restante de la lista de reproducción abierta.
  • K: se mueve a una pista marcada. Ya se habló de esto en las secciones de procedimientos de columna y marcadores.
  • Ctrl+k: sitúa un marcador de pista. Consulta la sección de marcadores para aprender cómo funciona.
  • Shift+r: escaneo de biblioteca. Esta es una función útil para iniciar un escaneo de biblioteca en segundo plano, viene bien si has añadido nuevas pistas desde una serie de carpetas mediante el diálogo de opciones de Studio. Consulta un artículo anterior sobre el escaneo de biblioteca para más detalles sobre el interior de esta característica.
  • F8: obtiene una instantánea con información de la lista de reproducción para la pista cargada actualmente, incluyendo la cantidad de pistas, pistas más cortas y más largas y artistas más populares. Esta función usa una combinación de las apis de Windows y Studio.
  • Shift+f8: solicita una transcripción de lista de reproducción (datos sobre la lista de reproducción cargada). Al igual que las instantáneas de listas de reproducción, aquí se usa una combinación de la navegación de objetos y la API de Windows.
  • F9: marca la posición actual de la lista de reproducción como inicio del análisis de tiempo de pista (se explica más sobre esto debajo).
  • F10: realiza el análisis de tiempo de pista (complemento 6.0).
Análisis de tiempo de pista: duración de las pistas seleccionadas

Hace unos meses, mientras conversaba en un chat de Skype con algunos usuarios del complemento, alguien sugirió una característica que permitiera a NVDA decir cuánto tiempo llevaría reproducir las pistas seleccionadas. Ya que este concepto era familiar para mí porque lo conocía de los scripts de Jaws, decidí trabajar en ello como parte del complemento 6.0.

El procedimiento resultante (que está disponible si tienes el foco en el visor principal de la lista de reproducción con la lista de reproducción cargada) es el siguiente:

  1. Te mueves a una posición en la lista de reproducción para marcarla como punto de inicio del análisis de tiempo de pista.
  2. Entras al asistente de SPL y pulsas f9.
  3. Te mueves a otra pista en la lista de reproducción, abres el asistente de SPL y pulsas f10. A partir de ahí, NVDA:
    1. Determina el rango del análisis. En la mayoría de casos será un análisis de arriba a abajo, pero en otros casos podría invertirse (de abajo a arriba). Además, se preparará una variable para almacenar la duración total.
    2. Para cada pista en el rango de análisis, NVDA obtendrá el nombre de archivo y la duración de la pista (en realidad, sesgo) mediante la api de Studio. Una vez se reciba la duración de la pista, se añadirá a la variable con la duración total.
    3. Una vez se hace el análisis de tiempo (cálculo de la duración total), NVDA verbalizará el número de pistas seleccionadas y la duración total usando el formato mm:ss.

Si eres un usuario experto de NVDA, puedes haberte dado cuenta de un patrón familiar: la orden para situar un marcador para copiar texto del cursor de revisión es NVDA+f9, y te moverías a una ubicación diferente y pulsarías NVDA+f10 para copiar el texto seleccionado al portapapeles. Reemplazando la tecla modificadora de NVDA con el asistente de SPL produce un resultado parecido: f9 marca la posición inicial del análisis de tiempo, y f10 realiza el cálculo. Elegí estas dos teclas de función intencionadamente para proporcionar una experiencia consistente y reforzar conceptos usados en el lector de pantalla NVDA: el cursor de revisión.

Anuncio de tiempo restante de la lista de reproducción

Hasta el complemento 6.x, el anuncio de tiempo restante de la lista de reproducción estaba basado en la api de Studio. Sin embargo, nos dimos cuenta de que este «tiempo restante» era el de la hora actual. Para solucionarlo, en el complemento 7.0 se reescribió este procedimiento para aprovechar la ventaja que daba el dial de pista introducido en el complemento 5.0 (mira la sección de más arriba sobre el dial de pista).

Técnicamente, se aplica una combinación de recuperación de contenido de columnas y procedimientos de navegación por pistas para solucionar el problema. Al pulsar asistente de SPL, D, NVDA anotará la pista con el foco y se moverá hacia abajo en la lista de reproducción (empezando desde la pista con el foco), registrando la secuencia (duración total de pista menos transición). En cuanto se completa la navegación por la lista de reproducción, se envía la duración total al procedimiento de verbalización de tiempo (mira arriba) para su procesamiento (conversión al formato de horas, minutos y segundos).

Instantáneas y transcripciones de lista de reproducción

Aunque en apariencia son similares, las transcripciones y las instantáneas en listas de reproducción son dos cosas diferentes. Ambas usan una combinación de navegación por objetos y la API de Windows, funcionan recuperando y analizando el contenido de las columnas en las pistas, e implican al asistente de SPL seguido de f8 con o sin modificadores. Mientras que una instantánea se usa para obtener estadísticas sobre la lista de reproducción cargada, una transcripción es la lista de reproducción entera formateada de diversas maneras. Además, después de invocar a la capa del asistente de SPL, simplemente pulsando f8 se tomará la instantánea, mientras que es necesario pulsar shift+f8 para obtener una transcripción de la lista de reproducción y elegir la acción apropiada, como el rango de transcripción, formato de salida y demás, mediante el diálogo que aparece después.

Una instantánea de lista de reproducción presenta estadísticas sobre la lista de reproducción cargada (o partes de ella). Entre la información recopilada se incluye cuántos elementos hay cargados (también marcadores de hora), pistas más largas y más cortas, y duración media de la pista. También, si se pide, se registran hasta diez artistas, categorías y/o géneros de pista. Esta información se representa bien mediante voz o braille, o si se pulsa la orden dos veces, en una ventana del modo exploración.

En contraste, una transcripción de lista de reproducción es una descripción completa de la lista de reproducción cargada (o partes de ella) presentada en diversos formatos. Esta descripción completa incluye datos de todas las columnas (y no simplemente las que se examinan en la instantánea de lista de reproducción). Una vez se recuperan todos los datos de las columnas en una pista, NVDA convierte esta información a diversos formatos, incluidos texto plano, una tabla HTML, valores separados por comas (CSV) y demás para que se pueda ver en una ventana del modo exploración, copiar al portapapeles (en algunos formatos) oo guardarla en un archivo.

Asistente de SPL 3: configuración

Hay otra tecla de función asignada al asistente de SPL: pulsar f12 cambiará a un perfil de cambio instantáneo (si está definido). Volveremos a explicar qué se entiende por «perfil de cambio instantáneo» y sus mecanismos (y el interior del asistente de SPL, f12) en la próxima sección.

Asistente de SPL 4: conseguir ayuda

Yo creo que un producto no está completo sin una buena documentación de calidad. Por esta razón, el asistente de SPL proporciona dos órdenes para ayudarte a usar la capa de órdenes o el complemento en sí. Son las siguientes:

  • F1: muestra un documento en modo exploración que presenta una lista de órdenes de la capa del asistente de SPL.
  • Shift+f1: abre la guía de usuario en línea (os.startfile).

Asistente de SPL 5: búsqueda de actualizaciones

Al usar el complemento 18.12 o anterior, si pulsas ctrl+shift+u después de entrar en la capa del asistente de SPL hará que el complemento busque actualizaciones de sí mismo. A diferencia del proceso automático de búsqueda de actualizaciones descrito anteriormente, esta es una búsqueda manual, y por tanto llevará a cabo acciones adicionales como detener el temporizador de búsqueda automática de actualizaciones antes de buscar actualizaciones realmente. Otra diferencia es que esta orden mostrará un diálogo de resultados si no hay actualizaciones o se encuentran otros errores, mientras que en el modo de búsqueda automática, no se muestran.

Una sorpresa: pueden invocarse algunas órdenes de la capa del asistente sin entrar a ella primero

Hay veces en las que un locutor querrá obtener cierta información rápidamente. Por eso nos hacemos la siguiente pregunta: «¿Hay forma de verbalizar algo sin tener que invocar primero la capa del asistente?». Sí, se puede asignar una orden personalizada a las siguientes funciones del asistente:

  • Nombre de la próxima pista.
  • Nombre de la pista actual.
  • Tiempo y temperatura.
  • Instantáneas de lista de reproducción.
  • Marcador de análisis de tiempo de pista.
  • Análisis de tiempo de pista.

En estos procedimientos, se lleva a cabo un paso adicional para asegurarse de que el indicador del asistente de SPL se apaga automáticamente tras pulsar el atajo de teclado en cuestión. Sin este paso, podrías acabar en una situación como la siguiente:

  1. Invocas la capa del asistente.
  2. Después pulsas la tecla rápida para la orden de capa que quieras usar (sin activar la capa antes).
  3. Pulsas otra tecla que puede verbalizar algo más, o incluso oyes lo mismo dos veces si pulsas el atajo de la capa homólogo a la orden que has pulsado. En efecto, has invocado dos órdenes de capa de una vez (el propósito del conjunto de capas es dejarte oír un mensaje a la vez).

Presentación de las funciones de configuración: diálogo de ajustes del complemento y perfiles de emisión

Hemos llegado a nuestra última parada del módulo de aplicación de Studio visto por dentro: gestión de la configuración. Esta característica permite que un locutor configure diversas opciones, como alarmas, orden de verbalización de columnas y demás, así como empaquetar ajustes para un programa como un perfil de emisión para invocarlo durante el programa. Durante el camino aprenderás cómo almacena el lector de pantalla NVDA diversos ajustes, qué pasa si algo va mal y verás cómo están hechos por dentro los perfiles de emisión.

ConfigObj: asistente de gestión de la configuración de NVDA

NVDA usa la biblioteca ConfigObj para gestionar sus ajustes. Este módulo Python, inspirado en el propio intérprete de configuración de Python, permite que los desarrolladores almacenen ajustes en un fichero de texto, leer e interpretar ajustes y validar opciones basándose en opciones de configuración por defecto.

NVDA viene con una colección de opciones por defecto. Estas residen en source/config/init y se emplean para varias cosas, incluyendo la presentación de preferencias, validación de configuración de usuario y demás. El módulo de gestión de configuración también incluye funciones para gestionar perfiles (un paquete de ajustes de usuario que se emplea en una aplicación, al verbalizar todo o se reserva para activación manual).

Perfiles de configuración de NVDA: varios archivos de configuración, una única base de datos en línea

Muchos usuarios preguntaron a NV Access si sería posible perfiles donde ciertos ajustes tengan efecto mientras se esté usando una aplicación o la función de verbalizar todo. NV Access escuchó e introdujo los perfiles de configuración ya entrado 2013. Desde agosto de 2015, se pueden crear perfil manual o automático (disparado), con estos últimos divididos en perfiles de verbalizar todo y específicos de una aplicación.

Los perfiles de configuración implican unos cuantos procedimientos de soporte y una cuidadosa coordinación entre archivos de configuración. En esencia, cada perfil de configuración (almacenado en la carpeta profiles en el directorio de configuración de usuario) es una instantánea de diferencias entre el perfil y el archivo de configuración principal del usuario (llamado nvda.ini). Cuando un perfil se activa, NVDA cargará el archivo de perfil asociado con el perfil dado y modificará los ajustes del usuario en función de los valores almacenados en el perfil recién activado. También registrará el nombre del archivo de perfil para recordarse a sí mismo qué perfil está activo (el perfil de configuración por defecto del usuario se llama «Configuración normal» y tiene como nombre de archivo nvda.ini).

¿Y si los ajustes tuvieran errores? Como parte del procedimiento de inicialización (partes de la función principal (source/core.py) antes de entrar en el bucle principal), NVDA mostrará un diálogo de error de configuración si detecta problemas serios con los valores de configuración (en realidad, ConfigObj notifica a NVDA de este problema). Verás que esto también está implementado en el módulo de aplicación de Studio para tratar con los problemas de configuración del complemento.

Todo sobre el gestor de configuración del complemento StationPlaylist

Hasta hace poco, el módulo de aplicación de Studio se encargaba de la gestión de la configuración del complemento. Con la llegada del complemento 5.0 y el diálogo de ajustes del complemento, los procedimientos de gestión de configuración se movieron a un gestor de configuración dedicado (splstudio.splconfig). El nuevo módulo se encarga de las funciones de configuración, incluida su validación, la presentación del diálogo de ajustes del complemento y otros diálogos dentro de él, la gestión de perfiles de emisión y más. En el complemento 7.0, los procedimientos pertenecientes al diálogo de configuración se movieron al módulo splconfui, estando los ajustes principales del complemento listados en el menú de preferencias de NVDA.

Cómo se cargan, se usan y se guardan los ajustes

Como se menciona en el capítulo que habla del ciclo de vida del módulo de aplicación de Studio, una de las cosas que el módulo de aplicación hace es cargar la base de datos con la configuración del complemento llamando a la función splconfig.initialize. El trabajo de esta función es cargar el mapa de configuración del complemento de diversos lugares (en el complemento 5.x, será sólo el mapa de configuración principal, mientras que el 6.0 también busca en la carpeta appModules/profiles para cargar los perfiles de emisión). El formato del archivo de configuración es el de un archivo ini típico, y tal y como NVDA está concebido, se trata de un diccionario.

Cuando la base de datos de configuración está lista, el módulo de aplicación empleará los valores almacenados en este diccionario de ajustes para llevar a cabo diversas tareas, incluyendo la alarma del micrófono, verbalización del número de oyentes y demás. Si se han definido varios perfiles, NVDA empezará con el primer mapa de configuración (perfil normal), y el perfil activo irá determinado por el mapa splconfig.SPLConfig (más sobre los perfiles en un momento).

Después de que acabes de usar Studio, ciérralo para que los ajustes puedan guardarse a disco. Esto implica guardar perfiles individuales, copiar los ajustes globales al perfil normal y guardar el perfil normal a disco.

El diálogo de ajustes del complemento StationPlaylist

El módulo de aplicación de Studio te permite configurar diversos ajustes de dos formas: mediante un atajo de teclado (expuesto en una sección que habla de configurar ajustes básicos), o mediante el diálogo de ajustes. Cuando usas un atajo de teclado para cambiar ajustes, NVDA buscará el valor para el ajuste, lo cambiará, verbalizará el nuevo ajuste y almacenará el valor recién cambiado en el mapa de ajustes.

Alternativamente, puedes configurar los ajustes mediante el diálogo de ajustes del complemento (alt+NVDA+0). Como es un diálogo de preferencias (proporcionado por gui.SettingsDialog), será muy parecido a todos los diálogos de preferencias de NVDA. Este diálogo es la única puerta para llegar a algunas opciones avanzadas (explicadas debajo).

Hasta 2018, las opciones del complemento estaban divididas en varios diálogos. Con la llegada de NVDA 2018.2, se hizo posible alojar todas las opciones bajo un mismo techo, divididas en diversos paneles de ajustes. Visualmente, recuerda a una disposición en dos columnas, teniendo la columna de la izquierda una lista de categorías de opciones, y la columna derecha mostrando los ajustes de la categoría elegida. Mira más abajo para ver notas sobre las opciones multicategoría.

En 2020, la gestión de perfiles de emisión se separó en su propio diálogo. Antes de esta división, la mayoría de paneles de opciones del complemento se apoyaban en el panel de perfiles de emisión para sincronizar y actualizar sus controles. Esto creaba complicaciones, especialmente cuando se abrían directamente paneles que no fueran el de perfiles de emisión. Para evitar este problema y hacer los paneles independientes entre sí, nació el diálogo de perfiles de emisión.

El diálogo de ajustes del complemento (splconfui.SPLConfigDialog) contiene las siguientes opciones:

  • Ajustes globales: son ajustes a los que no les afectan los perfiles. Estos incluyen anuncios de estado, verbalización del número de oyentes, opciones del escaneo de biblioteca y demás.
  • Ajustes específicos del perfil: actualmente las alarmas, el flujo de metadatos y los ajustes de verbalización de columnas son específicos del perfil. Estos son la alarma de fin de pista y la opción para usarla, la alarma de introducción y el ajuste para usarla, la alarma del micrófono y el intervalo de alarma de micrófono. También se incluyen URLS para flujo de metadatos y orden de verbalización e inclusión de columnas. Para los ajustes numéricos como el valor de la alarma, se muestra un botón giratorio (wx.SpinCtrl; usa las flechas arriba y abajo para cambiarlos).
  • Restablecer ajustes: NVDA te preguntará si quieres restablecer los ajustes del perfil activo actualmente a los que vienen por defecto de fábrica. Esto se hace usando una función en el módulo splconfig (splconfig.resetConfig) que establecerá los valores del perfil actual a los que vienen por defecto (con este propósito se incluye un mapa con la configuración por defecto; este mapa usa una especificación de configuración (confspec, parte de la definición del proceso de validación mediante el módulo validator (un amigo cercano de ConfigObj), la confspec se define en el módulo splconfspec).

Cuando pulses alt+NVDA+0 desde Studio para abrir este diálogo, ocurrirá lo siguiente:

  1. Al igual que con los diálogos de alarma (mira los artículos anteriores), NVDA se asegurará de que no hay otros diálogos abiertos.
  2. Después llamará a una función en el módulo splconfui, que se encargará de preparar el diálogo a mostrar.
  3. El procedimiento de preparación (SettingsDialog.makeSettings) poblará el diálogo con controles y opciones asociadas con cada control. Las opciones actuales vendrán de los valores de configuración almacenados en el perfil activo.
  4. Una vez el diálogo esté listo, aparecerá y aterrizarás en el botón «Ajustes generales del complemento» (anteriormente la casilla de verificación del mensaje de estado) o en la lista de perfiles activos, según la versión del complemento que tengas y los argumentos de la línea de órdenes del complemento (lo primero pasa en la 5.x o cuando haya restricciones en la carga de perfiles, lo segundo en la 6.0 y sin restricciones). A partir de ahí ya puedes usar órdenes estándar de navegación por diálogos para desplazarte por las distintas opciones.

Después de configurar algunos ajustes, haz clic en aceptar o aplicar. NVDA localizará a continuación el perfil seleccionado y pedirá a SPLConfig que use este perfil, luego almacenará las opciones del diálogo de ajustes dentro del mapa de configuración. Si se pulsa el botón aplicar y el perfil seleccionado no es el perfil activo, NVDA mostrará un mensaje recordando a los usuarios que los ajustes se guardarán en el perfil seleccionado en el diálogo de ajustes, y no en el que esté activo en ese momento. Después de eso, se guardarán los cambios y reaparecerán los ajustes del complemento.

En el caso de que descartes los nuevos ajustes (haciendo clic en cancelar), NVDA comprobará si se ha definido un perfil de cambio instantáneo, y si es así, realizará varias operaciones dependiendo de si el perfil instantáneo se ha renombrado o eliminado.

Ajustes en varias categorías

La descripción anterior se refiere a la interfaz antigua de ajustes del complemento, que estaba basada en las funciones antiguas de ajustes de NVDA. En el pasado hubo debates entre los usuarios del complemento relacionados con cambiar la interfaz actual del complemento a una interfaz agrupada en categorías de tal forma que todos los ajustes del complemento se encuentren bajo un mismo techo.

En los viejos tiempos, si se querían modificar ajustes en NVDA, había que abrir el menú de preferencias de NVDA y elegir el diálogo correcto. Por ejemplo, al cambiar los ajustes relacionados con el modo exploración, el lugar al que ir era menú de NVDA / preferencias / modo exploración. Esto significaba que la configuración estaba dispersa a lo largo de diferentes diálogos.

En 2018, se produjo un cambio importante en el paradigma de la propia interfaz de usuario de NVDA: la pantalla de opciones en categorías. En NVDA 2018.2 se lanzó una nueva pantalla de ajustes, combinando varios diálogos en un panel. Para muchos ajustes, ahora se puede abrir el menú de NVDA / preferencias / opciones, seleccionar la categoría de ajustes deseada, y luego cambiar los ajustes. Esto también tuvo el beneficio de mover muchos ajustes bajo un solo techo.

Como un seguimiento a este desarrollo, los ajustes del complemento de StationPlaylist están sufriendo un lavado de cara. Como se observa a lo largo de este artículo, los ajustes del complemento se alojan bajo diversos diálogos, sirviendo el diálogo principal de ajustes del complemento como una puerta hacia estos diálogos. En 2018, esto se ha convertido en una pantalla de ajustes con varias categorías.

Todo sobre los perfiles de emisión

En el mundo del módulo de aplicación de Studio, un perfil de emisión (o simplemente perfil, para abreviar) es un grupo de ajustes que se usan en un programa. Esto se lleva a cabo empleando un contenedor de perfiles de configuración (splconfig.SPLConfigPool en los complementos 6.x y 7.x, splconfig.SPLConfig.profiles en 8.0 y posteriores) para almacenar estos perfiles, uno de ellos se usa durante un tiempo específico (por defecto, el primer perfil).

Hay dos formas de crear un perfil: partiendo desde cero o como una copia. Ambas formas usan el mismo diálogo (splconfui.NewProfileDialog), siendo la única diferencia el perfil de partida que se usa. Para un nuevo perfil desde cero, se usarán los ajustes del perfil normal (excepto los ajustes específicos del perfil, para los que se establecen valores por defecto), y para la copia, el nuevo perfil contendrá todos los ajustes del perfil de partida. En ambos casos, se creará un perfil residente en memoria y se iniciará como cualquier otro perfil (splconfig.unlockConfig/splconfig.SPLConfig.createProfile, que reciben el nombre de perfil como parámetro); esto se hizo para reducir escrituras innecesarias en disco. Además, el diálogo de nuevo perfil o copiar perfil (y otros diálogos invocados desde el diálogo principal de configuración) deshabilitarán el diálogo principal de ajustes.

En caso de que el perfil seleccionado sea eliminado, este se quitará de la lista de perfiles, se eliminará el archivo de configuración asociado al perfil (si existe) y el perfil activo anteriormente tomará el control a menos que sea el perfil activo el que se vaya, en cuyo caso el perfil normal se establecerá como perfil activo. En caso de operaciones de renombrado, se buscará un perfil con el nombre antiguo y se cambiarán algunas propiedades para reflejar el cambio de nombre. Hay un paso muy importante que el módulo de aplicación llevará a cabo si se renombra o se elimina un perfil de cambio instantáneo (si se renombra, la variable del perfil instantáneo guardará el nuevo nombre, y si se elimina, el valor del perfil instantáneo será None). Se invocaba un procedimiento similar en versiones antiguas al tratar con perfiles basados en tiempo.

El diálogo de perfiles de emisión

Inspirado en el diálogo de perfiles de configuración del lector de pantalla NVDA, este diálogo (splconfui.BroadcastProfilesDialog) muestra diversos controles de gestión de perfiles. Al pulsar alt+NVDA+p para abrir este diálogo, se mostrará una lista con perfiles cargados y botones para crear un nuevo perfil o copiar un perfil existente, renombrar y eliminar perfiles. También contiene un botón «Disparadores» para configurar disparadores de perfiles, como cambio instantáneo.

Hay un control más: el botón Activar. Este botón se encuentra deshabilitado por defecto si el perfil seleccionado es el perfil activo, habilitándose en cualquier otro caso. Dejando aparte el botón Activar, al pulsar intro desde la lista de perfiles se activará el perfil seleccionado.

Pero no hay botones Aceptar y Cancelar – tan sólo hay un botón «Cerrar». ¿Cómo se puede «activar» un perfil al pulsar intro? Esto se hace configurando AffirmativeId en el botón «Activar». A efectos prácticos, el botón «Activar» actúa como el botón «Aceptar», que eventualmente llama al manejador asociado con el botón «Cerrar».

Presentación del centro de configuración

En los complementos 6.x y 7.x, se usaba una combinación del mapa SPLConfig y la pool de configuración para permitir a los usuarios cambiar entre perfiles. Debido a que se apoyaban en funciones proporcionadas por otros módulos, algunas variables personalizadas como el indicador del perfil activo debían residir en el módulo splconfig.

Para resolver este problema y permitir una gestión centralizada de perfiles, se introdujo el concepto de centro de configuración (ConfigHub) en el complemento 8.0. Inspirado por el propio sistema de gestión de configuración de NVDA y potenciado por un mapa en cadena (un diccionario que contiene varios mapas de búsqueda), esta clase no sólo almacena la lista de perfiles actualmente cargados y el diccionario que representa los ajustes actuales, también aloja diversos registros como el nombre del perfil activo, un historial de perfiles activados anteriormente y demás, así como procedimientos de soporte para la gestión de perfiles.

Los diversos cambios producidos al introducir el centro de configuración son:

  • Cambios de perfil: ya no existe la necesidad de copiar los ajustes de un sitio a otro entre el diccionario de configuración activa y el perfil en cuestión en la pool de configuración. El cambio de perfiles es tan simple como intercambiar los mapas del antiguo y el nuevo perfil.
  • Se guarda en este diccionario un historial de los perfiles activados.
  • Es posible pasar opciones adicionales al crear un nuevo perfil, tales como si debería meterse en caché, validarse en el momento y demás.

Cómo funciona el cambio de perfiles

Nota: la información de perfiles basados en tiempo se incluye por completitud.

Además de pasar a distintos perfiles mediante el diálogo de perfiles de emisión, se puede configurar un perfil para que se active instantáneamente durante un programa en directo. En versiones anteriores, se soportaban perfiles basados en tiempo, que NVDA activaba justo antes de que empezara el programa.

Un perfil de cambio instantáneo es un perfil al que se cambia si así lo indica el usuario. Se suelen usar antes de conectar a un servidor de emisión para cargar los ajustes apropiados para un programa (en el momento en que se escribe esto, sólo puede elegirse un único perfil como perfil de cambio instantáneo; para definirlo, selecciona el perfil de tu programa, ve al botón de cambio de perfil y selecciónalo).

En contraste, un perfil basado en tiempo es un tipo especial de perfil de cambio instantáneo que se activará en la fecha y hora especificadas. Se emplea un mapa separado (un mapa pickle) para almacenar los ajustes relativos a estos perfiles, sus opciones de interfaz de usuario pueden encontrarse en el diálogo de disparadores situado en los ajustes del complemento (mira la siguiente sección para ver una descripción de este diálogo y el formato de configuración de este mapa).

Para activar un perfil de cambio instantáneo, pulsa asistente de SPL, f12. Los perfiles basados en tiempo se activarán por sí solos cuando sea hora de hacerlo (con la ayuda de un temporizador de cuenta atrás situado en el módulo splmisc). En ambos casos, el procedimiento para cambiar el perfil es el siguiente:

  1. Se hacen varias comprobaciones, entre ellas:
    • Se comprueba si se ha definido un perfil de cambio (instantáneo o basado en tiempo).
    • Para los perfiles de cambio instantáneo, si hay alguno definido, se comprueba que no lo estás usando todavía.
  2. En los complementos 7.x y anteriores, se guarda el índice del perfil activo.
  3. Se ubica el nombre del perfil de cambio y el perfil asociado a él, y se cambia al perfil seleccionado (en el complemento 7.x, se reasigna SPLConfig para que use el perfil de cambio; en el 8.0 y posteriores, se intercambian los mapas del perfil normal y el perfil instantáneo). En ese momento, NVDA puede verbalizar el estado de los metadatos del flujo si está configurado para hacerlo al cambiar de perfil. En la versión 17.12, esto se ha simplificado a través de un notificador de punto de extensión de acción que indica a las funciones apropiadas que reaccionen ante la acción de cambio de perfil.
  4. Si un perfil basado en tiempo no tiene una duración especificada, NVDA establecerá la siguiente fecha y hora de cambio llamando a splconfig.setNextTrigger, en cualquier otro caso se espera a que el programa termine.
  5. Si se indica que hay que volver al perfil previamente activo, SPLConfig recibirá la orden de usar el perfil anterior (el índice para el perfil activo anteriormente ya se había guardado y se usa para recuperar el perfil de la pool de perfiles).
  6. Al desactivar un perfil basado en tiempo, NVDA averiguará cuándo serán las próximas fecha y hora de activación.

El diálogo de disparadores y campos del cambio basado en tiempo

Nota: esta sección ya no es aplicable desde 2020, pero se conserva por motivos históricos.

Para cada perfil basado en tiempo, se emplea una lista con siete campos para describir la fecha y hora del disparador (cambio). Son los siguientes:

  • Fecha del disparador (entero entre 0 y 127): un entero de 7 bits, indicando en cuántos días debería activarse un perfil. Este campo se usa en el diálogo de disparadores de perfil para marcar o desmarcar las casillas de verificación de días de activación. El valor 0 significa que el perfil no debería activarse, y si es así, se elimina del mapa de disparadores.
  • Fecha y hora del cambio (cinco enteros): los cinco primeros campos usados para construir datetime. El objeto datetime (año, mes, día, hora, minuto) se almacena. Se usa para que NVDA sepa cuándo cambiar de perfil.
  • Duración (entero entre 0 y 1439): un entero que especifica la duración de este perfil (programa) en minutos. Lo usa principalmente un temporizador que se activa al activarse el perfil en cuestión, y cuya única función es volver a otro perfil cuando el programa se acabe.

El diálogo de disparadores, usado para configurar estos campos en el perfil seleccionado, consta de dos grupos de controles:

  • Días del disparador: siete casillas de verificación, una para cada día de la semana. Al marcarlas o desmarcarlas se cambian los bits correspondientes en el campo de fecha del disparador.
  • Hora del cambio: tres campos de entrada numérica que indican cuándo se debe cambiar al perfil (hora y minuto) y la duración del programa (en minutos).

Una vez que se recopilan los datos, NVDA primero comprobará si están marcadas las casillas de verificación de fecha del disparador (si no hay ninguna marcada, se elimina el perfil). A continuación, NVDA comprobará si otro perfil tiene asignada la misma franja de tiempo, y si no es así, procederá a almacenar la fecha y hora del disparador (no se guardarán hasta que se haga clic en el botón aceptar en el diálogo principal de ajustes del complemento).

Caché de perfiles

Nota: la caché de perfiles fue un componente clave en la gestión de perfiles de emisión desde el complemento 7.0 hasta el 21.10, y se documenta por motivos históricos.

Los ordenadores más recientes vienen con un tipo de unidad llamada unidad de estado sólido (SSD). Esta es la versión en disco interno de una unidad flash de alta calidad mayor (físicamente y en capacidad) que una unidad flash USB normal. Al contrario que los discos duros giratorios, las unidades de estado sólido usan la memoria flash para almacenar información, y por tanto los datos se pueden leer y escribir más rápido.

Una desventaja de los SSD es la limitación de escrituras de datos. La memoria flash puede resistir un número limitado de ciclos de lectura y escritura antes de que los datos no se puedan escribir en una ubicación concreta. Para evitarlo, el software del sistema se esforzará en alojar el nuevo contenido por todo el disco de tal forma que se distribuya por todas las ubicaciones (esto se llama nivelación ware).

Para ayudar a prolongar la vida de los SSD, el complemento 7.0 introdujo una caché de perfiles de emisión. Siempre que se cargan los perfiles desde disco (incluyendo el perfil normal) por primera vez, se aloja una copia de los ajustes de estos perfiles en una caché en línea, un diccionario que usa nombres de perfiles como claves, que en cierto modo hacen referencia a la vista de diccionario de los ajustes del perfil. Cuando los perfiles se guardan, el contenido de cada perfil a guardar se compara con esta caché en línea y sus contenidos se escriben en disco si las opciones han cambiado. Esta suposición aprovecha el hecho de que los usuarios no cambian las opciones del complemento cada vez que se usa Studio.

Al principio, la caché de perfiles se veía como una forma de aumentar la vida de los SSD. Sin embargo, más tarde se descubrió que el mecanismo de caché volvía complejo el procedimiento para guardar perfiles. Además, la tecnología SSD es más robusta en 2021 comparada con la primera vez que se introdujo la caché de perfiles (2016). Por tanto, el mecanismo de caché de perfiles se eliminó en el complemento 21.10.

Así concluye un recorrido detallado por el interior del módulo de aplicación de Studio. El resto de los capítulos se enfocarán en la extensión global de utilidades de SPL, soporte del codificador y unas cuantas consideraciones sobre cómo se desarrolla el complemento, comenzando con un recorrido por las órdenes del controlador de SPL.

Introducción a las utilidades de SPL: SPLController y cómo llevar el foco a la ventana de Studio

Ahora que hemos cubierto el «kernel» (la parte más íntima) de este complemento, es hora de hablar sobre la extensión global de utilidades de SPL Studio y sus contenidos.

Nota: hasta 2019, el soporte de codificadores era parte de las utilidades de SPL, pero ahora forma parte del módulo de aplicación del motor de SPL, que se explica a continuación.

Descripción y contenidos de la extensión global

Como se ha descrito en la sección de diseño del complemento, el complemento de SPL viene con diversos módulos de aplicación y una extensión global. Esto era necesario no sólo para diferenciar entre tipos de módulos y experiencias, sino también para permitir que las funciones de Studio puedan invocarse desde otros programas.

Anteriormente, las utilidades de SPL constituían el hogar de los procedimientos de soporte del codificador, introducidos a finales de 2014 con el complemento 3.0. En 2020, el módulo de soporte del codificador se ha movido a su propio módulo de aplicación, conocido como motor de SPL (paquete splengine).

La extensión global de utilidades de SPL se aloja en globalPlugins/splUtils/init.py. El módulo consta de una clase de extensión global, el controlador de SPL y órdenes alojadas dentro de la clase, así como constantes globales usadas para comunicarse con Studio. También incluye un procedimiento para desplazar el foco a Studio desde cualquier parte.

La capa del controlador de SPL

La capa del controlador de SPL (orden de entrada sin asignar, mismo motivo que el de la orden de entrada al asistente de SPL) se usa para invocar funciones de Studio desde cualquier sitio. El procedimiento de entrada es similar a su homólogo del módulo de aplicación (el asistente de SPL) salvo por lo siguiente:

  • NVDA se asegurará de que Studio esté en ejecución (si es así, almacenará en caché el valor del manejador de ventana al igual que en el módulo de aplicación de Studio), en cualquier otro caso no se puede entrar en la capa del controlador de SPL.
  • Todas las órdenes (excepto dos) usan la api de Studio (en un artículo anterior se describió el funcionamiento de la api de Studio y la función SendMessage de user32.dll).

Para ver los mecanismos de las órdenes de capa, consulta un artículo anterior que hablaba del diseño del complemento, donde se expusieron estas órdenes.

Las siguientes órdenes utilizan la api de Studio:

  • A/Shift+A: activar o desactivar automatización.
  • L/Shift+L: activar o desactivar entrada de línea.
  • M/Shift+M/N: activar o desactivar micrófono, o conmutador instantáneo para encenderlo y apagarlo.
  • P: reproducir.
  • Q: obtener diversa información de estado (debido a cambios en la api, esta orden funciona mejor en Studio 5.20 y posteriores.)
  • R: tiempo restante de la pista actualmente en reproducción (si la hay).
  • Shift+r: progreso del escaneo de biblioteca y cantidad de elementos escaneados.
  • S/T: parar progresivamente o parar de forma instantánea.
  • U: reproducir / pausar.

Los lectores familiarizados con las órdenes te teclado de Studio se sentirán como en casa (en cierto modo estas son órdenes de Studio, salvo porque shift se usa para desactivar algunas características, y shift+r te recordará al ctrl+shift+r que se usa para escanear desde el diálogo de inserción de pistas). La letra Q se queda para «Consultar el estado de Studio» (consultar, en inglés, es query).

Estas son las dos excepciones

  • E: NVDA buscará y anunciará el estado de conexión de los codificadores. Esto se hace ubicando las ventanas de más alto nivel y buscando diversas ventanas de codificadores, para a continuación obtener la lista real de codificadores llamando a EnumChildWindows.
  • F1: abre un documento en modo exploración que muestra las órdenes de la capa del controlador (¿Te suena familiar?).

Cómo llevar el foco a la ventana de Studio desde cualquier parte

Según estás emitiendo un programa en Studio, puedes encontrarte en una situación donde necesitas pasar rápidamente a Studio para cambiar la automatización, insertar nuevas pistas y demás. Una situación ideal es pasar a Studio al pulsar alt+tab (no es el caso si tienes más de dos programas abiertos). Por esta razón, los scripts de lectores de pantalla para Studio incluyen una orden para pasar a Studio a petición (sin asignar en NVDA).

Hasta 2016, esto se llevaba a cabo con una función en el módulo de utilidades de SPL (SPLStudioUtils.fetchSPLForegroundWindow). No sólo la empleaba el módulo principal de la extensión global (llamada desde un script para llevar el foco a la ventana de Studio), sino que se usaba en los codificadores para diversos propósitos. El procedimiento era el siguiente:

  1. El script que lleva el foco a Studio comprobará si Studio está en ejecución, y si es así, llamará a la función de recuperación de ventana, que primero localiza la ventana del escritorio (shell) para usarla como punto de partida para encontrar la ventana de Studio.
  2. NVDA escaneará las ventanas de nivel superior (hijas del objeto del escritorio) hasta que se encuentre la ventana de Studio (donde el módulo de aplicación de la ventana sea el de Studio), y si la encuentra, incrementará un contador de ventanas de Studio candidatas.
  3. Una vez se haya completado el escaneo de ventanas de alto nivel, NVDA tomará acciones en base a lo que diga el contador de ventanas candidatas de Studio antes de devolver el objeto en primer plano al script principal. Puede hacer una de las siguientes cosas:
    1. Si el contador es 0 (fg es None), NVDA sabrá que has minimizado Studio, por lo que te lo dirá.
    2. Si el contador es 1, NVDA localizará la ventana de Studio buscándola (user32.dll está implicada).
    3. Para el resto de valores, NVDA asumirá que la última ventana que ha encontrado es la ventana de Studio (contenida en la variable fg) y la devolverá.
  4. De vuelta al script que lleva el foco a Studio, NVDA indicará que Studio está minimizado o pasará a la ventana en primer plano devuelta por la función de recuperación de ventana (fg.SetFocus).

En 2017, eso se ha simplificado para usar la función SetForegroundWindow de la api de Windows, siendo el manejador de la ventana de Studio el único parámetro necesario que se pasaba. Esto no sólo simplificó la función significativamente, sino que también aumentó el rendimiento de esta orden.

Un efecto colateral es que ya no es posible detectar Studio cuando está minimizado, pero nos podemos hacer una idea de ello cuando NVDA dice «No disponible» al intentar cambiar a Studio. En 2020, el procedimiento para llevar el foco a Studio se refinó para comprobar la visibilidad de la ventana de Studio, y si no se veía, presentaba un mensaje de error. De esa forma, el usuario puede ir a la bandeja del sistema y restaurar la ventana de Studio.

Soporte del codificador

Ahora hemos llegado al penúltimo capítulo en este artículo de complementos vistos por dentro para el complemento de StationPlaylist: soporte del codificador. Hablaremos sobre cómo ha sido implementado el soporte del codificador, cómo puede NVDA detectar las etiquetas del codificador y una descripción tras el escenario de lo que pasa cuando te conectas a un servidor de emisión.

Soporte del codificador: de la sugerencia a la implementación

Originalmente, no tenía planeado incluir soporte para el codificador en el complemento de SPL. Sin embargo, tras hablar con varios usuarios de Studio que utilizaban los codificadores SAM y viendo cómo les daban soporte otros lectores de pantalla, decidí investigar el soporte para el codificador SAM en verano del 2014, dando como resultado el soporte para los codificadores como parte de la extensión global de utilidades de SPL en el complemento 3.0.

El primer problema que tenía que resolver era hacer que NVDA reconociera las entradas de codificador en sí mismas. Una vez estuvo resuelto, la siguiente tarea era verbalizar los mensajes de error de conexión, lo que llevó a averiguar cómo reaccionaban los codificadores SAM al conectarse a un servidor de emisión.

Originalmente, manipulaba el texto escrito en pantalla para obtener los mensajes de estado necesarios (mediante text infos). Este procedimiento causaba que algunos usuarios experimentaran problemas de parpadeo al conectarse a un servidor de emisión. Esto se resolvió usando la descripción del codificador (obj.description), que abría una posibilidad de monitorizar cambios en el texto mediante un hilo en segundo plano (más sobre este procedimiento debajo), lo que también eliminó la necesidad de permanecer en la ventana de codificadores hasta estar conectado.

Mientras estaba resolviendo problemas con los codificadores SAM, trabajé también en refactorizar el código de soporte del codificador para dar compatibilidad con los codificadores de StationPlaylist (complemento 4.0). Inicialmente, el código de soporte para el codificador se optimizó para los codificadores SAM, pero la estructura actual del código (explicada debajo) se escribió para extender fácilmente el soporte básico de codificadores, y como resultado, las entradas del codificador de SAM y SPL (y otros codificadores) presentan interfaces y órdenes similares, incluyendo un diálogo común de configuración del codificador (complemento 7.0).

Pocos años después, el tema del soporte para codificadores se puso sobre la mesa cuando un locutor me pidió que añadiera soporte para Edcast en 2019. Edcast, aunque era gratis, llegaba al final de su vida, y AltaCast ocupó su lugar. Por suerte, añadir soporte para el codificador AltaCast (plugin de Winamp que debe ser reconocido por Studio y Streamer) fue sencillo, ya que su interfaz de usuario es similar a la de los codificadores de SPL. Por lo tanto, el soporte del codificador AltaCast es similar al de los codificadores de SPL, así que para los propósitos de esta sección, AltaCast es un sinónimo del codificador SPL.

Al mismo tiempo, se reorganizó el soporte del codificador. En 2014, con conocimientos limitados en motores de codificación, sentí que era mejor alojar el módulo de soporte del codificador dentro de las utilidades de SPL. Con el paso del tiempo, se encontraron dos motores de codificación: el motor SPL (splengine) y Streamer (splstreamer). Tras aprender que los motores de codificación estaban alojados dentro de estas aplicaciones, decidí en 2020 separar el módulo de soporte del codificador en su propio módulo de aplicación, transfiriendo el soporte del codificador de la extensión global al paquete del módulo de aplicación del motor SPL y derivando la mayoría de funciones de Streamer del paquete del motor de SPL.

Estructura del soporte del codificador

El soporte del codificador es parte de dos módulos de aplicación: SPL Engine y StationPlaylist Streamer, siendo el primero un módulo de aplicación similar al paquete SPL Studio. Por tanto, el cuadro al completo queda así:

  • Módulo de aplicación del motor de SPL (splengine/init.py), que proporciona servicios básicos de módulo de aplicación usados tanto por el motor SPL como por Streamer, como por ejemplo la detección de codificadores.
  • Soporte del codificador (splengine/encoders.py), describiendo el soporte de NVDA para diversos codificadores y que constituye el tema principal de esta sección.
  • Módulo de aplicación de Streamer (splstreamer.py), que simplemente importa todo del paquete del módulo de aplicación del motor de SPL y añade gestión de envoltura de clases para la interfaz de usuario de Streamer.

Entradas del codificador: otra familia más de clases de superposición

Al igual que los elementos de pista de Studio (mira la sección de elementos de pista), las entradas del codificador son clases de superposición. Cada tipo de codificador (SAM, SPL, AltaCast y los codificadores futuros) hereda de un único objeto codificador (splengine.encoders.Encoder), que proporciona servicios básicos como órdenes de ajustes, verbalización de etiquetas del codificador y demás. Cada tipo de codificador agrega procedimientos específicos del codificador como diferentes funciones de detección de conexión, formas de obtener etiquetas del codificador y demás. Hablando de etiquetas del codificador y ajustes, la clase base del codificador recibe ayuda de algunos amigos del módulo del codificador en sí, incluido un mapa de configuración para almacenar etiquetas de flujo y ajustes básicos, una función para obtener el identificador del codificador (cadena del codificador e identificador del hijo de IAccessible) y demás.

Construidas sobre la clase base del codificador hay tres clases de codificadores, representando las entradas de SAM, SPL y AltaCast. Las entradas del codificador SAM (splengine.encoders.SAMEncoder) se presentan como los elementos de pista de Studio con partes que derivan de objetos SysListView32, mientras que las entradas del codificador SPL (splengine.encoders.SPLEncoder) es un control SysListView32 típico (mira el artículo sobre procedimientos de columna para más información). Siendo similar en apariencia al codificador SPL, las entradas del codificador AltaCast (splengine.encoders.AltaCastEncoder) derivan de la clase del codificador SPL con diferencias específicas del codificador. Todas las clases proporcionan procedimientos similares, diferenciándose en cómo se capturan los mensajes de conexión y cómo se obtienen datos específicos del codificador, como el identificador de tipo de codificador usado para acceder a las opciones del codificador con la ayuda de sus identificadores.

Identificadores de codificador

Un identificador de codificador es una cadena que identifica unívocamente un codificador. Consta de una cadena que indica el tipo de codificador (SAM para el codificador SAM, por ejemplo), seguida de la posición del codificador (separada por un espacio). Por ejemplo, el primer codificador SAM tiene el identificador «SAM 1». Los identificadores se usan para recuperar y configurar ajustes e identificar codificadores monitorizados en segundo plano.

Servicios comunes: ajustes básicos, etiquetas del codificador y métodos relacionados

Todas las clases de codificadores proporcionan los siguientes servicios comunes:

  • Configuración de ajustes: se pueden configurar seis ajustes:
    • Se puede definir una etiqueta personalizada del codificador para facilitar su identificación.
    • Al pulsar f11 indicaremos a NVDA si este debería pasar a Studio cuando el codificador esté conectado.
    • Al pulsar shift+f11 se dirá a NVDA que pida a Studio que reproduzca la siguiente pista al conectarse.
    • Al pulsar ctrl+f11 se activará la monitorización del codificador en segundo plano (más sobre esto en un segundo).
    • Activar o desactivar la reproducción de tonos que indiquen el progreso de la conexión (complemento 7.0, configurable desde el diálogo de ajustes del codificador que se describe más abajo).
    • Anunciar el estado de conexión hasta que el codificador seleccionado se conecte (complemento 20.03), configurable también desde el diálogo de opciones del codificador.
    • Una vez se cambien estos ajustes, los nuevos valores se almacenarán en el indicador apropiado de la entrada del codificador, que eventualmente se guarda en el mapa de configuración.
  • Además de las etiquetas del codificador, recupera ajustes. Esto se hace con diversos métodos de propiedad – una vez llamados, estos métodos recuperarán diversos ajustes para el codificador desde el mapa de configuración (la clave es el indicador del ajuste, y el valor es el identificador del codificador). Las etiquetas del codificador se organizan de forma diferente (mira más abajo).
  • Monitorización y reacción ante cambios de estado de la conexión. El procedimiento de respuesta (método onConnection) intenta llevar el foco a Studio y / o reproducir la primera pista marcada si está configurado para hacerlo.
  • Carga las etiquetas del codificador cuando un codificador obtiene el foco por primera vez (si se habían cargado antes, podría suponer un gasto de espacio, especialmente si nunca se usan los codificadores).
  • Anuncia las etiquetas del codificador (si se han definido) mediante un método getter dedicado para el nombre. Las etiquetas se almacenan como claves de un diccionario y se corresponden con identificadores de codificadores bajo una sección dedicada a las etiquetas de los codificadores dentro del mapa de configuración.
  • Define y elimina etiquetas del codificador. Si una etiqueta está definida (cadena no vacía), la etiqueta del codificador se almacena en una colección de etiquetas del codificador, y en cualquier otro caso se elimina de la colección.
  • Actualiza la etiqueta del codificador e indica su posición a petición (mediante un diálogo, que se activa pulsando ctrl+f12). Esto es necesario si se han eliminado codificadores, ya que puedes oír la etiqueta de un codificador que ya no existe. Esto se implementa como una variación del algoritmo de búsqueda anterior.
  • Anuncia las columnas del codificador. La clase base puede verbalizar la posición del codificador (ctrl+NVDA+1) y su etiqueta (ctrl+NVDA+2), mientras que SAM puede verbalizar el formato del codificador, estado y descripción. SPL y AltaCast permiten oír el formato del codificador y la velocidad de transferencia / el estado de la conexión.
  • En el complemento 7.0, se ha añadido un diálogo de configuración central para configurar los ajustes del codificador seleccionado. Pulsa alt+NVDA+0 o f12 para abrir este diálogo.

Más y más hilos: mensajes de conexión y monitorización del codificador en segundo plano

Como vimos en un capítulo anterior, los hilos permiten a los desarrolladores hacer que los programas realicen ciertas tareas en segundo plano. Incluso en el soporte de codificadores, los hilos se emplean para diversas tareas, incluyendo la verbalización del mensaje de conexión y la monitorización del codificador en segundo plano.

Cada clase de superposición para cada codificador (y no el codificador base) incluye procedimientos dedicados para la gestión de conexión (reportConnectionStatus). Dependiendo de cómo los invoques, se pueden iniciar como sigue:

  • Si la monitorización del codificador en segundo plano está desactivada y pulsas f9 para conectar, NVDA ejecutará este procedimiento en un hilo separado. En SAM, se hace la comprobación justo después de enviar f9 a la aplicación, y en SPL se hace después de hacer click en «Connect» desde el menú de contexto del codificador (se manipula el foco en el proceso).
  • Si la monitorización del codificador en segundo plano está activada antes de pulsar f9, el procedimiento se ejecutará desde otro hilo cuando este ajuste se active. Cuando pulses f9, NVDA ya sabe que el hilo de monitorización en segundo plano está activo, por lo que se saltará el paso de arriba.

El procedimiento encargado de gestionar la conexión hace lo siguiente:

  1. Ubica el mensaje de estado para la entrada del codificador. En Sam, el mensaje de estado se reparte en dos columnas (objetos hijo), y en SPL se consulta la columna de velocidad de transferencia. Esto se hará mientras Studio y / o NVDA estén en ejecución (o lo que es lo mismo, si el hilo está en ejecución).
  2. Anuncia mensajes de error, si los hay, y prueba de nuevo tras esperar un rato (fracciones de segundo). Si se indica a NVDA que no anuncie el estado de conexión hasta que el codificador en cuestión esté conectado, el hilo informador de conexión se detendrá cuando se vea un mensaje de error.
  3. Si está conectado, NVDA reproducirá un tono, y entonces:
    • No hará nada si no se le indica que lleve el foco a Studio o reproduzca la siguiente pista.
    • Lleva el foco a Studio y / o reproduce la siguiente pista si no hay pistas reproduciéndose llamando al método onConnect.
  4. Para otros mensajes, NVDA reproducirá periódicamente un tono de progreso y verbalizará el estado de la conexión tan pronto como lo informe el codificador (los tonos de progreso no se reproducirán si se suprimen en el diálogo de ajustes del codificador).
  5. Este bucle se repite mientras el codificador esté siendo monitorizado en segundo plano.

Vigilancia de varios codificadores con el registro de codificadores

A veces se hace necesario vigilar varios codificadores a la vez, particularmente si se emite a distintos servidores o archivos. Para manejar esto, los hilos de vigilancia de los codificadores se alojan en un registro de codificadores, un diccionario que relaciona identificadores de codificadores con hilos informantes de conexión. Esto es útil para hacer que NVDA anuncie el estado de conexión de varios codificadores, uno cada vez, y si esto sucede, NVDA prefijará el estado del codificador con el identificador asociado del codificador en cuestión.

Procedimientos específicos del codificador

Además de los servicios básicos, cada procedimiento de codificación tiene sus propias ventajas, incluyendo:

Para los codificadores SAM:

  • Para desconectar, pulsa f10.
  • Puedes pulsar ctrl+f9 o ctrl+f10 para conectar y desconectar todos los codificadores (no funciona bien en versiones recientes de SAM, según mis pruebas). Se desarrolló un parche para solucionar este problema (abre el menú contextual y activa el elemento correspondiente por sí mismo).

Para la familia de codificadores de SPL (incluyendo AltaCast):

  • Cuando pulsas control+f9 para conectar todos los codificadores, NVDA hace lo siguiente:
    1. Ubica el botón conectar, y si está etiquetado como «connect», hace clic en él (obj.doAction).
    2. Mueve el foco de vuelta a la entrada (self.setFocus).
  • Para desconectar, pulsa tabulador hasta que llegues al botón «Desconectar» y pulsa la barra espaciadora.

Notas finales y descripción del proceso de desarrollo de un complemento

Ahora que hemos visitado el interior del complemento StationPlaylist, me gustaría hacerte un recorrido por mi laboratorio, donde desarrollo este complemento. Durante el camino aprenderás cómo nace un complemento, se codifica, se prueba, se publica y se mantiene.

Preparación del laboratorio, equipamiento de desarrollo y software

Para todos mis desarrollos de software, yo uso dos ordenadores: un portátil con pantalla táctil y un sobremesa, ambos ejecutando Windows 10 y las últimas versiones de desarrollo alpha de NVDA. Ambos también tienen Cygwin y / o el subsistema de Windows para Linux (WSL, conocido de otra manera como BASH en Ubuntu en Windows) para ejecutar varias herramientas de la línea de órdenes (git, SCons, etc.), y en caso que necesite compilar NVDA desde el código fuente, tengo instalado Visual Studio 2017 con la última actualización y otras dependencias.

En el caso del complemento SPL, tengo diferentes versiones de Studio instaladas: 5.11 en mi portátil y 5.20 en el sobremesa. Esto me permite trabajar en ambas versiones de una vez (ambos ordenadores tienen el código fuente completo del complemento, aunque tiendo a solucionar fallos en mi portátil y experimentar con cosas nuevas en mi sobremesa).

Git: un gestor de código fuente inteligente

Al igual que los desarrolladores de NVDA y muchos creadores de complementos, uso Git para gestionar el código fuente (contrariamente a su lema, Git es muy inteligente). Se trata de un sistema distribuido, lo que significa que un repositorio local contiene el registro completo de cómo se gestiona el código fuente (sin necesidad de conectarse a un servidor para confirmar y obtener actualizaciones). Por ejemplo, si uso simplemente mi copia local del código fuente del complemento de SPL, puedo ver el historial de commits y generar versiones antiguas del complemento.

Otra ventaja de Git es su extenso soporte para ramas. Una rama es un flujo de desarrollo separado de otras ramas. Por ejemplo, el lector de pantalla NVDA usa al menos tres ramas para su flujo de trabajo: alfa (rama master), beta (rama beta) y rc (candidata a liberación, usada para construir versiones oficiales). El complemento de SPL usa este enfoque así: hay al menos dos ramas en uso, llamadas master (renombrada a main en 2021) y stable, usadas para desarrollo activo y publicación y mantenimiento, respectivamente (volveremos a las ramas en un segundo). Con la llegada del programa de unidad de pruebas (mira debajo), se usa una tercera rama llamada «staging» o «next» para recopilar todo el trabajo hecho en ramas bajo un techo con propósitos de prueba (en 2018, esto ha cambiado significativamente).

Cómo nace una característica de un complemento

Recorramos el proceso típico de desarrollo de una característica de un complemento viendo cómo se desarrollaron los perfiles de emisión (para más información sobre los perfiles de emisión, consulta la sección de gestión de la configuración de arriba).

Comencé a trabajar en los perfiles de emisión en marzo de 2015 mientras desarrollaba el complemento 5.0. Se trataba de una extensión natural del diálogo de configuración del complemento: mientras que este cuadro de diálogo (y la base de datos de configuración que usa) sólo se ocupa de un único perfil, pensé que sería una buena idea permitir que se definan varios perfiles y hacer que el diálogo de configuración reaccione ante el cambio de perfiles.

Había una razón importante para crear esta característica: ya que NVDA soporta varios perfiles de configuración y ya que algunos locutores presentan varios programas, pensé que sería una buena idea implementar una característica similar en el complemento. Por lo tanto, preví que los perfiles de emisión serían usados principalmente por gente que tuviera varios programas, definiendo un perfil para cada programa.

En marzo y abril de 2015, comencé a reescribir ciertas partes del gestor de configuración del complemento (splstudio.splconfig) como preparación para desarrollar los perfiles de emisión (incluidos ahora como parte del complemento 6.0). Comencé anotando algunas tareas (donde correspondía) describiendo cómo debería ser la característica futura. Después modifiqué initConfig y saveConfig (expuestas en los artículos del módulo de aplicación), diciéndoles inicialmente que trabajaran con el perfil predeterminado (el único mapa de configuración por entonces), luego las dejé solas hasta que se publicó el complemento 5.0 en junio de 2015.

En junio de 2015, abrí una nueva rama (usando inicialmente el nombre «starfish») para alojar el código relacionado con los perfiles de emisión. Antes de escribir cualquier código «real», estudié el código fuente de NVDA relacionado con los perfiles de configuración para aprender más sobre cómo Jamie (James Teh de NV Access, ahora en Mozilla) implementó esta característica. Una vez entendí cómo funcionaba, copié, pegué y modifiqué el código para que encajara con el del complemento (dando a NV Access el crédito que merece).

Una de las primeras cosas que tenía que decidir era cómo almacenar los perfiles. Experimenté usando secciones de ConfigOBJ, una por perfil, pero resultó que daba problemas (se podía dar a un perfil el nombre de la clave de un mapa existente). Entonces fui de vuelta al código fuente de NVDA para saber cómo resolvió NV Access este problema (usando archivos ini separados), lo implementé, y me encontré con otro problema: la transferencia de valores entre perfiles. Esto lo resolví especificando si un ajuste era «global» (se aplica a todos los perfiles) o específico de un perfil. Lo siguiente que vino fueron los controles en el diálogo de configuración del complemento y usar eventos de elección para establecer valores para la alarma usando los valores del perfil seleccionado.

Lo último que hice antes de mezclar la rama de perfiles de emisión en la rama master en julio fue revisar el diálogo de error de configuración y escribir documentación para los perfiles de emisión. Una vez la documentación estuvo lista y se solucionaron pequeños errores después de hacer muchas rondas de pruebas (en mi propio ordenador y desde la rama de perfiles), se mezcló la rama de perfiles de emisión en master. Pero el desarrollo no se detuvo ahí: gracias a las previsiones que hice, fue bastante simple implementar los perfiles de cambio instantáneo (de nuevo hubo problemas que ya están de sobra resueltos).

Trato con código en hilos: dolores de cabeza durante el desarrollo de la característica de monitorización del codificador en segundo plano

Puedes recordar nuestra exposición sobre el explorador cart y cómo pasó unas extensas pruebas hasta llegar a su estado actual (fue un segmento de código difícil). Cuando hablamos de dificultad, nada supera al código multihilo, especialmente si implica varios hilos trabajando en paralelo (bueno, casi en paralelo). Yo saboreé esto al programar el monitor del codificador en segundo plano (complemento 5.0). Esto implicó rastrear cuántos hilos estaban ejecutándose para asegurarse de que no había hilos innecesarios, capturar errores y condiciones de carrera (un intento de conexión podía lanzar un hilo sin comprobar si el codificador estaba siendo monitorizado) y demás. Afortunadamente, pasé por unos problemas similares unos meses atrás cuando desarrollé el escaneo de biblioteca (complemento 4.0), y esa experiencia me enseñó a tener cuidado con los hilos (y experimentar menos dolores de cabeza).

El proceso de desarrollo del complemento

Sígueme mientras te muestro cómo se desarrolla, publica y mantiene una versión típica del complemento para SPL:

  1. Antes de comenzar a trabajar en la nueva versión del complemento, apunto algunos objetivos que el complemento debería alcanzar, incluyendo ideas de características, sugerencias del usuario (tuyas) y demás. Con los cambios del proceso en 2017, el desarrollo de una característica puede hacerse a lo largo de varias versiones.
  2. A continuación hago una conferencia por llamada con usuarios del complemento para ver qué piensan de algunas ideas y recopilar comentarios (que también me apunto).
  3. Después creo ramas separadas para cada característica para aislar código y no romper el código existente.
  4. Después, escribo comentarios para recordarme a mí mismo cómo debería ser la característica, luego empiezo a trabajar en ella. Según se va desarrollando cada característica, hago simulaciones mentales sobre cómo podrías usar la característica en desarrollo, posibles errores, mensajes verbalizados y así.
  5. Una vez la característica está bastante estable, la pruebo para descubrir fallos y rellenar los huecos que faltan. Cuando pasa a pruebas, pruebo la nueva rama de características en mis dos ordenadores, que ejecutan versiones distintas de Studio, para asegurarme de que funciona entre versiones (si no, vuelvo atrás y modifico el código para reconocer las diferencias entre versiones de Studio).
  6. Empezando a finales del 2015, he mezclado las ramas de desarrollo en una única rama con propósitos de prueba. Esta rama también se usa para generar compilaciones de prueba (programa de unidad de pruebas), así que todo el que se ha registrado para el programa de acceso previo puede dejar comentarios (las compilaciones de prueba se generan aproximadamente una vez a la semana).
  7. Después de probar la característica durante un rato y si esta es estable, mezclo la rama de la característica en master.
  8. Cada pocas semanas, publico versiones de desarrollo de la rama master para recopilar comentarios de los usuarios que estén dispuestos a probar versiones de desarrollo dirigidas. Con la llegada de las actualizaciones del complemento en la versión 7.0, cualquiera puede actualizar entre versiones estables o de prueba (la rama en uso será utilizada por el actualizador; por ejemplo, si alguien usa las versiones de desarrollo master, las actualizaciones sólo se recuperarán desde la rama master).
  9. En algún momento, establezco la fecha de la publicación de la próxima versión del complemento (para la versión 6.0, fue en diciembre del 2015). Esto determina cuándo debería ser la congelación de características y la fecha de liberación de la beta (para el 6.0, la beta 1 se publicó en octubre del 2015). Entre la congelación de características y la primera liberación beta, me concentro en refinar el código y arreglar fallos. Esto ha cambiado significativamente en 2017 (mira más abajo).
  10. Después de hacer varios ciclos beta (generalmente dos), pido a los revisores de la comunidad de NVDA que revisen el código de mi complemento y pidan su publicación en las fechas apropiadas (esto se hace mezclando la rama master en la rama stable).
  11. Una vez se publica la versión del complemento, las siguientes versiones de mantenimiento (actualización de traducciones, solución de fallos, retoques menores) se liberarán desde la rama stable, con la rama master conteniendo el código para la siguiente gran versión.
  12. Una vez la siguiente versión entra en el ciclo de betas, podría haber o podría no haber más versiones de mantenimiento (una excepción son las versiones de soporte extendido, descritas más abajo).

Cambios en 2017

Ya entrado 2016, algunos usuarios y yo tuvimos una conferencia por llamada relacionada con la dirección futura del complemento. Durante esta llamada, los participantes sentían que el complemento ya incluye todas las funciones que la gente necesita, por lo que se decidió dejar de escalar en características y enfocarse en la compatibilidad con nuevas versiones de Studio. Al final, se decidió que una versión del complemento que se publicaría en 2017 sería la versión final principal de este complemento, con las futuras actualizaciones siendo muy pequeñas.

Esta decisión también se alineó con mis otros complementos, donde las nuevas versiones se liberaban cada mes o así, junto al hecho de que las características están completas en la mayoría de complementos. La nueva programación regular de actualizaciones del complemento de SPL se implementó en junio de 2017.

Esta decisión implicaba dos cosas más:

  • No más esperas: una nueva característica estable no debería retenerse hasta dentro de seis meses si está lista para el público, y por tanto las actualizaciones incluyen nuevas características y solución de fallos.
  • El programa de pruebas dirigidas (mira más abajo) no debería ser algo exclusivo nunca más, sino un proceso al que se pueda optar, ya que yo sentía que el bucle de retroalimentación temprana es crucial para el éxito de un producto como este complemento.

Con esto en mente, las siguientes cosas cambiaron en 2017:

  • No más betas: la rama de desarrollo (ahora llamada anillo lento) se considera como rama de betas.
  • Cualquiera puede cambiar a la rama de compilaciones de prueba (llamada anillo rápido de instantáneas) suponiendo que estén interesados en proporcionar retroalimentación temprana.
  • Las actualizaciones de soporte extendido ahora están atadas a las nuevas versiones principales de Studio. Este criterio se extendió entrado 2017 para incluir cambios críticos en NVDA (mira más abajo).

Más cambios en 2018

En julio de 2018, NV Access informó sobre cambios en el proceso de liberación para facilitar la integración y las pruebas. Antes de eso, se estaba incubando una nueva función de NVDA en la versión de desarrollo next durante al menos dos semanas, y esto significaba tratar con el creciente número de solicitudes de cambio que en ocasiones producían conflictos. Para evitar esto, y hacer que las nuevas funciones vinieran a la rama master directamente, lo que solía ser la rama de beta en vivo (master) se convirtió en las versiones de desarrollo «alpha». Se movió a la gente que quería más estabilidad a las versiones beta.

Por parte del complemento para SPL, las compilaciones de los anillos lento y rápido del programa de prueba se han vuelto idénticas a finales de agosto de 2018. He sentido también que el desarrollo del complemento se ha ralentizado considerablemente mientras se preparaba la siguiente versión LTS (18.09) y, por tanto, reacondicioné el programa de unidad de pruebas.

Después, en agosto de 2018, publiqué Add-on Updater, un complemento que actuaba como prueba de concepto que permite que NVDA busque actualizaciones de todos los complementos registrados en el sitio web de complementos de la comunidad. En sus primeros días, se excluyó el complemento de SPL porque interfería con la propia función de búsqueda de actualizaciones de Add-on Updater. En realidad, generalicé el código de búsqueda de actualizaciones del complemento de SPL y lo transferí al nuevo Add-on Updater, por lo que era casi idéntico por dentro.

Dado que mi objetivo a largo plazo es hacer que NVDA busque por sí mismo las actualizaciones del complemento para SPL, además de las observaciones anteriores, pregunté a la comunidad si sería mejor cambiar la naturaleza del anillo rápido de la unidad de pruebas para que sea Add-on Updater el que busque todas las actualizaciones. La comunidad estuvo deacuerdo, por lo que se llevaron a cabo los siguientes cambios en agosto de 2018 antes de publicar la versión 18.09:

  • Los anillos rápido y lento de la unidad de pruebas se combinaron en un único canal de «desarrollo». Como consecuencia ya no existe la posibilidad de elegir el canal de actualización, siendo los usuarios los encargados de obtener la versión correcta desde la web de complementos. Esto se extendió en septiembre de 2018 para cubrir las versiones de soporte extendido.
  • Un nuevo concepto de «funciones piloto» reemplazó al anillo rápido de la unidad de pruebas, configurable mediante una casilla de verificación e indicadores internos.
  • Las nuevas funciones (arriesgadas) en desarrollo se activarán si la opción de funciones piloto se activa, en cualquier otro caso el contenido es idéntico al de las versiones de desarrollo regulares.
  • La función de búsqueda de actualizaciones del complemento la lleva a cabo Add-on Updater, y en el futuro, el propio NVDA. Como consecuencia, la función de búsqueda de actualizaciones y el código fuente que la controlaba se han eliminado en diciembre de 2018.

Versión de soporte extendido

Una versión típica de un complemento se soporta hasta que se publica la siguiente versión del complemento (actualmente varias semanas o meses). Sin embargo, hay veces en las que una versión de un complemento recibe soporte extendido (llamadas versiones lts). Esto pasa si se publica la siguiente versión de Studio, se publica una versión de Studio con cambios en la interfaz de usuario, o se esperan cambios críticos en versiones futuras de NVDA tales como finalización de soporte para una versión de Windows.

Una versión lts es una versión mayor o una versión periódica del complemento de SPL con algunas diferencias destacables:

  • Duración del soporte: una versión LTS recibe soporte durante al menos doce meses.
  • Características: una versión lts puede contener algunas características de versiones futuras del complemento que pueden agregarse de forma segura.
  • Versión soportada de Studio: una versión lts es la última versión en dar soporte a la versión de Studio más antigua soportada. Esto se diseña así para dar a la gente todo el tiempo posible para actualizarse a una nueva versión de Studio.
  • Última versión que usa tecnología de NVDA antigua: en algunos casos, las versiones lts están hechas para dar soporte a usuarios de versiones antiguas de NVDA. Después de crear la versión lts, el código fuente del complemento cambiará para usar el código más reciente de NVDA. Este criterio se aplicó primero a la versión 18.09 como resultado del fin de soporte de NVDA para Windows XP, Vista y 7 sin Service Pack 1, así como para la transición a Python 3.

A fecha de junio de 2021, la versión LTS más reciente es la 20.09.x (desde septiembre de 2020 hasta abril de 2021). Entre las versiones LTS anteriores se incluyen la 18.09.x (desde septiembre de 2018 a diciembre de 2019), la 15.x, conocida como 7.x hasta octubre de 2016 (de octubre de 2016 a abril de 2018). La versión lts anterior era la 3.x (de septiembre de 2014 a junio de 2015). Por ejemplo, el complemento 3.x se mantuvo de la siguiente manera:

  1. El complemento 3.0 fue publicado en 2014.
  2. El complemento 3.5 (diciembre de 2014) podría haber sido la última versión de mantenimiento del complemento 3.x si no hubiera sido una versión lts.
  3. Cuando se publicó el complemento 4.0 (enero de 2015), se liberó el complemento 3.6, que traía algunas características del 4.0. Se dijo a los usuarios que el complemento 3.x sería el último en dar soporte a versiones de Studio anteriores a la 5.00. A partir de entonces, el complemento 3.x se trasladó desde la rama stable a una rama interna.
  4. Cuando se publicó la beta del complemento 5.0 (mayo de 2015), el complemento 3.x (3.9 por entonces) entró en la cuenta atrás de fin de soporte (sin más versiones de mantenimiento).
  5. Unas semanas más tardes, cuando llegó el complemento 5.0 (junio de 2015), el complemento 3.x se quedó sin soporte.

Consideraciones finales

Según termino este artículo sobre los interiores del complemento de StationPlaylist Studio, siento que es el momento de que revele por qué mis complementos son gratuitos: es porque amo a los usuarios como un servicio para la comunidad de usuarios y desarrolladores de NVDA (y en extensión, a todos los locutores que usan SPL Studio). Lo que me trae alegría como escritor de complementos es el hecho de que este complemento (y la documentación que lo acompaña) haya impactado en vuestras vidas y las vidas de los oyentes de vuestros programas, así como a otros usuarios y desarrolladores de NVDA por todo el mundo. Gracias a vosotros, usuarios, por vuestro soporte continuo y vuestros comentarios, prometo una vez más que todo el código de mis complementos (incluido el complemento de SPL) será libre y cualquiera es bienvenido para estudiarlo y mejorarlo.

Para creadores de complementos que buscan documentación de calidad, espero que este artículo os haya inspirado para escribir buena documentación en vuestros proyectos futuros. Para la gente nueva en el desarrollo de complementos o para aquellos interesados en desarrollar un complemento, espero que este artículo de complementos de NVDA vistos por dentro haya servido como un recurso útil para vuestros proyectos, y extensión, os haya dado una idea de cómo funcionan ciertas funciones de NVDA. Si queréis hacer referencia a esta documentación o usarla como un modelo, os invito a hacerlo. Gracias a los revisores de la comunidad de complementos por vuestro soporte continuo y vuestras revisiones.

Avisos importantes y créditos

Me gustaría dar las gracias al personal de StationPlaylist Studio por su colaboración continua con usuarios de lectores de pantalla en cuanto a la accesibilidad de Studio. Va un agradecimiento especial para Jamie Teh de NV Access y Geoff Shang (el autor original del complemento) por darme a mí y a otros una base de la que partir. Y como siempre, el mayor agradecimiento es para vosotros, los usuarios del complemento para SPL, por vuestros continuos comentarios y por enseñarme cosas nuevas sobre Studio.

Aviso del código fuente: para proteger los derechos de copia, no se han documentado algunas partes de la api de Studio. Además, el código fuente expuesto a lo largo de este artículo puede cambiar según se desarrollen versiones futuras del complemento.

Derechos de copia: StationPlaylist Studio, la herramienta de pista y los codificadores de StationPlaylist tienen copyright de StationPlaylist.com. NonVisual Desktop Access tiene copyright 2006-2021 de NV access Limited (liberado bajo la GPL). Los codificadores SAM tienen copyright de Spatial Audio. Microsoft Windows y la API de Windows tienen copyright de Microsoft Corporation. Python tiene copyright de la Python Software Foundation. El complemento StationPlaylist Studio para NVDA tiene copyright 2011, 2013-2021 de Geoff Shang, Joseph Lee y otros (liberado bajo la GPL). Otros productos mencionados tienen copyright de sus respectivos propietarios (las licencias varían).

Referencias

  1. Scripts de JAWS para StationPlaylist Studio (Hartgen Consultancy): http://www.hartgen.org/studio.html
  2. Aplicación de Window-Eyes para StationPlaylist (Jeff Bishop/AI Squared): https://www.gwmicro.com/App_Central/Apps/App_Details/index.php?scriptid=1268&readMore&media=print
  3. Plug-in (Wikipedia): https://en.wikipedia.org/wiki/Plug-in_(computing)
  4. Application Programming Interface (Wikipedia): https://en.wikipedia.org/wiki/Application_programming_interface
  5. Descripción de la documentación de Python 2.7.13 (Python Software Foundation): https://docs.python.org/2/
  6. Handle (Wikipedia): https://en.wikipedia.org/wiki/Handle_(computing)
  7. Qué es un manejador de Windows (Stack Overflow): http://stackoverflow.com/questions/902967/what-is-a-windows-handle
  8. Referencia de FindWindow (user32.dll) (Windows API): https://msdn.microsoft.com/en-us/library/windows/desktop/ms633499(v=vs.85).aspx
  9. Referencia de SendMessage (user32.dll) (Windows API): https://msdn.microsoft.com/en-us/library/windows/desktop/ms644950(v=vs.85).aspx
  10. Guía del desarrollador de NVDA (NV Access): http://www.nvaccess.org/files/nvda/documentation/developerGuide.html
  11. Referencia de OpenProcess (kernel32.dll) (Windows API): https://msdn.microsoft.com/en-us/library/windows/desktop/ms684320(v=vs.85).aspx
  12. Documentación en línea de wxPython: http://www.wxpython.org/onlinedocs.php
  13. Funciones de alto nivel (cómo hacer una función de alto nivel): http://effbot.org/pyfaq/how-do-you-make-a-higher-order-function-in-python.htm
  14. Time (Documentación de Python, Python Software Foundation): https://docs.python.org/2/library/time.html
  15. Referencia de GetTimeFormat (kernel32.dll) (Windows API): https://msdn.microsoft.com/en-us/library/windows/desktop/dd318130(v=vs.85).aspx
  16. Bucle de eventos (Wikipedia): https://en.wikipedia.org/wiki/Event_loop
  17. Programación dirigida por eventos (Wikipedia): https://en.wikipedia.org/wiki/Event-driven_programming
  18. Sinclair, Rob. Arquitectura de Microsoft Active Accessibility, Microsoft Developer Network, Agosto del 2000. https://msdn.microsoft.com/en-us/library/ms971310.aspx
  19. Descripción de UI Automation, Microsoft Developer Network. https://msdn.microsoft.com/en-us/library/ms747327(v=vs.110).aspx
  20. Descripción de Java Access Bridge, Java SE Desktop Accessibility, Oracle. http://www.oracle.com/technetwork/articles/javase/index-jsp-136191.html
  21. Introducción a la POO (programación orientada a objetos) con Python, Voidspace. http://www.voidspace.org.uk/python/articles/OOP.shtml
  22. Tutorial de no programadores para Python 3/Introducción a la programación orientada a objetos en Python 3 – Wikibooks. https://en.wikibooks.org/wiki/Non-Programmer%27s_Tutorial_for_Python_3/Intro_to_Object_Oriented_Programming_in_Python_3
  23. Orden de resolución de métodos, la historia de Python, 23 de junio de 2010. http://python-history.blogspot.com/2010/06/method-resolution-order.html
  24. Vista de lista, Microsoft Developer Network: https://msdn.microsoft.com/en-us/library/windows/desktop/bb774737(v=vs.85).aspx
  25. Mensajes de vista de lista, Microsoft Developer Network: https://msdn.microsoft.com/en-us/library/windows/desktop/ff485961(v=vs.85).aspx
  26. Estructura de un elemento de una vista de lista, Microsoft Developer Network: https://msdn.microsoft.com/en-us/library/windows/desktop/bb774760(v=vs.85).aspx
  27. Referencia de VirtualAllocEx (kernel32.dll) (Windows API): https://msdn.microsoft.com/en-us/library/windows/desktop/aa366890(v=vs.85).aspx
  28. Referencia de VirtualFreeEx (kernel32.dll) (Windows API): https://msdn.microsoft.com/en-us/library/windows/desktop/aa366894(v=vs.85).aspx
  29. Referencia de WriteProcessMemory (kernel32.dll) (Windows API): https://msdn.microsoft.com/en-us/library/windows/desktop/ms681674(v=vs.85).aspx
  30. Referencia de ReadProcessMemory (kernel32.dll) (Windows API): https://msdn.microsoft.com/en-us/library/windows/desktop/ms680553(v=vs.85).aspx
  31. Ctypes (Documentación de Python, Python Software Foundation): https://docs.python.org/2/library/ctypes.html
  32. Thread (Wikipedia): https://en.wikipedia.org/wiki/Thread_(computing)
  33. Procesadores de múltiples núcleos (wikipedia): https://en.wikipedia.org/wiki/Multi-core_processor
  34. Introducción al multinúcleo, zona de desarrolladores de Intel, 5 de marzo de 2012: https://software.intel.com/en-us/articles/multi-core-introduction
  35. Especificaciones de Intel Core I7-5960X (Intel ARK): http://ark.intel.com/products/82930/Intel-Core-i7-5960X-Processor-Extreme-Edition-20M-Cache-up-to-3_50-GHz
  36. Especificaciones de Intel Xeon E7-8895V3 (Intel ARK): http://ark.intel.com/products/84689/Intel-Xeon-Processor-E7-8895-v3-45M-Cache-2_60-GHz
  37. Global Interpreter Lock (Python Wiki): https://wiki.python.org/moin/GlobalInterpreterLock
  38. Threading (Documentación de Python, Python Software Foundation): https://docs.python.org/2/library/threading.html
  39. Multiprocessing (Documentación de Python, Python Software Foundation): https://docs.python.org/2/library/multiprocessing.html#module-multiprocessing
  40. Valores separados por coma (Wikipedia): https://en.wikipedia.org/wiki/Comma-separated_values
  41. RFC 4180 (formato común y tipo MIME para archivos de valores separados por coma (CSV)), Internet Engineering Task Force: https://tools.ietf.org/html/rfc4180
  42. Importar o exportar texto (archivos .txt or .csv), soporte de Microsoft Office para Microsoft Excel: https://support.office.com/en-za/article/Import-or-export-text-txt-or-csv-files-5250ac4c-663c-47ce-937b-339e391393ba
  43. CSV (Documentación de Python, Python Software Foundation): https://docs.python.org/2/library/csv.html
  44. Caché (Wikipedia): https://en.wikipedia.org/wiki/Cache_(computing)
  45. Os (Documentación de Python, Python Software Foundation): https://docs.python.org/2/library/os.html
  46. Configparser (Documentación de Python, Python Software Foundation): https://docs.python.org/2/library/configparser.html
  47. Documentación de ConfigObj: http://www.voidspace.org.uk/python/configobj.html
  48. Documentación del módulo Validate: http://www.voidspace.org.uk/python/validate.html
  49. Documentación de Spin control (wx.SpinCtrl) (WXPython): http://wxpython.org/Phoenix/docs/html/SpinCtrl.html
  50. Referencia de SetForegroundWindow (user32.dll) (Windows API): https://msdn.microsoft.com/en-us/library/windows/desktop/ms633539(v=vs.85).aspx