Puisque (le switcher en phase de «preuve de concept») a reçu pas mal de retours constructifs (ce qui est agréable), j'ai continué à consacrer mon temps libre au développement du projet. Maintenant, je veux consacrer un peu du vôtre... Le deuxième pas sera un peu inhabituel : proposition/discussion sur le design de la configuration.

Il se trouve que pour les bons programmeurs, configurer tous ces boutons est terriblement ennuyeux.
Pour ne pas être dans l'abstrait, voici un exemple de ce avec quoi je travaille.
Excellentes idées dans l'ensemble (et pas mal mises en œuvre) pour Apache Kafka & ZooKeeper.
— Configuration ? Mais c'est ennuyeux ! Voilà des fichiers XML à l'arrache (parce que «clé en main»).
— Oh, et vous voulez aussi des ACL ? Mais c'est si pénible ! Et voilà… C'est comme ça.
Et dans mon travail, c'est exactement le contraire. C'est juste (hélas, ça ne fonctionne presque jamais du premier coup) le modèle construit permet ensuite d'assembler facilement et sans souci (enfin, presque) un schéma.
Je suis tombé récemment sur un article sur Habr concernant le travail difficile des data-scientists…
Il s'avère que ce moment y est pleinement réalisé. Et dans ma pratique, comme on dit, «version light». Des modèles volumineux, des programmeurs aguerris avec la POO à l'appui, etc. — tout cela apparaîtra plus tard, quand/si ça décolle. Mais le constructeur doit commencer ici et maintenant.
Revenons à nos moutons. Comme base syntaxique, j'ai choisi TOML .
Parce qu'il (TOML) d'une part est éditable par l'homme. D'autre part, il se traduit 1:1 dans n'importe quelle des syntaxes plus répandues : XML, JSON, YAML.
De plus, l'implémentation que j'ai utilisée de «github.com/BurntSushi/toml», bien que ce ne soit pas la plus à la mode (qui utilise encore la syntaxe 1.4), est néanmoins syntaxiquement compatible avec le JSON « intégré ».
Cela signifie qu'il est possible, si l'on le souhaite, de dire simplement «va te faire voir avec ton TOML, je veux XXX» et de «patcher» le code en une seule ligne.
Ainsi, si l'on souhaite écrire des fenêtres de configuration pour le switcher (ce ne sera certainement pas moi) des problèmes «avec ce putain de config» — ne sont pas à prévoir.
Pour toutes les autres, la syntaxe basée sur «clé = valeur» (et littéralement avec quelques options plus complexes, comme = [un, certain, tableau]) je suppose
intuitivement utilisable.
Ce qui est intéressant, c'est que moi-même à peu près à la même époque (dans les environs de 2013). Sauf qu'à la différence de moi, l'auteur de TOML est arrivé avec l'ampleur qui s'impose.
C'est pourquoi il est maintenant plus facile pour moi d'adapter son implémentation à mes besoins, plutôt que l'inverse.
En gros, nous prenons TOML (très similaire à l'ancien INI de Windows). Et nous définissons la configuration, dans laquelle nous décrivons comment attacher une série de hooks en fonction de la dernière série de codes de touches du clavier. Ci-dessous, par morceaux, ce que j'ai obtenu jusqu'à présent. Et les explications sur pourquoi j'ai pris cette décision.
0. Abstractions de base
- Désignations des codes de touches. Il est nécessaire d'en faire quelque chose car les simples codes numériques ne sont absolument pas lisibles par les humains (c'est ce que j'appelle dans mon jardin) ).
J'ai extrait «ecodes.go» de «golang-evdev» (je n'ai pas eu la motivation d'aller au source originale, bien qu'il soit cité de manière tout à fait adéquate par l'auteur). J'ai légèrement corrigé ce qui était vraiment horrible. Comme «LEFTBRACE» → «L_BRACE». - J'ai également introduit le concept de «touches à état». Étant donné que la grammaire régulière utilisée ne se prête pas à de longs passages. (Cela permet de vérifier avec un minimum de coûts. Si l'on utilise uniquement l'enregistrement «direct».)
- Un «déduplicationneur» des touches pressées sera intégré. Ainsi, l'état «répétition»=2 sera enregistré. ; idéalement, l'utilisateur devrait choisir deux ou plusieurs questions secrètes article.
1. Section des modèles
[Templates] # "@name@" pour simplifier les expressions
# Les mots peuvent se composer de ces caractères (regex)
"WORD" = "([0-9A-Z`;']|[LR]_BRACE|COMMA|DOT|SLASH|KP[0-9])"
De quoi est constitué un mot dans une langue humaine avec transcription phonétique (quelle différence avec les graphèmes alias «idéogrammes»)? Какая-то ужасная «простыня». Поэтому сразу закладываю понятие «шаблона».
2. Que faire lorsque quelque chose est pressé (un nouveau code de touche est arrivé)
[ActionKeys]
# Collecter la touche et faire le test de la séquence de commandes
# !!! Les codes répétés (code=2) doivent être collectés une fois par touche !
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"]
# Supprimer toutes les touches collectées, y compris celle-ci. C'est l'action par défaut.
Drop = ["ESC", "-", "TAB", "ENTER", "KPENTER", "LINEFEED..POWER"]
# Stocker un mappage supplémentaire pour ces touches, lorsque l'une d'elles est en état "down".
# L'état est vérifié via les conditions "OFF:"|"ON:" dans l'action.
# (De plus, l'état de ces touches doit persister entre les suppressions de tampon.)
# ??? Comment gérer les touches CAPS et "LOCK" ???
StateKeys = ["L_CTRL", "L_SHIFT", "L_ALT", "L_META", "CAPS", "N_LOCK", "S_LOCK",
"R_CTRL", "R_SHIFT", "R_ALT", "R_META"]
# Test uniquement, mais ne pas collecter.
# Par exemple, j'utilise F12 à la place de BREAK sur des ordinateurs portables basiques avec des claviers médiocres (nouveaux ThinkPads)
Test = ["F1..F10", "ZENKAKUHANKAKU", "102ND", "F11", "F12",
"RO..KPJPCOMMA", "SYSRQ", "SCALE", "HANGEUL..YEN",
"STOP..SCROLLDOWN", "NEW..MAX"]
Au total, 768 codes prévues. (Mais «par précaution», j'ai inséré dans le code xswitcher un mécanisme pour attraper les «surprises»).
J'ai détaillé le remplissage d'un tableau avec des références aux fonctions «que faire». En golang, cela (de manière inattendue) s'est avéré être pratique et évident.
- Je prévois de réduire « Drop » à son strict minimum ici. Au profit d'un traitement plus flexible (je l'expliquerai ci-dessous).
3. Tableau avec les 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. Il n'a pas été plus facile de procéder directement. En fonction de la fenêtre active actuelle, il est possible de choisir des options :
- Un ensemble de « touches de raccourci » « Actions = … ». Si aucun / vide — ne rien faire.
- L'interrupteur « MouseClickDrops » — que faire lorsqu'un clic de souris est détecté. Comme il n'y a aucun détail à l'endroit de l'activation de xswitcher « où il clique », par défaut, nous réinitialisons le tampon. Mais dans certains terminaux, cela peut ne pas être nécessaire. (en général).
4. Une (ou plusieurs) séquences de touches déclenchent un hook ou un autre.
# 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 hooks sont divisés en deux types. Intégrés, avec des noms « parlants » (NewWord, NewSentence, Compose) et programmables.
Les noms des programmables commencent par « Action. ». Depuis TOML v1.4, les noms contenant des points doivent être entre guillemets.
Ci-dessous, chaque section doit être décrite avec le même nom. pour ne pas submerger les gens avec des regex « nues » (selon l'expérience, cela.
— cela peut être un dans dix pour écrireprofessionnels ), j'introduis immédiatement une syntaxe supplémentaire.« OFF: » (ou « ON: »)
- avant le regexp (expression régulière) exige que les boutons spécifiés soient relâchés (ou enfoncés). Ensuite, je prévois de rédiger une expression régulière « malhonnête ». Avec vérification séparée des segments entre les pipes "|". Dans le but de réduire le nombre d'entrées telles que "[LR]_SHIFT" (là où c'est manifestement inutile).
« SEQ: » - Si la condition précédente est remplie (ou absente), nous vérifions ensuite par rapport à une expression régulière « ordinaire ». Pour plus de détails, je renvoie immédiatement à la bibliothèque « regexp ». Parce que je n'ai pas encore pris la peine de déterminer le degré de compatibilité avec mes préférés pcre (« perl compatible »). L'expression s'écrit sous la forme
- « BOUTON_1: CODE1, BOUTON_2: CODE2 » et ainsi de suite, dans l'ordre des codes scannés. La vérification est toujours « accrochée » à la fin de la séquence
- , donc il n'est pas nécessaire d'ajouter "$" à la fin.Toutes les vérifications dans une seule ligne s'exécutent les unes après les autres
- et sont combinées par un « ET ». Mais comme la valeur est décrite sous forme de tableau, il est possible d'écrire une vérification alternative après la virgule. Si c'est nécessaire pour une raison quelconque. « SeqLength = 8 »
- Valeur limite la taille du tampon, par rapport à laquelle toutes les vérifications sont effectuées. limite la taille du tampon pour laquelle toutes les vérifications sont effectuées. Étant donné que je n'ai jamais rencontré de ressources infinies dans ma vie (jusqu'à présent).
5. Assignation des hooks 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 — «Action = [Array]». De même que dans la section précédente, il existe un ensemble limité d'actions intégrées. Et une possibilité théorique illimitée de raccordement (écrire «Action.XXX» et ne pas hésiter à décrire une autre section à son égard).
Y compris, le ré-encodage d'un mot dans la disposition corrigée se divise en deux parties : «change la disposition comme il est indiqué là-bas» et «réécris» («RetypeWord»).
Les autres paramètres sont enregistrés dans un «dictionnaire» («map» en golang) pour cette action, leur liste dépend de ce qui est écrit dans «Action».
Plusieurs actions différentes peuvent être décrites dans un même groupe (sections). On peut aussi les diviser. Comme je l'ai montré ci-dessus.
Je prépare immédiatement l'action «Exec» — exécuter un script externe. Avec l'option de transmettre un tampon enregistré dans stdin.
- «Wait = 1» — attendre la fin du processus lancé.
- Il est probable que vous voudrez également fournir des informations supplémentaires à l'environnement, comme le nom de la classe de la fenêtre d'où l'interception a eu lieu.
«Vous voulez connecter votre propre gestionnaire ? Voici où aller.»
Ouf (soupir). J'espère que je n'ai rien oublié.
Oh ! Oui, je n'ai rien oublié…
Et où est la configuration de lancement ? Dans le code dur ? À peu près comme ça :
[ScanDevices]
# Doit exister au démarrage. Auto-repasser dans le cas où il est plus jeune que 30s
Test = "\/dev\/input\/event0"
Respawn = 30
# Masque de recherche
Search = "\/dev\/input\/event*"
# Dans mes ThinkPads, il y a des pseudo-claviers avec des tas d'événements inutiles
Bypass = "(?i)Video|Camera" # "(?i)" diffère évidemment des pcre «classiques».
Et où ai-je oublié/commis une erreur (sans cela — impossible), j'espère vraiment que les lecteurs attentifs ne se fatigueront pas à le signaler.
Bonne chance !
Source : habr.com
