Xswitcher layoutkorrigerare för Linux: steg två

Som tidigare publicering (xswitcher på "proof of concept"-stadiet) fick ganska mycket konstruktiv feedback (vilket är trevligt), fortsatte jag att ägna min fritid åt att utveckla projektet. Nu vill jag spendera lite av din... Det andra steget kommer inte att vara helt vanligt: ​​förslag/diskussion av konfigurationsdesign.

Xswitcher layoutkorrigerare för Linux: steg två

På något sätt visar det sig att vanliga programmerare tycker att det är otroligt tråkigt att ställa in alla dessa kontroller.

För att inte vara ogrundad är insidan ett exempel på vad jag sysslar med.
Överlag utmärkt utformad (och väl implementerad) Apache Kafka & ZooKeeper.
- Konfiguration? Men det är tråkigt! Dumb xml (eftersom det är "out of the box").
– Åh, vill du också ha ACL? Men det är så tråkigt! Tap-blooper... Något sådant.

Men i mitt arbete är det precis tvärtom. Höger (ja, nästan aldrig första gången) den konstruerade modellen gör att du enkelt och naturligt kan fortsätta vidare (Nästan) sätt ihop ett diagram.

Jag stötte nyligen på en artikel om Habré om datavetares hårda arbete...
Det visar sig att detta ögonblick är fullt realiserat för dem. Och i min praktik, som de säger, "light version". Flervolymsmodeller, erfarna programmerare med OOP redo, etc. — allt detta kommer att visas senare när/om det tar fart. Men designern måste börja någonstans här och nu.

Kom till saken. Jag tog TOML som en syntaktisk grund från denna medborgare.

För att han (TOML) å ena sidan, mänskligt redigerbar. Å andra sidan översätts det 1:1 till någon av de vanligare syntaxerna: XML, JSON, YAML.
Dessutom är implementeringen jag använde från “github.com/BurntSushi/toml”, även om den inte är den mest fashionabla (fortfarande 1.4-syntax), syntaktisk kompatibel med samma (”inbyggda”) JSON.

Det vill säga, om du vill kan du helt enkelt säga "gå genom skogen med din TOML, jag vill ha XXX" och "lappa" koden med bara en rad.

Således, om du vill skriva några fönster för att konfigurera xswitcher (Jag är inte säker) Inga problem förväntas "med den här jävla konfigurationen av dig."

För alla andra är syntaxen baserad på "nyckel = värde" (och bokstavligen ett par mer komplicerade alternativ, som = [några, det, array]) jag antar
intuitivt bekvämt.
Det som är intressant är det "bränd" ungefär samtidigt (cirka 2013). Bara, till skillnad från mig, gick författaren till TOML in i en ordentlig skala.

Därför är det nu lättare för mig att anpassa implementeringen så att den passar mig själv, och inte vice versa.

I allmänhet tar vi TOML (mycket likt det gamla Windows INI). Och vi har en konfiguration där vi beskriver hur man fäster en serie krokar beroende på uppsättningen av de senaste skanningskoderna från tangentbordet. Nedan, bit för bit, är vad som har hänt hittills. Och en förklaring till varför jag bestämde mig på det här sättet.

0. Grundläggande abstraktioner

  • Skanna kodbeteckningar. Något måste definitivt göras åt detta, eftersom digitala koder helt enkelt inte är läsbara för människor (det är bara jag loloswitcher).
    Jag skakade ut "ecodes.go" från "golang-evdev" (jag var för lat för att titta på originalkällan, även om författaren angav det ganska kulturellt). Jag rättade lite (för nu) något som var ganska skrämmande. Som "LEFTBRACE" → "L_BRACE".
  • Dessutom introducerade han begreppet "statsnycklar". Eftersom den vanliga grammatiken som används inte tillåter långa passager. (Men det låter dig kontrollera med minimal overhead. Om du bara använder "direkt" inspelning.)
  • Det kommer att finnas en inbyggd "deduplicering" av det som trycks. Således kommer tillståndet "repetera"=2 att skrivas en en gång.

1. Mallsektionen

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

Vad består ett mänskligt språkord med fonetisk notation av? (antingen en fråga om grafemer aka "hieroglyfer")? Något slags fruktansvärt "lakan". Därför introducerar jag omedelbart begreppet "mall".

2. Vad ska man göra när något klickas (en annan skanningskod har kommit)

[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 finns totalt 768 koder. (Men "för säkerhets skull" infogade jag fånga "överraskningar" i xswitcher-koden).
Inuti beskrev jag att fylla arrayen med länkar till funktioner "vad man ska göra". I golang är detta (plötsligt) Det visade sig vara bekvämt och självklart.

  • Jag planerar att minska "Drop" till ett minimum på denna plats. Till förmån för mer flexibel bearbetning (jag kommer att visa det nedan).

3. Tabell med fönsterklasser

# 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 rader är inom dubbla hakparenteser med dess namn. Det kunde inte ha varit enklare direkt. Beroende på vilket fönster som är aktivt kan du välja följande alternativ:

  • Din egen uppsättning "snabbtangenter" "Actions = ...". Om inte/tom, gör ingenting.
  • Byt "MouseClickDrops" - vad man ska göra när ett musklick upptäcks. Eftersom det inte finns några detaljer om "var de klickar" när xswitcher är påslagen, återställer vi bufferten som standard. Men i terminaler (till exempel) behöver du inte göra detta (vanligtvis).

4. En (eller flera) sekvenser av klick utlöser en eller annan 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)" ]

Krokar är uppdelade i två typer. Inbyggd, med "talande" namn (NewWord, NewSentence, Compose) och programmerbar.

Programmerbara namn börjar med "Action". Därför att TOML v1.4, namn med prickar måste stå inom citattecken.

Varje avsnitt ska beskrivas nedan med samma namn.

För att inte blåsa folks sinnen med "nakna" stammisar (av erfarenhet, deras skrivakanske en av tio proffs), implementerar jag omedelbart ytterligare syntax.

  • "AV:" (eller "PÅ:") innan regexp (reguljärt uttryck) kräver att följande knappar släpps (eller trycks ned).
    Därefter ska jag göra ett "orättvist" reguljärt uttryck. Med separat kontroll av bitar mellan rör "|". För att minska antalet poster som "[LR]_SHIFT" (där detta uppenbarligen inte är nödvändigt).
  • "SEQ:" Om det tidigare villkoret är uppfyllt (eller saknas), kontrollerar vi mot ett "normalt" reguljärt uttryck. För detaljer skickar jag omedelbart "regexp"-biblioteket till ^W. Eftersom jag fortfarande inte har brytt mig om att ta reda på graden av kompatibilitet med min favorit pcre ("perl-kompatibel").
  • Uttrycket skrivs i formen "BUTTON_1: CODE1, BUTTON_2: CODE2" etc., i den ordning i vilken skanningskoderna tas emot.
  • Checken är alltid "snugged" till slutet av sekvensen, så det finns inget behov av att lägga till "$" i svansen.
  • Alla kontroller på en rad utförs en efter en och kombineras med "jag". Men eftersom värdet beskrivs som en array kan du skriva en alternativ kontroll efter kommatecken. Om detta behövs av någon anledning.
  • Värde "SeqLength = 8" begränsar storleken på bufferten mot vilken alla kontroller utförs. Därför att Jag har (tills nu) aldrig mött oändliga resurser i mitt liv.

5. Inställning av krokarna som beskrivs i föregående 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.

Huvudsaken här är "Action = [Array]". I likhet med föregående avsnitt finns det en begränsad uppsättning inbyggda åtgärder. Och möjligheten till dockning är i princip inte begränsad (skriv "Action.XXX" och var inte för lat för att skriva ett annat avsnitt för det).
I synnerhet är återskrivningen av ett ord i den korrigerade layouten uppdelad i två delar: "ändra layouten som anges där" и "skriva om" ("RetypeWord").

De återstående parametrarna skrivs till "ordboken" ("karta" på golang) för en given åtgärd beror deras lista på vad som står i "Action".

Flera olika handlingar kan beskrivas i en hög (avsnitt). Eller så kan du dra isär den. Som jag visade ovan.

Jag ställde omedelbart in "Exec"-åtgärden för att köra det externa skriptet. Med möjlighet att trycka in den inspelade bufferten till stdin.

  • "Vänta = 1" - vänta tills den pågående processen är klar.
  • Förmodligen, "till högen" kommer du att vilja sätta ytterligare människor i miljön. information som namnet på fönsterklassen från vilken den avlyssnas.
    "Vill du koppla din förare? Det är dit du måste gå."

Puh (utandad). Det verkar som att jag inte har glömt någonting.

hoppsan! Ja, jag glömde inte...
Var är startkonfigurationen? I hårdkod? Sådär:

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

Var glömde jag/gjorde ett misstag? (inget sätt utan detta), jag hoppas verkligen att uppmärksamma läsare inte blir för lata för att sticka näsan.

Lycka till!

Källa: will.com

Lägg en kommentar