Xswitcher aranĝokorektilo por Linukso: paŝo du

Ekde antaŭa publikigo (xswitcher ĉe la "pruvo de koncepto-" stadio) ricevis sufiĉe multajn konstruajn sugestojn (kio estas bela), mi daŭre pasigis mian liberan tempon evoluigante la projekton. Nun mi volas elspezi iom de via... La dua paŝo ne estos tute kutima: propono/diskuto pri agorda dezajno.

Xswitcher aranĝokorektilo por Linukso: paŝo du

Iel rezultas, ke normalaj programistoj trovas ĝin nekredeble enuiga agordi ĉiujn ĉi tiujn kontrolojn.

Por ne esti senbaza, interne estas ekzemplo de tio, pri kio mi traktas.
Ĝenerale bonege konceptitaj (kaj bone efektivigitaj) Apache Kafka & ZooKeeper.
- Agordo? Sed ĝi estas enuiga! Stulta xml (ĉar ĝi estas "el la skatolo").
- Ho, ĉu vi ankaŭ volas ACL? Sed ĝi estas tiel enuiga! Tap-blooper... Io tia.

Sed en mia laboro estas ĝuste la malo. Ĝuste (ve, preskaŭ neniam la unuan fojon) la konstruita modelo ebligas al vi daŭrigi pli facile kaj nature (Preskaŭ) kunmeti diagramon.

Mi lastatempe trovis artikolon pri Habré pri la laborego de datumsciencistoj...
Montriĝas, ke ĉi tiu momento estas plene realigita por ili. Kaj en mia praktiko, kiel oni diras, "malpeza versio". Plurvolumaj modeloj, spertaj programistoj kun OOP preta, ktp. — ĉi ĉio aperos poste kiam/se ĝi ekflugos. Sed la dezajnisto devas komenci ie ĉi tie kaj nun.

Iru al la punkto. Mi prenis TOML kiel sintaksan bazon de ĉi tiu civitano.

Ĉar li (TOML) unuflanke, hom-redaktebla. Aliflanke, ĝi estas tradukita 1:1 al iu el la pli oftaj sintaksoj: XML, JSON, YAML.
Cetere, la efektivigo, kiun mi uzis de “github.com/BurntSushi/toml”, kvankam ne la plej moda (ankoraŭ 1.4 sintakso), estas sintakse kongrua kun la sama (“enkonstruita”) JSON.

Tio estas, se vi volas, vi povas simple diri "trairi la arbaron kun tiu via TOML, mi volas XXX" kaj "flaki" la kodon per nur unu linio.

Tiel, se vi volas skribi iujn fenestrojn por agordi xswitcher (Mi ne certas) Neniuj problemoj estas atendataj "kun ĉi tiu malbenita agordo via."

Por ĉiuj aliaj, la sintakso baziĝas sur "ŝlosilo = valoro" (kaj laŭvorte kelkaj pli komplikaj opcioj, kiel = [kelkaj, tio, tabelo]) mi supozas
intuicie oportuna.
Kio estas interesa estas tio "bruligita" ĉirkaŭ la sama tempo (ĉirkaŭ 2013). Nur, male al mi, la aŭtoro de TOML eniris laŭ taŭga skalo.

Tial nun estas pli facile por mi ĝustigi ĝian efektivigon laŭ mi mem, kaj ne inverse.

Ĝenerale, ni prenas TOML (tre simila al la malnova Windows INI). Kaj ni havas agordon en kiu ni priskribas kiel ligi serion da hokoj depende de la aro de la plej novaj skanaj kodoj de la klavaro. Malsupre, peco post peco, estas kio okazis ĝis nun. Kaj klarigo pri kial mi decidis tiel.

0. Bazaj Abstraktaĵoj

  • Skani kodnomojn. Io certe devas esti farita pri tio, ĉar simple ciferecaj kodoj estas absolute ne homlegeblaj (tio estas nur mi loloswitcher).
    Mi elskuis "ecodes.go" el "golang-evdev" (mi estis tro mallaborema por rigardi la originan fonton, kvankam la aŭtoro indikis ĝin sufiĉe kulture). Mi korektis iom (nuntempe) ion, kio estis sufiĉe timema. Kiel "LEFTBRACE" → "L_BRACE".
  • Plie, li lanĉis la koncepton de "ŝtataj ŝlosiloj". Ĉar la regula gramatiko uzata ne permesas longajn pasaĵojn. (Sed ĝi permesas vin kontroli kun minimuma superkosto. Se vi uzas nur "rektan" registradon.)
  • Estos enkonstruita "deduplikilo" de kio estas premita. Tiel, la stato "ripeto"=2 estos skribita один fojojn.

1. Ŝablonoj sekcio

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

El kio konsistas homlingva vorto kun fonetika notacio? (aŭ afero de grafemoj alinome "hieroglifoj")? Ia terura "folio". Tial mi tuj enkondukas la koncepton "ŝablono".

2. Kion fari kiam io estas klakita (alia skankodo alvenis)

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

