Layoutkorrigeraren "xswitcher" för linux: steg tvÄ

Eftersom föregĂ„ende publikation (xswitcher i "proof of concept"-stadiet) har fĂ„tt en hel del konstruktiv feedback (vilket Ă€r trevligt), jag fortsatte att spendera min fritid pĂ„ att utveckla projektet. Nu vill jag spendera lite av er
 Det andra steget kommer att vara ganska ovanligt: förslag/diskussion om konfigurationens design.

Layoutkorrigeraren "xswitcher" för linux: steg tvÄ

Det verkar stÄ sÄ till att det Àr djupt trÄkigt för normala programmerare att stÀlla in alla dessa kontroller.

För att inte vara orolig har jag ett exempel hÀr pÄ vad jag jobbar med.
UtmÀrkt genomtÀnkt (och vÀl genomfört) Apache Kafka & ZooKeeper.
— Konfiguration? Men det Ă€r sĂ„ trĂ„kigt! SlĂ€nga ihop xml (eftersom det Ă€r "ur lĂ„dan").
— Åh, vill ni ocksĂ„ ha ACL? Men det Ă€r sĂ„ komplicerat! SlĂ€nga ihop
 SĂ„dant.

Men i mitt arbete Àr det precis tvÀrtom. RÀtt (tyvÀrr Àr det nÀstan aldrig korrekt frÄn första gÄngen) den byggda modellen tillÄter enkelt och obehindrat vidare (nÄja, nÀstan) att samla schemat.

Jag snubblade nyligen över en artikel pĂ„ HabrĂ© om det tuffa arbetet hos data-scientister

Det visar sig att den aspekten Ă€r fullt realiserad hos dem. Men i min praktik, som man brukar sĂ€ga, "light-versionen". MĂ„ngfacetterade modeller, erfarna programmerare med OOP i bakgrunden osv. — det kommer senare, nĂ€r/om det tar fart. Men byggaren mĂ„ste börja hĂ€r och nu.

LÄt oss komma till saken. Som syntaktisk grund tog jag TOML frÄn denna medborgare.

För att han (TOML) Ă„ ena sidan Ă€r mĂ€nniskovĂ€nlig redigerbar. Å andra sidan översĂ€tts han 1:1 till vilken av de mer vanliga syntakterna: XML, JSON, YAML.
Mer Àn sÄ, min implementering frÄn "github.com/BurntSushi/toml" Àven om den inte Àr den mest trendiga (fortfarande syntax 1.4), Àr syntaxmÀssigt kompatibel med samma ("inbyggda") JSON.

Det vill sÀga, om man sÄ önskar kan man enkelt sÀga "gÄ bort med din TOML, jag vill ha XXX" och "patcha" koden med bara en rad.

DĂ€rmed, om man vill skriva nĂ„got för att stĂ€lla in xswitcher (definitivt inte jag) problem "med er jĂ€kla config" — förvĂ€ntas inte.

För alla andra Àr syntaxen baserad pÄ "nyckel = vÀrde" (och bokstavligen med ett par svÄrare alternativ, typ = [nÄgon, sorts, array]) tror jag
Àr intuitivt anvÀndbar.
Vad som Àr intressant Àr att han sjÀlv "brann upp" ungefÀr vid samma tid (runt 2013). Men till skillnad frÄn mig, sÄ kom TOML:s skapare in med rÀtt stor kraft.

DÀrför Àr det nu enklare för mig att anpassa dess implementering Ät mig sjÀlv, istÀllet för tvÀrtom.

I stort sett tar vi TOML (mycket lik den gamla Windows INI). Och vi sĂ€tter upp en konfiguration dĂ€r vi beskriver hur vi kopplar en serie hooks beroende pĂ„ den senaste uppsĂ€ttningen skenkoder frĂ„n tangentbordet. Nedan i stycken – det hĂ€r Ă€r vad som hittills har gjorts. Och förklaringar till varför jag har beslutat sĂ„.

0. GrundlÀggande abstraktioner

  • Beteckningar för skenkoder. Det hĂ€r mĂ„ste definitivt göras nĂ„got Ă„t, eftersom de bara numeriska koderna Ă€r helt otydliga för mĂ€nniskor (det Ă€r vad jag menar). loloswitcher).
    Jag har extraherat «ecodes.go» frĂ„n «golang-evdev» (jag orkade inte kolla kĂ€llan, Ă€ven om den anges kulturellt av författaren). Jag har lite (för tillfĂ€llet) justerat det som Ă€r riktigt ful. Typ «LEFTBRACE» → «L_BRACE».
  • Dessutom har jag introducerat begreppet «tangenter med tillstĂ„nd». Eftersom den anvĂ€nda reguljĂ€ra grammatiken inte tillĂ„ter lĂ€ngre passager. (Men det gör att man kan kontrollera med minimala overhead. Om man bara anvĂ€nder «direkt» inspelning.)
  • Det kommer att finnas en inbyggd «deduplicator» för tryckta tangenter. PĂ„ sĂ„ sĂ€tt kommer tillstĂ„ndet «upprepning»=2 att registreras. en gĂ„nger ner.

1. Mallsavsnitt

[Templates] # "@name@" för att förenkla uttryck
 # Ord kan bestÄ av dessa tecken (regex)
 "WORD" = "([0-9A-Z`;']|[LR]_BRACE|COMMA|DOT|SLASH|KP[0-9])"

Vad bestĂ„r ett ord i mĂ€nskligt sprĂ„k av med fonetisk skrivning (till skillnad frĂ„n grafemer aka «tecken»)? КаĐșая-Ń‚ĐŸ ŃƒĐ¶Đ°ŃĐœĐ°Ń Â«ĐżŃ€ĐŸŃŃ‚Ń‹ĐœŃÂ». ĐŸĐŸŃŃ‚ĐŸĐŒŃƒ ŃŃ€Đ°Đ·Ńƒ заĐșлаЎыĐČаю ĐżĐŸĐœŃŃ‚ĐžĐ” Â«ŃˆĐ°Đ±Đ»ĐŸĐœĐ°Â».

2. Vad göra nÀr nÄgot trycks pÄ (kom en ny skenkod)

[ActionKeys]
 # Samla nycklar och testa för kommandosekvens
 # !!! Upprepade koder (code=2) mÄste samlas in en gÄng per tangent!
 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"]

 # Droppa alla insamlade tangenter, inklusive denna. Detta Àr standardÄtgÀrd.
 Drop = ["ESC", "-", "TAB", "ENTER", "KPENTER", "LINEFEED..POWER"]
 # Lagra extra karta för dessa tangenter, nÀr nÄgon Àr i «down»-lÀge.
 # TillstÄndet kontrolleras via "OFF:"|"ON:" villkor i ÄtgÀrden.
 # (Dessutom mÄste tillstÄndet för dessa tangenter bestÄ mellan buffertdroppar.)
 # ??? Hur hanterar man CAPS och «LOCK»-tangenter ???
 StateKeys = ["L_CTRL", "L_SHIFT", "L_ALT", "L_META", "CAPS", "N_LOCK", "S_LOCK",
              "R_CTRL", "R_SHIFT", "R_ALT", "R_META"]

 # Testa endast, men samla inte in.
 # Exempelvis, jag anvÀnder F12 istÀllet för BREAK pÄ dumma bÀrbara datorer med dÄliga tangentbord (nya ThinkPads)
 Test = ["F1..F10", "ZENKAKUHANKAKU", "102ND", "F11", "F12",
          "RO..KPJPCOMMA", "SYSRQ", "SCALE", "HANGEUL..YEN",
          "STOP..SCROLLDOWN", "NEW..MAX"]

Totalt finns det 768 koder. (Men «för sÀkerhets skull» lade jag in en fÄngst av «överraskningar» i koden).
Inuti har jag sammanstÀllt en array med hÀnvisningar till funktionerna «vad man ska göra». PÄ golang Àr detta (överraskande nog) visade sig vara praktiskt och uppenbart.

  • Jag planerar att minimera «Drop» hĂ€r. Till förmĂ„n för en mer flexibel bearbetning (se nedan).

3. Tabell med klassnamn för fönster

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

Raderna i tabellen — inom dubbla hakparenteser med dess namn. Det var svĂ„rt att fĂ„ till det frĂ„n början. Beroende pĂ„ det aktuella aktiva fönstret kan alternativ vĂ€ljas:

  • Egen uppsĂ€ttning av «hotkeys» «Actions =  ». Om det inte finns/pĂ„ blank — gör ingenting.
  • Ombrytaren «MouseClickDrops» — vad som ska göras vid registrering av ett musklick. Eftersom det inte finns nĂ„gra detaljer om «var de klickar» i xswitcher, Ă„terstĂ€lls bufferten som standard. Men pĂ„ terminaler kan man kanske hoppa över det. (som regel).

4. En (eller flera) trycksekvenser aktiverar en viss 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)" ]

Jag har delat upp hooks i tvÄ typer. Inbyggda med «talar» namn (NewWord, NewSentence, Compose) och programmerbara.

Namn pÄ programmerbara börjar med «Action.». Eftersom TOML v1.4, ska namn med punkter vara inom citationstecken.

Nedan ska varje antecknas med en avsnitt med samma namn.

För att inte övervÀldiga folk med «nakna» reguljÀra uttryck (frÄn erfarenhet, deras skriva-det kan vara en av tio yrkesverksamma), introducerar jag genast en extra syntax.

  • «OFF:» (eller «ON:») före regexp (reguljĂ€rt uttryck) krĂ€ver att de angivna knapparna ska vara slĂ€ppta (eller tryckta).
    DÀrefter planerar jag att skapa ett «orÀttvist» reguljÀrt uttryck. Med separat verifiering av segment mellan rörledningar "|". För att minska antalet postningar som "[LR]_SHIFT" (dÀr det uppenbarligen inte behövs).
  • «SEQ:» Om det föregĂ„ende villkoret Ă€r uppfyllt (eller saknas), kontrolleras det gĂ€llande det «vanliga» reguljĂ€ra uttrycket. För detaljer hĂ€nvisar jag direkt till biblioteks «regexp». För jag har fortfarande inte lyckats ta reda pĂ„ graden av kompatibilitet med mina favoritpcre («perl compatible»).
  • Uttrycket skrivs som «KNAPP_1: KOD1, KNAPP_2: KOD2» och sĂ„ vidare, i ordning av inkommande skenkoder.
  • Kontrollen Ă€r alltid «tryckt» mot slutet av sekvensen, sĂ„ dĂ€rför behöver inget "$» lĂ€ggas till i slutet.
  • Alla kontroller i en rad utförs i följd och kombineras med «OCH». Men eftersom vĂ€rdet Ă€r beskrivet som en array kan en alternativ kontroll skrivas efter kommatecknet. Om det behövs av nĂ„gon anledning.
  • VĂ€rde «SeqLength = 8» begrĂ€nsar storleken pĂ„ bufferten, i förhĂ„llande till vilken alla kontroller utförs. Eftersom jag (inte Ă€n) har stött pĂ„ oĂ€ndliga resurser i mitt liv.

5. UppgiftshÀmtning som beskrivits 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.

Det viktiga hĂ€r Ă€r — «Action = [Array]». PĂ„ samma sĂ€tt som i föregĂ„ende avsnitt finns det en begrĂ€nsad uppsĂ€ttning inbyggda Ă„tgĂ€rder. Och en i princip obegrĂ€nsad möjlighet till koppling (att skriva «Action.XXX» och inte vara lat nog att beskriva en ytterligare sektion för den).
Inklusive, att omkonfigurera ordet i den korrekta layouten Àr uppdelat i tvÄ delar: «Àndra layout som angivet dÀr borta» och «omskriv» («RetypeWord»).

Övriga parametrar skrivs in i en «ordbok» («map» i golang) för denna Ă„tgĂ€rd, deras lista beror pĂ„ vad som skrivs i «Action».

Flera olika ÄtgÀrder kan beskrivas i ett enda block (sektioner). Och man kan dela upp dem. Som jag visade ovan.

Jag lĂ€gger omedelbart in Ă„tgĂ€rden «Exec» — utföra ett externt skript. Med alternativet att matas in i stdin en inspelad buffert.

  • «Wait = 1» — vĂ€nta pĂ„ att den startade processen ska avslutas.
  • Förmodligen, «dessutom» vill man stĂ€lla in ytterligare information i miljön, som namn pĂ„ fönsterklassen frĂ„n vilken det har fĂ„ngats.
    «Vill du ansluta din egen handler? Du gÄr hit.»

Puh (lÀttat). Verkar som jag inte glömde nÄgot.

Oj! Ja, jag glömde inte

Men var Àr startkonfigurationen? I hard-koden? UngefÀr sÄ hÀr:

[ScanDevices]
 # MÄste finnas vid start. SjÀlvrestarts om den Àr yngre Àn 30 sekunder
 Test = "/dev/input/event0"
 Respawn = 30
 # Sökmask
 Search = "/dev/input/event*"
 # I mina thinkPads finns det sÄdana pseudo-tangentbord med massor av onödiga hÀndelser
 Bypass = "(?i)Video|Camera" # "(?i)" skiljer sig tydligt frÄn "klassisk" pcre.

Och var glömde/eller gjorde jag fel (utan detta — inget), hoppas jag verkligen att uppmĂ€rksamma lĂ€sare inte tvekar att peka med nĂ€san.

Lycka till!

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster