Korrigjuesi i layout-eve "hswitcher" për linux: hapa i dytë

Sepse postimi i mëparshëm (switcher në fazën «provë koncepti») ka marrë mjaft komente konstruktive (çfarë është e këndshme), vazhdova të shpenzoj kohën time të lirë për të zhvilluar projektin. Tani dua të shpenzoj pak nga koha juaj... Hapi i dytë do të jetë disi i pazakontë: propozimi/diskutimi i dizajnit të konfiguracionit.

Korrigjuesi i layout-eve "hswitcher" për linux: hapa i dytë

Duket që programuesit normalë e gjejnë tejkalimin e këtyre rregullimeve mjaft të mërzitshëm.

Për të mos qenë thjesht flasës, brenda kam një shembull nga ajo me të cilën merrem.
Shumë mirë në thelb të menduar (dhe të realizuara mirë) Apache Kafka & ZooKeeper.
— Konfigurimi? Por kjo është e mërzitshme! Të bësh shpejt xml (sepse është «nga caja»).
— Oh, a doni edhe ACL? Por kjo është kaq e lodhshme! Të bësh shpejt… Ja si është.

Por në punën time — pikërisht e kundërta. E drejtë (fatkeqësisht, nga hera e parë pothuajse kurrë nuk ka) modeli i ndërtuar lejon më tej të ndërtohet me lehtësi dhe pa stres (më shumë ose më pak) një skemë.

Kohët e fundit pashë një artikull në Habr rreth punës së vështirë të data-scientistëve...
Duket se ky moment realizohet në mënyrë të plotë. Ndërsa në praktikën time, siç thonë, është "versioni light". Modelet shumëvëllimëshe, programuesit e sofistikuar me OOP dhe kështu me radhë - këto do të shfaqen më vonë, kur / nëse fluturojnë. Por konstruktori ka nevojë për të filluar tani dhe këtu me diçka.

Më afër temës. Si bazë sintaksore kam marrë TOML nga ky qytetar.

Sepse ai (TOML) nga njëra anë është i redaktueshëm nga njeriu. Ndërsa nga ana tjetër - përkthehet 1:1 në cilindo nga sintaksat më të përhapura: XML, JSON, YAML.
Për më tepër, implementimi që kam përdorur nga "github.com/BurntSushi/toml" është ndoshta jo më modern (është ende sintaksa 1.4), por është sintaksikisht e përputhshme me të njëjtin ("të integruar") JSON.

Pra, nëse dëshiron, mund të thuash "shko në pyll me këtë TOML tëndin, dua XXX" dhe "të patchosh" kodin me vetëm një rresht.

Prandaj, nëse dëshiron të shkruash për të konfiguruar hswitcher disa dritare (sigurisht që nuk jam unë) nuk parashikohet ndonjë problem me "këtë konfigurimin tuaj të mallkuar".

Për të gjitha sintaksat e tjera të bazuara në "çelësi = vlerë" (dhe me disa mundësi disi më të komplikuara, si = [ndonjë, array]) mendoj
e lehtë për t'u përdorur intuitivisht.
Çfarë është e habitshme, vetë «u ngjit» në të njëjtën kohë (rreth vitit 2013). Vetëm, ndryshe nga unë, autori i TOML-i e mori me një hap të duhur.

Prandaj tani më lehtë është të përshtas realizimin e tij për veten, e jo e kundërta.

Në përgjithësi, marrim TOML (shumë i ngjashëm me INI-n e vjetër të Windows). Dhe krijojmë konfigurimin, në të cilin përshkruajmë se si të mbështesim një seri hook-ash në varësi të setit të fundit të kodave të skenave nga tastiera. Më poshtë në copa — ajo që kam arritur deri tani. Dhe shpjegimet pse kam vendosur kështu.

0. Abstraksionet themelore

  • Shkronjat e kodave të skenave. Ndaj të sigurohemi që duhet të bëhet diçka, pasi kodat numerikë të thjeshtë janë plotësisht të pa lexueshëm për njeriun (kjo është gjithashtu në kopsht loloswitcher).
    Kam marrë ‘ecodes.go’ nga ‘golang-evdev’ (nuk pata dëshirë të hyj në burimin origjinal, edhe pse autori e ka përmendur atë me kulturë). Paksa (deri tani) kam korrigjuar disa gjëra të tmerrshme. Si ‘LEFTBRACE’ → ‘L_BRACE’.
  • Shtova gjithashtu nocionin e ‘çelësave me gjendje’. Duke pasur parasysh se gramatika e rregullt e përdorur nuk lejon pasazhe të gjata. (Megjithatë lejon të kontrollosh me kosto minimale. Nëse përdorim vetëm regjistrimin ‘direkt’.)
  • Do të ketë një «deduplication» të integruar të shtypjes. Kështu, gjendja «ripet» = 2 do të regjistrohet. një herë.