Estas 768 kodoj entute. (Sed "ĉiokaze" mi enmetis kapti "surprizojn" en la xswitcher-kodon).
Interne mi priskribis plenigi la tabelon per ligiloj al funkcioj "kion fari". En golang tio estas (subite) Ĝi montriĝis oportuna kaj evidenta.

  • Mi planas redukti "Faligi" al minimumo en ĉi tiu loko. En favoro de pli fleksebla prilaborado (mi montros ĝin sube).

3. Tablo kun fenestroklasoj

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

La vicoj de la tabelo estas en duoblaj kvadrataj krampoj kun ĝia nomo. Ĝi ne povus esti pli facila tuj de la bato. Depende de la nuntempe aktiva fenestro, vi povas elekti la jenajn opciojn:

  • Via propra aro de "hotklavoj" "Agoj = ...". Se ne/malplena, faru nenion.
  • Ŝaltu "MouseClickDrops" - kion fari kiam musklako estas detektita. Ĉar ĉe la punkto, kie xswitcher estas ŝaltita, ne estas detaloj pri "kie ili klakas", ni rekomencigas la bufron defaŭlte. Sed en terminaloj (ekzemple) vi ne devas fari ĉi tion (kutime).

4. Unu (aŭ pluraj) sekvencoj de klakoj ekigas unu aŭ alian hokon

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

Hokoj estas dividitaj en du tipojn. Enkonstruita, kun "parolantaj" nomoj (NewWord, NewSentence, Compose) kaj programeblaj.

Programeblaj nomoj komenciĝas per "Ago". Ĉar TOML v1.4, nomoj kun punktoj devas esti inter citiloj.

Ĉiu sekcio devus esti priskribita malsupre kun la sama nomo.

Por ne krevigi la mensojn de homoj kun "nudaj" ordinaruloj (de sperto, ilia skribieble unu el dek profesiuloj), mi tuj efektivigas plian sintakson.

  • "OFF:" (aŭ "ON:") antaŭ regexp (regula esprimo) postulas ke la sekvaj butonoj estu liberigitaj (aŭ premitaj).
    Poste mi faros "maljustan" regulan esprimon. Kun aparta kontrolo de pecoj inter tuboj "|". Por redukti la nombron da registroj kiel "[LR]_SHIFT" (kie tio klare ne estas necesa).
  • "SEQ:" Se la antaŭa kondiĉo estas plenumita (aŭ forestas), tiam ni kontrolas kontraŭ "normala" regula esprimo. Por detaloj, mi tuj sendas al ^W la bibliotekon "regexp". Ĉar mi ankoraŭ ne ĝenis ekscii la gradon de kongruo kun mia plej ŝatata pcre ("perl kongrua").
  • La esprimo estas skribita en la formo "BUTTON_1: CODE1, BUTTON_2: CODE2" ktp., en la ordo en kiu la skankodoj estas ricevitaj.
  • La ĉeko ĉiam estas "snugged" ĝis la fino de la sekvenco, do ne necesas aldoni "$" al la vosto.
  • Ĉiuj kontroloj en unu linio estas faritaj unu post la alia kaj estas kombinitaj per "mi". Sed ĉar la valoro estas priskribita kiel tabelo, vi povas skribi alternativan kontrolon post la komo. Se ĉi tio necesas ial.
  • valoro "SeqLength = 8" limigas la grandecon de la bufro kontraŭ kiu ĉiuj kontroloj estas faritaj. Ĉar Mi (ĝis nun) neniam renkontis senfinajn rimedojn en mia vivo.

5. Agordi la hokojn priskribitajn en la antaŭa sekcio

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

La ĉefa afero ĉi tie estas "Ago = [Tabelo]". Simile al la antaŭa sekcio, ekzistas limigita aro de enkonstruitaj agoj. Kaj la ebleco de albordiĝo principe ne estas limigita (skribi "Action.XXX" kaj ne tro maldiligentu skribi alian sekcion por ĝi).
Precipe, la retajpado de vorto en la korektita aranĝo estas dividita en du partojn: "ŝanĝi la aranĝon kiel specifite tie" и "retajpu" ("Retajpu Vorton").

La ceteraj parametroj estas skribitaj al la "vortaro" ("mapo" en golango) por donita ago, ilia listo dependas de tio, kio estas skribita en "Ago".

Pluraj malsamaj agoj povas esti priskribitaj en unu amaso (sekcioj). Aŭ vi povas disigi ĝin. Kiel mi montris supre.

Mi tuj starigis la agon "Exec" por ekzekuti la eksteran skripton. Kun la eblo puŝi la registritan bufron en stdin.

  • "Atendu = 1" - atendu ke la kuranta procezo finiĝos.
  • Verŝajne, "al la amaso" vi volos meti pliajn homojn en la medion. informoj kiel la nomo de la fenestroklaso de kiu ĝi estis kaptita.
    “Ĉu vi volas konekti vian prizorganton? Ĉi tie vi devas iri.”

Huf (elspiris). Ŝajnas, ke mi nenion forgesis.

Ho! Jes, mi ne forgesis...
Kie estas la lanĉa agordo? En malmola kodo? Tiel:

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

Kie mi forgesis/faris eraron? (neniel sen ĉi tio), Mi vere esperas, ke atentaj legantoj ne tro maldiligentos piki sian nazon.

Bonŝancon!

fonto: www.habr.com

Aldoni komenton