De toetsenbordindeling corrector 'hswitcher' voor linux: stap twee.

Aangezien vorige publicatie (de xswitcher is in de fase van 'proof of concept') heeft behoorlijk wat constructieve feedback gekregen (wat fijn is), ik bleef mijn vrije tijd besteden aan de ontwikkeling van het project. Nu wil ik een beetje van uw tijd besteden... De tweede stap zal niet helemaal gebruikelijk zijn: het voorstel/discussie van het configuratiedesign.

De toetsenbordindeling corrector 'hswitcher' voor linux: stap twee.

Het lijkt wel alsof het voor normale programmeurs extreem saai is om al deze schroefjes in te stellen.

Om niet zomaar te praten, hier is een voorbeeld van wat ik tegenkom.
Over het algemeen uitstekend ontworpen (en goed gerealiseerde) Apache Kafka & ZooKeeper.
— Configuratie? Maar dat is saai! Gewoon even snel XML (omdat 'het uit de doos komt').
— Oh, en u wilt ook ACL? Maar dat is zo vervelend! Even snel… Zoiets.

Maar in mijn werk is het precies het tegenovergestelde. Juist (helaas, bij de eerste poging is dat bijna nooit het geval) een goed gebouwde model maakt het gemakkelijk en zonder gedoe mogelijk (nou, bijna) de schema te verzamelen.

Onlangs stuitte ik op een artikel op Habr over het zware werk van data-scientists...
Blijkbaar wordt dit aspect bij hen volledig gerealiseerd. En in mijn praktijk, zoals men zegt, 'de light versie'. Meerlaagse modellen, ervaren programmeurs met OOP en zo verder — dat alles komt later, wanneer/als het van de grond komt. En de constructeur moet hier en nu ergens mee beginnen.

Laten we ter zake komen. Als syntactische basis nam ik TOML van deze burger.

Omdat het (TOML) enerzijds menselijk bewerkbaar is. Maar anderzijds wordt het 1:1 omgezet naar een van de meer gangbare syntaxes: XML, JSON, YAML.
Bovendien is de implementatie die ik gebruik van 'github.com/BurntSushi/toml' hoewel niet de meest trendy (nog steeds syntaxis 1.4), syntactisch compatibel met dezelfde ('ingebouwde') JSON.

Dus als je wilt, kun je eenvoudig zeggen 'ga met je TOML weg, ik wil XXX' en de code eenvoudig met één regel ‘patchen’.

Op deze manier, als je wilt, kun je voor de configuratie van de xswitcher allerlei vensters schrijven (zeker niet door mij) zijn er geen problemen met 'deze verdomde configuratie' te voorzien.

Voor alle andere syntaxes gebaseerd op 'sleutel = waarde' (en letterlijk een paar opties iets ingewikkelder, zoals = [een, array]) denk ik
intuitief gemakkelijk.
Wat interessant is, ik zelf had ongeveer rond dezelfde tijd (circa 2013) wat 'verbranding'. Alleen, in tegenstelling tot mij, ging de auteur van TOML met de nodige flair te werk. ongeveer rond dezelfde tijd (in de omgeving van 2013). Alleen, in tegenstelling tot mij, ging de auteur van TOML met de nodige flair te werk.

Daarom is het nu eenvoudiger voor mij om de implementatie aan te passen aan mijn behoeften, in plaats van andersom.

Over het algemeen nemen we TOML (dat lijkt veel op het oude Windows INI). En we stellen een configuratie op waarin we beschrijven hoe een reeks hooks aan te sluiten, afhankelijk van de laatste reeks toetsaanslagen. Hieronder in stukjes — wat ik tot nu toe heb bereikt. En uitleg waarom ik ervoor gekozen heb.

