Correcteur de disposition Xswitcher pour Linux: deuxième étape

Depuis que la publication précédente (xswitcher au stade de la «preuve de concept») a reçu de nombreuses critiques constructives (ce qui est bien) , j'ai continué à consacrer mon temps libre au développement du projet. Maintenant, je veux passer un peu de la vôtre ... La deuxième étape ne sera pas très familière: suggestion / discussion sur la conception de la configuration.



D'une certaine manière, il s'avère que les programmeurs normaux s'ennuient énormément à régler tous ces rebondissements.

Afin de ne pas être infondé, inside est un exemple de ce à quoi je fais face.
( ) Apache Kafka & ZooKeeper.
— ? ! - xml ( « »).
— , ACL ? ! -… - .

Et dans mon travail - exactement le contraire. Correctement (hélas, depuis la première fois presque jamais) le modèle construit vous permet d' assembler facilement et facilement (enfin, presque) le circuit.

Récemment, un article sur Habré est tombé sur un article sur le travail acharné des scientifiques des données ...
, . , , « ». , .. — , / . - .

Arriver au point. Comme base syntaxique, j'ai pris TOML à ce citoyen .

Parce qu'il (TOML) est d'une part modifiable par l'homme. Et de l'autre - il traduit 1: 1 dans l'une des syntaxes les plus courantes: XML, JSON, YAML.
De plus, l'implémentation que j'ai utilisée à partir de «github.com/BurntSushi/toml», bien qu'elle ne soit pas la plus en vogue (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 "parcourir les bois avec ce votre TOML, je veux XXX" et "patcher" le code avec une seule ligne.

Ainsi, si vous voulez écrire des fenêtres (certainement pas moi) pour configurer xswitcher, il n'y a aucun problème "avec cette putain de config".

Pour tous les autres, la syntaxe basée sur "clé = valeur" (et littéralement quelques options est plus compliquée, tapez = [certains, alors, tableau]) Je suppose
intuitivement pratique.
, «» ( 2013 ). , , TOML .

, .

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 en morceaux - ce qui s'est passé en ce moment. Et l'explication de ce que j'ai décidé ainsi.

0. Abstractions de base


  • -. - , - ( loloswitcher).
    «ecodes.go» «golang-evdev» ( , ). () . «LEFTBRACE» → «L_BRACE».
  • « ». . ( . «» .)
  • «» . , «»=2 .

1.


[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 en langage humain avec notation phonétique (ou s'agit-il d'un cas de graphème ou de «hiéroglyphes») ? Une sorte de "drap" terrible. Par conséquent, je pose immédiatement le concept de «modèle».

2. Que faire lorsque vous appuyez sur quelque chose (le prochain 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"]

Au total, 768 codes sont fournis. (Mais, juste au cas où, j'ai inséré une prise de "surprises" dans le code xswitcher).
À l'intérieur, j'ai peint en remplissant le tableau avec des liens vers les fonctions "que faire". Sur golang, cela s'est avéré (soudainement) pratique et évident.

  • "Drop" dans cet endroit, je prévois de réduire au minimum. En faveur d'un traitement plus flexible (je montrerai ci-dessous).

3. Étiquette de classe de fenêtre


# 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 crochets doubles avec son nom. Plus facile à faire n'a pas fonctionné. Selon la fenêtre active actuelle, vous pouvez choisir les options:

  • « » «Actions = …». / — .
  • «MouseClickDrops» — . xswitcher « », - . () ( ).

4. ( )


# 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 des noms "parlants" (NewWord, NewSentence, Compose) et programmable.

Les noms programmables commencent par "Action". Parce que TOML v1.4, les noms en pointillés doivent être entre guillemets.

Une section portant le même nom doit être décrite ci-dessous pour chacun .

Afin de ne pas faire exploser le cerveau des gens avec des habitués «nus» (par expérience, un professionnel sur dix peut les écrire ), j'introduis immédiatement une syntaxe supplémentaire.

  • «OFF:» ( «ON:») regexp ( ) ( ).
    «» . "|". "[LR]_SHIFT" ( ).
  • «SEQ:» ( ), «» . ^W «regexp». pcre («perl compatible»).
  • «_1: 1, _2: 2» .., -.
  • «» , "$" .
  • «». , . - .
  • «SeqLength = 8» , . .. ( ) .

5. ,


# 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 chose principale ici est "Action = [Array]" . Semblable à la section précédente, il existe un ensemble limité d'actions intégrées. Et illimité en principe la possibilité d'accostage (écrivez "Action.XXX" et ne soyez pas trop paresseux pour peindre une autre section pour cela) .
En particulier, le mot retaper dans la mise en page corrigée est divisé en deux parties: «changer la mise en page telle qu'elle y est définie» et «retaper» («RetypeWord») .

Les paramètres restants sont écrits dans le "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 segment (section) . Et vous pouvez le démonter. Comme je l'ai montré ci-dessus.

Posez immédiatement l'action «Exec» - exécutez un script externe. Avec l'option de pousser le tampon écrit dans stdin.

  • «Wait = 1» - attend la fin du processus en cours.
  • Probablement, "au tas" voudra mettre dans l'environnement supplémentaire. des informations telles que le nom de la classe de fenêtre à partir de laquelle il est intercepté.
    "Vous voulez brancher votre gestionnaire?" À toi ici. "

Ouf (expiré). Il semble n'avoir rien oublié.

Oops! Ouais, je n’ai pas oublié ...
? -́? :

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


Et là où j'ai oublié / fait une erreur (sans ça - pas question) , j'espère vraiment que les lecteurs attentifs ne sont pas trop paresseux pour se piquer le nez.

Bonne chance

All Articles