Xswitcher коректор на оформлението за linux: втора стъпка

Като предишна публикация (xswitcher на етап „доказване на концепцията“) получи доста конструктивна обратна връзка (което е хубаво), продължих да отделям свободното си време за разработване на проекта. Сега искам да похарча малко от твоя... Втората стъпка няма да бъде съвсем обичайна: предложение/обсъждане на конфигурационния дизайн.

Xswitcher коректор на оформлението за linux: втора стъпка

По някакъв начин се оказва, че нормалните програмисти намират за невероятно скучно да настройват всички тези контроли.

За да не бъда голословен, вътре има пример за това, с което се занимавам.
Като цяло отлично замислен (и добре изпълнен) Apache Kafka & ZooKeeper.
- Конфигурация? Но е скучно! Тъп xml (защото е „извън кутията“).
- О, искате ли и ACL? Но е толкова скучно! Tap-blooper... Нещо такова.

Но в моята работа е точно обратното. вярно (уви, почти никога за първи път) конструираният модел ви позволява да продължите по-нататък лесно и естествено (Почти) съберете диаграма.

Наскоро попаднах на статия в Habré за упоритата работа на специалистите по данни...
Оказва се, че този момент за тях е напълно осъзнат. И в моята практика, както се казва, „лека версия“. Многотомни модели, опитни програмисти с готов OOP и т.н. — всичко това ще се появи по-късно, когато/ако излети. Но дизайнерът трябва да започне някъде тук и сега.

Преминете към точката. Взех TOML като синтактична основа от този гражданин.

Защото той (TOML) от една страна, редактиран от хора. От друга страна, той се превежда 1:1 във всеки от по-често срещаните синтаксиси: XML, JSON, YAML.
Освен това реализацията, която използвах от „github.com/BurntSushi/toml“, макар и не най-модерната (все още синтаксис 1.4), е синтактично съвместима със същия („вграден“) JSON.

Тоест, ако желаете, можете просто да кажете „минете през гората с този ваш TOML, искам XXX“ и „закърпете“ кода само с един ред.

По този начин, ако искате да напишете някои прозорци, за да конфигурирате xswitcher (Не съм сигурен) Не се очакват проблеми „с тази твоя проклета конфигурация“.

За всички останали синтаксисът се основава на „ключ = стойност“ (и буквално няколко по-сложни опции, като = [some, that, array]) Предполагам
интуитивно удобно.
Интересното е, че "изгорял" приблизително по същото време (около 2013 г.). Само че, за разлика от мен, авторът на TOML влезе в подходящ мащаб.

Ето защо сега ми е по-лесно да коригирам изпълнението му според мен, а не обратното.

Като цяло вземаме TOML (много подобен на стария Windows INI). И имаме конфигурация, в която описваме как да прикрепим поредица от куки в зависимост от набора от най-новите кодове за сканиране от клавиатурата. По-долу, част по част, е какво се е случило досега. И обяснение защо реших така.

0. Основни абстракции

  • Сканиране на кодови обозначения. Определено трябва да се направи нещо по въпроса, тъй като просто цифровите кодове абсолютно не се четат от човека (това е само аз loloswitcher).
    Изтръсках „ecodes.go“ от „golang-evdev“ (мързеше ме да погледна оригиналния източник, въпреки че авторът го посочи доста културно). Поправих малко (засега) нещо, което беше доста страшно. Като „ЛЯВА скоба“ → „L_КРАЗКА“.
  • Освен това той въвежда концепцията за „държавни ключове“. Тъй като използваната обикновена граматика не позволява дълги пасажи. (Но ви позволява да проверявате с минимални разходи. Ако използвате само „директен“ запис.)
  • Ще има вграден „дедупликатор“ на натиснатото. Така ще бъде изписано състоянието "repeat"=2 един време.

1. Секция за шаблони

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

От какво се състои дума на човешки език с фонетична нотация? (или въпрос на графеми, известни още като „йероглифи“)? Някакъв ужасен „чаршаф“. Затова веднага въвеждам понятието „шаблон“.

2. Какво да направите, когато се щракне върху нещо (пристигнал е друг код за сканиране)

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

Има общо 768 кода. (Но „за всеки случай“ вмъкнах улавяне на „изненади“ в кода на xswitcher).
Вътре описах попълването на масива с връзки към функциите „какво да направя“. В golang това е (внезапно) Оказа се удобно и очевидно.

  • Планирам да намаля „Drop“ до минимум на това място. В полза на по-гъвкава обработка (ще го покажа по-долу).

3. Таблица с прозорци класове

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

Редовете на таблицата са в двойни квадратни скоби с нейното име. Не можеше да бъде по-лесно от самото начало. В зависимост от текущия активен прозорец можете да изберете следните опции:

  • Ваш собствен набор от „горещи клавиши“ „Действия = …“. Ако не/празно, не правете нищо.
  • Превключете „MouseClickDrops“ - какво да правите, когато бъде открито щракване с мишката. Тъй като в момента, в който xswitcher е включен, няма подробности за това „къде щракват“, ние нулираме буфера по подразбиране. Но в терминалите (например) не е нужно да правите това (обикновено).

4. Една (или няколко) последователности от кликвания задействат една или друга кука

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

Куките са разделени на два вида. Вграден, с „говорещи“ имена (NewWord, NewSentence, Compose) и програмируем.

Програмируемите имена започват с „Действие“. защото TOML v1.4, имената с точки трябва да са в кавички.

Всеки раздел трябва да бъде описан по-долу със същото име.

За да не взривяват съзнанието на хората с „голи“ редовни посетители (от опит, техните пишаможе би един от десет професионалисти), веднага прилагам допълнителен синтаксис.

  • „ИЗКЛ.:“ (или „ВКЛ.:“) преди regexp (регулярен израз) изисква следните бутони да бъдат освободени (или натиснати).
    След това ще направя „несправедлив“ регулярен израз. С отделна проверка на парчетата между тръбите "|". За да се намали броят на записите като "[LR]_SHIFT" (където това очевидно не е необходимо).
  • "SEQ:" Ако предишното условие е изпълнено (или липсва), тогава проверяваме срещу „нормален“ регулярен израз. За подробности изпращам незабавно на ^W библиотеката „regexp“. Тъй като все още не съм си направил труда да разбера степента на съвместимост с любимия ми pcre („perl съвместим“).
  • Изразът е записан във формата „BUTTON_1: CODE1, BUTTON_2: CODE2“ и т.н., в реда, в който са получени кодовете за сканиране.
  • Чекът винаги е „прилепен“ към края на последователността, така че няма нужда да добавяте „$“ към опашката.
  • Всички проверки в един ред се извършват една след друга и са комбинирани с „I“. Но тъй като стойността е описана като масив, можете да напишете алтернативна проверка след запетаята. Ако това е необходимо по някаква причина.
  • Стойност "SeqLength = 8" ограничава размера на буфера, спрямо който се извършват всички проверки. защото Никога (до сега) не съм срещал безкрайни ресурси в живота си.

5. Поставяне на куките, описани в предишния раздел

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

Основното тук е "Действие = [Масив]". Подобно на предишния раздел, има ограничен набор от вградени действия. И възможността за докинг принципно не е ограничена (напишете „Action.XXX“ и не бъдете прекалено мързеливи да напишете друг раздел за него).
По-специално, повторното въвеждане на дума в коригираното оформление е разделено на две части: „променете оформлението, както е посочено там“ и „повторно въвеждане“ („Повторно въвеждане на дума“).

Останалите параметри се записват в „речника“ („карта“ на golang) за дадено действие списъкът им зависи от това какво е написано в „Действие“.

Няколко различни действия могат да бъдат описани в една купчина (секции). Или можете да го разглобите. Както показах по-горе.

Веднага задам действието „Exec“ за изпълнение на външния скрипт. С опция за натискане на записания буфер в stdin.

  • „Изчакайте = 1“ — изчакайте завършването на изпълнявания процес.
  • Вероятно „на купчина“ ще искате да поставите допълнителни хора в околната среда. информация като името на класа на прозореца, от който е прихваната.
    „Искате ли да свържете вашия манипулатор? Това е мястото, където трябва да отидете.

Пфу (издиша). Изглежда, че не съм забравил нищо.

Опа! Да, не забравих...
Къде е конфигурацията за стартиране? В твърд код? така:

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

Къде забравих/сгреших? (без това няма как), наистина се надявам, че внимателните читатели няма да бъдат твърде мързеливи, за да си пъхнат носа.

На добър час!

Източник: www.habr.com

Добавяне на нов коментар