Keďže (xswitcher vo fáze „proof of concept“) dostal dosť veľa konštruktívnych spätných väzieb (čo je príjemné), pokračoval som v trávení svojho voľného času na rozvoj projektu. Teraz chcem stráviť kúsok vášho… Druhý krok bude trochu neobvyklý: návrh/diskusia dizajnu konfigurácie.

Nejak sa to stáva, že normálnym programátorom je nastavovať všetky tieto skrutky neskutočne nudné.
Aby som nebol bez dôkazov, tu je príklad z toho, s čím mám dočinenia.
Výborne celkovo navrhnuté (a dobre implementované) Apache Kafka & ZooKeeper.
— Konfigurácia? Ale to je nuda! Tiahni xml (pretože „z krabice“).
— Oh, a vy chcete aj ACL? Ale to je tak namáhavé! Tiahni… Tak nejak.
A v mojej práci — presne naopak. Správne (bohužiaľ, na prvýkrát takmer nikdy nie je) vytvorený model umožňuje ďalej jednoducho a bez námahy (nuž, takmer) zhromadiť schému.
Nedávno som narazil na článok na Habré o ťažkej práci data-scientistov...
Ukazuje sa, že tento moment sa u nich realizuje v plnej miere. A v mojej praxi, ako sa hovorí, „verzia light“. Mnohотомné modely, skúsení programátori s OOP na pleciach atď. — to všetko sa objaví neskôr, keď/ak to vzlietne. A konštruktér musí začať niekde tu a teraz.
Bližšie k veci. Ako syntaktický základ som vzal TOML .
Pretože on (TOML) z jednej strany je ľudsko-editovateľný. A z druhej — prekladá sa 1:1 do akéhokoľvek z rozšírenejších syntaktických foriem: XML, JSON, YAML.
Naviac, implementácia, ktorú používam od „github.com/BurntSushi/toml“, hoci nie je najmodernejšia (doteraz syntax 1.4), je syntakticky kompatibilná s rovnakým („vstavaným“) JSON.
Takže, ak by ste chceli, môžete jednoducho povedať „choď za svojím TOML, chcem XXX“ a „opatchovať“ kód celou jednou čiarou.
Takto, ak by ste chceli napísať pre nastavenie xswitcher nejaké okná (to určite nie som ja) problémy „s tým vaším hlúpym configom“ — sa nepredpokladajú.
Pre všetkých ostatných syntaktických základ „kľúč = hodnota“ (a doslova pár opcií zložitejších, typu = [nejakého, toho, poľa]) myslím
intuitívne pohodlné.
Čo je zaujímavé, sám približne v tom istom čase (v roku 2013). Iba, na rozdiel od mňa, autor TOML sa k tomu dostal s patričným rozmachom.
Preto teraz je pre mňa jednoduchšie prispôsobiť si jeho implementáciu, ako naopak.
V podstate zoberieme TOML (veľmi podobný starému Windows INI). A nastavíme konfiguráciu, v ktorej popíšeme, ako pripojiť sériu hookov v závislosti od poslednej sadzby kódov zo klávesnice. Nižšie sú úryvky — to, čo sa mi zatiaľ podarilo dosiahnuť, a vysvetlenia, prečo som sa takto rozhodol.
0. Základné abstrakcie
- Označenia kódov. S týmto je nevyhnutné niečo robiť, pretože jednoduché číselné kódy prakticky nie sú čitateľné (to hovorím vo svojej záhrade) ).
Vytiahol som "ecodes.go" z "golang-evdev" (nechcelo sa mi ísť do pôvodného zdroja, hoci autor ho pekne uvádza). Trochu (zatiaľ) som opravil naozaj zle vyzerajúce. Napríklad "LEFTBRACE" → "L_BRACE". - Ďalšie som zaviedol pojem "kláves s stavom". Pretože použitá regulárna gramatika nie je prispôsobená dlhým odstavcom. (Namiesto toho umožňuje kontrolovať s minimálnymi nákladmi. Ak použijete iba "priamy" zápis.)
- Bude integrovaný "deduplikátor" stlačených kláves. Takto bude stav "opakovať"=2 zapísaný. jednu raz.
1. Sekcia šablón
[Templates] # "@name@" na zjednodušenie výrazov
# Slová môžu byť zložené z týchto znakov (regex)
"WORD" = "([0-9A-Z`;']|[LR]_BRACE|COMMA|DOT|SLASH|KP[0-9])"
Z čoho sa skladá slovo ľudskej reči s fonetickým záznamom (to sa nedá porovnať s grafémami, aka „hieroglyphs“)? Какая-то ужасная «простыня». Поэтому сразу закладываю понятие «шаблона».
2. Čo robiť, keď sa niečo stlačilo (prišiel ďalší sken-kód)
[ActionKeys]
# Zhromaždiť kľúč a vykonať test na sekvenciu príkazov
# !!! Opakujúce kódy (code=2) musia byť zhromaždené raz za každý kľúč!
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"]
# Zrušiť všetky zhromaždené klávesy, vrátane tohto. Toto je predvolená akcia.
Drop = ["ESC", "-", "TAB", "ENTER", "KPENTER", "LINEFEED..POWER"]
# Uložiť extra mapu pre tieto klávesy, keď je niektorý v stave "down".
# Stav sa kontroluje cez podmienky "OFF:"|"ON:" v akcii.
# (Tiež, stav týchto klávesov musí prežiť medzi zrušeniami bufferu.)
# ??? Ako naložiť s CAPS a "LOCK"-klávesmi ???
StateKeys = ["L_CTRL", "L_SHIFT", "L_ALT", "L_META", "CAPS", "N_LOCK", "S_LOCK",
"R_CTRL", "R_SHIFT", "R_ALT", "R_META"]
# Test len, ale nezbierať.
# Napríklad používam F12 namiesto BREAK na hlúpych notebookoch so zlými klávesnicami (nové ThinkPad-y)
Test = ["F1..F10", "ZENKAKUHANKAKU", "102ND", "F11", "F12",
"RO..KPJPCOMMA", "SYSRQ", "SCALE", "HANGEUL..YEN",
"STOP..SCROLLDOWN", "NEW..MAX"]
Celkovo je predpokladaných 768 kódov. (Ale "pre každú prípad" som do kódu vložil záchyt "prekvapení").
Vnútri som rozpracoval vyplnenie poľa odkazmi na funkcie "čo robiť". Na golang, to (prekvapivo) sa ukázalo ako pohodlné a zrejmé.
- Plánujem skrátiť „Drop“ na minimum. V prospech flexibilnejšej správy (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ď to nešlo jednoduchšie. V závislosti od aktuálneho aktívneho okna, môžete vybrať možnosti:
- Vlastná sada „klávesových skratiek“ „Akcie = …“. Ak neexistuje/je prázdna – nič nerobiť.
- Prepínač „MouseClickDrops“ – čo robiť pri zistení kliknutia myšou. Keďže v bode zapnutia xswitcher nie sú žiadne podrobnosti „kam sa klika“, predvolene zresetujeme buffer. Ale v termináloch (napríklad) to možno ani nebudeme robiť. (zvyčajne).
4. Jedna (alebo viac) sekvencií stlačení spúšťa ten či onen hook
# 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)" ]
Hooky som rozdelil na dva typy. Vstavané, s „rozprávaním“ menami (NewWord, NewSentence, Compose) a programovateľné.
Názvy programovateľných začínajú s „Akcia.“. Od TOML v1.4 musia byť mená s bodkami v úvodzovkách.
Nižšie by mal byť opísaný segment pre každý z nich s rovnakým názvom.
Aby som nevybuchol ľuďom mozog „holými“ regulárkami (podľa skúseností, ich napísať-to môže jeden z desať profesionálov), ihneď implementujem ďalšiu syntax.
- „OFF:“ (alebo „ON:“) pred regexp (regulárnym výrazom) vyžaduje, aby uvedené tlačidlá boli uvoľnené (alebo stlačené).
Ďalej plánujem urobiť „nespravodlivý“ regulárny výraz. S oddelenou kontrolou kúskov medzi pípami "|". S cieľom znížiť počet záznamov typu "[LR]_SHIFT" (kde to nie je explicitne potrebné). - „SEQ:“ Ak bolo predchádzajúce podmienky splnené (alebo neexistuje), ďalej kontrolujeme vo vzťahu k „normálnemu“ regulárnemu výrazu. Pre podrobnosti hneď posielam na^Wdo knižnice „regexp“. Pretože som si stále neudělal čas zistiť stupeň kompatibility s mojimi obľúbenými pcre („perl compatible“).
- Výraz sa zapisuje vo forme „TLAČIDLO_1: KÓD1, TLAČIDLO_2: KÓD2“ a pod., podľa poradia prichádzajúcich sken kódov.
- Kontrola sa vždy „prilepí“ na koniec sekvencie, preto nie je potrebné dopisovať "$" na koniec.
- Všetky kontroly v jednom riadku sa vykonávajú jedna za druhou a kombinujú sa na základe „A“. Ale keďže je hodnota opísaná ako pole, môžete po čiarke napísať alternatívnu kontrolu. Ak je to z nejakého dôvodu potrebné.
- Hodnota „SeqLength = 8“ obmedzuje veľkosť buffera, vo vzťahu ku ktorému sa vykonávajú všetky kontroly. Keďže som sa zatiaľ v živote nestretol s nekonečnými zdrojmi.
5. Definovanie hookov uvedených v predchádzajúcej sekcii
# 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é tu je — „Action = [Pole]“. Podobne ako v predchádzajúcej sekcii, existuje obmedzený súbor zabudovaných akcií. A v zásade neobmedzená možnosť prepojenia. (napísať „Action.XXX“ a nezabudnúť rozpracovať pod ním ďalšiu sekciu).
Zahŕňa aj prepisovanie slova v opravenom rozložení, ktoré sa delí na dve časti: „zmeň rozloženie, ako je uvedené tam“ a „prepisovať“ („RetypeWord“).
Ostatné parametre sa zapisujú do „slovníka“ („map“ v golang) pre túto akciu, ich zoznam závisí od toho, čo je napísané v „Action“.
Viac rôznych akcií je možné opísať v jednej skupine (sekcia). A môžete to aj roztrhnúť. Ako som ukázal vyššie.
Okamžite zakladám akciu „Exec“ — spustiť externý skript. S možnosťou poslať mu do stdin zapísaný buffer.
- „Wait = 1“ — počkať na dokončenie spusteného procesu.
- Pravdepodobne sa „na okraj“ budete chcieť poskytnúť ďalšie informácie do prostredia, ako je meno triedy okna, z ktorého bolo zachytené.
„Chcete pripojiť svoj handler? Tu je cesta.“
Uf (vyľahčil som sa). Zdá sa, že som nič nezabudol.
Ups! Aha, nezabudol som...
A kde je konfigurácia spustenia? V hard-codede? Približne takto:
[ScanDevices]
# Musí existovať pri spustení. Automaticky sa obnoví, ak je mladší ako 30s
Test = "\/dev\/input\/event0"
Respawn = 30
# Hľadaná maska
Search = "\/dev\/input\/event*"
# Na mojich thinkPadoch sú také pseudo-klávesnice s množstvom zbytočných udalostí
Bypass = "(?i)Video|Camera" # „(?i)“ sa zjavne líši od „klasických“ pcre.
A kde som zabudol/oblast (bez toho — nikam), veľmi dúfam, že pozorní čitatelia sa nebudú báť ukázať prstom.
Veľa šťastia!
Zdroj: habr.com
