Xswitcher layout corrector para sa linux: ikalawang hakbang

Bilang nakaraang publikasyon (xswitcher sa yugto ng "patunay ng konsepto") ay nakatanggap ng napakaraming nakabubuong feedback (ang maganda), patuloy kong ginugugol ang aking libreng oras sa pagbuo ng proyekto. Ngayon gusto kong gumastos ng kaunti sa iyong... Ang pangalawang hakbang ay hindi magiging karaniwan: panukala/talakayan ng disenyo ng pagsasaayos.

Xswitcher layout corrector para sa linux: ikalawang hakbang

Sa paanuman, lumalabas na ang mga normal na programmer ay nakakatamad na i-set up ang lahat ng mga kontrol na ito.

Upang hindi maging walang batayan, sa loob ay isang halimbawa ng kung ano ang aking pakikitungo.
Sa pangkalahatan, mahusay na ipinaglihi (at mahusay na ipinatupad) ang Apache Kafka at ZooKeeper.
- Configuration? Pero nakakatamad! Dumb xml (dahil ito ay "out of the box").
- Oh, gusto mo rin ba ng ACL? Pero sobrang boring! Tap-blooper... Isang bagay na ganyan.

Ngunit sa aking trabaho ito ay eksaktong kabaligtaran. Tama (sayang, halos hindi sa unang pagkakataon) pinapayagan ka ng itinayong modelo na magpatuloy nang mas madali at natural (halos) bumuo ng isang diagram.

Nakatagpo ako kamakailan ng isang artikulo sa Habré tungkol sa pagsusumikap ng mga data scientist...
Ito ay lumiliko na ang sandaling ito ay ganap na natanto para sa kanila. At sa aking pagsasanay, tulad ng sinasabi nila, "magaan na bersyon". Mga multi-volume na modelo, mga batikang programmer na may nakahanda nang OOP, atbp. — lahat ito ay lilitaw sa ibang pagkakataon kapag/kung ito ay aalis. Ngunit ang taga-disenyo ay kailangang magsimula sa isang lugar dito at ngayon.

Umabot sa punto. Kinuha ko ang TOML bilang isang syntactic na batayan mula sa mamamayang ito.

Kasi siya (TOML) sa isang banda, nae-edit ng tao. Sa kabilang banda, isinalin ito sa 1:1 sa alinman sa mga mas karaniwang syntax: XML, JSON, YAML.
Bukod dito, ang pagpapatupad na ginamit ko mula sa "github.com/BurntSushi/toml", kahit na hindi ang pinaka-sunod sa moda (1.4 syntax pa rin), ay syntactically compatible sa parehong ("built-in") JSON.

Ibig sabihin, kung gusto mo, masasabi mo lang na "dumaan sa kakahuyan ang TOML mo, gusto ko ng XXX" at "i-patch" ang code sa isang linya lang.

Kaya, kung nais mong magsulat ng ilang mga bintana upang i-configure ang xswitcher (Hindi ako sigurado) Walang inaasahang problema "sa mapahamak na config mo."

Para sa lahat ng iba pa, ang syntax ay batay sa "key = value" (at literal na ilang mas kumplikadong mga opsyon, tulad ng = [some, that, array]) Siguro
intuitively maginhawa.
Ang kawili-wili ay iyon "nasunog" sa parehong oras (sa paligid ng 2013). Tanging, hindi katulad ko, ang may-akda ng TOML ay pumasok sa tamang sukat.

Samakatuwid, ngayon mas madali para sa akin na ayusin ang pagpapatupad nito upang umangkop sa aking sarili, at hindi kabaliktaran.

Sa pangkalahatan, kinukuha namin ang TOML (napakapareho sa lumang Windows INI). At mayroon kaming configuration kung saan inilalarawan namin kung paano mag-attach ng isang serye ng mga hook depende sa hanay ng mga pinakabagong scan code mula sa keyboard. Sa ibaba, pira-piraso, ay kung ano ang nangyari sa ngayon. At isang paliwanag kung bakit ako nagpasya sa ganitong paraan.

