A «хswitcher» layout switcher for Linux: step two

Mivel előző bejegyzés (a hswitcher a „proof of concept” fázisban) elég sok konstruktív visszajelzést kapott (ami kedves), továbbra is azzal töltöttem a szabad időmet, hogy fejlesszem a projektet. Most szeretném eltölteni egy kicsit a tiédből… A második lépés nem lesz teljesen megszokott: a konfigurációs tervezés javaslata/megbeszélése.

A «хswitcher» layout switcher for Linux: step two

Valahogy úgy tűnik, hogy a normális programozóknak rendkívül unalmas beállítani ezeket a csavarokat.

Hogy ne legyek szócséplő, itt egy példa arról, amivel foglalkozom.
Alapvetően jól megtervezett (és nem rosszul megvalósított) Apache Kafka & ZooKeeper.
— Konfiguráció? De ez unalmas! Tönkretett xml (mert „dobozból ki”).
— Ó, és te még ACL-t is akarsz? De ez annyira fárasztó! Csak tessék… Valahogy így.

Az én munkámban éppen ellenkezőleg. Helyesen (sajnos, az első alkalommal szinte sosem sikerül) a felépített modell lehetővé teszi, hogy könnyen és kényelmesen (nos, majdnem) összeállítsuk a sémát.

Nemrég olvastam egy cikket a Habr-on, amely a data scientist-ek nem könnyű munkájáról szólt...
Kiderült, hogy ez a pillanat náluk teljes mértékben megvalósul. Az én gyakorlatomban, ahogy mondani szokták, „light verzió”. Többkötetes modellek, tapasztalt programozók OOП-vel stb. — ez mind később fog megjelenni, amikor/ha felrepül. De a tervezőnek itt és most kell valahonnan kezdenie.

Térjünk a lényegre. A szintaktikai alapként a TOML-t választottam ennek a jónak a segítségével.

Mert ez (TOML) egyrészt ember által szerkeszthető. Másrészt 1:1 arányban átkonvertálható bármelyik elterjedtebb szintaxisra: XML, JSON, YAML.
Továbbá, az általam használt megvalósítás a „github.com/BurntSushi/toml” -tól, bár nem a legmenőbb (még mindig 1.4-es szintaxis), de szintaktikailag kompatibilis a beépített JSON-nal.

Tehát, ha szükség van rá, egyszerűen mondhatod: „menj a fenébe a te TOML-oddal, azt akarom, hogy XXX” és „javítsd” meg a kódot egyetlen sorban.

Így tehát, ha szükséges, a hswitcher konfigurálásához írhatsz valamilyen ablakot (nem biztos, hogy én) a problémák a „te kibaszott konfigurációd” kapcsán — nem várhatóak.

Mindenki más számára a „kulcs = érték” alapú szintaxis (és szó szerint egy pár opció bonyolultabb, mint pl. = [ezt-azt, tömb]) úgy vélem
intuitívan kényelmes.
Ami érdekes, hogy nekem „megégett” kb. ugyanebben az időszakban (kb. 2013 körül). Csak, ellentétben velem, a TOML szerzője megfelelő lendülettel érkezett.

Ezért most könnyebb nekem az ő megvalósítását a magam igényeihez igazítani, mint fordítva.

Alapvetően TOML-t használunk (nagyon hasonlít a régi Windowsos INI-re). És beállítunk egy konfigurációt, amelyben leírjuk, hogyan kapcsoljunk egy sor hookot a legutóbbi billentyűzetkódok alapján. Az alábbiakban a pillanatnyilag elkészült részletek találhatók, valamint magyarázatok, hogy miért így döntöttem.

0. Alapvető absztrakciók

  • A billentyűzetkódok jelölése. Ezzel mindenképp valamit kezdeni kell, mert a puszta numerikus kódok teljesen emberi szemmel olvashatatlanok. loloswitcher).
    Kihúztam az „ecodes.go”-t a „golang-evdev”-ből (a forráshoz nem volt kedvem belesni, de a szerzőnél ez jól van feltüntetve). Kicsit (egyelőre) javítottam a teljesen borzalmas részeket. Például: „LEFTBRACE” → „L_BRACE”.
  • Továbbá bevezettem a „billentyűk állapotának” fogalmát. Mivel a használt reguláris grammatikának nem kedveznek a hosszú passzusok. (Cserébe minimális terhelés mellett lehetővé teszi a tesztelést. Ha csak a „közvetlen” felvételt használjuk.)
  • Lesz egy beépített „deduplikátor” a lenyomott billentyűkhez. Így a „ismétlés” állapot = 2 jegyzetelve lesz. ; ideális esetben a felhasználónak két vagy több titkos kérdést kellene választania alkalommal.

