Korektor rasporeda 'hxswitcher' za linux: drugi korak

Budući da prethodna objava (h-switčer u fazi ‘proof of concept’) dobio je prilično mnogo konstruktivnih povratnih informacija (šteta što), nastavio sam trošiti svoje slobodno vrijeme na razvoj projekta. Sada želim potrošiti malo vašeg… Drugi korak bit će malo neobičan: prijedlog/rasprava o dizajnu konfiguracije.

Korektor rasporeda 'hxswitcher' za linux: drugi korak

Ispada da je normalnim programerima podešavanje svih tih gumbova neverovatno dosadno.

Da ne budem bez argumenata, unutra je primjer onoga s čime se suočavam.
Odlično u cjelini zamišljeni (i dobro provedeni) Apache Kafka & ZooKeeper.
— Konfiguracija? Pa to je dosadno! Tja-tja xml (jer ‘iz kutije’).
— Oh, a vi još želite i ACL? Ali to je tako zamorno! Tap-tap… Tako nekako.

A u mojoj je praksi — potpuno suprotno. Točno (nažalost, gotovo nikad ne uspije u prvom pokušaju) izgrađeni model omogućava daljnje lako i opušteno (pa, gotovo) izraditi shemu.

Nedavno sam naišao na članak na Habré o teškom radu data-scientista...
Ispostavlja se da se taj trenutak kod njih u potpunosti ostvaruje. A u mojoj praksi, kako kažu, ‘light verzija’. Višegodišnji modeli, iskusni programeri s OOP-om i tako dalje — to će se sve pojaviti kada/ako uzleti. A konstruktoru je ovdje i sada potrebno s nečim započeti.

Bliže se temi. Kao sintaktičku osnovu uzeo sam TOML od ovog gospodina.

Zato što je (TOML) s jedne strane čovjeku-urediv, a s druge — prevodi se 1:1 u bilo koju od uobičajenijih sintaksa: XML, JSON, YAML.
Štoviše, implementacija koju sam koristio s ‘github.com/BurntSushi/toml’ iako nije najmodernija (do sada sintaksa 1.4), ipak je sintaktički kompatibilna s istim (‘ugrađenim’) JSON-om.

To jest, ako želite, možete samo reći ‘idi u šumu s tim tvojim TOML-om, želim XXX’ i ‘patchirati’ kod cijelom jednom rečenicom.

Tako da, ako želite napisati neka okna za podešavanje h-switčer (definitivno ne ja) problemi ‘s tim vašim prokletim configom’ — ne predviđaju se.

Za sve ostale sintaksa temeljen na ‘ključ = vrijednost’ (i doslovno par složenijih opcija, tipa = [neki, niz]) smatram
intuitivno prikladnim.
Što je zanimljivo, sam ‘izgorio’ otprilike u isto vrijeme (oko 2013. godine). Samo, za razliku od mene, autor TOML-a prišao je s pravom pompom.

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

Ukratko, uzimamo TOML (vrlo sličan starom Windows INI). I postavljamo konfiguraciju u kojoj opisujemo kako spojiti niz hookova ovisno o skupu posljednjih sken-kodova s tipkovnice. Ispod su dijelovi onoga što je trenutno postignuto. I pojašnjenja zašto sam tako odlučio.

0. Osnovne apstrakcije

  • Oznake sken-kodova. S tim definitivno treba nešto učiniti jer su jednostavni brojčani kodovi potpuno nečitljivi za ljude (to sam u povrtnjaku). loloswitcher).
    Izdvojio sam "ecodes.go" iz "golang-evdev" (nema smisla ići u izvor, mada je autor to lijepo naveo). Malo (za sada) sam popravio što je bilo doista ružno. Tipa "LEFTBRACE" → "L_BRACE".
  • Dodatno sam uveo pojam "tipke sa stanjem". Kako korištena regularna gramatika ne omogućuje duge pasuse. (Zato može provjeravati s minimalnim preklapanjima. Ako se koristi samo "izravni" zapis.)
  • Bit će ugrađeni "dedupplikator" pritisnutih tipki. Tako će stanje "ponavljanje"=2 biti zabilježeno. jedan putu.

1. Odjeljak predložaka

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

Od čega se sastoji riječ ljudskog jezika sa fonetskim zapisom (to su grafeme aka "znakovi")? Какая-то ужасная «простыня». Поэтому сразу закладываю понятие «шаблона».

2. Što učiniti kada se nešto pritisne (stigao je novi sken-kod)

[ActionKeys]
 # Sakupi tipku i testiraj za naredbeni niz
 # !!! Kodovi ponavljanja (code=2) moraju se sakupiti jednom po tipki!
 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"]

 # Ispusti sve prikupljene tipke, uključujući ovu. Ovo je zadana akcija.
 Drop = ["ESC", "-", "TAB", "ENTER", "KPENTER", "LINEFEED..POWER"]
 # Pohrani dodatnu mapu za ove tipke, kada je neka u stanju "dolje".
 # Stanje se provjerava putem uvjeta "OFF:"|"ON:" u akciji.
 # (Također, stanje ovih tipki mora ostati između ispuštanja bafera.)
 # ??? Kako se nositi s CAPS i "LOCK"-tipkama ???
 StateKeys = ["L_CTRL", "L_SHIFT", "L_ALT", "L_META", "CAPS", "N_LOCK", "S_LOCK",
              "R_CTRL", "R_SHIFT", "R_ALT", "R_META"]

 # Samo za testiranje, ali ne sakupljaj.
 # Na primjer, ja koristim F12 umjesto BREAK na glupim prijenosnicima sa lošim tipkovnicama (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 ubacio u kod hswitchera hvatanje "iznenađenja").
Unutra sam razradio popunjavanje niza vezama na funkcije "što učiniti". Na golangu je to (iznenađujuće) bilo praktično i očito.

  • Planiram skratiti "Drop" na minimum. U korist fleksibilnijeg obrade (vidi dolje).

3. Tablica s 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 tablice — u dvostrukim kvadratnim zagradama s njenim imenom. Nije bilo lakše odmah. Ovisno o trenutnom aktivnom prozoru, moguće je odabrati opcije:

  • Moj skup "brzih tipki" "Actions = …". Ako nema/prazno — ne raditi ništa.
  • Preklopnik "MouseClickDrops" — što učiniti kada se otkrije klik miša. Budući da u trenutku uključivanja xswitcher nema nikakvih detalja "gdje se klikne", prema zadanim postavkama brišemo spremnik. No, u terminalima (na primjer) to se može i ne raditi. (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)" ]

Hooks sam podijelio na dva tipa. Ugrađeni, s "pričljivim" imenima (NewWord, NewSentence, Compose) i programabilni.

Imena programabilnih počinju s "Action.". Budući da je TOML v1.4, imena s točkama moraju biti u navodnicima.

Dolje za svaki treba biti opisan odjeljak s istim imenom.

Kako ne bih eksplodirao ljudima mozak s "golim" regularnim izrazima (prema iskustvu, njih napisati-može jedan od deset profesionalaca), odmah uvodim dodatnu sintaksu.

  • "OFF:" (ili "ON:") ispred regexp (regularnog izraza) zahtijevaju da navedeni gumbi budu otpušteni (ili pritisnuti).
    Dalje planiram napraviti "nepošten" regularni izraz. S odvojenim provjerama dijelova između cijevi "|". S ciljem smanjenja broja unosa poput "[LR]_SHIFT" (gdje to očito nije potrebno).
  • "SEQ:" Ako je prethodni uvjet ispunjen (ili ne postoji), dalje provjeravamo u odnosu na "običan" regularni izraz. Za više informacija odmah šaljem na^Wu biblioteku "regexp". Jer sam sam još nije utvrdio stupanj kompatibilnosti sa svojim omiljenim pcre ("perl compatible").
  • Izraz se zapisuje u obliku "BUTTON_1: CODE1, BUTTON_2: CODE2" i sl., u redoslijedu dolaska sken-kodova.
  • Provjera se uvijek "prikida" na kraju sekvencije, stoga "$" na kraj ne treba dodavati.
  • Sve provjere u jednom redu se izvode jedna za drugom i kombiniraju se prema "AND". No, budući da je vrijednost opisana u obliku niza, nakon zareza može se napisati alternativna provjera. Ako je to nekako potrebno.
  • Vrijednost "SeqLength = 8" ograničava veličinu bafer, u odnosu na koji se provode sve provjere. Budući da u životu nisam naišao na beskonačne resurse.

5. Zadatak hookova, opisani u prethodnom odeljku

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

Ono što je osnovno je — «Akcija = [Niz]». Analogno prethodnom delu, postoji ograničen broj ugrađenih akcija. I neograničena mogućnost povezivanja. (napisati «Action.XXX» i ne zaboraviti da dodate još jedan odeljak za to).
U tom slučaju, ponovna prepisivanja reči u ispravnoj postavci deli se na dva dela: «promeni postavku kao što je zadano tamo» i «ponovno otkucaj» («RetypeWord»).

Ostali parametri se beleže u «rečnik» («map» u golang-u) za ovu akciju, njihov spisak zavisi od onoga što je napisano u «Action».

Nekoliko različitih akcija može se opisati u jednoj grupi (odeljci). A može se i podeliti. Kao što sam gore pokazao.

Odmah ulažem akciju «Exec» — izvršavanje spoljnog skripta. Sa opcijom da se gurnu podaci u stdin.

  • «Wait = 1» — čekajte da se pokrenuti proces završi.
  • Verovatno, «za dodatak» želećete da postavite dodatne informacije u okruženju kao što je ime klase prozora iz kojeg je preuzeto.
    «Želite li povezati svoj handler? Ovde idite.»

Uf (izdahnuo). Čini se da ništa nisam zaboravio.

Op! Aha, nisam zaboravio…
A gde je konfiguracija pokretanja? U hard-kodu? Otprilike ovako:

[ScanDevices]
 # Mora postojati pri pokretanju. Automatski se ponovo pokreće ako je mlađi od 30s
 Test = "/dev/input/event0"
 Respawn = 30
 # Maskiranje pretrage
 Search = "/dev/input/event*"
 # U mojim ThinkPad-ovima postoje takve pseudo-tastature sa puno nepotrebnih događaja
 Bypass = "(?i)Video|Camera" # "(?i)" očigledno se razlikuje od "klasične" pcre.

A gde sam zaboravio/napravio grešku (bez ovoga — nikako), veoma se nadam da se pažljivi čitaoci neće ustezati da me upute.

Srećno!

Izvor: habr.com

Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere 🔥 Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere | ProHoster