Xswitcher layout-korrektor til linux: trin to

siden tidligere udgivelse (xswitcher på "proof of concept"-stadiet) modtog en del konstruktiv feedback (hvilket er rart), fortsatte jeg med at bruge min fritid på at udvikle projektet. Nu vil jeg bruge lidt af din... Det andet trin vil ikke være helt normalt: forslag/diskussion af konfigurationsdesign.

Xswitcher layout-korrektor til linux: trin to

På en eller anden måde viser det sig, at normale programmører finder det utroligt kedeligt at sætte alle disse kontroller op.

For ikke at være ubegrundet, er indeni et eksempel på, hvad jeg har med at gøre.
Generelt fremragende udtænkt (og velimplementeret) Apache Kafka & ZooKeeper.
- Konfiguration? Men det er kedeligt! Dumb xml (fordi det er "ud af boksen").
- Åh, vil du også have en ACL? Men det er så kedeligt! Tap-blooper... Sådan noget.

Men i mit arbejde er det stik modsat. Højre (ak, næsten aldrig første gang) den konstruerede model giver dig mulighed for nemt og naturligt at fortsætte videre (Næsten) sammensætte et diagram.

Jeg stødte for nylig på en artikel om Habré om dataforskeres hårde arbejde...
Det viser sig, at dette øjeblik er fuldt ud realiseret for dem. Og i min praksis, som man siger, "light version". Multi-volume modeller, erfarne programmører med OOP klar osv. — alt dette vil dukke op senere, når/hvis det starter. Men designeren skal starte et sted her og nu.

Kom til sagen. Jeg tog TOML som et syntaktisk grundlag fra denne borger.

Fordi han (TOML) på den ene side, menneskelig redigerbar. På den anden side er det oversat 1:1 til en af ​​de mere almindelige syntakser: XML, JSON, YAML.
Desuden er implementeringen, jeg brugte fra "github.com/BurntSushi/toml", selvom ikke den mest fashionable (stadig 1.4-syntaks), syntaktisk kompatibel med den samme ("indbyggede") JSON.

Det vil sige, hvis du ønsker det, kan du blot sige "gå gennem skoven med din TOML, jeg vil have XXX" og "lappe" koden med kun én linje.

Således, hvis du vil skrive nogle vinduer for at konfigurere xswitcher (Jeg er ikke sikker) Der forventes ingen problemer "med denne forbandede konfiguration af din."

For alle andre er syntaksen baseret på "nøgle = værdi" (og bogstaveligt talt et par mere komplicerede muligheder, som = [nogle, det, array]) jeg tror
intuitivt praktisk.
Det interessante er det "brændt" omkring samme tid (omkring 2013). Kun, i modsætning til mig, gik forfatteren til TOML ind på en ordentlig skala.

Derfor er det nu nemmere for mig at tilpasse implementeringen, så den passer til mig selv og ikke omvendt.

Generelt tager vi TOML (meget lig det gamle Windows INI). Og vi har en konfiguration, hvor vi beskriver, hvordan man fastgør en række kroge afhængigt af sættet af de seneste scanningskoder fra tastaturet. Nedenfor, stykke for stykke, er hvad der er sket indtil videre. Og en forklaring på, hvorfor jeg besluttede mig på denne måde.

0. Grundlæggende abstraktioner

  • Scan kodebetegnelser. Der skal helt sikkert gøres noget ved dette, da simpelthen digitale koder absolut ikke kan læses af mennesker (det er bare mig loloswitcher).
    Jeg rystede "ecodes.go" ud fra "golang-evdev" (jeg var for doven til at se på den originale kilde, selvom forfatteren angav det ret kulturelt). Jeg rettede lidt (indtil videre) noget, der var ret frygteligt. Ligesom "LEFTBRACE" → "L_BRACE".
  • Derudover introducerede han begrebet "statsnøgler". Da den almindelige grammatik ikke tillader lange passager. (Men det giver dig mulighed for at tjekke med minimal overhead. Hvis du kun bruger "direkte" optagelse.)
  • Der vil være en indbygget "deduplikator" af det, der trykkes. Således vil tilstanden "gentag"=2 blive skrevet en enkelt gang.

1. Skabelonafsnit

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

Hvad består et menneskesprogligt ord med fonetisk notation af? (enten et spørgsmål om grafemer aka "hieroglyffer")? En slags forfærdeligt "ark". Derfor introducerer jeg straks begrebet "skabelon".

2. Hvad skal man gøre, når der klikkes på noget (en anden scanningskode er ankommet)

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

Der er 768 koder i alt. (Men "just in case" indsatte jeg fangende "overraskelser" i xswitcher-koden).
Indeni beskrev jeg at fylde arrayet med links til funktioner "hvad man skal gøre". I golang er dette (pludselig) Det viste sig at være praktisk og indlysende.

  • Jeg planlægger at reducere "Drop" til et minimum på dette sted. Til fordel for mere fleksibel behandling (jeg viser det nedenfor).

3. Bord med vinduesklasser

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

Tabellens rækker er i dobbelte firkantede parenteser med dets navn. Det kunne ikke have været nemmere lige med det samme. Afhængigt af det aktuelt aktive vindue kan du vælge følgende muligheder:

  • Dit eget sæt "genvejstaster" "Handlinger = ...". Hvis ikke/tom, så gør ingenting.
  • Skift "MouseClickDrops" - hvad skal man gøre, når et museklik registreres. Da der på det tidspunkt, hvor xswitcher er slået til, ikke er nogen detaljer om "hvor de klikker", nulstiller vi bufferen som standard. Men i terminaler (for eksempel) behøver du ikke at gøre dette (som regel).

4. En (eller flere) sekvenser af klik udløser en eller anden hook

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

Kroge er opdelt i to typer. Indbygget, med "talende" navne (NewWord, NewSentence, Compose) og programmerbar.

Programmerbare navne begynder med "Handling". Fordi TOML v1.4, navne med prikker skal stå i anførselstegn.

Hvert afsnit skal beskrives nedenfor med samme navn.

For ikke at blæse folks sind med "nøgne" stamgæster (af erfaring, deres skrivemåske en ud af ti fagfolk), implementerer jeg straks yderligere syntaks.

  • "OFF:" (eller "ON:") før regexp (regulært udtryk) kræver, at følgende knapper slippes (eller trykkes ned).
    Dernæst vil jeg lave et "uretfærdigt" regulært udtryk. Med separat kontrol af stykker mellem rør "|". For at reducere antallet af poster som "[LR]_SHIFT" (hvor dette tydeligvis ikke er nødvendigt).
  • "SEQ:" Hvis den tidligere betingelse er opfyldt (eller fraværende), så kontrollerer vi mod et "normalt" regulært udtryk. For detaljer sender jeg straks "regexp"-biblioteket til ^W. For jeg har stadig ikke gidet at finde ud af graden af ​​kompatibilitet med min yndlings pcre ("perl-kompatibel").
  • Udtrykket er skrevet i formen "BUTTON_1: CODE1, BUTTON_2: CODE2" osv., i den rækkefølge, som scanningskoderne modtages i.
  • Checken "snugges" altid til slutningen af ​​sekvensen, så der er ingen grund til at tilføje "$" til halen.
  • Alle kontroller på en linje udføres efter hinanden og kombineres med "jeg". Men da værdien er beskrevet som et array, kan du skrive en alternativ check efter kommaet. Hvis dette er nødvendigt af en eller anden grund.
  • Value "SeqLength = 8" begrænser størrelsen af ​​den buffer, som alle kontroller udføres mod. Fordi Jeg har (indtil nu) aldrig mødt uendelige ressourcer i mit liv.

5. Indstilling af krogene beskrevet i det foregående afsnit

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

Det vigtigste her er "Handling = [Array]". I lighed med det foregående afsnit er der et begrænset sæt indbyggede handlinger. Og muligheden for docking er i princippet ikke begrænset (skriv "Action.XXX", og vær ikke for doven til at skrive endnu et afsnit til det).
Især genindtastningen af ​​et ord i det korrigerede layout er opdelt i to dele: "ændre layoutet som angivet der" и "retype" ("RetypeWord").

De resterende parametre skrives til "ordbogen" ("kort" på golang) for en given handling afhænger deres liste af, hvad der står i "Handling".

Flere forskellige handlinger kan beskrives i én bunke (afsnit). Eller du kan trække den fra hinanden. Som jeg viste ovenfor.

Jeg indstillede straks "Exec"-handlingen til at udføre det eksterne script. Med mulighed for at skubbe den optagede buffer ind i stdin.

  • "Vent = 1" - vent på, at den kørende proces er fuldført.
  • Sandsynligvis, "til dyngen" vil du gerne sætte flere mennesker i miljøet. oplysninger såsom navnet på vinduesklassen, hvorfra den blev opsnappet.
    “Vil du forbinde din handler? Det er her, du skal hen."

Puha (udåndede). Det virker som om jeg ikke har glemt noget.

Ups! Ja, jeg glemte ikke...
Hvor er lanceringskonfigurationen? I hård kode? Sådan:

[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/begået en fejl? (ingen måde uden dette), Jeg håber virkelig, at opmærksomme læsere ikke vil være for dovne til at stikke næsen.

Held og lykke!

Kilde: www.habr.com

Tilføj en kommentar