Corrector de deseño de Xswitcher para Linux: paso dous

Como publicación anterior (xswitcher na fase de "proba de concepto") recibiu bastantes comentarios construtivos (que é bo), seguín dedicando o meu tempo libre a desenvolver o proxecto. Agora quero gastar un pouco do teu... O segundo paso non será moi habitual: proposta/discusión do deseño da configuración.

Corrector de deseño de Xswitcher para Linux: paso dous

Dalgunha maneira, resulta que aos programadores normais resulta incriblemente aburrido configurar todos estes controis.

Para non ser infundado, dentro hai un exemplo do que estou a tratar.
En xeral, Apache Kafka e ZooKeeper excelentemente concibidos (e ben implementados).
- Configuración? Pero é aburrido! XML tonto (porque está "fóra da caixa").
- Ah, tamén queres un ACL? Pero é tan aburrido! Tap-blooper... Algo así.

Pero no meu traballo é exactamente o contrario. Certo (ai, case nunca a primeira vez) o modelo construído permítelle continuar máis facilmente e con naturalidade (Case) montar un diagrama.

Recentemente atopeime cun artigo sobre Habré sobre o duro traballo dos científicos de datos...
Resulta que este momento está plenamente realizado para eles. E na miña práctica, como din, "versión lixeira". Modelos de varios volumes, programadores experimentados con POO listo, etc. — Todo isto aparecerá máis tarde cando/se despegue. Pero o deseñador debe comezar nalgún lugar aquí e agora.

Chegar ao grano. Tomei TOML como base sintáctica deste cidadán.

Porque el (TOML) por unha banda, editable por humanos. Por outra banda, tradúcese 1:1 a calquera das sintaxes máis comúns: XML, JSON, YAML.
Ademais, a implementación que usei de “github.com/BurntSushi/toml”, aínda que non é a máis de moda (aínda a sintaxe 1.4), é sintácticamente compatible co mesmo JSON (“integrado”).

É dicir, se o desexa, simplemente pode dicir "pase polo bosque con ese TOML teu, quero XXX" e "parche" o código cunha soa liña.

Así, se queres escribir algunhas fiestras para configurar xswitcher (Non estou seguro) Non se esperan problemas "con esta maldita configuración túa".

Para todos os demais, a sintaxe baséase en "key = value" (e, literalmente, un par de opcións máis complicadas, como = [algúns, iso, matriz]) supoño
intuitivamente cómodo.
O interesante é iso "queimado" na mesma época (ao redor de 2013). Só, a diferenza de min, o autor de TOML entrou nunha escala adecuada.

Polo tanto, agora é máis fácil para min axustar a súa implementación para que se adapte a min, e non viceversa.

En xeral, tomamos TOML (moi semellante ao antigo INI de Windows). E temos unha configuración na que describimos como conectar unha serie de ganchos dependendo do conxunto dos últimos códigos de escaneo do teclado. Abaixo, peza a peza, está o que pasou ata agora. E unha explicación de por que decidín deste xeito.

0. Abstraccións básicas

  • Escanear designacións de códigos. Definitivamente hai que facer algo ao respecto, xa que simplemente os códigos dixitais non son absolutamente lexibles polos humanos (eso son só eu loloswitcher).
    Sacudín “ecodes.go” de “golang-evdev” (demasiado preguiceiro para mirar a fonte orixinal, aínda que o autor indicouno bastante culturalmente). Corrixín un pouco (polo de agora) algo que me daba bastante medo. Como "LEFTBRACE" → "L_BRACE".
  • Ademais, introduciu o concepto de "claves de estado". Xa que a gramática regular empregada non permite longas pasaxes. (Pero permíteche verificar cunha sobrecarga mínima. Se usas só a gravación "directa".)
  • Haberá un "desduplicador" incorporado do que se preme. Así, escribirase o estado "repetir"=2 un veces.

1. Sección de modelos