1. Sablonok szakasza

[Templates] # "@name@" az egyszerűsített kifejezésekhez
 # A szavak ezekből a karakterekből (regex) állhatnak
 "WORD" = "([0-9A-Z`;']|[LR]_BRACE|COMMA|DOT|SLASH|KP[0-9])"

Miből áll egy emberi nyelv szava fonetikai írással (míg a grafémák aka „hieroglifák”)? Какая-то ужасная «простыня». Поэтому сразу закладываю понятие «шаблона».

2. Mit tegyünk, amikor valamit megnyomtak (megérkezett egy új billentyűzetkód)

[ActionKeys]
 # Gyűjtsd össze a billentyűt és végezz el tesztet a parancssorozatra
 # !!! A megismétlődő kódokat (code=2) legalább egyszer össze kell gyűjteni minden billentyűnél!
 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"]

 # Dobd el az összes összegyűjtött billentyűt, beleértve ezt is. Ez az alapértelmezett művelet.
 Drop = ["ESC", "-", "TAB", "ENTER", "KPENTER", "LINEFEED..POWER"]
 # Tárolj extra térképet ezekhez a billentyűkhöz, amikor bármelyik „le” állapotban van.
 # Az állapotot az akcióban az "OFF:"|"ON:" feltételek alapján ellenőrizzük.
 # (Ezenkívül ezeknek a billentyűknek az állapota fennmarad a pufferdobás között.)
 # ??? Hogyan kezeljük a CAPS-t és a „LOCK” billentyűket ???
 StateKeys = ["L_CTRL", "L_SHIFT", "L_ALT", "L_META", "CAPS", "N_LOCK", "S_LOCK",
              "R_CTRL", "R_SHIFT", "R_ALT", "R_META"]

 # Csak tesztelésre, de nem gyűjtsd össze.
 # Például, én az F12-t használom a BREAK helyett buta laptopokon, amelyek szörnyű billentyűzetekkel rendelkeznek (új ThinkPad-ek).
 Test = ["F1..F10", "ZENKAKUHANKAKU", "102ND", "F11", "F12",
          "RO..KPJPCOMMA", "SYSRQ", "SCALE", "HANGEUL..YEN",
          "STOP..SCROLLDOWN", "NEW..MAX"]

Összesen 768 kód van előirányozva. (De „minden esetre” beraktam a kódba a xswitcher meglepetések elkapását.)
Belenyúltam a tömb kitöltésébe, hivatkozásokkal a „mit kell tenni” funkciókra. A golangban ez (meglepő módon) kényelmesnek és nyilvánvalónak bizonyult.

  • A "Drop" helyett most minimumra fogom csökkenteni. A rugalmasabb feldolgozás javára (lásd lentebb).

3. Ablakosztályok táblázata

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

A táblázat sorai dupla négyzetes zárójelben vannak ezzel a névvel. Elsőre nem sikerült könnyen. A jelenleg aktív ablak függvényében lehet választani lehetőségeket:

  • Saját „gyorsbillentyű” készlet „Actions = ...”. Ha nincs/üres — nem teszünk semmit.
  • A „MouseClickDrops” kapcsoló — mit tegyünk, ha kattintást észlelünk. Mivel az xswitcher aktiválási pontján nincsenek részletek arról, „hova kattintanak”, alapértelmezés szerint ürítjük a puffert. De például a terminálokban ezt meg sem kell tennünk. (általában).

4. Egy (vagy több) nyomtatási sorozat indítja el ezt vagy azt az hookot.

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

A hookokat két típusra osztottam. Beépített, „beszélő” nevekkel (NewWord, NewSentence, Compose) és programozható.

A programozható névvel kezdődnek „Action.”. Mivel a TOML v1.4, a ponttal rendelkező neveknek idézőjelben kell lenniük.

