Eftersom (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.

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