0. Basisabstracties

  • Aanduidingen van toetsaanslagen. Hier moet absoluut iets mee gedaan worden, want alleen numerieke codes zijn helemaal niet leesbaar voor mensen (en dat zeg ik als een buitenstaander). loloswitcher.).
    Ik heb "ecodes.go" uit "golang-evdev" gehaald (ik was te lui om de originele bron te bekijken, hoewel de auteur het heel netjes heeft vermeld). Ik heb een beetje (voorlopig) de echt lelijke delen aangepast. Zoals "LEFTBRACE" → "L_BRACE".
  • Daarnaast heb ik het begrip "toetsen met status" geïntroduceerd. Aangezien de gebruikte reguliere grammatica niet uitnodigt tot lange passages. (Maar het maakt het mogelijk om met minimale overhead te controleren. Als je alleen "directe" invoer gebruikt.)
  • Er zal een ingebouwde "deduplicator" zijn voor toetsaanslagen. Op deze manier zal de status "herhaal"=2 worden vastgelegd. één keer.

1. Sjabloonindeling

[Templates] # "@name@" om uitdrukkingen te vereenvoudigen
 # Woorden kunnen uit deze tekens bestaan (regex)
 "WORD" = "([0-9A-Z`;']|[LR]_BRACE|COMMA|DOT|SLASH|KP[0-9])"

Waaruit een woord in de menselijke taal bestaat met fonetische schrift. (dat is iets heel anders dan grafemen aka "hiërogliefen")? Какая-то ужасная «простыня». Поэтому сразу закладываю понятие «шаблона».

2. Wat te doen wanneer iets wordt ingedrukt (wanneer een nieuwe toetsaanslag binnenkomt)

[ActionKeys]
 # Verzamel toetsaanslagen en test de opdrachtvolgorde
 # !!! Herhalingscodes (code=2) moeten eenmaal per toets worden verzameld!
 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"]

 # Verwijder alle verzamelde toetsen, inclusief deze. Dit is de standaardactie.
 Drop = ["ESC", "-", "TAB", "ENTER", "KPENTER", "LINEFEED..POWER"]
 # Bewaar een extra kaart voor deze toetsen, wanneer er een in de "down" status is.
 # De status wordt gecontroleerd via "OFF:"|"ON:" voorwaarden in de actie.
 # (Bovendien moet de status van deze toetsen blijven bestaan tussen buffer verwijderen.)
 # ??? Hoe om te gaan met CAPS en "LOCK"-toetsen ???
 StateKeys = ["L_CTRL", "L_SHIFT", "L_ALT", "L_META", "CAPS", "N_LOCK", "S_LOCK",
              "R_CTRL", "R_SHIFT", "R_ALT", "R_META"]

 # Alleen testen, maar niet verzamelen.
 # Bijvoorbeeld, ik gebruik F12 in plaats van BREAK op domme laptops met slechte toetsenborden (nieuwe ThinkPads)
 Test = ["F1..F10", "ZENKAKUHANKAKU", "102ND", "F11", "F12",
          "RO..KPJPCOMMA", "SYSRQ", "SCALE", "HANGEUL..YEN",
          "STOP..SCROLLDOWN", "NEW..MAX"]

Er zijn in totaal 768 codes voorzien. (Maar "voor de zekerheid" heb ik in de code van hswitcher een vangst voor "verrassingen" geplaatst).
Binnenin heb ik de invulling van de array beschreven met verwijzingen naar de functies "wat te doen". In golang bleek dit (verrassend) handig en vanzelfsprekend te zijn.

  • Ik ben van plan om "Drop" op dit punt tot een minimum te beperken, ten gunste van een flexibelere verwerking (onderaan meer hierover).

3. Tabel met vensterklassen

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

Tabelrijen staan in dubbele vierkante haakjes met de naam ervan. Eenvoudiger was het niet. Afhankelijk van het huidige actieve venster kunnen opties worden geselecteerd:

  • Je eigen set van 'hotkeys' 'Actions = ...'. Als er geen is/het leeg is — doe dan niets.
  • Schakelaar 'MouseClickDrops' — wat te doen bij het detecteren van een muisklik. Aangezien er op het punt van inschakeling xswitcher geen details zijn over 'waar er geklikt wordt', resetten we standaard de buffer. Maar in terminals (bijvoorbeeld) kan dat zo blijven. (over het algemeen).

4. Een (of meerdere) reeks toetsen drukken start die of die 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)" ]

Ik heb hooks in twee types verdeeld. Ingebouwd, met 'sprekende' namen (NewWord, NewSentence, Compose) en programmeerbare.

Programmeerbare namen beginnen met 'Action.'. Volgens TOML v1.4 moeten namen met punten tussen aanhalingstekens staan.

