Corrector de diseño Xswitcher para Linux: paso dos

desde publicación anterior (xswitcher en la etapa de "prueba de concepto") recibió bastantes comentarios constructivos (lo cual es bueno), Seguí dedicando mi tiempo libre a desarrollar el proyecto. Ahora quiero gastar un poco de tu... El segundo paso no será del todo habitual: propuesta/discusión del diseño de configuración.

Corrector de diseño Xswitcher para Linux: paso dos

De alguna manera resulta que a los programadores normales les resulta increíblemente aburrido configurar todos estos controles.

Para no ser infundado, dentro hay un ejemplo de lo que estoy tratando.
En general, Apache Kafka y ZooKeeper están excelentemente concebidos (y bien implementados).
- ¿Configuración? ¡Pero es aburrido! XML tonto (porque está "listo para usar").
- Oh, ¿también quieres un LCA? ¡Pero es tan aburrido! Tap-blooper... Algo así.

Pero en mi trabajo es exactamente lo contrario. Bien (por desgracia, casi nunca la primera vez) el modelo construido le permite continuar más fácil y naturalmente (Casi) armar un diagrama.

Recientemente encontré un artículo sobre Habré sobre el arduo trabajo de los científicos de datos...
Resulta que este momento se realiza plenamente para ellos. Y en mi práctica, como dicen, “versión ligera”. Modelos de varios volúmenes, programadores experimentados con programación orientada a objetos listos, etc. — Todo esto aparecerá más tarde cuando despegue. Pero el diseñador necesita empezar en algún lugar aquí y ahora.

Llegar al punto. Tomé TOML como base sintáctica. de este ciudadano.

Porque él (TOML) por un lado, editable por humanos. Por otro lado, se traduce 1:1 a cualquiera de las sintaxis más comunes: XML, JSON, YAML.
Además, la implementación que utilicé de "github.com/BurntSushi/toml", aunque no es la más moderna (aún sintaxis 1.4), es sintácticamente compatible con el mismo JSON ("integrado").

Es decir, si lo deseas, puedes simplemente decir “vete por el bosque con ese TOML tuyo, quiero XXX” y “parchear” el código con solo una línea.

Por lo tanto, si desea escribir algunas ventanas para configurar xswitcher (No estoy seguro) No se esperan problemas "con esta maldita configuración tuya".

Para todos los demás, la sintaxis se basa en “clave = valor” (y literalmente un par de opciones más complicadas, como = [algunos, eso, matriz]) supongo
intuitivamente conveniente.
Lo interesante es que "quemado" aproximadamente al mismo tiempo (alrededor de 2013). Sólo que, a diferencia de mí, el autor de TOML entró en la escala adecuada.

Por lo tanto, ahora me resulta más fácil ajustar su implementación a mis necesidades y no al revés.

En general, tomamos TOML (muy similar al antiguo INI de Windows). Y tenemos una configuración en la que describimos cómo colocar una serie de ganchos en función del conjunto de últimos códigos de escaneo del teclado. A continuación, pieza por pieza, lo que ha sucedido hasta ahora. Y una explicación de por qué decidí así.

0. Abstracciones básicas

  • Designaciones de códigos de escaneo. Definitivamente es necesario hacer algo al respecto, ya que los códigos simplemente digitales no son en absoluto legibles por humanos (sólo soy yo). loloswitcher).
    Saqué "ecodes.go" de "golang-evdev" (era demasiado vago para mirar la fuente original, aunque el autor lo indicó de manera bastante cultural). Corregí un poco (por ahora) algo que daba bastante miedo. Como "LEFTBRACE" → "L_BRACE".
  • Además, introdujo el concepto de “claves de estado”. Dado que la gramática habitual utilizada no permite pasajes largos. (Pero le permite comprobar con una sobrecarga mínima. Si utiliza sólo la grabación "directa").
  • Habrá un “deduplicador” incorporado de lo que se presiona. Así, se escribirá el estado "repetir"=2 uno tiempos

