Xswitcher korektor izgleda za linux: drugi korak

Pošto prethodna publikacija (xswitcher u fazi “dokaz koncepta”) dobio je dosta konstruktivnih povratnih informacija (što je lijepo), nastavio sam da provodim svoje slobodno vrijeme razvijajući projekat. Sada želim da potrošim malo vašeg... Drugi korak neće biti sasvim uobičajen: prijedlog/diskusija o dizajnu konfiguracije.

Xswitcher korektor izgleda za linux: drugi korak

Nekako se ispostavilo da je normalnim programerima nevjerovatno dosadno postavljati sve ove kontrole.

Da ne budem neosnovan, unutra je primjer onoga čime se bavim.
Sve u svemu, odlično zamišljeni (i dobro implementirani) Apache Kafka & ZooKeeper.
- Konfiguracija? Ali dosadno je! Glup xml (jer je "van kutije").
- Oh, da li i ti želiš ACL? Ali to je tako dosadno! Tap-blooper... Nešto tako.

Ali u mom radu je upravo suprotno. U redu (avaj, skoro nikad prvi put) konstruisani model vam omogućava da nastavite dalje lako i prirodno (skoro) sastaviti dijagram.

Nedavno sam naišao na članak na Habréu o teškom radu naučnika podataka...
Ispostavilo se da im je ovaj trenutak u potpunosti ostvaren. I u mojoj praksi, kako kažu, "light verzija". Višetomni modeli, iskusni programeri sa spremnim OOP-om, itd. — sve će se to pojaviti kasnije kada/ako poleti. Ali dizajner mora početi negdje ovdje i sada.

Pređi na stvar. Uzeo sam TOML kao sintaksičku osnovu od ovog građanina.

Jer on (TOML) s jedne strane, uređivane od strane ljudi. S druge strane, prevodi se 1:1 u bilo koju od uobičajenih sintaksa: XML, JSON, YAML.
Štaviše, implementacija koju sam koristio sa „github.com/BurntSushi/toml“, iako nije najmodernija (i dalje sintaksa 1.4), sintaktički je kompatibilna sa istim („ugrađenim“) JSON-om.

Odnosno, ako želite, možete jednostavno reći “idi kroz šumu sa tim svojim TOML-om, želim XXX” i “zakrpiti” kod sa samo jednom linijom.

Dakle, ako želite da napišete neke prozore da konfigurišete xswitcher (Nisam siguran) Ne očekuju se problemi "sa ovom tvojom prokletom konfiguracijom."

Za sve ostale, sintaksa je zasnovana na “ključ = vrijednost” (i doslovno par komplikovanijih opcija, kao što je = [neki, onaj, niz]) pretpostavljam
intuitivno zgodan.
Ono što je zanimljivo je to "spržen" otprilike u isto vrijeme (oko 2013.). Samo, za razliku od mene, autor TOML-a je ušao u odgovarajućem obimu.

Stoga mi je sada lakše prilagoditi njegovu implementaciju kako bi mi odgovaralo, a ne obrnuto.

Općenito, uzimamo TOML (veoma sličan starom Windows INI). I imamo konfiguraciju u kojoj opisujemo kako pričvrstiti niz kukica ovisno o skupu najnovijih kodova za skeniranje s tipkovnice. U nastavku, dio po dio, je ono što se do sada dogodilo. I objašnjenje zašto sam se odlučio na ovaj način.

0. Osnovne apstrakcije

  • Oznake kodova skeniranja. Nešto definitivno treba učiniti u vezi s tim, jer jednostavno digitalni kodovi apsolutno nisu čitljivi ljudima (to sam samo ja loloswitcher).
    Izbacio sam “ecodes.go” iz “golang-evdev” (bio sam previše lijen da pogledam izvorni izvor, iako je autor to naznačio prilično kulturno). Ispravio sam malo (za sada) nešto što je bilo prilično strašno. Kao “LEFTBRACE” → “L_BRACE”.
  • Osim toga, uveo je koncept „ključeva stanja“. Pošto uobičajena gramatika ne dozvoljava dugačke pasuse. (Ali omogućava vam da provjerite uz minimalne troškove. Ako koristite samo „direktno“ snimanje.)
  • Biće ugrađeni “deduplikator” onoga što se pritisne. Tako će biti zapisano stanje "repeat"=2 один puta.

1. Odjeljak Predlošci

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

Od čega se sastoji riječ na ljudskom jeziku sa fonetskim zapisom? (ili je stvar grafema zvani "hijeroglifi")? Neka vrsta strašnog "čaršava". Stoga, odmah uvodim pojam “template”.

2. Šta učiniti kada se nešto klikne (stigao je drugi kod za skeniranje)

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

