Korektor układu Xswitcher dla systemu Linux: krok drugi

ponieważ poprzednia publikacja (xswitcher na etapie „weryfikacji koncepcji”) otrzymał całkiem sporo konstruktywnych komentarzy (co jest miłe), nadal spędzałem wolny czas na rozwijaniu projektu. Teraz chcę spędzić trochę twojego... Drugi krok nie będzie całkiem zwyczajny: propozycja/dyskusja na temat projektu konfiguracji.

Korektor układu Xswitcher dla systemu Linux: krok drugi

W jakiś sposób okazuje się, że dla zwykłych programistów konfigurowanie tych wszystkich elementów sterujących jest niezwykle nudne.

Żeby nie było bezpodstawnie, w środku znajduje się przykład tego z czym mam do czynienia.
Ogólnie doskonale pomyślany (i dobrze wdrożony) Apache Kafka i ZooKeeper.
- Konfiguracja? Ale to jest nudne! Głupi XML (ponieważ jest „od razu po wyjęciu z pudełka”).
- Och, czy ty też chcesz ACL? Ale to takie nudne! Wpadka z kranu... Coś w tym stylu.

Ale w mojej pracy jest dokładnie odwrotnie. Prawidłowy (niestety, prawie nigdy za pierwszym razem) skonstruowany model pozwala na łatwą i naturalną kontynuację dalszej pracy (Prawie) złożyć diagram.

Niedawno natknąłem się na artykuł na temat Habré o ciężkiej pracy analityków danych…
Okazuje się, że ten moment jest dla nich w pełni zrealizowany. A w mojej praktyce, jak to mówią, „wersja lekka”. Modele wielotomowe, doświadczeni programiści z gotowym OOP itp. — to wszystko pojawi się później, kiedy/jeśli wystartuje. Ale projektant musi zacząć tu i teraz.

Przejdź do rzeczy. Wziąłem TOML jako podstawę składniową od tego obywatela.

Ponieważ on (TOML) z jednej strony edytowalne przez człowieka. Z drugiej strony jest tłumaczony 1:1 na dowolną z bardziej powszechnych składni: XML, JSON, YAML.
Co więcej, implementacja, z której korzystałem, z „github.com/BurntSushi/toml”, choć nie najmodniejsza (wciąż składnia 1.4), jest syntaktycznie zgodna z tym samym („wbudowanym”) JSON.

Oznacza to, że jeśli chcesz, możesz po prostu powiedzieć „idź przez las z tym swoim TOML-em, chcę XXX” i „popraw” kod za pomocą tylko jednej linii.

Tak więc, jeśli chcesz napisać kilka okien, aby skonfigurować xswitcher (Nie jestem pewny) Nie oczekuje się żadnych problemów „z tą twoją cholerną konfiguracją”.

W przypadku wszystkich pozostałych składnia opiera się na „klucz = wartość” (i dosłownie kilka bardziej skomplikowanych opcji, takich jak = [some, that, array]) Przypuszczam
intuicyjnie wygodne.
Interesujące jest to "spalony" mniej więcej w tym samym czasie (około 2013 r.). Tyle że w przeciwieństwie do mnie autor TOML-a wkroczył na odpowiednią skalę.

Dlatego teraz łatwiej mi dostosować jego realizację do siebie, a nie odwrotnie.

Ogólnie rzecz biorąc, bierzemy TOML (bardzo podobny do starego INI Windows). I mamy konfigurację, w której opisujemy sposób mocowania szeregu zaczepów w zależności od zestawu najnowszych kodów skanujących z klawiatury. Poniżej, kawałek po kawałku, co wydarzyło się do tej pory. I wyjaśnienie, dlaczego zdecydowałem się na taki sposób.

0. Podstawowe abstrakcje

  • Oznaczenia kodów skanowania. Zdecydowanie trzeba coś z tym zrobić, ponieważ zwykłe kody cyfrowe są absolutnie nieczytelne dla człowieka (to tylko ja loloswitcher).
    Wytrząsnąłem „ecodes.go” z „golang-evdev” (byłem zbyt leniwy, żeby zajrzeć do oryginalnego źródła, chociaż autor wskazał to dość kulturowo). Poprawiłem trochę (na razie) coś, co było dość przerażające. Na przykład „LEFTBRACE” → „L_BRACE”.
  • Dodatkowo wprowadził koncepcję „kluczy stanu”. Ponieważ zastosowana gramatyka regularna nie pozwala na długie fragmenty. (Ale pozwala to na sprawdzenie przy minimalnym nakładzie pracy. Jeśli używasz tylko nagrywania „bezpośredniego”).
  • Będzie wbudowany „deduplikator” tego, co zostanie wciśnięte. Zatem zapisany zostanie stan „repeat”=2 jeden razy

1. Sekcja szablonów

[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 czego składa się słowo w języku ludzkim z zapisem fonetycznym? (albo kwestia grafemów, czyli „hieroglifów”)? Jakaś okropna „prześcieradło”. Dlatego od razu wprowadzam pojęcie „szablonu”.

2. Co zrobić, gdy coś zostanie kliknięte (przyszedł kolejny kod skanujący)

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

W sumie jest 768 kodów. (Ale „na wszelki wypadek” wstawiłem łapanie „niespodzianek” do kodu xswitcher).
Wewnątrz opisałem wypełnianie tablicy linkami do funkcji „co robić”. W golangu tak jest (nagle) Okazało się to wygodne i oczywiste.

  • Planuję w tym miejscu ograniczyć „Drop” do minimum. Na rzecz bardziej elastycznego przetwarzania (pokażę to poniżej).

3. Tabela z klasami 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"

Wiersze tabeli wraz z jej nazwą ujęto w podwójne nawiasy kwadratowe. Od samego początku nie mogło być łatwiej. W zależności od aktualnie aktywnego okna można wybrać następujące opcje:

  • Twój własny zestaw „klawiszy skrótu” „Akcje =…”. Jeśli nie/puste, nic nie rób.
  • Przełącznik „MouseClickDrops” – co zrobić w przypadku wykrycia kliknięcia myszą. Ponieważ w momencie włączenia xswitcher nie ma żadnych szczegółów na temat „miejsca kliknięcia”, domyślnie resetujemy bufor. Ale w terminalach (na przykład) nie musisz tego robić (zazwyczaj).

4. Jedna (lub kilka) sekwencja kliknięć uruchamia jeden lub drugi hak

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

Haki dzielą się na dwa typy. Wbudowane, z „mówiącymi” nazwami (NewWord, NewSentence, Compose) i programowalne.

Programowalne nazwy zaczynają się od „Akcja”. Ponieważ TOML v1.4, nazwy z kropkami muszą być ujęte w cudzysłów.

Poniżej należy opisać każdą sekcję o tej samej nazwie.

Aby nie zaskakiwać ludzi „nagimi” bywalcami (z doświadczenia, ich pisaćmoże jeden na dziesięć profesjonaliści), natychmiast wdrażam dodatkową składnię.

  • „WYŁ.:” (lub „WŁ.:”) przed wyrażeniem regularnym (wyrażeniem regularnym) wymagają zwolnienia (lub naciśnięcia) następujących przycisków.
    Następnie zrobię „niesprawiedliwe” wyrażenie regularne. Z oddzielnym sprawdzaniem elementów pomiędzy rurami „|”. Aby zmniejszyć liczbę rekordów typu „[LR]_SHIFT” (gdzie wyraźnie nie jest to konieczne).
  • „SEQ:” Jeśli poprzedni warunek jest spełniony (lub nie występuje), sprawdzamy względem „normalnego” wyrażenia regularnego. Po szczegóły natychmiast wysyłam do ^W bibliotekę „regexp”. Ponieważ nadal nie zadałem sobie trudu sprawdzenia stopnia kompatybilności z moim ulubionym pcre („kompatybilny z Perlem”).
  • Wyrażenie jest zapisane w formie „Przycisk_1: KOD1, PRZYCISK_2: KOD2” itp., w kolejności otrzymania kodów skanowania.
  • Czek jest zawsze „dopasowany” do końca sekwencji, więc nie ma potrzeby dodawania „$” do ogona.
  • Wszystkie kontrole w jednej linii są wykonywane jedna po drugiej i są połączone przez „ja”. Ponieważ jednak wartość jest opisana jako tablica, po przecinku możesz napisać alternatywną kontrolę. Jeśli jest to z jakiegoś powodu potrzebne.
  • Wartość „Długość kolejnej = 8” ogranicza rozmiar bufora, względem którego przeprowadzane są wszystkie kontrole. Ponieważ Nigdy w życiu (do tej pory) nie spotkałem się z nieskończonymi zasobami.

5. Ustawienie haków opisane w poprzednim rozdziale

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

Najważniejsze tutaj jest „Akcja = [tablica]”. Podobnie jak w poprzedniej sekcji, istnieje ograniczony zestaw wbudowanych akcji. Możliwość dokowania nie jest w zasadzie ograniczona (napisz „Akcja.XXX” i nie bądź zbyt leniwy, aby napisać dla niego kolejną sekcję).
W szczególności przepisanie słowa w poprawionym układzie dzieli się na dwie części: „zmień układ zgodnie z opisem” и „wpisz ponownie” („wpisz ponownie słowo”).

Pozostałe parametry zapisywane są do „słownika” („mapa” w języku golang) dla danej akcji ich lista zależy od tego, co jest napisane w „Akcji”.

W jednym stercie można opisać kilka różnych działań (Sekcje). Lub możesz to rozdzielić. Jak pokazałem powyżej.

Natychmiast ustawiam akcję „Exec”, aby wykonać zewnętrzny skrypt. Z opcją wepchnięcia nagranego bufora do standardowego wejścia.

  • „Wait = 1” — poczekaj na zakończenie uruchomionego procesu.
  • Prawdopodobnie „do góry nogami” będziesz chciał umieścić w środowisku dodatkowe osoby. informacje, takie jak nazwa klasy okna, z której został przechwycony.
    „Czy chcesz podłączyć swojego handlera? To tutaj musisz się udać.”

Uff (wydech). Wygląda na to, że o niczym nie zapomniałem.

Ups! Tak, nie zapomniałem…
Gdzie jest konfiguracja uruchamiania? W twardym kodzie? Tak:

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

Gdzie zapomniałem/popełniłem błąd? (bez tego nie ma mowy), Mam wielką nadzieję, że uważni czytelnicy nie będą zbyt leniwi, aby wtykać nosy.

Powodzenia!

Źródło: www.habr.com

Dodaj komentarz