Korektor rozložení Xswitcher pro linux: druhý krok

Jak předchozí publikace (xswitcher ve fázi „proof of concept“) obdržel poměrně hodně konstruktivní zpětné vazby (což je hezké), svůj volný čas jsem nadále věnoval vývoji projektu. Teď chci utratit trochu tvého... Druhý krok nebude zcela obvyklý: návrh/diskuze návrhu konfigurace.

Korektor rozložení Xswitcher pro linux: druhý krok

Nějak se ukazuje, že normálním programátorům připadá neuvěřitelně nudné nastavovat všechny tyto ovládací prvky.

Aby to nebylo neopodstatněné, uvnitř je ukázka toho, čím se zabývám.
Celkově skvěle vymyšlený (a dobře implementovaný) Apache Kafka & ZooKeeper.
- Konfigurace? Ale je to nuda! Hloupý xml (protože je „z krabice“).
- Oh, chceš také ACL? Ale je to tak nudné! Tap-blooper... Něco takového.

Ale v mé práci je to přesně naopak. Že jo (bohužel, téměř nikdy poprvé) sestrojený model umožňuje pokračovat dále snadno a přirozeně (Téměř) sestavit schéma.

Nedávno jsem na Habrém narazil na článek o tvrdé práci datových vědců...
Ukazuje se, že tento okamžik je pro ně plně realizován. A v mé praxi, jak se říká, „odlehčená verze“. Vícesvazkové modely, ostřílení programátoři s připraveným OOP atd. — to vše se objeví později, když/pokud vzlétne. Ale designér potřebuje začít někde tady a teď.

Dostat se k věci. TOML jsem vzal jako syntaktický základ od tohoto občana.

Protože on (TOML) na jedné straně upravitelné člověkem. Na druhou stranu se překládá 1:1 do kterékoli z běžnějších syntaxí: XML, JSON, YAML.
Navíc implementace, kterou jsem použil z „github.com/BurntSushi/toml“, i když není nejmódnější (stále 1.4 syntaxe), je syntakticky kompatibilní se stejným („vestavěným“) JSON.

To znamená, že pokud chcete, můžete jednoduše říct „projděte lesem s tím svým TOMLem, chci XXX“ a „opravte“ kód pouze jedním řádkem.

Pokud tedy chcete napsat některá okna pro konfiguraci xswitcher (Nejsem si jistý) „S touhle vaší zatracenou konfigurací se neočekávají žádné problémy“.

U všech ostatních je syntaxe založena na „klíč = hodnota“ (a doslova několik složitějších možností, jako je = [some, that, array]) hádám
intuitivně pohodlné.
Zajímavé je to "Spálený" přibližně ve stejnou dobu (kolem roku 2013). Jen na rozdíl ode mě do toho autor TOMLu šel v pořádném měřítku.

Proto je nyní pro mě snazší upravit si jeho implementaci podle sebe, a ne naopak.

Obecně bereme TOML (velmi podobný starému Windows INI). A máme konfiguraci, ve které popisujeme, jak připojit řadu háčků v závislosti na sadě nejnovějších skenovacích kódů z klávesnice. Níže, kousek po kousku, je to, co se dosud stalo. A vysvětlení, proč jsem se tak rozhodl.

0. Základní abstrakce

  • Naskenujte označení kódu. S tím je rozhodně potřeba něco udělat, protože prostě digitální kódy nejsou absolutně čitelné pro člověka (to jsem jen já loloswitcher).
    Vytřepal jsem „ecodes.go“ z „golang-evdev“ (byl jsem líný podívat se na původní zdroj, ačkoli to autor označil docela kulturně). Trochu jsem (zatím) opravil něco, co bylo docela děsivé. Jako „LEFTBRACE“ → „L_BRACE“.
  • Navíc představil koncept „stavových klíčů“. Protože použitá běžná gramatika neumožňuje dlouhé pasáže. (Umožňuje však kontrolu s minimální režií. Pokud používáte pouze „přímé“ nahrávání.)
  • K dispozici bude vestavěný „deduplikátor“ toho, co se lisuje. Bude tedy zapsán stav "repeat"=2 jeden krát.

1. Sekce šablon