Hieronder moet voor elk een sectie beschreven worden met dezelfde naam.

Om mensen niet te overbelasten met 'naakte' regulieren (op basis van ervaring, zijn ze om te schrijven- het is gewoon één op de tien professionals), introduceer ik meteen een aanvullende syntaxis.

  • 'OFF:' (of 'ON:') voor regexp (reguliere expressies) vereist dat de na te geven knoppen losgelaten (of ingedrukt) zijn.
    Daarna ben ik van plan om een 'oneerlijk' reguliere expressie te maken. Met gescheiden controles van de stukken tussen de pipes "|". Met als doel het aantal vermeldingen zoals "[LR]_SHIFT" (waar dat duidelijk niet nodig is) te verminderen.
  • 'SEQ:' Als de vorige voorwaarde is vervuld (of ontbreekt), controleren we verder ten opzichte van 'gewone' reguliere expressies. Voor details stuur ik je onmiddellijk naar de bibliotheek 'regexp'. Omdat ik zelf nog steeds niet de moeite heb genomen om de compatibiliteit met mijn favoriete pcre ('perl compatible') uit te zoeken.
  • De expressie wordt genoteerd als 'BUTTON_1: CODE1, BUTTON_2: CODE2' enzovoort, in volgorde van binnenkomende scan-codes.
  • De controle wordt altijd 'tegen de achterkant' van de reeks geplaatst, daarom hoeft "$" niet aan het einde te worden toegevoegd.
  • Alle controles in één regel worden achtereenvolgens uitgevoerd en gecombineerd met 'EN'. Maar aangezien de waarde als een array is beschreven, kan er na een komma een alternatieve controle worden geschreven. Als dat om een bepaalde reden nodig is.
  • Waarde 'SeqLength = 8' beperkt de grootte van de buffer waarop alle controles worden uitgevoerd. Aangezien ik in mijn leven (tot nu toe) geen eindeloze bronnen ben tegengekomen.

5. Het instellen van hooks, zoals beschreven in de vorige sectie

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

Het belangrijkste hier is — «Action = [Array]». Net als in de vorige sectie is er een beperkt aantal ingebouwde acties. En in principe is er geen beperking op de mogelijkheden voor integratie. (Schrijf «Action.XXX» en vergeet niet om een andere sectie voor hem te beschrijven).
Inclusief, het opnieuw typen van een woord in de gecorrigeerde indeling wordt in twee delen gesplitst: «Verander de indeling zoals daar opgegeven» en «Hertypen» («RetypeWord»).

De overige parameters worden in een «woordenboek» geschreven («map» in golang) voor deze actie, de lijst is afhankelijk van wat er in «Action» is geschreven.

Verschillende acties kunnen in één set worden beschreven (secties). Maar het kan ook verspreid worden. Zoals ik hierboven heb getoond.

Ik neem onmiddellijk de actie «Exec» op — voer een extern script uit. Met de optie om de opgenomen buffer naar stdin te sturen.

  • «Wait = 1» — wacht op de voltooiing van het gestart proces.
  • Waarschijnlijk wil je «extra» informatie zoals de naam van de vensterklasse waarvan het is afgevangen in de omgeving instellen.
    «Wil je je eigen handler aansluiten? Hier is het voor jou.»

Pfoe (ik haalde adem). Het lijkt erop dat ik niets vergeten ben.

Oeps! Ja, ik ben niets vergeten…
Maar waar is de opstartconfiguratie? In de hard-coded? Ongelooflijk zo:

[ScanDevices]
 # Moet aanwezig zijn bij de start. Zelf-herstart als het jonger is dan 30s
 Test = "\/dev\/input\/event0"
 Respawn = 30
 # Zoekmasker
 Search = "\/dev\/input\/event*"
 # In mijn thinkPads zijn er zulke pseudo-toetsenborden met tons onnodige gebeurtenissen
 Bypass = "(?i)Video|Camera" # "(?i)" verschilt duidelijk van "klassieke" pcre's.

En waar ben ik vergeten/ben ik fout gegaan (zonder dit — is niet mogelijk), ik hoop echt dat oplettende lezers zich niet zullen verzetten om met hun neus te wijzen.

Veel succes!

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster