Xswitcheri paigutuse korrektor Linuxi jaoks: teine ​​samm

Kui eelmine väljaanne (xswitcher “kontseptsiooni tõestamise” etapis) sai üsna palju konstruktiivset tagasisidet (mis on tore), jätkasin oma vaba aja veetmist projekti arendamisega. Nüüd tahan kulutada natuke teie... Teine samm ei ole päris tavaline: konfiguratsiooni kavandamise ettepanek/arutelu.

Xswitcheri paigutuse korrektor Linuxi jaoks: teine ​​samm

Kuidagi tuleb välja, et tavalistel programmeerijatel on kõigi nende juhtnuppude seadistamine uskumatult igav.

Et mitte olla alusetu, siis sees on näide sellest, millega ma tegelen.
Üldiselt suurepäraselt läbimõeldud (ja hästi rakendatud) Apache Kafka & ZooKeeper.
- Konfiguratsioon? Aga see on igav! Dumb xml (kuna see on "karbist väljas").
- Oh, kas sa tahad ka ACL-i? Aga see on nii igav! Tap-blooper... Midagi sellist.

Kuid minu töös on see täpselt vastupidine. Õige (paraku, peaaegu mitte kunagi esimest korda) konstrueeritud mudel võimaldab hõlpsalt ja loomulikult jätkata (Peaaegu) koosta skeem.

Sattusin hiljuti Habré artiklile andmeteadlaste raskest tööst...
Selgub, et see hetk on nende jaoks täielikult realiseeritud. Ja minu praktikas, nagu öeldakse, "kerge versioon". Mitmeköitelised mudelid, kogenud programmeerijad, kellel on OOP valmis jne. — see kõik ilmub hiljem, kui/kui see õhku tõuseb. Kuid disainer peab alustama kuskilt siin ja praegu.

Tulge asja juurde. Võtsin süntaktiliseks aluseks TOML-i sellelt kodanikult.

Sest ta (TOML) ühelt poolt inimese poolt muudetav. Teisest küljest tõlgitakse see vahekorras 1:1 mis tahes tavalisemasse süntaksisse: XML, JSON, YAML.
Veelgi enam, juurutus, mida ma saidilt „github.com/BurntSushi/toml” kasutasin, kuigi mitte kõige moekam (ikka 1.4 süntaks), ühildub süntaktiliselt sama (“sisseehitatud”) JSON-iga.

See tähendab, et kui soovite, võite lihtsalt öelda "mine läbi metsa selle oma TOML-iga, ma tahan XXX" ja "patch" koodi vaid ühe reaga.

Seega, kui soovite kirjutada mõned aknad xswitcheri konfigureerimiseks (Ma pole kindel) Selle neetud konfiguratsiooniga pole probleeme oodata.

Kõigi teiste puhul põhineb süntaks "võti = väärtus" (ja sõna otseses mõttes paar keerukamat valikut, näiteks = [mõned, see, massiiv]) ma arvan
intuitiivselt mugav.
Huvitav on see "põlenud" umbes samal ajal (umbes 2013). Ainult, erinevalt minust, läks TOML-i autor korralikus skaalas sisse.

Seetõttu on mul nüüd lihtsam kohandada selle rakendamist enda jaoks sobivaks ja mitte vastupidi.

Üldiselt võtame TOML-i (väga sarnane vana Windows INI-ga). Ja meil on konfiguratsioon, milles kirjeldame, kuidas kinnitada konksude seeriat sõltuvalt klaviatuuri viimaste skannimiskoodide komplektist. Allpool on tükkhaaval välja toodud, mis on seni juhtunud. Ja selgitus, miks ma nii otsustasin.

0. Põhilised abstraktsioonid

  • Skanni kooditähistused. Sellega tuleb kindlasti midagi ette võtta, sest lihtsalt digitaalsed koodid pole absoluutselt inimloetavad (see on ainult mina loloswitcher).
    Raputasin “golang-evdevist” välja “ecodes.go” (oli laisk, et algallikat vaadata, kuigi autor viitas sellele üsna kultuurselt). Ma parandasin natuke (praegu) midagi, mis oli üsna hirmutav. Nagu „LEFTBRACE” → „L_BRACE”.
  • Lisaks tutvustas ta olekuvõtmete mõistet. Kuna kasutatav tavagrammatika ei võimalda pikki lõike. (Kuid see võimaldab teil kontrollida minimaalse üldkuluga. Kui kasutate ainult otsest salvestamist.)
  • Seal on sisseehitatud "deduplikaator", mida vajutatakse. Seega kirjutatakse olek "korda"=2 üks korda.

1. Mallide jaotis

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

Millest koosneb häälikulise tähistusega inimkeelne sõna? (kas või grafeemide ehk "hieroglüüfide" küsimus)? Mingi kohutav “leht”. Seetõttu tutvustan kohe mõistet "mall".

2. Mida teha, kui millelegi klõpsatakse (saabunud on teine ​​skannimiskood)

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

Kokku on 768 koodi. (Aga "igaks juhuks" sisestasin xswitcheri koodi sisse "üllatuste" püüdmine).
Sees kirjeldasin massiivi täitmist linkidega funktsioonide "mida teha". Golangis on see nii (äkki) See osutus mugavaks ja ilmseks.

  • Kavatsen "Drop" selles kohas vähendada miinimumini. Paindlikuma töötlemise kasuks (näitan seda allpool).

3. Tabel aknaklassidega

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

Tabeli read on selle nimega kahekordsetes nurksulgudes. See ei oleks saanud kohe lihtsam olla. Sõltuvalt hetkel aktiivsest aknast saate valida järgmised valikud.

  • Teie enda kiirklahvide komplekt "Toimingud = ...". Kui pole/tühi, siis ära tee midagi.
  • Lüliti "MouseClickDrops" – mida teha, kui tuvastatakse hiireklõps. Kuna hetkel, kus xswitcher on sisse lülitatud, puuduvad andmed selle kohta, kus nad klõpsavad, lähtestasime puhvri vaikimisi. Kuid terminalides (näiteks) te seda tegema ei pea (tavaliselt).

4. Üks (või mitu) klõpsude jada käivitab ühe või teise konksu

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

Konksud jagunevad kahte tüüpi. Sisseehitatud, "rääkivate" nimedega (NewWord, NewSentence, Compose) ja programmeeritav.

Programmeeritavad nimed algavad sõnaga "Action". Sest TOML v1.4, punktidega nimed peavad olema jutumärkides.

Iga jaotist tuleks allpool kirjeldada sama nimega.

Selleks, et mitte puhuda inimeste päid "alasti" püsiklientidega (kogemuse põhjal, nende kirjutavõib-olla üks kümnest spetsialistid), rakendan kohe täiendava süntaksi.

  • "OFF:" (või "ON:") enne regexp (regulaaravaldis) nõuavad järgmiste nuppude vabastamist (või vajutamist).
    Järgmisena teen "ebaõiglase" regulaaravaldise. Torude vaheliste tükkide eraldi kontrollimisega "|". Et vähendada selliste kirjete arvu nagu "[LR]_SHIFT" (kui see pole ilmselgelt vajalik).
  • "SEQ:" Kui eelmine tingimus on täidetud (või puudub), siis kontrollime "tavalise" regulaaravaldise vastu. Üksikasjade saamiseks saadan kohe ^W-le regexpi teegi. Sest ma pole ikka veel viitsinud oma lemmik-pcre-ga (“perl ühilduv”) ühilduvusastet välja uurida.
  • Väljend kirjutatakse vormis "BUTTON_1: CODE1, BUTTON_2: CODE2" jne skannimiskoodide vastuvõtmise järjekorras.
  • Tšekk on alati jada lõppu kinni jäänud, seega pole vaja sabale “$” lisada.
  • Kõik kontrollid ühel real tehakse üksteise järel ja neid ühendab "mina". Kuid kuna väärtust kirjeldatakse massiivina, võite koma järele kirjutada alternatiivse kontrolli. Kui see on mingil põhjusel vajalik.
  • Väärtus "SeqLength = 8" piirab puhvri suurust, mille alusel kõiki kontrolle tehakse. Sest Ma pole (seni) kunagi oma elus kohanud lõputuid ressursse.

5. Eelmises jaotises kirjeldatud konksude seadistamine

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

Peamine on siin "Action = [Massiiv]". Sarnaselt eelmisele jaotisele on sisseehitatud toimingute hulk piiratud. Ja dokkimisvõimalus pole põhimõtteliselt piiratud (kirjutage "Action.XXX" ja ärge olge liiga laisk, et kirjutada sellele uus jaotis).
Eelkõige on parandatud paigutuses sõna uuesti sisestamine jagatud kaheks osaks: "Muuda paigutust vastavalt seal määratletule" и "tüüpi uuesti" ("RetypeWord").

Ülejäänud parameetrid kirjutatakse sõnaraamatusse ("kaart" golangi keeles) antud toimingu puhul sõltub nende nimekiri sellest, mis on kirjas jaotises “Action”.

Ühes hunnikus saab kirjeldada mitut erinevat tegevust (jaotised). Või võite selle lahti tõmmata. Nagu ma eespool näitasin.

Seadsin kohe välise skripti käivitamiseks toimingu "Exec". Võimalusega suruda salvestatud puhver stdini.

  • “Oota = 1” – oodake, kuni tööprotsess on lõpule viidud.
  • Tõenäoliselt tahate "kuhjani" keskkonda rohkem inimesi panna. teave, näiteks selle aknaklassi nimi, millest see kinni võeti.
    „Kas soovite oma käitleja ühendada? See on koht, kuhu peate minema."

Phew (hingates välja). Tundub, et ma pole midagi unustanud.

Oih! Jah, ma ei unustanud...
Kus on käivituskonfiguratsioon? Kõvas koodis? Nagu see:

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

Kus ma unustasin/vea tegin? (ilma selleta mitte kuidagi), loodan väga, et tähelepanelikud lugejad ei jää liiga laisaks oma nina pista.

Õnn kaasa!

Allikas: www.habr.com

Lisa kommentaar