Corector de aspect Xswitcher pentru Linux: pasul doi

Ca publicația anterioară (xswitcher la etapa „dovada conceptului”) a primit destul de mult feedback constructiv (ceea ce e frumos), am continuat să-mi petrec timpul liber dezvoltând proiectul. Acum vreau să petrec puțin din... Al doilea pas nu va fi chiar obișnuit: propunerea/discuția despre designul configurației.

Corector de aspect Xswitcher pentru Linux: pasul doi

Cumva, se dovedește că programatorilor normali le este incredibil de plictisitor să configureze toate aceste comenzi.

Pentru a nu fi neîntemeiat, în interior este un exemplu cu ceea ce am de-a face.
În general, excelent concepute (și bine implementate) Apache Kafka și ZooKeeper.
- Configurare? Dar e plictisitor! XML prost (pentru că este „din cutie”).
- Oh, vrei și tu un ACL? Dar e atât de plictisitor! Tap-blooper... Ceva de genul ăsta.

Dar în munca mea este exact opusul. Dreapta (din păcate, aproape niciodată prima dată) modelul construit vă permite să continuați mai departe ușor și natural (Aproape) asamblați o diagramă.

Am găsit recent un articol despre Habré despre munca grea a cercetătorilor de date...
Se pare că acest moment este pe deplin realizat pentru ei. Și în practica mea, așa cum se spune, „versiunea ușoară”. Modele cu mai multe volume, programatori experimentați, cu OOP pregătit, etc. — toate acestea vor apărea mai târziu când/dacă decolează. Dar designerul trebuie să înceapă de undeva aici și acum.

Treci la subiect. Am luat TOML ca bază sintactică de la acest cetatean.

Deoarece el (TOML) pe de o parte, editabil de om. Pe de altă parte, este tradus 1:1 în oricare dintre cele mai comune sintaxe: XML, JSON, YAML.
Mai mult, implementarea pe care am folosit-o de la „github.com/BurntSushi/toml”, deși nu este cea mai la modă (încă 1.4 sintaxă), este compatibilă sintactic cu același JSON („built-in”).

Adică, dacă doriți, puteți spune pur și simplu „treceți prin pădure cu acel TOML al vostru, vreau XXX” și „reglați” codul doar cu o singură linie.

Astfel, dacă doriți să scrieți niște ferestre pentru a configura xswitcher (Nu sunt sigur) Nu sunt de așteptat probleme „cu această configurație nenorocita a ta”.

Pentru toate celelalte, sintaxa se bazează pe „cheie = valoare” (și literalmente câteva opțiuni mai complicate, cum ar fi = [unele, că, matrice]) presupun
intuitiv convenabil.
Ceea ce este interesant este că "ars" cam în aceeași perioadă (în jurul anului 2013). Numai că, spre deosebire de mine, autorul cărții TOML a intrat la o scară adecvată.

Prin urmare, acum îmi este mai ușor să-i adaptez implementarea pentru a se potrivi și nu invers.

În general, luăm TOML (foarte similar cu vechiul Windows INI). Și avem o configurație în care descriem cum să atașăm o serie de cârlige în funcție de setul celor mai recente coduri de scanare de la tastatură. Mai jos, bucată cu bucată, este ceea ce s-a întâmplat până acum. Și o explicație a motivului pentru care am decis așa.

0. Abstracții de bază

  • Scanarea desemnărilor codurilor. Cu siguranță trebuie făcut ceva în privința asta, deoarece pur și simplu codurile digitale nu sunt absolut lizibile de om (acesta sunt doar eu loloswitcher).
    Am scos „ecodes.go” din „golang-evdev” (mi-a fost prea lene să mă uit la sursa originală, deși autorul a indicat-o destul de cultural). Am corectat puțin (deocamdată) ceva care era destul de înfricoșător. Ca „LEFTBRACE” → „L_BRACE”.
  • În plus, el a introdus conceptul de „chei de stat”. Deoarece gramatica obișnuită folosită nu permite treceri lungi. (Dar vă permite să verificați cu un cost minim. Dacă utilizați doar înregistrarea „directă”.)
  • Va exista un „deduplicator” încorporat pentru ceea ce este apăsat. Astfel, se va scrie starea „repeat”=2 o timp.

1. Secțiunea șabloane

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

În ce constă un cuvânt în limbaj uman cu notație fonetică? (fie o chestiune de grafeme aka „hieroglife”)? Un fel de „foaie” groaznică. Prin urmare, introduc imediat conceptul de „șablon”.

2. Ce să faceți când se face clic pe ceva (a sosit un alt cod de scanare)

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

Există 768 de coduri în total. (Dar „pentru orice eventualitate” am inserat „surprize” de prindere în codul xswitcher).
În interior am descris completarea matricei cu link-uri către funcțiile „ce să faci”. În golang aceasta este (brusc) S-a dovedit a fi convenabil și evident.

  • Plănuiesc să reduc „Drop” la minimum în acest loc. În favoarea unei procesări mai flexibile (o voi arăta mai jos).

3. Tabel cu clase de ferestre

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

Rândurile tabelului sunt între paranteze pătrate duble cu numele acestuia. Nu putea fi mai ușor de la început. În funcție de fereastra activă în prezent, puteți selecta următoarele opțiuni:

  • Propriul set de „taste rapide” „Acțiuni = …”. Dacă nu/gol, nu faceți nimic.
  • Comutați „MouseClickDrops” - ce să faceți când este detectat un clic de mouse. Deoarece în punctul în care xswitcher este pornit, nu există detalii despre „unde dau clic”, resetam memoria tampon în mod implicit. Dar în terminale (de exemplu) nu trebuie să faci asta (de obicei).

4. Una (sau mai multe) secvențe de clicuri declanșează unul sau altul cârlig

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

Cârligele sunt împărțite în două tipuri. Încorporat, cu nume „vorbitoare” (NewWord, NewSentence, Compose) și programabil.

Numele programabile încep cu „Acțiune”. Deoarece TOML v1.4, numele cu puncte trebuie să fie între ghilimele.

Fiecare secțiune ar trebui să fie descrisă mai jos cu acelasi nume.

Pentru a nu sufla mințile oamenilor cu obișnuiți „goși” (din experiență, lor scriepoate unul din zece profesioniști), implementez imediat sintaxă suplimentară.

  • „OFF:” (sau „ON:”) înainte ca regexp (expresie regulată) să necesite ca următoarele butoane să fie eliberate (sau apăsate).
    În continuare, voi face o expresie regulată „nedreptă”. Cu verificare separată a pieselor între țevi „|”. Pentru a reduce numărul de înregistrări precum „[LR]_SHIFT” (unde acest lucru nu este în mod clar necesar).
  • „SEQ:” Dacă condiția anterioară este îndeplinită (sau absentă), atunci verificăm cu o expresie regulată „normală”. Pentru detalii, trimit imediat la ^W biblioteca „regexp”. Pentru că încă nu m-am obosit să aflu gradul de compatibilitate cu pcre-ul meu preferat („compatibil perl”).
  • Expresia se scrie sub forma „BUTTON_1: CODE1, BUTTON_2: CODE2” etc., în ordinea în care sunt primite codurile de scanare.
  • Verificarea este întotdeauna „agățată” până la sfârșitul secvenței, deci nu este nevoie să adăugați „$” la coadă.
  • Toate verificările dintr-o linie sunt efectuate una după alta și sunt combinate de „Eu”. Dar, deoarece valoarea este descrisă ca o matrice, puteți scrie o verificare alternativă după virgulă. Dacă acest lucru este necesar dintr-un motiv oarecare.
  • Valoare „SeqLength = 8” limitează dimensiunea tamponului față de care sunt efectuate toate verificările. Deoarece Nu am întâlnit (până acum) resurse nesfârșite în viața mea.

5. Setarea cârligelor descrise în secțiunea anterioară

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

Principalul lucru aici este „Acțiune = [Matrice]”. Similar cu secțiunea anterioară, există un set limitat de acțiuni încorporate. Iar posibilitatea de andocare nu este limitată în principiu (scrieți „Action.XXX” și nu fi prea leneș să scrieți o altă secțiune pentru el).
În special, retastarea unui cuvânt în aspectul corectat este împărțită în două părți: „schimbați aspectul așa cum este specificat acolo” и „reintroduceți” („Reintroduceți Cuvântul”).

Parametrii rămași sunt scrieți în „dicționar” („hartă” în golang) pentru o anumită acțiune, lista lor depinde de ceea ce este scris în „Acțiune”.

Mai multe acțiuni diferite pot fi descrise într-un singur heap (secțiuni). Sau o poți desprinde. După cum am arătat mai sus.

Am setat imediat acțiunea „Exec” pentru a executa scriptul extern. Cu opțiunea de a împinge tamponul înregistrat în stdin.

  • „Așteptați = 1” — așteptați finalizarea procesului de rulare.
  • Probabil, „la grămada” veți dori să puneți oameni suplimentari în mediu. informații precum numele clasei de ferestre din care a fost interceptată.
    „Vrei să-ți conectezi handlerul? Aici trebuie să mergi.”

Puff (expirat). Se pare că nu am uitat nimic.

Hopa! Da, nu am uitat...
Unde este configurația de lansare? În cod dur? Ca asta:

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

Unde am uitat/facut o greseala? (nici putin fara asta), sper cu adevărat că cititorii atenți nu vor fi prea leneși să-și bage nasul.

Noroc!

Sursa: www.habr.com

Adauga un comentariu