Correttore di layout Xswitcher per Linux: passo due

Come pubblicazione precedente (xswitcher nella fase di “prova di concetto”) ha ricevuto molti feedback costruttivi (che è carino), ho continuato a dedicare il mio tempo libero allo sviluppo del progetto. Ora voglio spendere un po' del tuo... Il secondo passo non sarà del tutto consueto: proposta/discussione del progetto di configurazione.

Correttore di layout Xswitcher per Linux: passo due

In qualche modo si scopre che i normali programmatori trovano incredibilmente noioso impostare tutti questi controlli.

Per non essere infondato, all'interno c'è un esempio di ciò di cui mi sto occupando.
Nel complesso Apache Kafka e ZooKeeper sono concepiti in modo eccellente (e ben implementati).
- Configurazione? Ma è noioso! XML stupido (perché è "pronto all'uso").
- Oh, vuoi anche un ACL? Ma è così noioso! Tap-blooper... Qualcosa del genere.

Ma nel mio lavoro è esattamente il contrario. Giusto (ahimè, quasi mai la prima volta) il modello costruito consente di proseguire oltre in modo semplice e naturale (Quasi) assemblare un diagramma.

Recentemente mi sono imbattuto in un articolo su Habré sul duro lavoro dei data scientist...
Si scopre che questo momento è pienamente realizzato per loro. E nella mia pratica, come si suol dire, "versione light". Modelli multivolume, programmatori esperti con OOP pronto, ecc. - tutto questo apparirà più tardi quando/se decollerà. Ma il designer deve iniziare da qualche parte, qui e ora.

Arriva al punto. Ho preso TOML come base sintattica da questo cittadino.

Perché lui (TOML) da un lato, modificabile dall'uomo. D'altra parte, viene tradotto 1:1 in una qualsiasi delle sintassi più comuni: XML, JSON, YAML.
Inoltre, l'implementazione che ho utilizzato da “github.com/BurntSushi/toml”, sebbene non sia la più alla moda (ancora la sintassi 1.4), è sintatticamente compatibile con lo stesso JSON (“integrato”).

Cioè, se lo desideri, puoi semplicemente dire "vai per il bosco con quel tuo TOML, voglio XXX" e "patch" il codice con una sola riga.

Quindi, se vuoi scrivere alcune finestre per configurare xswitcher (Non sono sicuro) Non sono previsti problemi “con questa tua dannata configurazione”.

Per tutti gli altri la sintassi è basata su “chiave = valore” (e letteralmente un paio di opzioni più complicate, come = [some, that, array]) Credo
intuitivamente conveniente.
La cosa interessante è questo "bruciato" nello stesso periodo (intorno al 2013). Solo che, a differenza di me, l'autore di TOML è entrato su larga scala.

Pertanto, ora è più facile per me adattare la sua implementazione alle mie esigenze e non viceversa.

In generale utilizziamo TOML (molto simile al vecchio INI di Windows). E abbiamo una configurazione in cui descriviamo come attaccare una serie di ganci a seconda dell'insieme degli ultimi codici di scansione dalla tastiera. Di seguito, pezzo per pezzo, quello che è successo finora. E una spiegazione del perché ho deciso in questo modo.