Mindezek alá mindenkihez egy szakasz tartozik ezzel a névvel. ugyanazzal a névvel.

Hogy ne robbantsuk fel az emberek agyát „meztelen” reguláris kifejezésekkel (tapasztalat alapján, az megírni-tíz közül egy lehet professzionális), azonnal bevezetem a kiegészítő szintaxist.

  • „OFF:” (vagy „ON:”) a regexp (reguláris kifejezés) előtt megköveteli, hogy az ezt követő gombokat fel kellene szabadítani (vagy megnyomni).
    Továbbiakban egy „tisztességtelen” reguláris kifejezést fogok készíteni. Azáltal, hogy a pipe-ok "|" közötti darabok külön-külön ellenőrizve vannak, csökkenteni kívánom az ilyen bejegyzések számát, mint „[LR]_SHIFT” (ahol ez nyilvánvalóan nem szükséges).
  • „SEQ:” Ha a korábbi feltétel teljesül (vagy hiányzik), akkor azután a „normális” reguláris kifejezés alapján ellenőrizni fogjuk. Részletekért azonnal küldök a „regexp” könyvtárba. Mert én magam még nem vettem a fáradságot, hogy kiderítsem a kompatibilitás fokát a kedvenc pcre-immel („perl compatible”).
  • A kifejezés így írható le: „BUTTON_1: CODE1, BUTTON_2: CODE2” és így tovább, a beolvasott kódok sorrendjében.
  • Ellenőrzés mindig a sorozat végéhez „nyomódik”, ezért "$" a végére nem szükséges.
  • Minden ellenőrzés egy sorban egymás után történik és „ÉS”-sel egyesülnek. De mivel az érték tömbben van megadva, a vessző után alternatív ellenőrzést is lehet írni. Ha erre valamilyen okból szükséges.
  • Value „SeqLength = 8” korlátozza a pufferméretet, amelyhez képest minden ellenőrzés végrehajtásra kerül. Mivel eddig még nem találkoztam végtelen erőforrásokkal az életemben.

5. Az előző szakaszban leírt hook-ok beállítása

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

Itt a lényeg — „Action = [Tömb]”. Hasonlóan az előző szakaszhoz, egy korlátozott számú beépített művelet áll rendelkezésre. És alapvetően korlátlan lehetőség a kapcsolódásra (írd be az „Action.XXX”-t, és ne habozz további szakaszt írni hozzá).
Ideértve, hogy a javított billentyűzetkiosztás szétválik két részre: „változtasd a kiosztást a megadott módon” és „újból beírás” („RetypeWord”).

A többi paramétert a „szótárba” írjuk („map” a golang-ban) ehhez az akcióhoz, a lista a „Action”-ban írtaktól függ.

Több különböző akciót le lehet írni egy halomban (szakaszok). De el is lehet oszlatni. Ahogy már korábban mutattam.

Azonnal beállítom az „Exec” akciót — egy külső szkript végrehajtása. Az opció, hogy belepakoljam a stdin-be a megírt puffert.

  • „Wait = 1” — várakozás az elindított folyamat befejezésére.
  • Valószínűleg „további” információkat szeretnél beállítani a környezetbe, például az ablak osztályának nevét, ahonnan el lett csípve.
    „Szeretnéd csatlakoztatni a saját kezelőd? Itt van a helye.”

Uff (kileheltem). Úgy tűnik, semmit nem felejtettem el.

Hopp! Aha, nem felejtettem…
De hol van a konfigurációs indítás? A hard-kódban? Körülbelül így:

[ScanDevices]
 # Indításkor léteznie kell. Automatikusan újraindul, ha fiatalabb mint 30s
 Test = "/dev/input/event0"
 Respawn = 30
 # Keresési maszk
 Search = "/dev/input/event*"
 # A thinkPadjeimben ilyen álkulcstartók vannak, amelyek rengeteg fölösleges eseményt okoznak
 Bypass = "(?i)Video|Camera" # "(?i)" nyilvánvalóan különbözik a "hagyományos" pcre-től.

De hol hagytam ki/hibáztam (e nélkül — sehogy), nagyon remélem, hogy a figyelmes olvasók nem fognak lustálkodni és odafigyelnek.

Sok szerencsét!

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster