Korektor rozloženia Xswitcher pre Linux: druhý krok

Ako predchádzajúcej publikácii (xswitcher v štádiu „proof of concept“) dostal pomerne veľa konštruktívnej spätnej väzby (čo je pekné), svoj voľný čas som naďalej venoval rozvoju projektu. Teraz chcem minúť trochu z tvojho... Druhý krok nebude celkom obvyklý: návrh/diskusia návrhu konfigurácie.

Korektor rozloženia Xswitcher pre Linux: druhý krok

Akosi sa ukazuje, že normálnym programátorom pripadá neuveriteľne nudné nastavovať všetky tieto ovládacie prvky.

Aby som nebol neopodstatnený, vo vnútri je príklad toho, čo riešim.
Celkovo vynikajúco koncipovaný (a dobre implementovaný) Apache Kafka & ZooKeeper.
- Konfigurácia? Ale je to nuda! Hlúpy xml (pretože je „vybalený z krabice“).
- Oh, chceš tiež ACL? Ale je to taká nuda! Tap-blooper... Niečo také.

Ale v mojej práci je to presne naopak. Správny (žiaľ, takmer nikdy prvýkrát) zostrojený model umožňuje pokračovať ďalej ľahko a prirodzene (takmer) zostaviť schému.

Nedávno som narazil na článok na Habré o tvrdej práci dátových vedcov...
Ukazuje sa, že tento moment je pre nich plne realizovaný. A v mojej praxi, ako sa hovorí, „odľahčená verzia“. Viacobjemové modely, ostrieľaní programátori s pripraveným OOP atď. — toto všetko sa objaví neskôr, keď/ak vzlietne. Dizajnér však musí začať niekde tu a teraz.

Choďte k veci. TOML som bral ako syntaktický základ od tohto občana.

Pretože on (TOML) na jednej strane upraviteľné človekom. Na druhej strane sa prekladá 1:1 do ktorejkoľvek bežnejšej syntaxe: XML, JSON, YAML.
Navyše implementácia, ktorú som použil z „github.com/BurntSushi/toml“, aj keď nie je najmódnejšia (stále 1.4 syntax), je syntakticky kompatibilná s rovnakým („zabudovaným“) JSON.

To znamená, že ak chcete, môžete jednoducho povedať „choď cez les s tým tvojím TOML, chcem XXX“ a „opraviť“ kód iba jedným riadkom.

Ak teda chcete napísať nejaké okná na konfiguráciu xswitcher (Nie som si istý) „S touto tvojou prekliatou konfiguráciou sa neočakávajú žiadne problémy“.

Pre všetky ostatné je syntax založená na „kľúč = hodnota“ (a doslova pár komplikovanejších možností, napríklad = [some, that, array]) hádam
intuitívne pohodlné.
Čo je zaujímavé, je to "spálený" približne v rovnakom čase (okolo roku 2013). Len na rozdiel odo mňa do toho autor TOMLu išiel v riadnej mierke.

Preto je teraz pre mňa jednoduchšie prispôsobiť si jeho implementáciu podľa seba a nie naopak.

Vo všeobecnosti berieme TOML (veľmi podobný starému Windows INI). A máme konfiguráciu, v ktorej popisujeme, ako pripojiť sériu háčikov v závislosti od sady najnovších skenovacích kódov z klávesnice. Nižšie, kúsok po kúsku, je to, čo sa doteraz stalo. A vysvetlenie, prečo som sa takto rozhodol.

0. Základné abstrakcie

  • Skenujte kódové označenia. Rozhodne s tým treba niečo urobiť, pretože jednoducho digitálne kódy nie sú človekom vôbec čitateľné (to som len ja loloswitcher).
    Vytrepal som „ecodes.go“ z „golang-evdev“ (bol som lenivý pozrieť sa na pôvodný zdroj, hoci to autor označil dosť kultúrne). Trochu som (zatiaľ) opravil niečo, čo bolo dosť strašné. Ako „LEFT BRACE“ → „L_BRACE“.
  • Okrem toho predstavil koncept „štátnych kľúčov“. Keďže používaná pravidelná gramatika neumožňuje dlhé pasáže. (Umožňuje však kontrolu s minimálnou réžiou. Ak používate iba „priame“ nahrávanie.)
  • Bude tam zabudovaný „deduplikátor“ toho, čo je stlačené. Zapíše sa teda stav "opakovať"=2 jeden raz.

1. Sekcia šablón

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

Z čoho pozostáva slovo v ľudskom jazyku s fonetickým zápisom? (či už ide o grafémy známe ako „hieroglyfy“)? Nejaký strašný „list“. Preto okamžite uvádzam pojem „šablóna“.

2. Čo robiť, keď sa na niečo klikne (prišiel ďalší skenovací kód)

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

Spolu je 768 kódov. (Ale „pre každý prípad“ som do kódu xswitcher vložil zachytávacie „prekvapenia“).
Vnútri som opísal vyplnenie poľa odkazmi na funkcie „čo robiť“. V golangu je to tak (zrazu) Ukázalo sa, že je to pohodlné a zrejmé.

  • Na tomto mieste plánujem znížiť „Drop“ na minimum. V prospech flexibilnejšieho spracovania (ukážem nižšie).

