Korektor postavitve Xswitcher za Linux: drugi korak

Kot prejšnja objava (xswitcher na stopnji »dokaz koncepta«) je prejel precej konstruktivnih povratnih informacij (kar je lepo), svoj prosti čas sem še naprej posvečal razvoju projekta. Zdaj želim porabiti malo vašega... Drugi korak ne bo povsem običajen: predlog/razprava o zasnovi konfiguracije.

Korektor postavitve Xswitcher za Linux: drugi korak

Nekako se je izkazalo, da se normalnim programerjem zdi neverjetno dolgočasno nastavljati vse te kontrole.

Da ne bom neutemeljen, notri je primer s čim se ukvarjam.
Na splošno odlično zasnovan (in dobro implementiran) Apache Kafka & ZooKeeper.
- Konfiguracija? Ampak to je dolgočasno! Neumen xml (ker je "izven škatle").
- Oh, ali želite tudi ACL? Ampak to je tako dolgočasno! Tap-blooper... Nekaj ​​takega.

Pri mojem delu pa je ravno obratno. Prav (žal, skoraj nikoli prvič) izdelan model vam omogoča enostavno in naravno nadaljevanje (skoraj) sestavite diagram.

Nedavno sem na Habréju naletel na članek o trdem delu podatkovnih znanstvenikov ...
Izkazalo se je, da je ta trenutek za njih v celoti realiziran. In v moji praksi, kot pravijo, "lahka različica". Večvolumenski modeli, izkušeni programerji s pripravljenim OOP itd. — vse to se bo pojavilo kasneje, ko/če bo vzletelo. Toda oblikovalec mora začeti nekje tukaj in zdaj.

Preidi k bistvu. Za sintaktično osnovo sem vzel TOML od tega državljana.

Ker on (TOML) po eni strani človeško urejanje. Po drugi strani pa je preveden 1:1 v katero koli od pogostejših sintaks: XML, JSON, YAML.
Poleg tega je implementacija, ki sem jo uporabil iz »github.com/BurntSushi/toml«, čeprav ni najbolj modna (še vedno sintaksa 1.4), sintaktično združljiva z istim (»vgrajenim«) JSON.

Če želite, lahko preprosto rečete "pojdite skozi gozd s tistim svojim TOML, želim XXX" in "popravite" kodo s samo eno vrstico.

Torej, če želite napisati nekaj oken za konfiguracijo xswitcherja (Nisem prepričan) Težav ni pričakovati "s to vašo prekleto konfiguracijo."

Za vse druge sintaksa temelji na "ključ = vrednost" (in dobesedno nekaj bolj zapletenih možnosti, kot je = [some, that, array]) ugibam
intuitivno priročno.
Kar je zanimivo, je to "zažgano" približno ob istem času (okoli 2013). Samo, za razliko od mene, je avtor TOML-a šel notri v primernem merilu.

Zato zdaj lažje prilagodim njegovo izvedbo sebi in ne obratno.

Na splošno vzamemo TOML (zelo podoben staremu Windows INI). In imamo konfiguracijo, v kateri opisujemo, kako pritrditi vrsto kavljev, odvisno od nabora najnovejših skeniranih kod s tipkovnice. Spodaj, po delih, je tisto, kar se je zgodilo do zdaj. In pojasnilo, zakaj sem se tako odločil.

0. Osnovne abstrakcije

  • Oznake skeniranih kod. Glede tega je vsekakor treba nekaj narediti, saj preprosto digitalne kode niso berljive za ljudi (to sem samo jaz loloswitcher).
    Iztresel sem "ecodes.go" iz "golang-evdev" (bil sem prelen, da bi pogledal prvotni vir, čeprav ga je avtor navedel precej kulturno). Malo sem popravil (za zdaj) nekaj, kar je bilo precej strah. Na primer »LEVI Oklepaj« → »L_Oklepaj«.
  • Poleg tega je uvedel koncept »državnih ključev«. Ker uporabljena redna slovnica ne dopušča dolgih odlomkov. (Vendar vam omogoča preverjanje z minimalnimi stroški. Če uporabljate samo »neposredno« snemanje.)
  • Tam bo vgrajen "deduplikator" tega, kar je pritisnjeno. Tako bo zapisano stanje "ponovi"=2 1 krat.

1. Razdelek s predlogami

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

Iz česa je sestavljena beseda človeškega jezika s fonetičnim zapisom? (bodisi gre za grafeme ali »hieroglife«)? Nekakšen grozen "list". Zato takoj uvedem koncept "predloge".

2. Kaj storiti, ko se nekaj klikne (prispela je druga skenirana koda)

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

Skupaj je 768 kod. (Toda "za vsak slučaj" sem v kodo xswitcher vstavil "lovljenje presenečenj").
Znotraj sem opisal polnjenje matrike s povezavami do funkcij "kaj narediti". V golangu je to (nenadoma) Izkazalo se je priročno in očitno.

  • Na tem mestu nameravam zmanjšati »Drop« na minimum. V prid bolj prilagodljivi obdelavi (to bom prikazal spodaj).

3. Tabela z razredi oken

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

Vrstice tabele so v dvojnih oglatih oklepajih z njenim imenom. Ne bi moglo biti lažje takoj na začetku. Glede na trenutno aktivno okno lahko izberete naslednje možnosti:

  • Vaš lasten nabor »vročih tipk« »Dejanja = …«. Če ni/prazno, ne storite ničesar.
  • Preklopite »MouseClickDrops« - kaj storiti, ko je zaznan klik miške. Ker na točki, kjer je xswitcher vklopljen, ni podrobnosti o tem, »kje kliknejo«, smo medpomnilnik privzeto ponastavili. Toda v terminalih (na primer) vam tega ni treba storiti (običajno).

4. Eno (ali več) zaporedij klikov sproži eno ali drugo kljuko

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

Kljuke delimo na dve vrsti. Vgrajeno, s samoumevnimi imeni (Nova beseda, Nova poved, Sestavi) in programabilno.

Imena, ki jih je mogoče programirati, se začnejo z »Dejanje«. Ker TOML v1.4, imena s pikami morajo biti v narekovajih.

Vsak razdelek je treba opisati spodaj z istim imenom.

Da ne bi razburjali ljudi z "golimi" stalnimi obiskovalci (po izkušnjah njihovimi pisatimorda eden od desetih strokovnjaki), takoj implementiram dodatno sintakso.

  • "OFF:" (ali "ON:") preden regexp (regularni izraz) zahteva, da se naslednji gumbi sprostijo (ali pritisnejo).
    Nato bom naredil "nepošten" regularni izraz. Z ločenim preverjanjem kosov med cevmi "|". Da bi zmanjšali število zapisov, kot je "[LR]_SHIFT" (kjer to očitno ni potrebno).
  • "SEQ:" Če je prejšnji pogoj izpolnjen (ali odsoten), potem preverimo z "normalnim" regularnim izrazom. Za podrobnosti takoj pošljem ^W knjižnico »regexp«. Ker se še vedno nisem potrudil, da bi ugotovil stopnjo združljivosti z mojim najljubšim pcre ("perl compatible").
  • Izraz je zapisan v obliki "BUTTON_1: CODE1, BUTTON_2: CODE2" itd., v vrstnem redu, v katerem so bile prejete skenirane kode.
  • Ček je vedno »pritrjen« na konec zaporedja, zato ni treba dodati "$" na rep.
  • Vsi pregledi v eni liniji se izvajajo drug za drugim in so združeni z "I". Ker pa je vrednost opisana kot niz, lahko za vejico napišete alternativno preverjanje. Če je to iz nekega razloga potrebno.
  • Vrednost "SeqLength = 8" omejuje velikost vmesnega pomnilnika, v katerem se izvajajo vsa preverjanja. Ker Nikoli v življenju se (do zdaj) nisem srečal z neskončnimi viri.

5. Nastavitev kavljev, opisana v prejšnjem razdelku

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

Glavna stvar tukaj je "Dejanje = [Matrika]". Podobno kot v prejšnjem razdelku, obstaja omejen nabor vgrajenih dejanj. In možnost priklopa načeloma ni omejena (napišite »Action.XXX« in ne bodite preveč leni, da napišete še en razdelek zanj).
Zlasti pretipkavanje besede v popravljeni postavitvi je razdeljeno na dva dela: "spremeni postavitev, kot je tam določeno" и »pretipkaj« (»RetypeWord«).

Preostali parametri so zapisani v "slovar" ("zemljevid" v golangu) za določeno dejanje je njihov seznam odvisen od tega, kar je zapisano v »Akcija«.

Na enem kupu je mogoče opisati več različnih dejanj (oddelki). Ali pa ga lahko razstavite. Kot sem pokazal zgoraj.

Takoj sem nastavil dejanje »Exec« za izvedbo zunanjega skripta. Z možnostjo potiskanja posnetega medpomnilnika v stdin.

  • "Počakaj = 1" — počakajte, da se tekoči proces zaključi.
  • Verjetno boste "na kup" želeli postaviti dodatne ljudi v okolje. informacije, kot je ime razreda okna, iz katerega je bil prestrežen.
    »Ali želite povezati svojega skrbnika? Sem moraš iti.”

Fuj (izdihnil). Zdi se, kot da nisem ničesar pozabil.

Ups! Ja, nisem pozabil...
Kje je konfiguracija zagona? V trdi kodi? Kot to:

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

Kje sem pozabil/naredil napako? (brez tega ne gre), resnično upam, da pozorni bralci ne bodo preveč leni, da bi pomolili nos.

Srečno!

Vir: www.habr.com

Dodaj komentar