Ispravljač izgleda Xswitcher za Linux: drugi korak

Kao prethodna objava (xswitcher u fazi "dokaza koncepta") dobio je dosta konstruktivnih povratnih informacija (što je lijepo), nastavio sam provoditi svoje slobodno vrijeme razvijajući projekt. Sada želim potrošiti malo vašeg... Drugi korak neće biti sasvim uobičajen: prijedlog/rasprava o dizajnu konfiguracije.

Ispravljač izgleda Xswitcher za Linux: drugi korak

Nekako ispada da je normalnim programerima nevjerojatno dosadno postavljati sve te kontrole.

Da ne budem neutemeljen, unutra je primjer čime se bavim.
Sveukupno izvrsno zamišljen (i dobro implementiran) Apache Kafka & ZooKeeper.
- Konfiguracija? Ali dosadno je! Glupi xml (jer je "iz kutije").
- Oh, želite li i ACL? Ali tako je dosadno! Tap-blooper... Tako nešto.

Ali u mom poslu je upravo suprotno. Pravo (jao, skoro nikada prvi put) konstruirani model omogućuje vam da lako i prirodno nastavite dalje (Skoro) sastaviti dijagram.

Nedavno sam naišao na članak na Habréu o teškom radu podatkovnih znanstvenika...
Ispada da je taj trenutak za njih u potpunosti realiziran. A u mojoj praksi, kako kažu, "light verzija". Modeli s više volumena, iskusni programeri sa spremnim OOP-om itd. — ovo će se sve pojaviti kasnije kada/ako poleti. Ali dizajner treba početi negdje ovdje i sada.

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

Jer on (TOML) s jedne strane, uređivati ​​ih ljudi. S druge strane, prevodi se 1:1 u bilo koju od uobičajenih sintaksi: XML, JSON, YAML.
Štoviše, implementacija koju sam koristio s “github.com/BurntSushi/toml”, iako nije najmodernija (još uvijek 1.4 sintaksa), sintaktički je kompatibilna s istim (“ugrađenim”) JSON-om.

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

Stoga, ako želite napisati neke prozore za konfiguraciju xswitchera (Nisam siguran) Ne očekuju se problemi "s ovom vašom prokletom konfiguracijom."

Za sve ostale, sintaksa se temelji na "ključ = vrijednost" (i doslovno nekoliko kompliciranijih opcija, poput = [some, that, array]) pretpostavljam
intuitivno prikladno.
Ono što je zanimljivo je da "spaljeno" otprilike u isto vrijeme (oko 2013.). Samo što je, za razliku od mene, autor TOML-a ušao u pravoj mjeri.

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