0. Astrazioni di base

  • Scansione delle designazioni dei codici. Bisogna assolutamente fare qualcosa al riguardo, dal momento che i semplici codici digitali non sono assolutamente leggibili dall'uomo (sono solo io loloswitcher).
    Ho eliminato "ecodes.go" da "golang-evdev" (ero troppo pigro per guardare la fonte originale, anche se l'autore lo ha indicato in modo abbastanza culturale). Ho corretto un po' (per ora) una cosa che faceva abbastanza paura. Come “LEFTBRACE” → “L_BRACE”.
  • Inoltre, ha introdotto il concetto di “chiavi di Stato”. Poiché la grammatica regolare utilizzata non consente passaggi lunghi. (Ma ti consente di controllare con un sovraccarico minimo. Se usi solo la registrazione “diretta”.)
  • Ci sarà un “deduplicatore” integrato di ciò che viene premuto. Pertanto verrà scritto lo stato "ripetizione"=2 uno tempo.

1. Sezione Modelli

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

In cosa consiste una parola del linguaggio umano con notazione fonetica? (o una questione di grafemi ovvero “geroglifici”)? Una specie di terribile "lenzuolo". Pertanto introduco subito il concetto di “template”.

2. Cosa fare quando si fa clic su qualcosa (è arrivato un altro codice di scansione)

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

Ci sono 768 codici in totale. (Ma “per ogni evenienza” ho inserito delle “sorprese” nel codice xswitcher).
All'interno ho descritto come riempire l'array con collegamenti alle funzioni “cosa fare”. A Golang questo è (all'improvviso) Si è rivelato conveniente e ovvio.

  • Ho intenzione di ridurre al minimo il "Drop" in questo posto. A favore di un trattamento più flessibile (lo mostrerò di seguito).

3. Tabella con classi di finestre

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

Le righe della tabella sono racchiuse tra doppie parentesi quadre con il relativo nome. Non avrebbe potuto essere più semplice fin dall'inizio. A seconda della finestra attualmente attiva, è possibile selezionare le seguenti opzioni:

  • Il tuo set di "tasti di scelta rapida" "Azioni = ...". In caso contrario/vuoto, non fare nulla.
  • Cambia "MouseClickDrops": cosa fare quando viene rilevato un clic del mouse. Poiché nel momento in cui xswitcher è acceso non ci sono dettagli su "dove fanno clic", ripristiniamo il buffer per impostazione predefinita. Ma nei terminali (ad esempio) non devi farlo (Generalmente).

4. Una (o più) sequenze di clic attivano l'uno o l'altro hook

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

I ganci sono divisi in due tipi. Integrato, con nomi autoesplicativi (NewWord, NewSentence, Compose) e programmabile.

I nomi programmabili iniziano con "Azione". Perché TOML v1.4, i nomi con punti devono essere racchiusi tra virgolette.

Ogni sezione dovrebbe essere descritta di seguito con lo stesso nome.

Per non far impazzire la gente con clienti abituali “nudi” (per esperienza, loro scrivereforse uno su dieci профессионалов), implemento immediatamente una sintassi aggiuntiva.

  • "SPENTO:" (o "ACCESO:") prima di regexp (espressione regolare) è necessario rilasciare (o premere) i seguenti pulsanti.
    Successivamente creerò un’espressione regolare “ingiusta”. Con controllo separato dei pezzi tra i tubi "|". Per ridurre il numero di record come "[LR]_SHIFT" (dove chiaramente non è necessario).
  • "SEQ:" Se la condizione precedente è soddisfatta (o assente), controlliamo con un'espressione regolare "normale". Per i dettagli invio subito a ^W la libreria “regexp”. Perché non mi sono ancora preso la briga di verificare il grado di compatibilità con il mio pcre preferito (“compatibile con perl”).
  • L'espressione è scritta nella forma "PULSANTE_1: CODICE1, PULSANTE_2: CODICE2" ecc., nell'ordine in cui vengono ricevuti i codici di scansione.
  • Il controllo è sempre “bloccato” fino alla fine della sequenza, quindi non è necessario aggiungere "$" alla coda.
  • Tutti i controlli in una riga vengono eseguiti uno dopo l'altro e sono combinati da “I”. Ma poiché il valore è descritto come un array, puoi scrivere un controllo alternativo dopo la virgola. Se questo è necessario per qualche motivo.
  • Valore "LunghezzaSeq = 8" limita la dimensione del buffer rispetto al quale vengono eseguiti tutti i controlli. Perché Non ho mai incontrato (fino ad ora) risorse infinite in vita mia.

5. Impostazione dei ganci descritti nella sezione precedente

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

La cosa principale qui è "Azione = [Array]". Similmente alla sezione precedente, esiste un insieme limitato di azioni integrate. E la possibilità di attracco non è limitata in linea di principio (scrivi "Azione.XXX" e non essere troppo pigro per scrivere un'altra sezione).
In particolare, la riscrittura di una parola nel layout corretto si divide in due parti: “cambia il layout come specificato lì” и “ridigitare” (“RidigitareParola”).

I restanti parametri vengono scritti nel “dizionario” ("mappa" in Golang) per una determinata azione, la loro lista dipende da quanto scritto in “Azione”.

Diverse azioni diverse possono essere descritte in un unico heap (sezioni). Oppure puoi smontarlo. Come ho mostrato sopra.

Ho immediatamente impostato l'azione "Exec" per eseguire lo script esterno. Con l'opzione di inserire il buffer registrato in stdin.

  • “Wait = 1”: attende il completamento del processo in esecuzione.
  • Probabilmente, "nel mucchio" vorrai mettere altre persone nell'ambiente. informazioni come il nome della classe finestra da cui è stato intercettato.
    “Vuoi connettere il tuo gestore? È qui che devi andare.”

Uff (espirando). Sembra che non abbia dimenticato nulla.

Ops! Sì, non ho dimenticato...
Dov'è la configurazione di lancio? Nel codice rigido? Come quello:

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

Dove ho dimenticato/commesso un errore? (assolutamente impossibile senza questo), Spero davvero che i lettori attenti non siano troppo pigri per ficcare il naso.

In bocca al lupo!

Fonte: habr.com

Aggiungi un commento