Meqenëse (hxwitcher në fazën "provë e konceptit") ka marrë shumë mendime konstruktive (çfarë është e këndshme), vazhdova të shpenzoj kohën time të lirë për të zhvilluar projektin. Tani do të doja të shpenzoja pak nga koha juaj... Hapi i dytë do të jetë disi ndryshe: propozimi/diskutimi për dizajnin e konfiguracionit.

Si duket, programuesit normalë janë tepër të mërzitur për të rregulluar të gjitha këto mekanizma.
Për të mos qenë fjalëpërpërdorës, ja një shembull nga ajo me të cilën merrem.
Dhe përgjithësisht, janë të menduara mirë (dhe realizohen mirë) Apache Kafka & ZooKeeper.
-- Konfigurimi? Por kjo është e mërzitur! Thjesht xml (sepse "e gatshme për përdorim").
-- Oh, a doni edhe ACL? Por kjo është kaq ndërlikuar! Thjesht... Siç duket.
Por në punën time, është krejt ndryshe. Saktësisht (fatkeqësisht, nga hera e parë ndoshta kurrë) modeli i ndërtuar lejon më pas të lehtë dhe pa shqetësim (po, pothuajse) të mbledh skemën.
Para disa kohësh, përshtata një artikull në Habr për punën e vështirë të data-scientistëve...
Duket se ky moment realizohet plotësisht prej tyre. Ndërsa në praktikën time, siç thotë shprehja, "versioni lehtësuar". Modelet shumëvëllimëshe, programuesit e përkushtuar me OOP dhe kështu me radhë — gjithçka do të shfaqet më vonë, kur/ nëse ngrihet. Dhe ndërtuesi duhet të fillojë nga diçka këtu dhe tani.
Më në fund, për temën. Si bazë sintaksore, kam marrë TOML .
Sepse ai (TOML) në një anë është i redaktueshëm nga njeriu. Në anën tjetër—transmitohet 1:1 në çdo një nga sintaksat më të zakonshme: XML, JSON, YAML.
Më tej, implementimi që kam përdorur nga "github.com/BurntSushi/toml" megjithëse nuk është më modern (ndërkohë sintaksa 1.4), është gjithmonë sintaksikisht i përputhshëm me të njëjtat ("]
Pra, nëse dëshiron, mund të thuash "shkonte në pyll me këtë TOML tuajin, dua XXX" dhe "përdor" kodin e vetëm një linjë.
Kështu, nëse dëshiron të shkruash disa hapësira për konfigurimin e hxwitcher (sigurisht, jo unë) problemet "me këtë konfigurat tuaj" nuk parashikohen.
Për të tjerët, sintaksa e bazuar në "çelës = vlerë" (dhe dosje me dy mundësi më të komplikuara, si = [disa, array]) mendoj
se është intuitivisht e përshtatshme.
Çfarë është interesante, vetë afërsisht në të njëjtën kohë (rreth vitit 2013). Ndryshe nga unë, autori i TOML hyri me përmasat e duhura.
Prandaj tani më është më e lehtë të përshtatim implementimin e saj për veten time, sesa e kundërta.
Në thelb, marrim TOML (shumë i ngjashëm me INI-në e vjetër të Windows). Dhe krijojmë konfigurimin, në të cilin përshkruajmë si të lidhim një seri hook-esh në varësi të grupit të fundit të kodit të skenës nga tastiera. Më poshtë në pjesë — ajo që kam arritur deri tani. Dhe shpjegimet pse vendosa kështu.
0. Abstraksionet bazë
- Simbolet e kodit të skenës. Kështu që duhet të bëjmë diçka me këtë, pasi kodet numerike thjesht nuk janë të lexueshme nga njeriu (kjo është në kopsht) ).
Kam nxjerrë "ecodes.go" nga "golang-evdev" (nuk munda të hyj në burimin e parë, edhe pse autori e ka përmendur në mënyrë të kulturuar). Pak (për tani) kam rregulluar disa gjëra të frikshme. Njësoj si "LEFTBRACE" → "L_BRACE". - Shtova gjithashtu konceptin e "çelësave me gjendje". Pasi gramatika e rregullt e përdorur nuk lejon pasazhe të gjata. (Megjithatë lejon të kontrollohet me mbivendosje minimale. Nëse përdoret vetëm regjistrimi "direkt".)
- Do të ketë një "deduplicitator" të integruar për shtypjet. Kështu që, gjendja "përsërit"=2 do të regjistrohet një herë.
1. Seksioni i shablloneve
[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])"
Nga çfarë përbëhet një fjalë e gjuhës njerëzore me shkrimin fonetik (sikur të ishin grafemat aka "hieroglifet")? Какая-то ужасная «простыня». Поэтому сразу закладываю понятие «шаблона».
2. Çfarë të bëni kur diçka është shtypur (ka ardhur një kod i ri skene)
[ActionKeys]
# Mbledh çelësin dhe teston sekuencën e komandës
# !!! Kodet e përsëritura (kode=2) duhet të mblidhen një herë për çelësin!
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, përfshirë këtë. Kjo është veprimi i paracaktuar.
Hiq = ["ESC", "-", "TAB", "ENTER", "KPENTER", "LINEFEED..POWER"]
# Ruani hartën shtesë për këta çelësa, kur ndonjëri është në gjendjen "poshtë".
# Gjendja kontrollohet përmes kushteve "OFF:"|"ON:" në veprim.
# (Gjithashtu, gjendja e këtyre çelësave duhet të ruhet midis heqjeve të tamponit.)
# ??? Si të merremi me CAPS dhe "LOCK"-çelësat ???
StateKeys = ["L_CTRL", "L_SHIFT", "L_ALT", "L_META", "CAPS", "N_LOCK", "S_LOCK",
"R_CTRL", "R_SHIFT", "R_ALT", "R_META"]
# Vetëm test, por mos mblidhni.
# Shembuj, unë përdor F12 në vend të BREAK në laptopë të trashë me tastiera të këqija (ThinkPad të rinj)
Test = ["F1..F10", "ZENKAKUHANKAKU", "102ND", "F11", "F12",
"RO..KPJPCOMMA", "SYSRQ", "SCALE", "HANGEUL..YEN",
"STOP..SCROLLDOWN", "NEW..MAX"]
Janë parashikuar gjithsej 768 kode. (Por "për çdo rast" kam vendosur në kod hswitcher kapjen e "surprizave").
Brenda kam shkruar plotësimin e vargut me lidhje në funksionet "çfarë të bëj". Në golang kjo (papritur) u tregua e përshtatshme dhe e qartë.
- Plani për ta reduktuar "Drop" në minimum në këtë pikë. Për një trajtim më fleksibël (do ta dëshmoj më poshtë).
3. Tabela me klasa dritareje
# 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 janë në skeletin e dyfishtë me emrin e saj. Nuk doli më lehtë menjëherë. Në varësi të dritares aktive aktuale, mund të zgjidhen opsione:
- Grupi juaj i "tasteve të nxehta" "Actions = ...". Nëse nuk ka/është bosh - mos bëni asgjë.
- Ndërruesi "MouseClickDrops" - çfarë duhet të bëni kur zbulohet një klikim i miut. Duke qenë se në pikën e përfshirjes xswitcher nuk ka detaje "ku po klikojnë", për defolt e rikthejmë tamponin. Por në terminale (p.sh.) mund të mos e bëni këtë. (zakonisht).
4. Një (ose disa) sekuenca të shtypjes ngjallin një apo tjetër gjilpërë
# 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)" ]
Kam ndarë gjilpëra në dy tipe. Në ndihmë të brendshme, me emra "të folur" (NewWord, NewSentence, Compose) dhe të programueshme.
Emrat e programueshme fillojnë me "Action.". Që nga TOML v1.4, emrat me pika duhet të jenë në thonjëza.
Më poshtë për secilin duhet të përshkruhet një seksion me një emër të tillë.
Për të mos i eksploduar trurin njerëzve me "rregulla" të zbuluara (sipër përvojës, ata për të shkruar- ndoshta një nga dhjetë profesionistëve), menjëherë po fus një sintaksë shtesë.
- "OFF:" (ose "ON:") para regexp (shprehjes së rregullt) kërkojnë që butonat e renditur më poshtë të jenë të lëshuara (ose të shtypura).
Dhe më pas planifikoj të krijoj një shprehje të "pabesë" rregullore. Me verifikimin e ndarë të pjesëve mes "|". Në qëllim për të reduktuar numrin e regjistrimeve të tipit "[LR]_SHIFT" (aty ku është në mënyrë të qartë e panevojshme). - "SEQ:" Nëse kushti i mëparshëm përmbushet (ose mungon), më pas kontrollojmë lidhur me shprehjen "normale" të rregullt. Për detaje, menjëherë dërgoj në bibliotekën "regexp". Sepse unë vetë ende nuk kam bërë përpjekje për të kuptuar shkallën e përputhshmërisë me pcre-të e mia të preferuara ("perl compatible").
- Shprehja shkruhet në formë "BUTTON_1: CODE1, BUTTON_2: CODE2" etj., në rendin e ardhjes së kodit të skeneve.
- Kontrolli gjithmonë "ngjitet" në fund të sekuencës, prandaj "$" nuk është e nevojshme të shtohet në fund.
- Të gjitha kontrollimet në një rresht kryhen njëra pas tjetrës dhe bashkohen me "DHE". Por ashtu si vlera është përshkruar si një varg, mund të shkruani një kontroll alternativ pas presjes. Nëse është e nevojshme për ndonjë arsye.
- Vlera "SeqLength = 8" kufizon madhësinë e tamponit, në lidhje me të cilin kryhen të gjitha kontrollet. Sepse në jetën time (ende) nuk kam hasur burime të pafundme.
5. Caktimi i hooks, të shkruara 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.
Këtu e themi — «Veprimi = [Masa]». Njëlloj si në seksionin e mëparshëm, ka një grup të kufizuar veprimesh të integruara. Dhe një mundësi e pakufizuar për lidhje (shkruani «Action.XXX» dhe mos hezitoni të shkruani një seksion tjetër për të).
Dhe, ri-shkruar fjalën në rendin e saktë ndan në dy pjesë: «ndrysho renditjen siç është caktuar atje» dhe «ri-shkruaj» («RetypeWord»).
Parametrat e tjerë shkruhen në një «fjalor» («map» në golang) për këtë veprim, lista e tyre varet nga ajo që është shkruar në «Action».
Disa veprime të ndryshme mund të përshkruhen në një grumbull (seksione). Mund të ndahen gjithashtu. Siç tregova më lart.
Menjëherë e vendos veprimin «Exec» — për të ekzekutuar një skenar të jashtëm. Me opsionin për t'i dërguar atij në stdin buffer-in e shkruar.
- «Prit = 1» — priste për përfundimin e procesit të nisur.
- Ndoshta, «për rastin» dëshiron të vendosësh në ambient informacione shtesë si emri i klasës së dritares nga e cila është kapur.
«Doni të lidhni procesorin tuaj? Ja ku është.»
Uf (shkëputi). Duket sikur nuk kam harruar asgjë.
Op! Ah, nuk harrova…
Dhe konfigurimi i nisjes ku është? Në kodin e fortë? Përafërsisht kështu:
[ScanDevices]
# Duhet të ekzistojë në fillim. Riinici nëse është më e re se 30 sekonda
Test = "\/dev\/input\/event0"
Riinici = 30
# Maskë kërkimi
Kërko = "\/dev\/input\/event*"
# Në thinkPad-t e mi ka këto pseudo-tastiera me tonelata ngjarjesh të panevojshme
Anashkalo = "(?i)Video|Kamera" # "(?i)" dëshpërimisht ndryshon nga "klasik" pcre.
Dhe ku harrova / gabova (pa këtë — ashtu siç duhet), shpresoj shumë që lexuesit e vëmendshëm nuk do të ngurronin të më tregojnë.
UPD.
Burimi: habr.com