[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 que consiste unha palabra da linguaxe humana con notación fonética? (xa sexa unha cuestión de grafemas tamén coñecidos como "xeroglifos")? Algún tipo de "folla" terrible. Polo tanto, introduzo inmediatamente o concepto de "modelo".

2. Que facer cando se fai clic en algo (chegou outro código de dixitalización)

[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"]

Hai 768 códigos en total. (Pero "por se acaso" inseriu capturar "sorpresas" no código xswitcher).
Dentro describín encher a matriz con ligazóns ás funcións "que facer". En golang isto é (de súpeto) Resultou conveniente e obvio.

  • Planeo reducir "Drop" ao mínimo neste lugar. A favor dunha tramitación máis flexible (mostrareino a continuación).

3. Táboa con clases de fiestras

# 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"

As filas da táboa están entre corchetes dobres co seu nome. Non puido ser máis sinxelo desde o primeiro momento. Dependendo da xanela activa actualmente, pode seleccionar as seguintes opcións:

  • O teu propio conxunto de "teclas rápidas" "Accións =...". Se non/baleiro, non fagas nada.
  • Cambia "MouseClickDrops": que facer cando se detecta un clic do rato. Dado que no punto no que xswitcher está activado non hai detalles sobre "onde fan clic", restablecemos o búfer por defecto. Pero nos terminais (por exemplo) non tes que facelo (normalmente).

4. Unha (ou varias) secuencias de clics desencadean un ou outro 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)" ]

Os ganchos divídense en dous tipos. Integrado, con nomes "falantes" (NewWord, NewSentence, Compose) e programable.

Os nomes programables comezan por "Acción". Porque TOML v1.4, os nomes con puntos deben ir entre comiñas.

Cada sección debe describirse a continuación co mesmo nome.

Para non botarlles a cabeza á xente con habituais "espidos" (por experiencia, o seu escribirquizais un de cada dez profesionais), inmediatamente implemento sintaxe adicional.

  • "OFF:" (ou "ON:") antes de que a expresión regular (expresión regular) requira que se solten (ou se premen) os seguintes botóns.
    A continuación vou facer unha expresión regular "inxusta". Con comprobación separada de pezas entre tubos "|". Para reducir o número de rexistros como "[LR]_SHIFT" (onde claramente non é necesario).
  • "SEQ:" Se a condición anterior se cumpre (ou ausente), entón comprobamos contra unha expresión regular "normal". Para obter máis información, envío inmediatamente a ^W a biblioteca "regexp". Porque aínda non me molestei en descubrir o grao de compatibilidade co meu pcre favorito ("compatible con perl").
  • A expresión escríbese na forma "BUTTON_1: CODE1, BUTTON_2: CODE2" etc., na orde en que se reciben os códigos de dixitalización.
  • A verificación sempre está "axustada" ao final da secuencia, polo que non hai que engadir "$" á cola.
  • Todas as comprobacións nunha liña realízanse unha tras outra e combínanse por "I". Pero xa que o valor se describe como unha matriz, pode escribir unha verificación alternativa despois da coma. Se isto é necesario por algún motivo.
  • Valor "SeqLength = 8" limita o tamaño do búfer contra o que se realizan todas as comprobacións. Porque Nunca (ata agora) atopei recursos infinitos na miña vida.

5. Colocación dos ganchos descritos no 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.

O principal aquí é "Acción = [Matriz]". Do mesmo xeito que na sección anterior, hai un conxunto limitado de accións integradas. E a posibilidade de atracar non está limitada en principio (escribe "Action.XXX" e non sexas preguiceiro para escribir outra sección para iso).
En particular, a reedición dunha palabra no deseño corrixido divídese en dúas partes: "cambiar o deseño como se especifica alí" и "reescriba" ("Reescriba a palabra").

O resto dos parámetros escríbense no "dicionario" ("mapa" en golang) para unha acción determinada, a súa lista depende do que estea escrito en "Acción".

Pódense describir varias accións diferentes nun montón (seccións). Ou pode separalo. Como mostrei arriba.

Inmediatamente configurei a acción "Exec" para executar o script externo. Coa opción de empuxar o búfer gravado en stdin.

  • "Wait = 1" - agarde a que se complete o proceso en execución.
  • Probablemente, "ao montón" quererá poñer persoas adicionais no medio ambiente. información como o nome da clase de xanela desde a que foi interceptada.
    "Queres conectar o teu controlador? Aquí é onde tes que ir".

Uf (exhalado). Parece que non esquecín nada.

Vaia! Si, non me esquecín...
Onde está a configuración de lanzamento? En código duro? Así:

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

Onde esquecín/me equivoquei? (de ningún xeito sen isto), Realmente espero que os lectores atentos non sexan demasiado preguiceiros para meter o nariz.

Boa sorte!

Fonte: www.habr.com

Engadir un comentario