1. Seksioni i template-ve

[Templates] # "@name@" për të thjeshtuar shprehjet
 # Fjalët mund të përbëhen nga këto karaktere (regex)
 "WORD" = "([0-9A-Z`;']|[LR]_BRACE|COMMA|DOT|SLASH|KP[0-9])"

Çfarë përbën një fjalë në gjuhën njerëzore me shkrim fonetik. (nuk ka të bëjë me grafemat pra «hieroglifet»)? Какая-то ужасная «простыня». Поэтому сразу закладываю понятие «шаблона».

2. Çfarë të bëni kur diçka është shtypur (ka ardhur një kodi i ri skenimi)

[ActionKeys]
 # Mblidhni çelësin dhe bëni testin për sekuencën e komandës
 # !!! Kodet e përsëritura (kode=2) duhet të mblidhen një herë për çelës!
 Shto = ["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"]

 # Hiqni të gjithë çelësat e mbledhur, duke përfshirë këtë. Kjo është veprimi default.
 Hiq = ["ESC", "-", "TAB", "ENTER", "KPENTER", "LINEFEED..POWER"]
 # Ruani një hartë shtesë për këta çelës, kur ndonjëri është në gjendjen "poshtë".
 # Gjendja kontrollohet përmes kushteve "OFF:"|"ON:" në veprim.
 # (Gjendja e këtyre çelësave duhet të vazhdojë mes heqjes së tamponëve.)
 # ??? Si të merremi me CAPS dhe çelësat "LOCK" ???
 StateKeys = ["L_CTRL", "L_SHIFT", "L_ALT", "L_META", "CAPS", "N_LOCK", "S_LOCK",
              "R_CTRL", "R_SHIFT", "R_ALT", "R_META"]

 # Vetëm për testim, por mos e mblidhni.
 # Për shembull, unë përdor F12 në vend të BREAK në laptopët e padashur me tastiera të këqija (ThinkPads e reja)
 Testo = ["F1..F10", "ZENKAKUHANKAKU", "102ND", "F11", "F12",
          "RO..KPJPCOMMA", "SYSRQ", "SCALE", "HANGEUL..YEN",
          "STOP..SCROLLDOWN", "NEW..MAX"]

Ka gjithsej 768 kode. (Por «për çdo rast» e kam futur në kod xswitcher për të kapur «surprizat»).
Brenda e përshkruan plotësimin e një array me lidhjet për funksionin "çfarë të bëj". Në golang kjo është (papritmas) dukur e përshtatshme dhe e qartë.

  • "Drop" këtu planifikoj ta shkurtoj në minimum. Në favor të përpunimit më fleksibël (do ta tregoj më poshtë).

3. Tabela me klasat e dritareve

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

Rreshtat e tabelës - në katrorët e dyfishtë me emrin e saj. Nuk pati qasje të thjeshtë. Në varësi të dritares aktive aktualisht, mund të zgjidhni opsionet:

  • Grumbulli juaj i "çelësave të nxehtë" "Actions = ...". Nëse s'ka / është e zbrazët - mos bëni asgjë.
  • Ndërruesi "MouseClickDrops" - çfarë të bëni kur zbulohet një klikim me miun. Përderisa në pikën e aktivizimit të xswitcher, nuk ka detaje "ku klikojnë", si parazgjedhje e kthen bufferin. Por në terminale (për shembull) kjo mund të mos bëhet fare. (së zakonsh mim).

4. Një (ose disa) sekuenca të shtypjeve aktivizojnë një hook të caktuar

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

Hooks i kam ndarë në dy lloje. Të integruar, me emra "flisës" (NewWord, NewSentence, Compose) dhe të programueshëm.

Emrat e atyre që mund të programohen fillojnë me "Action.!". Përsa i përket TOML v1.4, emrat me pika duhet të jenë në cita.

Më poshtë për çdo njësi duhet të jepet një seksion me të njëjtin emër.