[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 čeho se skládá slovo v lidském jazyce s fonetickým zápisem? (buď záležitost grafémů alias „hieroglyfů“)? Nějaký druh hrozného „listu“. Proto okamžitě zavádím pojem „šablona“.

2. Co dělat, když se na něco klikne (přišel další 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"]

Celkem je 768 kódů. (Ale „pro jistotu“ jsem do kódu xswitcher vložil chytání „překvapení“).
Uvnitř jsem popsal naplnění pole odkazy na funkce „co dělat“. V golangu to tak je (náhle) Ukázalo se, že je to pohodlné a zřejmé.

  • Plánuji na tomto místě snížit „Drop“ 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 jsou ve dvojitých hranatých závorkách s jejím názvem. Hned na začátku to nemohlo být jednodušší. V závislosti na aktuálně aktivním okně můžete vybrat následující možnosti:

  • Vaše vlastní sada „horkých kláves“ „Akce = …“. Pokud ne/prázdné, nedělejte nic.
  • Přepínač „MouseClickDrops“ – co dělat, když je detekováno kliknutí myší. Protože v okamžiku, kdy je xswitcher zapnutý, nejsou žádné podrobnosti o tom, „kde kliknou“, resetujeme vyrovnávací paměť ve výchozím nastavení. Ale v terminálech (například) to nemusíte dělat (obvykle).

4. Jedna (nebo několik) sekvencí kliknutí spustí jeden nebo druhý háček

# 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áčky se dělí na dva typy. Vestavěné, s „mluvícími“ názvy (NewWord, NewSentence, Compose) a programovatelné.

Programovatelné názvy začínají na „Akce“. Protože TOML v1.4, názvy s tečkami musí být v uvozovkách.

Každý oddíl by měl být popsán níže se stejným jménem.

Aby to lidem nefoukalo do myslí „nahými“ štamgasty (ze zkušenosti jejich psátmožná jeden z deseti profesionálů), okamžitě implementuji další syntaxi.

  • "OFF:" (nebo "ON:") před regulárním výrazem (regulární výraz) vyžadují uvolnění (nebo stisknutí) následujících tlačítek.
    Dále udělám „nespravedlivý“ regulární výraz. Se samostatnou kontrolou kusů mezi trubkami "|". Aby se snížil počet záznamů jako "[LR]_SHIFT" (kde to zjevně není nutné).
  • "SEQ:" Pokud je předchozí podmínka splněna (nebo chybí), pak provedeme kontrolu proti „normálnímu“ regulárnímu výrazu. Pro podrobnosti okamžitě posílám do ^W knihovnu „regexp“. Protože jsem se stále neobtěžoval zjistit stupeň kompatibility s mým oblíbeným pcre („perl compatible“).
  • Výraz je zapsán ve tvaru "BUTTON_1: CODE1, BUTTON_2: CODE2" atd., v pořadí, v jakém byly přijaty skenovací kódy.
  • Kontrola je vždy „přisunuta“ na konec sekvence, takže není třeba přidávat „$“ na konec.
  • Všechny kontroly v jednom řádku se provádějí jedna po druhé a jsou kombinovány „já“. Ale protože je hodnota popsána jako pole, můžete za čárku napsat alternativní kontrolu. Pokud je to z nějakého důvodu potřeba.
  • Hodnota "SeqLength = 8" omezuje velikost vyrovnávací paměti, proti které se provádějí všechny kontroly. Protože V životě jsem se (až dosud) nikdy nesetkal s nekonečnými zdroji.

5. Nastavení háčků popsané v předchozí části

# 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 zde "Akce = [Pole]". Podobně jako v předchozí části je zde omezená sada vestavěných akcí. A možnost dokování není v zásadě omezena (napište „Akce.XXX“ a nebuďte příliš líní napsat pro to další sekci).
Zejména přepisování slova v opraveném rozložení je rozděleno do dvou částí: „změňte rozvržení, jak je tam uvedeno“ и "přepište" ("přepište slovo").

Zbývající parametry jsou zapsány do „slovníku“ ("mapa" v golangu) pro danou akci závisí jejich seznam na tom, co je napsáno v „Akci“.

Na jedné hromadě lze popsat několik různých akcí (sekce). Nebo to můžete rozebrat. Jak jsem ukázal výše.

Okamžitě jsem nastavil akci „Exec“ pro spuštění externího skriptu. S možností přesunout nahraný buffer do stdin.

  • “Wait = 1” – počkejte na dokončení běžícího procesu.
  • Pravděpodobně „na hromadu“ budete chtít dát do prostředí další lidi. informace, jako je název třídy okna, ze které byl zachycen.
    „Chcete připojit váš psovod? Tady musíš jít."

Fuj (vydechl). Zdá se, že jsem na nic nezapomněl.

Jejda! Jo, nezapomněl jsem...
Kde je konfigurace spuštění? V tvrdém kódu? Takhle:

[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 jsem zapomněl/udělal chybu? (bez toho to nejde), opravdu doufám, že pozorní čtenáři nebudou líní strkat nos.

Good luck!

Zdroj: www.habr.com

Přidat komentář