Protože (hswitcher ve fázi „proof of concept“) dostal docela dost konstruktivních zpětných vazeb (což je příjemné), pokračoval jsem v trávení svého volného času na rozvoji projektu. Teď chci strávit kousek vašeho času... Druhý krok bude poněkud neobvyklý: návrh/diskuze o designu konfigurace.

Nějak se stává, že normálním programátorům nastavování všech těchto věcí připadá hrozně nudné.
Abych nebyl jen tak hloupý, tady je příklad toho, s čím mám co do činění.
Skvěle naplánované (a slušně realizované) Apache Kafka & ZooKeeper.
— Konfigurace? Ale to je přece nudné! Takhle naplácat xml (protože „out of the box“).
— No a vy ještě chcete ACL? To je tak zdlouhavé! Tohle a tamto... Jak se to říká.
A v mé práci je to úplně naopak. Správně (bohužel, napoprvé téměř nikdy neexistuje) vytvořený model umožňuje dál snadno a bez námahy (no, téměř) sestavit schéma.
Nedávno jsem na Habr narazil na článek o nelehké práci data scientistů…
Ukazuje se, že tento okamžik se u nich plně realizuje. A v mé praxi, jak se říká, „light verze“. Víceobjemové modely, zkušení programátoři s OOP na krku atd. — to všechno se objeví, když/jesli to vzletí. Ale konstruktér musí začít hned teď s něčím.
K věci. Jako syntaktický základ jsem vzal TOML .
Protože je (TOML) z jedné strany lidsky editovatelný. A z druhé strany — převádí se 1:1 do jakéhokoli rozšířenějšího syntaktického formátu: XML, JSON, YAML.
Více než to, moje implementace z „github.com/BurntSushi/toml“ i když není nejmódnější (přes syntaxi 1.4), je syntakticky kompatibilní s tímto („vestavěným“) JSON.
To znamená, že pokud byste chtěli, můžete klidně říct „běž s tím svým TOML pryč, chci XXX“ a „opravit“ kód jediným řádkem.
Takže pokud byste chtěli napsat nějaká okna pro konfiguraci hswitcheru (určitě ne já) potíže „s tím vaším zatraceným configem“ — se neočekávají.
Pro všechny ostatní je syntaxe na bázi „klíč = hodnota“ (a doslova s pár složitějšími možnostmi, typu = [nějaké, pole]) předpokládám
intuitivně pohodlnou.
Co je zajímavé, že sám přibližně ve stejnou dobu (kolem roku 2013). Jen, na rozdíl ode mě, autor TOML přišel s patřičným rozmachom.
Protože teď je pro mě snazší přizpůsobit jeho implementaci sobě než naopak.
V podstatě bereme TOML (velmi podobný starému Windows INI). A vytváříme konfiguraci, ve které popisujeme, jak připojit sérii háčků podle sady posledních skenovacích kódů z klávesnice. Níže jsou kousky — to, co jsem dosud vytvořil. A vysvětlení, proč jsem se tak rozhodl.
0. Základní abstrakce
- Označení skenovacích kódů. S tím je nutné něco udělat, protože pouze číselné kódy jsou naprosto nečitelné pro člověka (tohle je v zahradě, ).
Vytáhl jsem "ecodes.go" z "golang-evdev" (do původního zdroje se mi nechtělo, i když autor ho má docela kultivovaně uvedený). Trochu (zatím) jsem opravil to opravdu ošklivé. Typu "LEFTBRACE" → "L_BRACE". - Navíc jsem zavedl pojem „kláves s stavem“. Protože použitá regulární gramatika nenabízí dlouhé pasáže. (Oproti tomu umožňuje kontrolovat s minimálními náklady. Pokud použijete pouze "přímý" záznam.)
- Bude zabudovaný „deduplikační“ systém pro stisknuté klávesy. Tím pádem stav „opakování“=2 bude zaznamenán. ; ideálně by si uživatel měl vybrat dvě nebo více tajných otázek čísle.
1. Sekce šablon
[Templates] # "@name@" pro zjednodušení výrazů
# Slova mohou sestávat z těchto znaků (regex)
"WORD" = "([0-9A-Z`;']|[LR]_BRACE|COMMA|DOT|SLASH|KP[0-9])"
Z čeho se skládá slovo lidského jazyka s fonetickým zápisem (to je jiná věc, grafémy aka „hieroglyfy“)? Какая-то ужасная «простыня». Поэтому сразу закладываю понятие «шаблона».
2. Co dělat, když je něco stisknuto (přišel další skenovací kód)
[ActionKeys]
# Shromažďujte klávesu a testujte příkazovou sekvenci
# !!! Opakující se kódy (kód=2) musí být shromážděny pouze jednou na klávesu!
Přidat = ["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"]
# Zahodit všechny shromážděné klávesy, včetně této. To je výchozí akce.
Zahodit = ["ESC", "-", "TAB", "ENTER", "KPENTER", "LINEFEED..POWER"]
# Uložte další mapu pro tyto klávesy, když je některá v "down" stavu.
# Stav se kontroluje pomocí podmínek "OFF:"|"ON:" v akci.
# (Také, stav těchto kláves musí přetrvávat mezi likvidacemi bufferu.)
# ??? Jak zacházet s CAPS a klávesami "LOCK" ???
StateKeys = ["L_CTRL", "L_SHIFT", "L_ALT", "L_META", "CAPS", "N_LOCK", "S_LOCK",
"R_CTRL", "R_SHIFT", "R_ALT", "R_META"]
# Testovat pouze, ale neshromažďovat.
# Např. používám F12 místo BREAK na hloupých laptopech s podělanými klávesnicemi (nové ThinkPady)
Test = ["F1..F10", "ZENKAKUHANKAKU", "102ND", "F11", "F12",
"RO..KPJPCOMMA", "SYSRQ", "SCALE", "HANGEUL..YEN",
"STOP..SCROLLDOWN", "NEW..MAX"]
Celkem je navrženo 768 kódů. (Ale „pro všechny případy“ jsem vložil do kódu xswitcher záchyt „překvapení“).
Uvnitř jsem podrobně popsala naplnění pole odkazy na funkce „co dělat“. Na golangu je to (najednou) se ukázalo být pohodlné a zřejmé.
- «Drop» v tomto místě plánují zkrátit na minimum. Ve prospěch flexibilnějšího zpracování (ukážu níže).
3. Tabulka s třídami oken
# 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"
Řádky tabulky — v dvojitých hranatých závorkách s jejím názvem. S tím se hned nezdařilo. V závislosti na aktuálním aktivním okně lze zvolit možnosti:
- Svůj vlastní soubor „klávesových zkratek“ „Actions = …“. Pokud není/je prázdný — nic nedělat.
- Přepínač „MouseClickDrops“ — co udělat při detekci kliknutí myší. Vzhledem k tomu, že v bodu zapnutí xswitcher nejsou žádné podrobnosti „kam tam klikají“, ve výchozím nastavení vymažeme buffer. Ale v terminálech (například) to tak udělat nemusíte. (obvykle).
4. Jedna (nebo více) sekvencí stisků spouští 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 jsem rozdělil na dva typy. Vstavené, s „mluvícími“ názvy (NewWord, NewSentence, Compose) a programovatelné.
Názvy programovatelných začínají „Action.“. Tím, že TOML v1.4, názvy s tečkami musí být v uvozovkách.
Níže by pro každý měl být popsán oddíl s tímto názvem.
Abych lidem nevybuchoval mozek „holými“ regulárními výrazy (podle zkušenosti, jejich napsat-to zvládne jeden z deseti profesionálů), ihned zavádím dodatečnou syntaxi.
- „OFF:“ (nebo „ON:“) před regexp (regulárním výrazem) vyžadují, aby uvedené příslušné klávesy byly uvolněny (nebo stisknuty).
Dále plánuji vytvořit „nespravedlivý“ regulární výraz. S odděleným ověřením kousků mezi svislými čarami "|". S cílem snížit počet záznamů typu "[LR]_SHIFT" (kde to není výslovně nutné). - „SEQ:“ Pokud je předchozí podmínka splněna (nebo chybí), dále ověřujeme vzhledem k „běžnému“ regulárnímu výrazu. Pro podrobnosti posílám hned na^Wdo knihovny „regexp“. Protože sám jsem se dosud neunavoval zjistit míru kompatibility s mými oblíbenými pcre („perl compatible“).
- Výraz se zapisuje ve formátu „KLOPKA_1: KÓD1, KLOPKA_2: KÓD2“ a tak dále, v pořadí přicházejících skenovacích kódů.
- Ověření se vždy „připevňuje“ na konec sekvence, proto "$" na konec dopisovat není třeba.
- Všechna ověření v jednom řádku jsou prováděna za sebou a spojena podle „A“. Ale protože je hodnota popsána ve formě pole, můžete po čárce napsat alternativní ověření. Pokud je to k něčemu potřeba.
- Hodnota „SeqLength = 8“ omezuje velikost bufferu, ve vztahu k němuž se provádějí všechna ověření. Protože jsem se dosud nesetkal s nekonečnými zdroji.
5. Přiřazení hooků, jak bylo uvedeno v předchozí sekci
# 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í věc je — „Akce = [Pole]“. Podobně jako v předchozí sekci existuje omezený soubor vestavěných akcí. A zároveň neomezená možnost propojení. (napsat „Action.XXX“ a nezapomenout přidat ještě jednu sekci).
Mezi nimi se přepis slova v opravené klávesnici dělí na dvě části: „přepni klávesnici, jak je uvedeno tam“ a „přepiš“ („RetypeWord“).
Ostatní parametry se zapisují do „slovníku“ („map“ v golang) pro tuto akci, jejich seznam závisí na tom, co je napsáno v „Action“.
Několik různých akcí lze popsat v jedné hromadě (sekce). A můžeš to rozdělit. Jak jsem ukázal výše.
Ihned zakládám akci „Exec“ — provést externí skript. S možností poslat do stdin uložený buffer.
- „Wait = 1“ — počkat na dokončení spuštěného procesu.
- Pravděpodobně si „navíc“ budete chtít nastavit do prostředí další informace, jako je název třídy okna, z něhož bylo zachyceno.
„Chcete připojit svůj zpracovatel? Tady je to.“
Uf (vydechl jsem). Vypadá to, že jsem nic nezapomněl.
Hop! Aha, nezapomněl jsem…
A kde je konfigurace spuštění? V hardcodovaném? Přibližně takto:
[ScanDevices]
# Musí existovat při spuštění. Samoznovuobnovení, pokud je mladší než 30s
Test = "\/dev\/input\/event0"
Respawn = 30
# Hledací maska
Search = "\/dev\/input\/event*"
# V mých thinkPadech existují takové pseudo-klávesnice s tunami zbytečných událostí
Bypass = "(?i)Video|Camera" # "(?i)" se samozřejmě liší od „klasických“ pcre.
A kde jsem zapomněl/chybnil (bez toho — to nejde), velmi doufám, že pozorní čtenáři se nebudou bát mi na to ukázat.
Hodně štěstí!
Zdroj: habr.com
