Коректор распореда Кссвитцхер-а за Линук: други корак

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

Коректор распореда Кссвитцхер-а за Линук: други корак

Некако се испоставило да је нормалним програмерима невероватно досадно да постављају све ове контроле.

Да не бих био неоснован, унутра је пример онога чиме се бавим.
Све у свему, одлично замишљени (и добро имплементирани) Апацхе Кафка & ЗооКеепер.
- Конфигурација? Али досадно је! Глуп кмл (јер је „ван кутије“).
- Ох, да ли и ти желиш АЦЛ? Али тако је досадно! Тап-блоопер... Нешто тако.

Али у мом раду је управо супротно. Јел тако (авај, скоро никад први пут) конструисани модел вам омогућава да наставите даље лако и природно (Скоро) саставити дијаграм.

Недавно сам наишао на чланак на Хабреу о тешком раду научника података...
Испоставило се да је овај тренутак за њих у потпуности остварен. И у мојој пракси, како кажу, "лака верзија". Вишетомни модели, искусни програмери са спремним ООП-ом, итд. — све ће се то појавити касније када/ако полети. Али дизајнер треба да почне негде овде и сада.

Доћи до тачке. Узео сам ТОМЛ као синтаксичку основу од овог грађанина.

Јер он (ТОМЛ) с једне стране, уређује се људима. С друге стране, он је преведен 1:1 у било коју од уобичајених синтакса: КСМЛ, ЈСОН, ИАМЛ.
Штавише, имплементација коју сам користио са „гитхуб.цом/БурнтСусхи/томл“, иако није најмодернија (и даље синтакса 1.4), синтактички је компатибилна са истим („уграђеним“) ЈСОН-ом.

То јест, ако желите, можете једноставно рећи „иди кроз шуму са тим својим ТОМЛ-ом, желим КСКСКС“ и „закрпити“ код са само једном линијом.

Дакле, ако желите да напишете неке прозоре за конфигурисање ксвитцхер-а (Нисам сигуран) Не очекују се проблеми „са овом твојом проклетом конфигурацијом“.

За све остале, синтакса је заснована на „кључ = вредност“ (и буквално неколико компликованијих опција, као што је = [неки, тај, низ]) ваљда
интуитивно згодан.
Оно што је занимљиво је то "изгорео" отприлике у исто време (око 2013). Само, за разлику од мене, аутор ТОМЛ-а је ушао у правим размерама.

Стога ми је сада лакше да прилагодим његову имплементацију како би ми одговарало, а не обрнуто.

Генерално, узимамо ТОМЛ (веома сличан старом Виндовс ИНИ). И имамо конфигурацију у којој описујемо како да причврстимо серију кукица у зависности од скупа најновијих кодова за скенирање са тастатуре. У наставку, део по део, шта се до сада догодило. И објашњење зашто сам се одлучио на овај начин.

0. Основне апстракције

  • Ознаке кодова скенирања. Нешто дефинитивно треба да се уради у вези са овим, пошто једноставно дигитални кодови апсолутно нису читљиви људима (то сам само ја лолосвитцхер).
    Избацио сам „ецодес.го“ из „голанг-евдев“ (био сам превише лењ да погледам оригинални извор, иако је аутор то сасвим културно назначио). Исправио сам мало (за сада) нешто што је било прилично страшно. Као „ЛЕФТБРАЦЕ“ → „Л_БРАЦЕ“.
  • Поред тога, увео је концепт „државних кључева“. Пошто уобичајена граматика која се користи не дозвољава дугачке пасусе. (Али вам омогућава да проверите са минималним трошковима. Ако користите само „директно“ снимање.)
  • Биће уграђени „дедупликатор“ онога што се притисне. Тако ће бити написано стање "поновити"=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 кодова. (Али „за сваки случај“ убацио сам хватање „изненађења“ у ксвитцхер код).
Унутра сам описао попуњавање низа везама до функција „шта да се ради“. У голангу је ово (изненада) Испоставило се да је згодно и очигледно.

  • Планирам да смањим „Дроп“ на минимум на овом месту. У корист флексибилније обраде (приказаћу то у наставку).

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"

Редови табеле су у дуплим угластим заградама са именом. Није могло бити лакше одмах. У зависности од тренутно активног прозора, можете изабрати следеће опције:

  • Ваш сопствени сет „врућих тастера“ „Акције =…“. Ако није/празно, не радите ништа.
  • Пребаците „МоусеЦлицкДропс“ - шта да радите када се открије клик мишем. Пошто на месту где је ксвитцхер укључен, нема детаља о томе „где кликну“, подразумевано ресетујемо бафер. Али у терминалима (на пример) не морате то да радите (обично).

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

Куке су подељене на два типа. Уграђени, са „говорним“ именима (НевВорд, НевСентенце, Цомпосе) и програмабилни.

Програмабилни називи почињу са „Радња“. Јер ТОМЛ в1.4, имена са тачкама морају бити у наводницима.

Сваки одељак треба да буде описан у наставку са истим именом.

Да не би разбијали људе „голим“ сталним гостима (из искуства, њиховим пишиможда један од десет професионалци), одмах имплементирам додатну синтаксу.

  • „ИСКЉУЧЕНО:“ (или „УКЉУЧЕНО:“) пре редовног израза (регуларног израза) захтевају да се отпусте (или притисну) следећа дугмад.
    Затим ћу направити „непоштен“ регуларни израз. Уз одвојену проверу комада између цеви "|". Да би се смањио број записа попут „[ЛР]_СХИФТ“ (где то очигледно није неопходно).
  • "СЕК:" Ако је претходни услов испуњен (или одсутан), онда проверавамо у односу на „нормалан“ регуларни израз. За детаље, одмах шаљем ^В библиотеку „регекп“. Зато што се још увек нисам потрудио да сазнам степен компатибилности са мојим омиљеним пцре („перл цомпатибле”).
  • Израз је написан у облику „БУТТОН_1: ЦОДЕ1, БУТТОН_2: ЦОДЕ2“ итд., редоследом којим се примају кодови за скенирање.
  • Провера је увек „привучена“ до краја низа, тако да нема потребе за додавањем "$" у реп.
  • Све провере у једној линији се врше једна за другом и комбиновани су са „ја“. Али пошто је вредност описана као низ, можете написати алтернативну проверу после зареза. Ако је ово потребно из неког разлога.
  • Вредност „СекЛенгтх = 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.

Овде је главна ствар „Акција = [Низ]“. Слично као у претходном одељку, постоји ограничен скуп уграђених радњи. А могућност пристајања у принципу није ограничена (напишите „Ацтион.КСКСКС“ и не будите лијени да напишете још један одељак за то).
Конкретно, поновно куцање речи у исправљеном распореду подељено је на два дела: „промени изглед како је тамо наведено“ и „поновно укуцај“ („поново упиши реч“).

Преостали параметри се уписују у "речник" („мапа“ на голангу) за дату акцију, њихова листа зависи од тога шта је написано у „Акција“.

Неколико различитих радњи се може описати у једној гомили (одељци). Или га можете раставити. Као што сам показао горе.

Одмах сам подесио акцију „Екец“ да изврши спољну скрипту. Са опцијом да се снимљени бафер гурне у стдин.

  • „Чекај = 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.

Где сам заборавио/погрешио? (нема шансе без овога), заиста се надам да пажљиви читаоци неће бити лењи да гурају нос.

Срећно!

Извор: ввв.хабр.цом

Додај коментар