Općenito, uzimamo TOML (vrlo 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, ono što se dogodilo do sada. I objašnjenje zašto sam se tako odlučio.

0. Osnovne apstrakcije

  • Oznake skeniranog koda. Nešto svakako treba učiniti u vezi s tim, jer jednostavno digitalni kodovi nisu apsolutno čitljivi za ljude (to sam samo ja loloswitcher).
    Istresao sam “ecodes.go” iz “golang-evdev” (bio sam previše lijen da pogledam izvorni izvor, iako je autor to sasvim kulturno naznačio). Ispravio sam malo (za sada) nešto što je bilo prilično strah. Kao “LIJEVA ZATVICA” → “L_ZATVICA”.
  • Dodatno, uveo je koncept "državnih ključeva". Budući da korištena regularna gramatika ne dopušta duge odlomke. (Ali omogućuje vam provjeru uz minimalne troškove. Ako koristite samo "izravno" snimanje.)
  • Bit će ugrađen "deduplikator" onoga što je pritisnuto. Tako će biti ispisano stanje "repeat"=2 jedan vrijeme.

1. Odjeljak s predlošcima

[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č ljudskog jezika s fonetskim zapisom? (bilo stvar grafema aka "hijeroglifa")? Neka vrsta strašne "plahte". Stoga odmah uvodim koncept "predloška".

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

[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).
Unutar sam opisao punjenje niza poveznicama na funkcije "što učiniti". U golangu je ovo (iznenada) Pokazalo se prikladnim i očiglednim.

  • Planiram svesti "Drop" na minimum na ovom mjestu. U korist fleksibilnije obrade (pokazat ću to u nastavku).

3. Tablica s prozorskim klasama

# 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 su u dvostrukim uglastim zagradama sa svojim nazivom. Nije moglo biti lakše odmah na početku. Ovisno o trenutno aktivnom prozoru, možete odabrati sljedeće opcije:

  • Vaš vlastiti set "vrućih tipki" "Radnje = …". Ako nije/prazno, ne činite ništa.
  • Prebacite “MouseClickDrops” - što učiniti kada se otkrije klik mišem. Budući da u trenutku kada je xswitcher uključen nema detalja o tome "gdje kliknu", resetirali smo međuspremnik prema zadanim postavkama. Ali u terminalima (na primjer) to ne morate učiniti (obično).

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

# 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, s nazivima koji "govore" (Nova Riječ, Nova Rečenica, Sastavi) i programabilni.

Nazivi koji se mogu programirati počinju s "Akcija". Jer TOML v1.4, nazivi s točkama moraju biti u navodnicima.

Svaki odjeljak treba biti opisan u nastavku s istim imenom.

Kako se ljudima ne bi obijali "goli" stalni gosti (iz iskustva, njihovi za pisanjemožda jedan od deset profesionalci), odmah implementiram dodatnu sintaksu.

  • "ISKLJUČENO:" (ili "UKLJ.:") prije nego što regexp (regularni izraz) zahtijeva da se sljedeći gumbi otpuste (ili pritisnu).
    Zatim ću napraviti "nepravedan" regularni izraz. S odvojenom provjerom dijelova između cijevi "|". Kako bi se smanjio broj zapisa poput "[LR]_SHIFT" (gdje to očito nije potrebno).
  • "SEQ:" Ako je prethodni uvjet ispunjen (ili odsutan), tada provjeravamo "normalan" regularni izraz. Za detalje, odmah šaljem ^W biblioteku “regexp”. Zato što se još uvijek nisam potrudio saznati stupanj kompatibilnosti s mojim omiljenim pcre-om ("perl kompatibilan").
  • Izraz je napisan u obliku "BUTTON_1: CODE1, BUTTON_2: CODE2" itd., redoslijedom kojim su skenirani kodovi primljeni.
  • Ček se uvijek "prilijepi" na kraj niza, tako da nema potrebe dodavati “$” na rep.
  • Sve provjere u jednoj liniji izvode se jedna za drugom a kombinirani su s "I". Ali budući da je vrijednost opisana kao niz, možete napisati alternativnu provjeru nakon zareza. Ako je to iz nekog razloga potrebno.
  • Vrijednost "SeqLength = 8" ograničava veličinu međuspremnika prema kojem se izvode sve provjere. Jer Nisam (do sada) nikada u životu naišao na beskrajne resurse.

5. Postavljanje kuka opisanih u prethodnom odjeljku

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

Ovdje je glavna stvar "Akcija = [Niz]". Slično prethodnom odjeljku, postoji ograničen skup ugrađenih radnji. A mogućnost pristajanja u načelu nije ograničena (napišite “Akcija.XXX” i nemojte biti previše lijeni da napišete još jedan odjeljak za to).
Konkretno, ponovno upisivanje riječi u ispravljenom rasporedu podijeljeno je u dva dijela: "promijenite izgled kako je tamo navedeno" и “ponoviti” (“RetypeWord”).

Preostali parametri se zapisuju u "rječnik" ("karta" na golang) za određenu akciju, njihov popis ovisi o tome što je napisano u “Akcija”.

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

Odmah sam postavio radnju "Exec" za izvršavanje vanjske skripte. Uz opciju guranja snimljenog međuspremnika u stdin.

  • “Čekaj = 1” — pričekajte da se pokrenuti proces završi.
  • Vjerojatno ćete "na hrpu" htjeti staviti dodatne ljude u okruženje. informacija kao što je naziv klase prozora iz koje je presretnuta.
    “Želite li povezati svog rukovatelja? Ovo je mjesto gdje trebate ići."

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

Ups! Da, nisam zaboravio...
Gdje je konfiguracija pokretanja? U tvrdom kodu? ovako:

[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), stvarno se nadam da pažljivi čitatelji neće biti previše lijeni zabadati nos.

Sretno!

Izvor: www.habr.com

Dodajte komentar