0. Mga pangunahing abstraction

  • I-scan ang mga pagtatalaga ng code. May kailangang gawin tungkol dito, dahil ang simpleng mga digital code ay talagang hindi nababasa ng tao (ako lang iyon loloswitcher).
    Inalis ko ang "ecodes.go" mula sa "golang-evdev" (tamad akong tumingin sa orihinal na pinagmulan, kahit na ipinahiwatig ito ng may-akda sa kultura). Nagtama ako ng kaunti (sa ngayon) isang bagay na medyo nakakatakot. Tulad ng “LEFTBRACE” → “L_BRACE”.
  • Bukod pa rito, ipinakilala niya ang konsepto ng "mga susi ng estado". Dahil ang regular na gramatika na ginamit ay hindi nagpapahintulot ng mahabang mga sipi. (Ngunit pinapayagan ka nitong suriin nang may kaunting overhead. Kung gumagamit ka lamang ng "direktang" pag-record.)
  • Magkakaroon ng built-in na "deduplicator" ng kung ano ang pinindot. Kaya, ang estado na "repeat"=2 ay isusulat isa beses.

1. Seksyon ng mga template

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

Ano ang binubuo ng salitang wika ng tao na may phonetic notation? (alinman sa mga graphemes aka "hieroglyphs")? Isang uri ng kahila-hilakbot na "sheet". Samakatuwid, agad kong ipinakilala ang konsepto ng "template".

2. Ano ang gagawin kapag may na-click (may dumating na ibang scan code)

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

Mayroong 768 na mga code sa kabuuan. (Ngunit "kung sakali" ipinasok ko ang pagkuha ng "mga sorpresa" sa xswitcher code).
Sa loob ay inilarawan ko ang pagpuno sa array ng mga link sa mga function na "ano ang gagawin". Sa golang ito ay (bigla) Ito ay naging maginhawa at halata.

  • Plano kong bawasan ang "Drop" sa pinakamababa sa lugar na ito. Pabor sa mas nababaluktot na pagproseso (ipapakita ko ito sa ibaba).

3. Table na may window classes

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

Ang mga hilera ng talahanayan ay nasa double square bracket na may pangalan nito. Hindi ito maaaring naging mas madali kaagad sa bat. Depende sa kasalukuyang aktibong window, maaari mong piliin ang mga sumusunod na opsyon:

  • Ang iyong sariling hanay ng "mga hot key" "Mga Pagkilos = ...". Kung wala/walang laman, walang gawin.
  • Lumipat sa "MouseClickDrops" - kung ano ang gagawin kapag may nakitang pag-click ng mouse. Dahil sa punto kung saan naka-on ang xswitcher ay walang mga detalye tungkol sa "kung saan sila nag-click," ni-reset namin ang buffer bilang default. Ngunit sa mga terminal (halimbawa) hindi mo kailangang gawin ito (karaniwan).

4. Ang isa (o ilang) sequence ng mga click ay nagti-trigger ng isa o isa pang 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)" ]

Ang mga kawit ay nahahati sa dalawang uri. Built-in, na may mga pangalang nagpapaliwanag sa sarili (NewWord, NewSentence, Compose) at programmable.

Nagsisimula sa “Action” ang mga programmable na pangalan. kasi TOML v1.4, ang mga pangalan na may mga tuldok ay dapat nasa mga quote.

Ang bawat seksyon ay dapat na inilarawan sa ibaba na may parehong pangalan.

Upang hindi mapukaw ang isip ng mga tao sa "hubad" na mga regular (mula sa karanasan, kanilang isulatbaka isa sa sampu mga propesyonal), agad akong nagpapatupad ng karagdagang syntax.

  • "OFF:" (o "ON:") bago ang regexp (regular na expression) ay nangangailangan na ang mga sumusunod na pindutan ay ilabas (o pinindot).
    Susunod na gagawin ko ang isang "hindi patas" na regular na expression. Sa hiwalay na pagsuri ng mga piraso sa pagitan ng mga tubo "|". Upang mabawasan ang bilang ng mga tala tulad ng "[LR]_SHIFT" (kung saan ito ay malinaw na hindi kinakailangan).
  • "SEQ:" Kung ang nakaraang kundisyon ay natugunan (o wala), pagkatapos ay susuriin namin ang isang "normal" na regular na expression. Para sa mga detalye, ipinadala ko kaagad sa ^W ang “regexp” library. Dahil hindi pa rin ako nag-abala na malaman ang antas ng pagiging tugma sa aking paboritong pcre (“perl compatible”).
  • Ang expression ay nakasulat sa form "BUTTON_1: CODE1, BUTTON_2: CODE2" atbp., sa pagkakasunud-sunod kung saan natanggap ang mga scan code.
  • Ang tseke ay palaging "nakakulong" hanggang sa dulo ng pagkakasunud-sunod, kaya hindi na kailangang magdagdag ng "$" sa buntot.
  • Ang lahat ng mga tseke sa isang linya ay isinasagawa ng isa-isa at pinagsama ng "I". Ngunit dahil ang value ay inilalarawan bilang array, maaari kang magsulat ng alternatibong check pagkatapos ng kuwit. Kung ito ay kinakailangan para sa ilang kadahilanan.
  • Halaga "SeqLength = 8" nililimitahan ang laki ng buffer kung saan isinasagawa ang lahat ng pagsusuri. kasi Wala akong (hanggang ngayon) nakatagpo ng walang katapusang mga mapagkukunan sa aking buhay.

5. Pagtatakda ng mga kawit na inilarawan sa nakaraang seksyon

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

Ang pangunahing bagay dito ay "Action = [Array]". Katulad ng nakaraang seksyon, mayroong isang limitadong hanay ng mga built-in na aksyon. At ang posibilidad ng docking ay hindi limitado sa prinsipyo (isulat ang "Action.XXX" at huwag masyadong tamad na magsulat ng isa pang seksyon para dito).
Sa partikular, ang muling pag-type ng isang salita sa itinamang layout ay nahahati sa dalawang bahagi: "baguhin ang layout gaya ng tinukoy doon" и “retype” (“RetypeWord”).

Ang natitirang mga parameter ay isinulat sa "diksyonaryo" ("mapa" sa golang) para sa isang partikular na aksyon, ang kanilang listahan ay nakasalalay sa kung ano ang nakasulat sa "Action".

Maraming iba't ibang mga aksyon ang maaaring ilarawan sa isang bunton (mga seksyon). O maaari mong hilahin ito. Gaya ng ipinakita ko sa itaas.

Agad kong itinakda ang pagkilos na "Exec" upang maisagawa ang panlabas na script. Gamit ang opsyon na itulak ang naitalang buffer sa stdin.

  • “Maghintay = 1” — maghintay para makumpleto ang proseso ng pagpapatakbo.
  • Malamang, "to the heap" gugustuhin mong maglagay ng karagdagang mga tao sa kapaligiran. impormasyon tulad ng pangalan ng window class kung saan ito naharang.
    “Gusto mo bang ikonekta ang handler mo? Dito mo kailangan pumunta."

Phew (exhale). Parang wala akong nakalimutan.

Oops! Oo, hindi ko nakalimutan...
Nasaan ang configuration ng paglunsad? Sa hard code? Tulad niyan:

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

Saan ako nakalimutan/nagkamali? (walang paraan kung wala ito), I really hope na ang mga maasikasong mambabasa ay hindi masyadong tamad na tumusok ng ilong.

Good luck!

Pinagmulan: www.habr.com

Magdagdag ng komento