Ukupno ima 768 kodova. (Ali “za svaki slučaj” ubacio sam hvatanje “iznenađenja” u xswitcher kod).
Unutra sam opisao popunjavanje niza vezama do funkcija „šta da se radi“. Na golangu je ovo (iznenada) Ispostavilo se da je zgodno i očigledno.

  • Planiram da na ovom mjestu smanjim “Drop” na minimum. U korist fleksibilnije obrade (prikazat ću to 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 su u duplim uglatim zagradama sa njenim imenom. Nije moglo biti lakše odmah. Ovisno o trenutno aktivnom prozoru, možete odabrati sljedeće opcije:

  • Vaš vlastiti set “vrućih tipki” “Akcije =…”. Ako nije/prazno, ne radite ništa.
  • Prebacite “MouseClickDrops” - šta učiniti kada se otkrije klik mišem. Budući da na mjestu gdje je uključen xswitcher nema detalja o tome „gdje kliknu“, mi resetujemo bafer po defaultu. Ali u terminalima (na primjer) ne morate to raditi (obično).

4. Jedan (ili više) nizova klikova pokreće jednu ili drugu udicu

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

Kuke se dijele na dvije vrste. Ugrađeni, sa "govornim" imenima (NewWord, NewSentence, Compose) i programibilni.

Programabilni nazivi počinju sa "Akcija". Jer TOML v1.4, imena sa tačkama moraju biti u navodnicima.

Svaki dio bi trebao biti opisan u nastavku sa istim imenom.

Kako ne bi razbijali ljude "golim" stalnim gostima (iz iskustva, njihovim pisatimožda jedan od deset profesionalci), odmah implementiram dodatnu sintaksu.

  • "ISKLJUČENO:" (ili "UKLJUČENO:") prije regexpa (regularnog izraza) zahtijevaju da se otpuste (ili pritisnu) sljedeća dugmad.
    Zatim ću napraviti "nepošten" regularni izraz. Uz odvojenu provjeru komada između cijevi "|". Kako bi se smanjio broj zapisa poput "[LR]_SHIFT" (gdje to očito nije potrebno).
  • "SEQ:" Ako je prethodni uslov ispunjen (ili odsutan), onda provjeravamo u odnosu na “normalan” regularni izraz. Za detalje, odmah šaljem ^W biblioteku “regexp”. Zato što se još uvijek nisam potrudio da saznam stepen kompatibilnosti sa mojim omiljenim pcre („perl compatible”).
  • Izraz je napisan u obliku "BUTTON_1: CODE1, BUTTON_2: CODE2" itd., redoslijedom kojim se primaju kodovi za skeniranje.
  • Provjera je uvijek "privučena" do kraja niza, tako da nema potrebe za dodavanjem “$” u rep.
  • Sve provjere u jednom redu izvode se jedna za drugom i kombinovani su sa "I". Ali pošto je vrijednost opisana kao niz, možete napisati alternativnu provjeru iza zareza. Ako je ovo potrebno iz nekog razloga.
  • vrijednost "SeqLength = 8" ograničava veličinu bafera prema kojem se izvode sve provjere. Jer Nikada (do sada) se u životu nisam susreo sa beskrajnim resursima.

5. Postavljanje kukica opisanih u prethodnom dijelu

# 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 je ovde "Akcija = [Niz]". Slično kao u prethodnom odeljku, postoji ograničen skup ugrađenih radnji. A mogućnost pristajanja u principu nije ograničena (napišite “Action.XXX” i ne budite lijeni da napišete još jedan odjeljak za to).
Konkretno, ponovno upisivanje riječi u ispravljenom rasporedu podijeljeno je na dva dijela: “promijenite izgled kako je tamo navedeno” и "ponovno ukucaj" ("ponovo upiši riječ").

Preostali parametri se upisuju u "rječnik" ("mapa" na golangu) za datu akciju, njihova lista zavisi od toga šta je napisano u „Akcija“.

Nekoliko različitih akcija može se opisati u jednoj hrpi (odjeljci). Ili ga možete rastaviti. Kao što sam pokazao gore.

Odmah sam podesio akciju “Exec” da izvrši eksternu skriptu. Sa opcijom guranja snimljenog bafera u stdin.

  • “Wait = 1” — sačekajte da se proces koji radi završi.
  • Vjerovatno ćete “na gomilu” htjeti staviti dodatne ljude u okruženje. informacije kao što je ime klase prozora iz koje je presretnuta.
    „Želite li da povežete svog rukovaoca? Ovdje morate ići.”

Fuj (izdahnuo). Izgleda da nisam ništa zaboravio.

Ups! Da, nisam zaboravio...
Gdje je konfiguracija za pokretanje? U tvrdom kodu? Kao 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.

Gdje sam zaboravio/pogriješio? (nema šanse bez ovoga), zaista se nadam da pažljivi čitaoci neće biti lijeni da guraju nos.

Sretno!

izvor: www.habr.com

Dodajte komentar