Xswitcher-layoutkorrigering for Linux: trinn to

siden tidligere utgivelse (xswitcher på «proof of concept»-stadiet) fikk ganske mange konstruktive tilbakemeldinger (som er fint), fortsatte jeg å bruke fritiden min på å utvikle prosjektet. Nå vil jeg bruke litt av din... Det andre trinnet vil ikke være helt vanlig: forslag/diskusjon av konfigurasjonsdesign.

Xswitcher-layoutkorrigering for Linux: trinn to

På en eller annen måte viser det seg at vanlige programmerere synes det er utrolig kjedelig å sette opp alle disse kontrollene.

For ikke å være ubegrunnet er innsiden et eksempel på hva jeg har med å gjøre.
Alt i alt utmerket utformet (og godt implementert) Apache Kafka & ZooKeeper.
- Konfigurasjon? Men det er kjedelig! Dumb xml (fordi det er "ut av esken").
– Å, vil du også ha ACL? Men det er så kjedelig! Tap-blooper... Noe sånt.

Men i mitt arbeid er det stikk motsatt. Ikke sant (Dessverre, nesten aldri første gang) den konstruerte modellen lar deg fortsette videre enkelt og naturlig (Nesten) sette sammen et diagram.

Jeg kom nylig over en artikkel om Habré om det harde arbeidet til dataforskere...
Det viser seg at dette øyeblikket er fullt ut realisert for dem. Og i min praksis, som de sier, "lettversjon". Multivolumsmodeller, erfarne programmerere med OOP klar, etc. — alt dette vil dukke opp senere når/hvis det tar av. Men designeren må begynne et sted her og nå.

Kom til poenget. Jeg tok TOML som et syntaktisk grunnlag fra denne borgeren.

Fordi han (TOML) på den ene siden, menneskelig redigerbar. På den annen side er det oversatt 1:1 til en av de mer vanlige syntaksene: XML, JSON, YAML.
Dessuten er implementeringen jeg brukte fra “github.com/BurntSushi/toml”, selv om den ikke er den mest fasjonable (fortsatt 1.4-syntaks), syntaktisk kompatibel med den samme (“innebygde”) JSON.

Det vil si, hvis du ønsker det, kan du ganske enkelt si "gå gjennom skogen med den TOMLen din, jeg vil ha XXX" og "lappe" koden med bare én linje.

Derfor, hvis du vil skrive noen vinduer for å konfigurere xswitcher (Jeg er ikke sikker) Ingen problemer forventes "med denne jævla konfigurasjonen din."

For alle andre er syntaksen basert på "nøkkel = verdi" (og bokstavelig talt et par mer kompliserte alternativer, som = [noen, det, array]) jeg antar
intuitivt praktisk.
Det som er interessant er det "brent" rundt samme tid (rundt 2013). Bare, i motsetning til meg, gikk forfatteren av TOML inn i en skikkelig skala.

Derfor er det nå lettere for meg å justere implementeringen for å passe meg selv, og ikke omvendt.

Generelt tar vi TOML (svært lik den gamle Windows INI). Og vi har en konfigurasjon der vi beskriver hvordan du fester en rekke kroker avhengig av settet med de siste skannekodene fra tastaturet. Nedenfor, bit for bit, er det som har skjedd så langt. Og en forklaring på hvorfor jeg bestemte meg på denne måten.

0. Grunnleggende abstraksjoner

  • Skann kodebetegnelser. Noe må definitivt gjøres med dette, siden bare digitale koder er absolutt ikke lesbare for mennesker (det er bare meg loloswitcher).
    Jeg ristet ut "ecodes.go" fra "golang-evdev" (jeg var for lat til å se på originalkilden, selv om forfatteren antydet det ganske kulturelt). Jeg korrigerte litt (foreløpig) noe som var ganske fryktelig. Som «LEFTBRACE» → «L_BRACE».
  • I tillegg introduserte han konseptet "statsnøkler". Siden den vanlige grammatikken som brukes ikke tillater lange passasjer. (Men det lar deg sjekke med minimal overhead. Hvis du bare bruker "direkte" opptak.)
  • Det vil være en innebygd "deduplikator" av det som trykkes. Dermed vil tilstanden "gjenta"=2 skrives en tid.

1. Seksjon for maler

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

Hva består et menneskespråklig ord med fonetisk notasjon av? (enten et spørsmål om grafemer aka "hieroglyfer")? Et slags forferdelig "ark". Derfor introduserer jeg umiddelbart konseptet "mal".

2. Hva du skal gjøre når noe klikkes (en annen skannekode har kommet)

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

Det er totalt 768 koder. (Men "bare i tilfelle" la jeg inn fangende "overraskelser" i xswitcher-koden).
Inne beskrev jeg å fylle matrisen med lenker til funksjoner "hva du skal gjøre". I golang er dette (plutselig) Det viste seg å være praktisk og opplagt.

  • Jeg planlegger å redusere "Drop" til et minimum på dette stedet. Til fordel for mer fleksibel behandling (jeg viser det nedenfor).

3. Tabell med vindusklasser

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

Radene i tabellen er i doble firkantede parenteser med navnet. Det kunne ikke vært enklere med en gang. Avhengig av det aktive vinduet kan du velge følgende alternativer:

  • Ditt eget sett med "hurtigtaster" "Handlinger = ...". Hvis ikke/tom, gjør ingenting.
  • Bytt "MouseClickDrops" - hva du skal gjøre når et museklikk oppdages. Siden på punktet der xswitcher er slått på, er det ingen detaljer om "hvor de klikker", tilbakestiller vi bufferen som standard. Men i terminaler (for eksempel) trenger du ikke å gjøre dette (som oftest).

4. En (eller flere) sekvenser med klikk utløser en eller annen krok

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

Kroker er delt inn i to typer. Innebygd, med "talende" navn (NewWord, NewSentence, Compose) og programmerbar.

Programmerbare navn begynner med "Handling". Fordi TOML v1.4, navn med prikker må stå i anførselstegn.

Hver del skal beskrives nedenfor med samme navn.

For ikke å blåse folks sinn med "nakne" stamgjester (av erfaring, deres å skrivekanskje en av ti fagfolk), implementerer jeg ekstra syntaks umiddelbart.

  • "AV:" (eller "PÅ:") før regexp (regulært uttrykk) krever at følgende knapper slippes (eller trykkes).
    Deretter skal jeg lage et "urettferdig" regulært uttrykk. Med separat kontroll av stykker mellom rør "|". For å redusere antall poster som "[LR]_SHIFT" (hvor dette tydeligvis ikke er nødvendig).
  • "SEQ:" Hvis den forrige betingelsen er oppfylt (eller fraværende), sjekker vi mot et "normalt" regulært uttrykk. For detaljer sender jeg umiddelbart "regexp"-biblioteket til ^W. Fordi jeg fortsatt ikke har brydd meg med å finne ut graden av kompatibilitet med min favoritt pcre ("perl-kompatibel").
  • Uttrykket er skrevet i formen "BUTTON_1: CODE1, BUTTON_2: CODE2" osv., i den rekkefølgen skannekodene mottas i.
  • Sjekken "snugged" alltid til slutten av sekvensen, så det er ikke nødvendig å legge til "$" i halen.
  • Alle kontroller på en linje utføres etter hverandre og er kombinert med "jeg". Men siden verdien er beskrevet som en matrise, kan du skrive en alternativ sjekk etter kommaet. Hvis dette er nødvendig av en eller annen grunn.
  • Verdi "SeqLength = 8" begrenser størrelsen på bufferen som alle kontroller utføres mot. Fordi Jeg har (til nå) aldri møtt uendelige ressurser i livet mitt.

5. Sette inn krokene beskrevet i forrige avsnitt

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

Hovedsaken her er "Handling = [Array]". I likhet med forrige seksjon er det et begrenset sett med innebygde handlinger. Og muligheten for dokking er ikke begrenset i prinsippet (skriv "Action.XXX" og ikke vær for lat til å skrive en ny seksjon for det).
Spesielt er gjenskrivingen av et ord i den korrigerte layouten delt inn i to deler: "endre oppsettet som spesifisert der" и "skriv på nytt" ("RetypeWord").

De resterende parametrene skrives til "ordboken" ("kart" på golang) for en gitt handling avhenger listen deres av hva som er skrevet i "Handling".

Flere forskjellige handlinger kan beskrives i en haug (seksjoner). Eller du kan trekke den fra hverandre. Som jeg viste ovenfor.

Jeg satte umiddelbart "Exec"-handlingen for å utføre det eksterne skriptet. Med muligheten til å skyve den innspilte bufferen inn i stdin.

  • "Vent = 1" - vent til den kjørende prosessen er fullført.
  • Sannsynligvis, "til haugen" vil du ønske å sette flere mennesker i miljøet. informasjon som navnet på vindusklassen den ble fanget opp fra.
    «Vil du koble til din behandler? Det er her du må gå."

Puff (pustet ut). Det virker som jeg ikke har glemt noe.

Oops! Ja, jeg glemte ikke...
Hvor er lanseringskonfigurasjonen? I hard kode? Slik:

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

Hvor har jeg glemt/gjort en feil? (ingen måte uten dette), Jeg håper virkelig at oppmerksomme lesere ikke blir for late til å stikke nesen.

Lykke til!

Kilde: www.habr.com

Legg til en kommentar