3. Tabuľka s triedami okien

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

Riadky tabuľky sú v dvojitých hranatých zátvorkách s jej názvom. Hneď od začiatku to nemohlo byť jednoduchšie. V závislosti od aktuálne aktívneho okna môžete vybrať nasledujúce možnosti:

  • Vaša vlastná sada „klávesových skratiek“ „Akcie = ...“. Ak nie/prázdne, nerobte nič.
  • Prepínač „MouseClickDrops“ – čo robiť, keď sa zistí kliknutie myšou. Keďže v bode, kde je xswitcher zapnutý, nie sú žiadne podrobnosti o tom, „kde kliknú“, predvolene resetujeme vyrovnávaciu pamäť. Ale v termináloch (napríklad) to nemusíte robiť (zvyčajne).

4. Jedna (alebo niekoľko) sekvencií kliknutí spustí jeden alebo druhý háčik

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

Háčiky sú rozdelené do dvoch typov. Vstavané, s „hovoriacimi“ názvami (NewWord, NewSentence, Compose) a programovateľné.

Programovateľné názvy začínajú „Akcia“. Pretože TOML v1.4, názvy s bodkami musia byť v úvodzovkách.

Každá časť by mala byť opísaná nižšie s rovnakým názvom.

Aby sa ľuďom nefúkalo do mysle „nahými“ štamgastmi (zo skúseností ich zapísaťmožno jeden z desiatich profesionálov), okamžite implementujem ďalšiu syntax.

  • "OFF:" (alebo "ON:") pred regulárnym výrazom (regulárny výraz) vyžadujú uvoľnenie (alebo stlačenie) nasledujúcich tlačidiel.
    Ďalej urobím „nespravodlivý“ regulárny výraz. So samostatnou kontrolou kusov medzi rúrkami "|". Aby sa znížil počet záznamov ako „[LR]_SHIFT“ (kde to zjavne nie je potrebné).
  • "SEQ:" Ak je predchádzajúca podmienka splnená (alebo chýba), vykonáme kontrolu oproti „normálnemu“ regulárnemu výrazu. Pre podrobnosti okamžite pošlem do ^W knižnicu „regexp“. Pretože som sa stále neobťažoval zistiť stupeň kompatibility s mojím obľúbeným pcre („perl compatible“).
  • Výraz je napísaný vo forme "BUTTON_1: CODE1, BUTTON_2: CODE2" atď., v poradí, v akom sa prijímajú skenovacie kódy.
  • Kontrola je vždy „pritiahnutá“ na koniec sekvencie, takže nie je potrebné pridávať „$“ na koniec.
  • Všetky kontroly v jednom riadku sa vykonávajú jedna po druhej a sú spojené „ja“. Ale keďže je hodnota opísaná ako pole, môžete za čiarku napísať alternatívnu kontrolu. Ak je to z nejakého dôvodu potrebné.
  • Hodnota "SeqLength = 8" obmedzuje veľkosť vyrovnávacej pamäte, proti ktorej sa vykonávajú všetky kontroly. Pretože Nikdy som sa (až doteraz) nestretol s nekonečnými zdrojmi.

5. Nastavenie hákov popísaných v predchádzajúcej časti

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

Hlavná vec je tu "Akcia = [Pole]". Podobne ako v predchádzajúcej časti je tu obmedzený súbor vstavaných akcií. A možnosť dokovania nie je v princípe obmedzená (napíšte „Akcia.XXX“ a nebuďte príliš leniví napísať pre to ďalšiu sekciu).
Najmä prepísanie slova v opravenom rozložení je rozdelené na dve časti: „zmeniť rozloženie, ako je tam uvedené“ и „pretypovať“ („Prepísať slovo“).

Zostávajúce parametre sa zapíšu do „slovníka“ ("mapa" v golangu) pre danú akciu ich zoznam závisí od toho, čo je napísané v „Akcia“.

Na jednej hromade možno opísať niekoľko rôznych akcií (sekcie). Alebo ho môžete rozobrať. Ako som ukázal vyššie.

Okamžite som nastavil akciu „Exec“ na spustenie externého skriptu. S možnosťou vložiť nahratú vyrovnávaciu pamäť do stdin.

  • „Čakať = 1“ – počkajte na dokončenie spusteného procesu.
  • Pravdepodobne „do kopy“ budete chcieť dať do prostredia ďalších ľudí. informácie, ako je názov triedy okna, z ktorej bol zachytený.
    „Chcete pripojiť svojho psovoda? Toto je miesto, kam musíte ísť."

Fuj (vydýchol). Zdá sa, že som na nič nezabudol.

Ojoj! Ano, nezabudol som...
Kde je konfigurácia spustenia? V tvrdom kóde? Ako 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.

Kde som zabudol/spravil chybu? (bez toho v žiadnom prípade), naozaj dúfam, že pozorní čitatelia nebudú leniví strkať nos.

Good luck!

Zdroj: hab.com

Pridať komentár