Për të mos shpërthyer mendjet e njerëzve me "rregullat e zbrazëta" (sipas përvojës, ato shkruaj- mund të jetë një nga dhjetë profesionistët), menjëherë po zbatoj sintaksë shtesë.

  • "OFF:" (ose "ON:") para regexp (shprehje të rregullta) kërkohet që butonat e cituar më pas të jenë liruar (ose të shtypur).
    Më tutje do të bëj një shprehje të rregullt "të pandershme". Me kontroll të ndarë të segmenteve midis pipeve "|". Me qëllim për të reduktuar numrin e regjistrimeve të tipit "[LR]_SHIFT" (aty ku kjo nuk është qartë e nevojshme).
  • "SEQ:" Nëse kusht i mëparshëm është përmbushur (ose mungon), më pas kontrollojmë në lidhje me shprehjen "e zakonshme" të rregullt. Për detaje, po e përcjell menjëherë në^Wbibliotekën "regexp". Sepse akoma nuk kam marrë mundimin të zbuloj shkallën e përputhshmërisë me pcr të dashura për mua ("perl compatible").
  • Shprehja regjistrohet në formën e "BUTONA_1: KOD1, BUTONA_2: KOD2" dhe kështu me radhë, në rendin e ardhjes së kodave input.
  • Kontrolli gjithmonë "ngjitet" në fund të sekuencës, prandaj "$" në fund nuk duhet shtuar.
  • Të gjitha verifikimet në një rresht kryhen njëra pas tjetrës dhe bashkohen sipas "I". Por, pasi vlera është përshkruar si një varg, mund të shkruani një kontroll alternativ pas presjes. Nëse kjo është ndonjëherë e nevojshme.
  • Vlera "SeqLength = 8" kufizon madhësinë e buffers, në lidhje me të cilin bëhen të gjitha kontrollet. Sepse, në jetën time (ende), nuk kam hasur burime të pafundme.

5. Caktimi i hook-ëve, të cilat janë shpjeguara në seksionin e mëparshëm

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

E rëndësishme këtu është — "Action = [Array]". Njëjtë si në seksionin e mëparshëm, ka një grup të kufizuar të veprimeve të integruara. Dhe një mundësi e pakufizuar për ndërlidhje (shkruani "Action.XXX" dhe mos u bëni lenkë për të shpjeguar një seksion tjetër për të).
Përfshirë, rrethanat e fjalës në disposicionin e korigjuar ndahen në dy pjesë: "ndrysho disposicionin siç është përcaktuar atje" dhe "rishkruaj" ("RetypeWord").

Parametrat e tjera regjistrohen në "fjalorin" ("map" në golang) për këtë veprim, lista e tyre varet nga ajo që shkruhet në "Action".

Disa veprime të ndryshme mund të përshkruhen në një grup (seksione). Ose mund t’i ndajmë. Siç e kam treguar më lart.

Menjëherë vendos veprimin "Exec" — ekzekuto skenarin e jashtëm. Me opsionin për të futur në stdin buffer-in e regjistruar.

  • «Wait = 1» — prisni përfundimin e procesit të nisur.
  • Në dukje, mund të dëshirosh të shtosh informacione shtesë në mjedis, si emrin e klasës së dritares nga e cila është kapur.
    «Doni të lidhni trajtuesin tuaj? Ja këtu.»

Uf (shkëputur). Të paktën nuk kam harruar ndonjë gjë.

Op! Po, nuk e harrova...
Po ku është konfigurimi i lançimit? Në hard-kodim? Diçka si:

[ScanDevices]
 # Duhet të ekzistojë në fillim. Rishfaqet në rast se është më i ri se 30s
 Test = "/dev/input/event0"
 Respawn = 30
 # Maske e kërkimit
 Search = "/dev/input/event*"
 # Në ThinkPad-et e mia ka një keyboard të tillë virtual që ka shumë ngjarje të panevojshme
 Bypass = "(?i)Video|Camera" # "(?i)" natyrisht ndryshon nga pcre klasike.

Po ku harrova/gabova (pa këtë — asnjëherë), shpresoj shumë që lexuesit e kujdesshëm nuk do të kursehen për të na shtyrë në fytyrë.

Fat të mirë!

Burimi: habr.com

Bli një hosting të besueshëm për faqet me mbrojtje DDoS, VPS VDS serverë 🔥 Bli një hosting të besueshëm për faqet me mbrojtje DDoS, VPS VDS serverë | ProHoster