1. Sección de plantillas

[Templates] # "@name@" to simplify expressions
 # Words can consist of these chars (regex)
 "WORD" = "([0-9A-Z`;']|[LR]_BRACE|COMMA|DOT|SLASH|KP[0-9])"

¿En qué consiste una palabra del lenguaje humano con notación fonética? (ya sea una cuestión de grafemas también conocidos como “jeroglíficos”)? Una especie de "sábana" terrible. Por tanto, introduzco inmediatamente el concepto de “plantilla”.

2. Qué hacer cuando se hace clic en algo (ha llegado otro código de escaneo)

[ActionKeys]
 # Collect key and do the test for command sequence
 # !!! Repeat codes (code=2) must be collected once per key!
 Add = ["1..0", "=", "BS", "Q..]", "L_CTRL..CAPS", "N_LOCK", "S_LOCK",
        "KP7..KPDOT", "R_CTRL", "KPSLASH", "R_ALT", "KPEQUAL..PAUSE",
        "KPCOMMA", "L_META..COMPOSE", "KPLEFTPAREN", "KPRIGHTPAREN"]

 # Drop all collected keys, including this.  This is default action.
 Drop = ["ESC", "-", "TAB", "ENTER", "KPENTER", "LINEFEED..POWER"]
 # Store extra map for these keys, when any is in "down" state.
 # State is checked via "OFF:"|"ON:" conditions in action.
 # (Also, state of these keys must persist between buffer drops.)
 # ??? How to deal with CAPS and "LOCK"-keys ???
 StateKeys = ["L_CTRL", "L_SHIFT", "L_ALT", "L_META", "CAPS", "N_LOCK", "S_LOCK",
              "R_CTRL", "R_SHIFT", "R_ALT", "R_META"]

 # Test only, but don't collect.
 # E.g., I use F12 instead of BREAK on dumb laptops whith shitty keyboards (new ThinkPads)
 Test = ["F1..F10", "ZENKAKUHANKAKU", "102ND", "F11", "F12",
          "RO..KPJPCOMMA", "SYSRQ", "SCALE", "HANGEUL..YEN",
          "STOP..SCROLLDOWN", "NEW..MAX"]

Hay 768 códigos en total. (Pero "por si acaso" inserté "sorpresas" en el código de xswitcher).
En el interior describí cómo llenar la matriz con enlaces a funciones "qué hacer". En golang esto es (de repente) Resultó conveniente y obvio.

  • Planeo reducir la “caída” al mínimo en este lugar. A favor de una tramitación más flexible (lo mostraré a continuación).

3. Tabla con clases de ventana.

# Some behaviour can depend on application currently doing the input.
[[WindowClasses]]
 # VNC, VirtualBox, qemu etc. emulates there input independently, so never intercept.
 # With the exception of some stupid VNC clients, which does high-level (layout-based) keyboard input.
 Regex = "^VirtualBox"
 Actions = "" # Do nothing while focus stays in VirtualBox

[[WindowClasses]]
 Regex = "^konsole"
 # In general, mouse clicks leads to unpredictable (at the low-level where xswitcher resides) cursor jumps.
 # So, it's good choise to drop all buffers after click.
 # But some windows, e.g. terminals, can stay out of this problem.
 MouseClickDrops = 0
 Actions = "Actions"

[[WindowClasses]] # Default behaviour: no Regex (or wildcard like ".")
 MouseClickDrops = 1
 Actions = "Actions"

Las filas de la tabla están entre corchetes dobles con su nombre. No podría haber sido más fácil desde el principio. Dependiendo de la ventana actualmente activa, puede seleccionar las siguientes opciones:

  • Su propio conjunto de “teclas de acceso rápido” “Acciones =…”. Si no está vacío, no hagas nada.
  • Cambie "MouseClickDrops": qué hacer cuando se detecta un clic del mouse. Dado que en el momento en que se activa xswitcher no hay detalles sobre "dónde hacen clic", restablecemos el búfer de forma predeterminada. Pero en terminales (por ejemplo) no tienes que hacer esto (generalmente).

4. Una (o varias) secuencias de clics activan uno u otro gancho.

# action = [ regex1, regex2, ... ]
# "CLEAN" state: all keys are released
[Actions]
# Inverse regex is hard to understand, so extract negation to external condition.
# Expresions will be checked in direct order, one-by-one. Condition succceds when ALL results are True.
 # Maximum key sequence length, extra keys will be dropped. More length - more CPU.
 SeqLength = 8
 # Drop word buffer and start collecting new one
 NewWord = [ "OFF:(CTRL|ALT|META)  SEQ:(((BACK)?SPACE|[LR]_SHIFT):[01],)*(@WORD@:1)", # "@WORD@:0" then collects the char
             "SEQ:(@WORD@:2,@WORD@:0)", # Drop repeated char at all: unlikely it needs correction
             "SEQ:((KP)?MINUS|(KP)?ENTER|ESC|TAB)" ] # Be more flexible: chars line "-" can start new word, but must not completelly invalidate buffer!
 # Drop all buffers
 NewSentence = [ "SEQ:(ENTER:0)" ]

 # Single char must be deleted by single BS, so there is need in compose sequence detector.
 Compose = [ "OFF:(CTRL|L_ALT|META|SHIFT)  SEQ:(R_ALT:1,(R_ALT:2,)?(,@WORD@:1,@WORD@:0){2},R_ALT:0)" ]

 "Action.RetypeWord" = [ "OFF:(CTRL|ALT|META|SHIFT)  SEQ:(PAUSE:0)" ]
 "Action.CyclicSwitch" = [ "OFF:(R_CTRL|ALT|META|SHIFT)  SEQ:(L_CTRL:1,L_CTRL:0)" ] # Single short LEFT CONTROL
 "Action.Respawn" = [ "OFF:(CTRL|ALT|META|SHIFT)  SEQ:(S_LOCK:2,S_LOCK:0)" ] # Long-pressed SCROLL LOCK

 "Action.Layout0" = [ "OFF:(CTRL|ALT|META|R_SHIFT)  SEQ:(L_SHIFT:1,L_SHIFT:0)" ] # Single short LEFT SHIFT
 "Action.Layout1" = [ "OFF:(CTRL|ALT|META|L_SHIFT)  SEQ:(R_SHIFT:1,R_SHIFT:0)" ] # Single short RIGHT SHIFT

 "Action.Hook1" = [ "OFF:(CTRL|R_ALT|META|SHIFT)  SEQ:(L_ALT:1,L_ALT:0)" ]

Los ganchos se dividen en dos tipos. Integrado, con nombres “parlantes” (NewWord, NewSentence, Compose) y programable.

Los nombres programables comienzan con "Acción". Porque TOML v1.4, los nombres con puntos deben estar entre comillas.

Cada sección debe describirse a continuación. con el mismo nombre.

Para no sorprender a la gente con clientes habituales "desnudos" (por experiencia, sus escribirtal vez uno de cada diez profesional), implemento inmediatamente una sintaxis adicional.

  • "APAGADO:" (o "ENCENDIDO:") antes de regexp (expresión regular) requiere que se suelten (o presionen) los siguientes botones.
    A continuación voy a crear una expresión regular "injusta". Con control separado de piezas entre tuberías "|". Para reducir la cantidad de registros como "[LR]_SHIFT" (donde esto claramente no es necesario).
  • "SEC:" Si la condición anterior se cumple (o no existe), entonces comparamos con una expresión regular "normal". Para obtener más detalles, envío inmediatamente a ^W la biblioteca "regexp". Porque todavía no me he molestado en averiguar el grado de compatibilidad con mi pcre favorito (“compatible con Perl”).
  • La expresión se escribe en la forma "BOTÓN_1: CÓDIGO1, BOTÓN_2: CÓDIGO2" etc., en el orden en que se reciben los códigos de escaneo.
  • El cheque siempre está "ajustado" al final de la secuencia., por lo que no es necesario agregar "$" al final.
  • Todas las comprobaciones de una línea se realizan una tras otra. y están combinados por "yo". Pero como el valor se describe como una matriz, puedes escribir una verificación alternativa después de la coma. Si esto es necesario por alguna razón.
  • Valor "Longitud de secuencia = 8" limita el tamaño del búfer contra el cual se realizan todas las comprobaciones. Porque (Hasta ahora) nunca he encontrado recursos infinitos en mi vida.

5. Colocación de los ganchos descritos en el apartado anterior

# Action is the array, so actions could be chained (m.b., infinitely... Have I to check this?).
# For each action type, extra named parameters could be collected. Invalid parameters will be ignored(?).
[Action.RetypeWord] # Switch layout, drop last word and type it again
 Action = [ "Action.CyclicSwitch", "RetypeWord" ] # Call Switch() between layouts tuned below, then RetypeWord()

[Action.CyclicSwitch] # Cyclic layout switching
 Action = [ "Switch" ] # Internal layout switcher func
 Layouts = [0, 1]

[Action.Layout0] # Direct layout selection
 Action = [ "Layout" ] # Internal layout selection func
 Layout = 0

[Action.Layout1] # Direct layout selection
 Action = [ "Layout" ] # Internal layout selection func
 Layout = 1

[Action.Respawn] # Completely respawn xswitcher. Reload config as well
 Action = [ "Respawn" ]

[Action.Hook1] # Run external commands
  Action = [ "Exec" ]
  Exec = "/path/to/exec -a -b --key_x"
  Wait = 1
  SendBuffer = "Word" # External hook can process collected buffer by it's own means.

Lo principal aquí es "Acción = [Matriz]". Al igual que en la sección anterior, hay un conjunto limitado de acciones integradas. Y la posibilidad de atracar no está limitada en principio. (escribe “Action.XXX” y no seas demasiado vago para escribir otra sección para ello).
En particular, la reescritura de una palabra en el diseño corregido se divide en dos partes: "cambiar el diseño como se especifica allí" и “volver a escribir” (“ReescribirPalabra”).

Los parámetros restantes se escriben en el "diccionario" ("mapa" en golang) para una acción determinada, su lista depende de lo que esté escrito en “Acción”.

Se pueden describir varias acciones diferentes en un montón. (secciones). O puedes separarlo. Como mostré arriba.

Inmediatamente configuré la acción "Exec" para ejecutar el script externo. Con la opción de insertar el búfer grabado en stdin.

  • “Esperar = 1”: espere a que se complete el proceso en ejecución.
  • Probablemente, "al montón" querrás poner personas adicionales en el entorno. información como el nombre de la clase de ventana desde la que fue interceptado.
    “¿Quieres conectar a tu controlador? Aquí es donde tienes que ir".

Uf (exhalado). Parece que no me he olvidado de nada.

¡Ups! Sí, no lo olvidé...
¿Dónde está la configuración de lanzamiento? ¿En código duro? Como eso:

[ScanDevices]
 # Must exist on start. Self-respawn in case it is younger then 30s
 Test = "/dev/input/event0"
 Respawn = 30
 # Search mask
 Search = "/dev/input/event*"
 # In my thinkPads there are such a pseudo-keyboards whith tons of unnecessary events
 Bypass = "(?i)Video|Camera" # "(?i)" obviously differs from "classic" pcre's.

¿Dónde lo olvidé/cometí un error? (de ninguna manera sin esto)Realmente espero que los lectores atentos no sean demasiado vagos para asomar la nariz.

¡Buena suerte!

Fuente: habr.com

Añadir un comentario