Correcteur de mise en page Xswitcher pour Linux : deuxième étape

depuis publication précédente (xswitcher en phase de « preuve de concept ») a reçu pas mal de retours constructifs (ce qui est sympa), j'ai continué à consacrer mon temps libre à développer le projet. Maintenant, je veux dépenser un peu de votre... La deuxième étape ne sera pas tout à fait habituelle : proposition/discussion de conception de configuration.

Correcteur de mise en page Xswitcher pour Linux : deuxième étape

D'une manière ou d'une autre, il s'avère que les programmeurs normaux trouvent incroyablement ennuyeux de configurer tous ces contrôles.

Afin de ne pas être sans fondement, vous trouverez à l'intérieur un exemple de ce à quoi je fais face.
Dans l’ensemble, Apache Kafka et ZooKeeper sont parfaitement conçus (et bien implémentés).
- Configuration? Mais c'est ennuyeux ! XML stupide (parce qu'il est "prêt à l'emploi").
- Oh, tu veux aussi un ACL ? Mais c'est tellement ennuyeux ! Tap-blooper... Quelque chose comme ça.

Mais dans mon travail, c’est exactement le contraire. Droite (hélas, presque jamais la première fois) le modèle construit permet de continuer plus loin facilement et naturellement (Presque) assembler un schéma.

Je suis récemment tombé sur un article sur Habré sur le travail acharné des data scientists...
Il s'avère que ce moment est pleinement réalisé pour eux. Et dans ma pratique, comme on dit, « version allégée ». Modèles multivolumes, programmeurs chevronnés avec POO à portée de main, etc. — tout cela apparaîtra plus tard quand/s'il décolle. Mais le concepteur doit commencer quelque part ici et maintenant.

Arriver au point. J'ai pris TOML comme base syntaxique de ce citoyen.

Parce qu'il (TOML) d'une part, modifiable par l'homme. D'autre part, il est traduit 1:1 dans l'une des syntaxes les plus courantes : XML, JSON, YAML.
De plus, l'implémentation que j'ai utilisée depuis « github.com/BurntSushi/toml », bien que n'étant pas la plus à la mode (toujours la syntaxe 1.4), est syntaxiquement compatible avec le même JSON (« intégré »).

Autrement dit, si vous le souhaitez, vous pouvez simplement dire « parcourez les bois avec votre TOML, je veux XXX » et « corrigez » le code avec une seule ligne.

Ainsi, si vous souhaitez écrire des fenêtres pour configurer xswitcher (Je ne suis pas sûr) Aucun problème n’est attendu « avec votre foutue configuration ».

Pour tous les autres, la syntaxe est basée sur « clé = valeur » (et littéralement quelques options plus compliquées, comme = [some, that, array]) Je suppose
intuitivement pratique.
Ce qui est intéressant c'est que "brûlé" à peu près à la même époque (vers 2013). Seulement, contrairement à moi, l'auteur de TOML s'y est lancé à une échelle appropriée.

Par conséquent, il m’est désormais plus facile d’ajuster sa mise en œuvre à ma convenance, et non l’inverse.

En général, nous prenons TOML (très similaire à l'ancien Windows INI). Et nous avons une configuration dans laquelle nous décrivons comment attacher une série de crochets en fonction de l'ensemble des derniers codes de numérisation du clavier. Ci-dessous, pièce par pièce, voici ce qui s’est passé jusqu’à présent. Et une explication de pourquoi j'ai décidé de cette façon.

0. Abstractions de base

  • Scannez les désignations des codes. Il faut absolument faire quelque chose à ce sujet, car les codes numériques ne sont absolument pas lisibles par l'homme (c'est juste moi loloswitcher).
    J'ai extrait "ecodes.go" de "golang-evdev" (j'étais trop paresseux pour regarder la source originale, même si l'auteur l'a indiqué de manière assez culturelle). Un petit peu (pour l'instant) a corrigé quelque chose qui faisait très peur. Comme « LEFTBRACE » → « L_BRACE ».
  • De plus, il a introduit le concept de « clés d’état ». Puisque la grammaire régulière utilisée ne permet pas de longs passages. (Mais cela vous permet de vérifier avec un minimum de surcharge. Si vous utilisez uniquement l'enregistrement « direct ».)
  • Il y aura un « déduplicateur » intégré de ce qui est pressé. Ainsi, l'état "repeat"=2 s'écrira un fois

1. Section Modèles

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

En quoi consiste un mot du langage humain avec notation phonétique ? (soit une question de graphèmes alias « hiéroglyphes »)? Une sorte de « drap » terrible. Par conséquent, j'introduis immédiatement la notion de « modèle ».

2. Que faire lorsque l'on clique sur quelque chose (un autre code de numérisation est arrivé)

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

Il y a 768 codes au total. (Mais "juste au cas où", j'ai inséré des "surprises" capturantes dans le code xswitcher).
À l'intérieur, j'ai décrit le remplissage du tableau avec des liens vers des fonctions « que faire ». En Golang, c'est (soudain) Cela s’est avéré pratique et évident.

  • Je prévois de réduire « Drop » au minimum à cet endroit. En faveur d'un traitement plus flexible (je le montrerai ci-dessous).

3. Table avec classes de fenêtres

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

Les lignes du tableau sont entre doubles crochets avec son nom. Cela n’aurait pas pu être plus simple dès le départ. En fonction de la fenêtre actuellement active, vous pouvez sélectionner les options suivantes :

  • Votre propre ensemble de « touches de raccourci » « Actions = … ». Si ce n'est pas le cas/vide, ne faites rien.
  • Changez « MouseClickDrops » - que faire lorsqu'un clic de souris est détecté. Puisqu'au moment où xswitcher est activé, il n'y a aucun détail sur « l'endroit où ils cliquent », nous réinitialisons le tampon par défaut. Mais dans les terminaux (par exemple), vous n’êtes pas obligé de faire ça (généralement).

4. Une (ou plusieurs) séquences de clics déclenchent l'un ou l'autre crochet

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

Les crochets sont divisés en deux types. Intégré, avec noms « parlants » (NewWord, NewSentence, Compose) et programmable.

Les noms programmables commencent par « Action ». Parce que TOML v1.4, les noms avec des points doivent être entre guillemets.

Chaque section doit être décrite ci-dessous avec le même nom.

Afin de ne pas épater les gens avec des habitués « nus » (par expérience, leurs écrirepeut-être un sur dix professionnels), j'implémente immédiatement une syntaxe supplémentaire.

  • "OFF :" (ou "ON :") avant l'expression rationnelle (expression régulière), il faut que les boutons suivants soient relâchés (ou enfoncés).
    Ensuite, je vais créer une expression régulière « injuste ». Avec contrôle séparé des pièces entre les tuyaux "|". Afin de réduire le nombre d'enregistrements comme "[LR]_SHIFT" (là où cela n'est clairement pas nécessaire).
  • « SÉQ : » Si la condition précédente est remplie (ou absente), alors nous vérifions par rapport à une expression régulière « normale ». Pour plus de détails, j'envoie immédiatement à ^W la bibliothèque « regexp ». Parce que je n'ai toujours pas pris la peine de connaître le degré de compatibilité avec mon pcre préféré (« compatible perl »).
  • L'expression s'écrit sous la forme "BOUTON_1 : CODE1, BOUTON_2 : CODE2" etc., dans l'ordre dans lequel les codes de numérisation sont reçus.
  • Le chèque est toujours « calé » à la fin de la séquence, il n'est donc pas nécessaire d'ajouter « $ » à la queue.
  • Tous les contrôles d'une ligne sont effectués les uns après les autres et sont combinés par « je ». Mais comme la valeur est décrite comme un tableau, vous pouvez écrire une vérification alternative après la virgule. Si cela est nécessaire pour une raison quelconque.
  • Valeur "Longueur Séq = 8" limite la taille du tampon par rapport auquel toutes les vérifications sont effectuées. Parce que Je n’ai (jusqu’à présent) jamais rencontré de ressources infinies dans ma vie.

5. Pose des crochets décrits dans la section précédente

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

L'essentiel ici est "Action = [Tableau]". Semblable à la section précédente, il existe un ensemble limité d’actions intégrées. Et la possibilité d'amarrage n'est en principe pas limitée (écrivez « Action.XXX » et ne soyez pas trop paresseux pour écrire une autre section pour cela).
En particulier, la retape d'un mot dans la mise en page corrigée se divise en deux parties : "changer la mise en page comme spécifié ici" и "retaper" ("RetaperWord").

Les paramètres restants sont écrits dans le « dictionnaire » ("carte" en golang) pour une action donnée, leur liste dépend de ce qui est écrit dans « Action ».

Plusieurs actions différentes peuvent être décrites dans un seul tas (sections). Ou vous pouvez le démonter. Comme je l'ai montré ci-dessus.

J'ai immédiatement défini l'action « Exec » pour exécuter le script externe. Avec la possibilité de pousser le tampon enregistré dans stdin.

  • "Wait = 1" - attendez la fin du processus en cours.
  • Probablement, « au tas », vous souhaiterez mettre des personnes supplémentaires dans l'environnement. des informations telles que le nom de la classe de fenêtre à partir de laquelle elle a été interceptée.
    « Voulez-vous connecter votre gestionnaire ? C’est là que vous devez aller.

Ouf (expiré). Il me semble que je n'ai rien oublié.

Oops! Ouais, je n'ai pas oublié...
Où est la configuration de lancement ? En code dur ? Comme ça:

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

Où ai-je oublié/fait une erreur ? (pas question sans ça), j'espère vraiment que les lecteurs attentifs ne seront pas trop paresseux pour se mettre le nez.

Bonne chance!

Source: habr.com

Ajouter un commentaire