Корректор раскладок «хswitcher» для linux: шаг второй

Ker prejšnja objava (hswitcher je v fazi «dokaz koncepta») je prejel kar nekaj konstruktivnih povratnih informacij (kar je prijetno), sem še naprej porabljal svoj prosti čas za razvoj projekta. Zdaj želim malo porabiti vašega ... Drugi korak ne bo povsem običajen: predlog / razprava o zasnovi konfiguracije.

Корректор раскладок «хswitcher» для linux: шаг второй

Na nek način se zgodi, da normalni programerji nastavitev vseh teh nastavitev izredno dolgočasijo.

Da ne bi bil brez dokazov, tukaj je primer tistega, s čimer imam opravka.
Odlično zasnovane (in dobro реализованное) Apache Kafka & ZooKeeper.
— Konfiguracija? Ampak to je dolgočasno! Hitra XML (saj je «iz škatle»).
— Oh, ali želite še ACL? Ampak to je tako mukotrpno! Hitro... Tako.

In v mojem delu — ravno nasprotno. Prav (žal, skoraj nikoli ni prvič uspešno) zgrajen model omogoča enostavno in brezskrbno (no, skoraj) zbrati shemo.

Nedavno sem naletel na članek na Habré o težkem delu data-scientistov ...
Izkazalo se je, da se ta trenutek pri njih v celoti uresničuje. In v moji praksi, kot pravijo, «lahka različica». Večvolumski modeli, izkušeni programerji z OOP in podobno — to bo vse prišlo, ko / če vzleti. A konstruktor se mora zdaj in tukaj od nečesa začeti.

Bližje k dejanju. Kot sintaktično osnovo sem vzel TOML od tega gospodarja.

Ker je (TOML) na eni strani človeku prijazen za urejanje. Po drugi strani pa se 1:1 pretvarja v katerikoli bolj razširjeni sintaks: XML, JSON, YAML.
Kar je zanimivo, implementacija, ki sem jo uporabil na «github.com/BurntSushi/toml» čeprav ni najbolj moderna (sintaksa še vedno 1.4), pa je sintaktično združljiva s tistim («vgrajenim») JSON-om.

To pomeni, da lahko po želji preprosto rečeš «idi stran s tem svojim TOML-jem, želim XXX» in «popraviš» kodo v eni vrstici.

Tako lahko po želji napišem nekaj okvirov za nastavitev hswitcher (niti ne jaz) problems «s tem vašim prekletim configom» - ne predvideva.

Za vse druge sintaksa na osnovi «ključ = vrednost» (in dobesedno par možnosti nekoliko bolj zapletenih, kot npr. = [kakšen, nek, niz]) menim
intuitivno prijazen.
Kar je zanimivo, je tudi sam »gorela« približno ob istem času (okrog leta 2013). Samo, nasprotno od mene, avtor TOML je prišel s primernim zamahom.

Zato mi je sada lakše prilagoditi njegovu implementaciju sebi, a ne obrnuto.

Uglavnom, uzimamo TOML (vrlo sličan starom Windows INI-u). I postavljamo konfiguraciju u kojoj opisujemo kako pričvrstiti niz hookova u zavisnosti od skupa poslednjih sken-kodova sa tastature. Ispod su delovi — to što je trenutno urađeno. I objašnjenja zašto sam to tako rešio.

0. Osnovne apstrakcije

  • Obeležavanje sken-kodova. Ovo je nešto što svakako treba uraditi, jer su samo numerički kodovi potpuno nečitljivi za ljude (to sam mislio na voćnjak) loloswitcher.).
    Izvukao sam «ecodes.go» iz «golang-evdev» (alenjao sam da gledam u prvobitni izvor, iako je autor to solidno naveo). Malo (za sada) sam sredio zaista ružno. Tipa «LEFTBRACE» → «L_BRACE».
  • Dodatno sam uveo pojam «tastera sa stanjem». Budući da regularna gramatika koja se koristi ne podstiče na duge pasuse. (Ali omogućava proveru sa minimalnim preklapanjima. Ako se koristi samo «direktno» snimanje.)
  • Biće ugrađeni «dedup likator» pritisnutih tastera. Tako će stanje «ponovi»=2 biti zabeleženo ; v idealnem primeru bi moral uporabnik izbrati dva ali več skrivnih vprašanj puta.

1. Odeljak šablona

[Templates] # "@name@" za pojednostavljenje izraza
 # Reči mogu da se sastoje od ovih znakova (regex)
 "WORD" = "([0-9A-Z`;']|[LR]_BRACE|COMMA|DOT|SLASH|KP[0-9])"

Iz čega se sastoji reč ljudskog jezika sa fonetskim zapisom (ne kao grafeme odnosno «hieroglife»)? Какая-то ужасная «простыня». Поэтому сразу закладываю понятие «шаблона».

2. Šta raditi kada se nešto pritisne (stigao je novi sken-kod)

[ActionKeys]
 # Prikupi taster i uradi test za sekvencu komandi
 # !!! Kodovi ponavljanja (code=2) moraju se sakupiti jednom po tasteru!
 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"]

 # Odbaci sve prikupljene tastere, uključujući ovaj. Ovo je podrazumevana radnja.
 Drop = ["ESC", "-", "TAB", "ENTER", "KPENTER", "LINEFEED..POWER"]
 # Čuvaj dodatnu mapu za ove tastere, kada je neki u „down“ stanju.
 # Stanje se proverava putem "OFF:"|"ON:" uslova u akciji.
 # (Takođe, stanje ovih tastera mora da opstane između pada bafer.)
 # ??? Kako se nositi sa CAPS i "LOCK"-tasterima ???
 StateKeys = ["L_CTRL", "L_SHIFT", "L_ALT", "L_META", "CAPS", "N_LOCK", "S_LOCK",
              "R_CTRL", "R_SHIFT", "R_ALT", "R_META"]

 # Samo test, ali ne prikupljaj.
 # Na primer, koristim F12 umesto BREAK na glupim laptopima sa lošim tastaturama (novi ThinkPadi)
 Test = ["F1..F10", "ZENKAKUHANKAKU", "102ND", "F11", "F12",
          "RO..KPJPCOMMA", "SYSRQ", "SCALE", "HANGEUL..YEN",
          "STOP..SCROLLDOWN", "NEW..MAX"]

Ukupno je predviđeno 768 kodova. (Ali «za svaki slučaj» sam u kod ubacio hswitcher da uhvati «iznenađenja»).
Unutra sam opisao popunjavanje niza linkovima na funkcije «šta raditi». Na golangu je to (iznenada) ispalo povoljno i očigledno.

  • «Drop» namer planiram skratiti na minimum. U korist fleksibilnije obrade (prikazat ću u nastavku).

3. Tabela sa klasama prozora

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

Redovi tabele — u dvostrukim uglastim zagradama sa njenim nazivom. Nije išlo lakše odmah. U zavisnosti od trenutnog aktivnog prozora, mogu se odabrati opcije:

  • Svoj skup «prečica» «Actions = …». Ako nema/prazno — ništa ne raditi.
  • Prekidač «MouseClickDrops» — šta raditi kada se otkrije klik mišem. Budući da u tački uključivanja xswitcher nema detalja «gdje se klikće», po defaultu brisemo bafer. Ali na terminalima (na primer) to može i ne biti potrebno. (obično).

4. Jedna (ili više) sekvenci pritisaka pokreću određeni 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)" ]

Hookovi su podeljeni u dva tipa. Ugrađeni, sa «govorećim» imenima (NewWord, NewSentence, Compose) i programabilni.

Imena programabilnih počinju sa «Action.». Pošto je TOML v1.4, imena sa tačkama moraju biti u navodnicima.

Ispod svakog treba opisati sekciju sa istim nazivom.

Da ne prelazim ljudima mozak «golim» regularnim izrazima (na osnovu iskustva, njih napisati-to može jedan od deset profesionalaca), odmah uvodim dodatnu sintaksu.

  • «OFF:» (ili «ON:») ispred regexp (regularnog izraza) zahteva da navedeni dugmići budu otpušteni (ili pritisnuti).
    Dalje nameravam da napravim «nepošten» regularni izraz. Sa odvojenim proverama delova između cevi "|". Sa ciljem smanjenja broja unosa tipa "[LR]_SHIFT" (gde to očigledno nije potrebno).
  • «SEQ:» Ako je prethodni uslov ispunjen (ili odsutan), dalje proveravamo u vezi «običnog» regularnog izraza. Za detalje odmah šaljem na^Wu biblioteku «regexp». Zato što još uvek nisam uspeo da saznam nivo kompatibilnosti sa mojim omiljenim pcre («perl compatible»).
  • Izraz se zapisuje u obliku «BUTTON_1: CODE1, BUTTON_2: CODE2» i tako dalje, u redosledu dolaska sken kodova.
  • Provera se uvek «priključuje» na kraj sekvence, zato "$" na kraju ne treba dodavati.
  • Sve provere u jednom redu se izvršavaju jedna za drugom i kombinuju se po «I». Ali pošto je vrednost opisana u obliku niza, može se posle zareza napisati alternativna provera. Ako je to iz nekog razloga potrebno.
  • Vrijednost «SeqLength = 8» ograničava veličinu bafera, u odnosu na koju se sprovode sve provere. Ker v življenju (še vedno) nisem srečal neskončnih virov.

5. Določitev hook-ov, opisanih 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.

Osnovno tukaj je — „Action = [Masa]“. Podobno kot v prejšnjem razdelku, obstaja omejena zbirka vgrajenih dejanj. In načeloma neomejena možnost usklajevanja (napiši „Action.XXX“ in ne pozabi napisati dodatnega razdelka zanj).
Vključno z ponovnim vnosom besede v popravljenem razporedu, se delijo na dva dela: „spremeni razporeditev, kot je tam navedeno“ in „ponovno vnesi“ („RetypeWord“).

Preostali parametri se zapišejo v „slovar“ („map“ v golangu) za to dejanje, njihov seznam je odvisen od tistega, kar je zapisano v „Action“.

Več različnih dejanj je mogoče opisati v enem kupu (razdelki). Lahko pa jih razdelimo. Kot sem že pokazal.

Takoj postavljam dejanje „Exec“ — izvesti zunanji skript. Z možnostjo, da mu v stdin potisnem zapisano vsebino.

  • „Wait = 1“ — počakajte na dokončanje zagnanega procesa.
  • Verjetno bo „za malo več“ dobro imeti dodatne informacije, kot je ime razreda okna, iz katerega je bilo zajeto.
    „Želite povezati svoj obdelovalnik? Poglejte sem.“

Uf (izdihnil). Zdi se, da nisem pozabil nič.

Ops! Aha, nisem pozabil…
Kje pa je konfiguracija za zagon? V hard-kodu? Približno takole:

[ScanDevices]
 # Mora biti prisoten ob zagonu. Samoobnovitev, če je mlajši od 30s
 Test = "dev/input/event0"
 Respawn = 30
 # Iskalna maska
 Search = "dev/input/event*"
 # Na mojem thinkPadu obstajajo tovrstne pseudo-tipkovnice z množico nepotrebnih dogodkov
 Bypass = "(?i)Video|Camera" # "(?i)" se očitno razlikuje od "klasičnega" pcre.

Kje pa sem pozabil/zmotil (brez tega — nikakor), zelo upam, da nepozorni bralci ne bodo imeli težav s tem.

Srečno!

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster