Corrector de diseño Xswitcher para Linux: paso dos

Dado que la publicación anterior (xswitcher en la etapa de "prueba de concepto") recibió muchas críticas constructivas (lo cual es bueno) , continué dedicando mi tiempo libre al desarrollo del proyecto. Ahora quiero gastar un poco de los suyos ... El segundo paso no será muy familiar: sugerencia / discusión del diseño de la configuración.



De alguna manera resulta que los programadores normales están aburridos de sintonizar todos estos giros.

Para no ser infundado, dentro hay un ejemplo de lo que estoy tratando.
( ) Apache Kafka & ZooKeeper.
— ? ! - xml ( « »).
— , ACL ? ! -… - .

Y en mi trabajo, exactamente lo contrario. Correctamente (por desgracia, la primera vez que casi nunca existe) el modelo construido le permite montar fácil y fácilmente (bueno, casi) el circuito.

Recientemente, un artículo sobre Habré encontró un artículo sobre el trabajo duro de los científicos de datos ...
, . , , « ». , .. — , / . - .

Llegar al punto. Como base sintáctica, tomé TOML de este ciudadano .

Porque (TOML) es, por un lado, editable por humanos. Y por otro lado, traduce 1: 1 en 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 (todavía la sintaxis 1.4), es sintácticamente compatible con el mismo JSON ("incorporado").

Es decir, si lo desea, simplemente puede decir "pase por el bosque con este su TOML, quiero XXX" y "parchee" el código con solo una línea.

Por lo tanto, si desea escribir algunas ventanas (definitivamente no yo) para configurar xswitcher, no hay problemas "con esta su maldita configuración".

Para todos los demás, la sintaxis basada en "clave = valor" (y, literalmente, un par de opciones es más complicada, escriba = [algunos, luego, matriz]) Supongo
Intuitivamente conveniente.
, «» ( 2013 ). , , TOML .

, .

En general, tomamos TOML (muy similar al antiguo INI de Windows). Y tenemos una configuración en la que describimos cómo conectar una serie de ganchos dependiendo del conjunto de los últimos códigos de escaneo desde el teclado. A continuación en pedazos: lo que sucedió en este momento. Y la explicación de lo que decidí así.

0. abstracciones básicas


  • -. - , - ( loloswitcher).
    «ecodes.go» «golang-evdev» ( , ). () . «LEFTBRACE» → «L_BRACE».
  • « ». . ( . «» .)
  • «» . , «»=2 .

1.


[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 de lenguaje humano con notación fonética (o es un caso de grafema, también conocido como "jeroglíficos") ? Algún tipo de terrible "hoja". Por lo tanto, inmediatamente pongo el concepto de "plantilla".

2. Qué hacer cuando se presiona algo (ha llegado el siguiente 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"]

Se proporcionan un total de 768 códigos. (Pero, por si acaso, inserté una captura de "sorpresas" en el código xswitcher).
En el interior pinté rellenando el conjunto con enlaces a las funciones "qué hacer". En Golang, (de repente) resultó ser conveniente y obvio.

  • "Caída" en este lugar que planeo reducir al mínimo. A favor de un procesamiento más flexible (lo mostraré a continuación).

3. Etiqueta de clase 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. Más fácil de ir no funcionó. Dependiendo de la ventana activa actual, puede elegir las opciones:

  • « » «Actions = …». / — .
  • «MouseClickDrops» — . xswitcher « », - . () ( ).

4. ( )


# 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. Incorporado, con nombres "parlantes" (NewWord, NewSentence, Compose) y programable.

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

A continuación se describe una sección con el mismo nombre para cada uno .

Para no hacer estallar el cerebro de las personas con clientes habituales (en la experiencia, uno de cada diez profesionales puede escribirlos ), inmediatamente presento una sintaxis adicional.

  • «OFF:» ( «ON:») regexp ( ) ( ).
    «» . "|". "[LR]_SHIFT" ( ).
  • «SEQ:» ( ), «» . ^W «regexp». pcre («perl compatible»).
  • «_1: 1, _2: 2» .., -.
  • «» , "$" .
  • «». , . - .
  • «SeqLength = 8» , . .. ( ) .

5. ,


# 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 = [Array]" . Similar a la sección anterior, hay un conjunto limitado de acciones integradas. E ilimitada, en principio, la posibilidad de acoplamiento (escriba "Action.XXX" y no sea demasiado vago para pintar otra sección) .
En particular, la reescritura de palabras en el diseño corregido se divide en dos partes: "cambiar el diseño tal como está configurado allí" y "volver a escribir" ("RetypeWord") .

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

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

Inmediatamente coloque la acción "Exec": ejecute un script externo. Con la opción de insertar el búfer escrito en stdin.

  • "Esperar = 1": espera a que se complete el proceso de ejecución.
  • Probablemente, "al montón" querrá poner en el medio ambiente extra. información como el nombre de la clase de ventana desde la cual se intercepta.
    "¿Quieres conectar tu controlador?" Para ti aquí.

Uf (exhalado). Parece no haber olvidado nada.

¡Uy! Sí, no lo olvidé ...
? -́? :

[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.


Y donde olvidé / cometí un error (sin esto, de ninguna manera) , realmente espero que los lectores atentos no sean demasiado vagos para meter la nariz.

Buena suerte

All Articles