Eftersom (hswitcher på stadiet «proof of concept») har fått ganske mange konstruktive tilbakemeldinger (som er hyggelig), jeg fortsatte å bruke min fritid på å utvikle prosjektet. Nå vil jeg bruke litt av deres tid... Det andre steget vil være litt uvanlig: forslag/diskusjon av konfigurasjonsdesign.

Det ser ut til at det for normale programmerere er dødskjedelig å justere alle disse kontrollene.
For å ikke være ordløs, her er et eksempel på det jeg jobber med.
Fabelaktig på det hele tatt, godt designet (og ganske godt realisert) Apache Kafka & ZooKeeper.
— Konfigurasjon? Men det er kjedelig! Raskt og rotete xml (fordi «out-of-the-box»).
— Åh, og vil dere også ha ACL? Men det er så tidkrevende! Raskt og rotete… Slik som dette.
Men i mitt arbeid er det akkurat motsatt. Riktig (dessverre, fra første gang er det nesten aldri riktig) den bygde modellen tillater videre enkel og uanstrengt (vel, nesten) å samle skjemaet.
Jeg kom nylig over en artikkel på Habr om den vanskelige jobben data-scientister.
Det viser seg at dette punktet er fullt ut realisert hos dem. Og i min praksis, som man sier, «light-versjonen». Flere volumer med modeller, erfarne programmerere med OOP i ryggen og så videre - det er alt noe som vil komme etter hvert når/om det tar av. Og konstruktøren må starte et sted her og nå.
Nå til saken. Som den syntaktiske basen tok jeg TOML .
Fordi han (TOML) på den ene siden er menneske-redigerbar. På den andre siden oversettes den 1:1 til hvilken som helst av de mer utbredte syntaksene: XML, JSON, YAML.
Videre, implementasjonen jeg brukte fra «github.com/BurntSushi/toml» er kanskje ikke den mest moderne (siden den fortsatt bruker syntaks 1.4), men den er syntaktisk kompatibel med den samme (»innebygde») JSON.
Det vil si, hvis man ønsker, kan man bare si «gå til helvete med denne TOML-en, jeg vil ha XXX» og «patche» koden med bare én linje.
Dermed, hvis man vil skrive noen vinduer for å konfigurere hswitcher (det er definitivt ikke meg) problemer «med denne forbaskede konfigurasjonen» - er ikke å forutsi.
For alle andre er syntaksen basert på «nøkkel = verdi» (og bokstavelig talt et par enklere alternativer, som = [noen, array]) jeg antar
intuitivt praktisk.
Det interessante er at selve omtrent på samme tid (rundt 2013). Bare, i motsetning til meg, gikk forfatteren av TOML inn med nødvendig sus.
Derfor er det nå lettere for meg å tilpasse implementeringen til meg selv, enn omvendt.
I utgangspunktet tar vi TOML (som ligner veldig på det gamle Windows INI). Og vi lager en konfigurasjon som beskriver hvordan man kobler til en rekke hooks avhengig av settet med de siste scan-kodene fra tastaturet. Nedenfor er hva jeg har fått til så langt, samt forklaringer på hvorfor jeg har valgt det.
0. Grunnleggende abstraksjoner
- Betegnelser for scan-koder. Dette må definitivt gjøres noe med, siden bare numeriske koder er helt uleselige for mennesker (og jeg står i hagen) ).
Jeg fikk «ecodes.go» fra «golang-evdev» (jeg gadd ikke å gå til kilden, selv om forfatteren oppgir den på en påkrevende måte). Jeg har gjort noen små justeringer (til nå) på det som var ekstremt stygt. Som for eksempel «LEFTBRACE» → «L_BRACE». - Jeg har lagt til et begrep om «taster med tilstand». Siden den brukte regulære grammatikken ikke tillater lange passasjer. (Men det tillater testing med minimale kostnader. Hvis jeg kun bruker en «direkte» opptak.)
- Det vil være en innebygd «dedupliker» for trykk. Så tilstanden «gjentakelse»=2 vil bli registrert. en ganger.
1. Malinndeling
[Templates] # "@name@" for å forenkle uttrykk
# Ord kan bestå av disse tegnene (regex)
"WORD" = "([0-9A-Z`;']|[LR]_BRACE|COMMA|DOT|SLASH|KP[0-9])"
Hva et ord på menneskespråk består av med fonetisk skriving. (til forskjell fra grafemer aka «tegn»)? Какая-то ужасная «простыня». Поэтому сразу закладываю понятие «шаблона».
2. Hva gjøre når noe blir trykket (når en ny scan-kode kommer in)
[ActionKeys]
# Samle taste og utføre testen for kommando-sekvens
# !!! Gjentakelses-koder (kode=2) må samles en gang per tast!
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"]
# Slipp alle innsamlede taster, inklusive denne. Dette er standard handling.
Drop = ["ESC", "-", "TAB", "ENTER", "KPENTER", "LINEFEED..POWER"]
# Lagrer ekstra kart for disse tastene, når noen er i "ned»-tilstand.
# Tilstand sjekkes via "OFF:"|"ON:" betingelser i handlingen.
# (I tillegg må tilstanden til disse tastene vedvare mellom buffer-slipp.)
# ??? Hvordan håndtere CAPS og "LOCK"-taster ???
StateKeys = ["L_CTRL", "L_SHIFT", "L_ALT", "L_META", "CAPS", "N_LOCK", "S_LOCK",
"R_CTRL", "R_SHIFT", "R_ALT", "R_META"]
# Test bare, men samle ikke.
# For eksempel bruker jeg F12 i stedet for BREAK på dårlige bærbare med crappy tastaturer (nye ThinkPads)
Test = ["F1..F10", "ZENKAKUHANKAKU", "102ND", "F11", "F12",
"RO..KPJPCOMMA", "SYSRQ", "SCALE", "HANGEUL..YEN",
"STOP..SCROLLDOWN", "NEW..MAX"]
Totalt er det forutsatt 768 koder. (Men «for en sikkerhets skyld» har jeg lagt inn i koden til hswitcher en fangst av «overraskelser»).
Inni har jeg detaljert fyllet i arrayet med henvisninger til funksjoner «hva skal gjøres». På golang var dette (overraskende) veldig praktisk og åpenbart.
- Jeg planlegger å redusere «Drop» til et minimum på dette punktet. Til fordel for en mer fleksibel behandling (vil vise nedenfor).
3. Tabell med klassevinduer
# 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"
Tabellradene – i doble firkantede parenteser med navnet sitt. Det ble ikke enklere med det første. Avhengig av det nåværende aktive vinduet, kan man velge alternativer:
- Egen samling av «hurtigtaster» «Actions = …». Hvis ingen/tilfeldig – gjør ingenting.
- Bryter «MouseClickDrops» – hva som skal gjøres ved oppdagelse av museklikk. Siden det ikke finnes noen detaljer om «hvor det klikkes» i x-switcheren, tilbakestiller vi bufferet som standard. Men i terminaler (for eksempel) kan man kanskje la være. (som regel).
4. En (eller flere) sekvenser av tastetrykk starter en eller annen 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)" ]
Hooks er delt inn i to typer. Innebygde, med «sigende» navn (NewWord, NewSentence, Compose) og programmerbare.
Navnene til de programmerbare begynner med «Action.». Siden TOML v1.4, må navn med punkter være i anførselstegn.
Nedenfor skal hver ha en beskrivelse av seksjonen med samme navn.
For å unngå å overvelde folk med «kalde» regulære uttrykk (basert på erfaring, å skrive- det kan en av ti yrkesutøvere), implementerer jeg med en gang ekstra syntaks.
- «OFF:» (eller «ON:») før regexp (regulært uttrykk) krever at de angitte knappene etterfølgende er sluppet (eller trykket).
Videre planlegger jeg å lage et «uregelmessig» regulært uttrykk. Med separat sjekk av seksjoner mellom pipe-tegnene "|". Med mål om å redusere antall registreringer som «[LR]_SHIFT» (hvor dette åpenbart ikke er nødvendig). - «SEQ:» Hvis den forrige betingelsen er oppfylt (eller mangler), sjekker vi deretter i forhold til «vanlig» regulært uttrykk. For detaljer sender jeg deg til biblioteket «regexp». Fordi jeg selv fortsatt ikke har funnet ut graden av kompatibilitet med mine favoritter pcre («perl compatible»).
- Uttrykket skrives i form av «BUTTON_1: CODE1, BUTTON_2: CODE2» og så videre, i rekkefølge av mottatte scankoder.
- Kontrollen «klemmer» alltid mot slutten av sekvensen, så derfor trenger vi ikke å legge til "$" på slutten.
- Alle sjekker i én linje utføres etter hverandre og kombineres med «OG». Men siden verdien er beskrevet som en matrise, kan man etter kommaet skrive en alternativ sjekk. Hvis det er nødvendig.
- Verdi «SeqLength = 8» begrenser størrelsen på bufferen, i forhold til hvilken alle sjekker utføres. Siden jeg (fortsatt) ikke har møtt uendelige ressurser i livet.
5. Oppgåver for hukene, beskrevet i forrige seksjon
# 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 viktigste her — «Action = [Array]». Som i forrige seksjon, er det et begrenset sett med innebygde handlinger. Og i prinsippet ikke en begrenset mulighet for integrasjon (skriv «Action.XXX» og ikke vær lat med å spesifisere en ny seksjon for det).
Blant annet blir gjenopptegnig av ord med den korrigerte oppsettet delt opp i to deler: «endre oppsett som angitt der borte» og «gjenoppta» («RetypeWord»).
De andre parameterne skrives inn i et «ordbok» («map» i golang) for denne handlingen, listen deres avhenger av hva som er skrevet i «Action».
Flere forskjellige handlinger kan beskrives i en haug (seksjoner). Eller de kan deles opp. Som jeg viste tidligere.
Setter umiddelbart handlingen «Exec» — kjør et eksternt skript. Med muligheten til å sende innholdet til stdin.
- «Wait = 1» — vent på at den kjørte prosessen skal fullføres.
- Det er sannsynlig at man «tilfeldigvis» vil ønske å stille ytterligere informasjon slik som navnet på vindusklassen hvor det ble fanget opp.
«Vil du koble til din egen behandler? Gå til dette stedet.»
Puh (utånding). Det ser ut til at jeg ikke glemte noe.
Opp! Ja, glemte ikke…
Men hvor er oppstartsinnstillingene? I hardkoden? Omtrent slik:
[ScanDevices]
# Må eksistere ved start. Selvrespawning hvis den er yngre enn 30 sekunder
Test = "\/dev\/input\/event0"
Respawn = 30
# Søkemaske
Search = "\/dev\/input\/event*"
# I mine thinkPads er det slike pseudotastaturer med masse unødvendige hendelser
Bypass = "(?i)Video|Camera" # "(?i)" skiller seg åpenbart fra "klassisk" pcre.
Og hvor glemte jeg/feilet (uten dette — ingen måte), jeg håper virkelig at oppmerksomme lesere ikke er for late til å peke det ut.
Lykke til!
Kilde: habr.com
