Xswitcher-Layout-Korrektur für Linux: Schritt zwei

Als vorherige Veröffentlichung (xswitcher in der „Proof of Concept“-Phase) erhielt ziemlich viel konstruktives Feedback (was nett ist)Ich verbrachte weiterhin meine Freizeit mit der Entwicklung des Projekts. Jetzt möchte ich ein wenig von deinem... ausgeben. Der zweite Schritt wird nicht ganz üblich sein: Vorschlag/Diskussion des Konfigurationsdesigns.

Xswitcher-Layout-Korrektur für Linux: Schritt zwei

Irgendwie stellt sich heraus, dass normale Programmierer es unglaublich langweilig finden, all diese Steuerelemente einzurichten.

Um nicht unbegründet zu sein, finden Sie hier ein Beispiel dafür, womit ich es zu tun habe.
Insgesamt hervorragend konzipierter (und gut implementierter) Apache Kafka & ZooKeeper.
- Aufbau? Aber es ist langweilig! Dummes XML (weil es „out of the box“ ist).
- Oh, willst du auch eine ACL? Aber es ist so langweilig! Tap-Blooper... So etwas in der Art.

Aber in meiner Arbeit ist es genau das Gegenteil. Rechts (Leider fast nie beim ersten Mal) Das konstruierte Modell ermöglicht Ihnen ein einfaches und natürliches Weitergehen (Fast) Stellen Sie ein Diagramm zusammen.

Kürzlich bin ich auf Habré auf einen Artikel über die harte Arbeit von Datenwissenschaftlern gestoßen ...
Es stellt sich heraus, dass dieser Moment für sie vollständig verwirklicht ist. Und in meiner Praxis, wie man sagt, „Light-Version“. Mehrbändige Modelle, erfahrene Programmierer mit OOP im Griff usw. — das alles wird später erscheinen, wenn/falls es abhebt. Aber der Designer muss hier und jetzt irgendwo anfangen.

Komm zum Punkt. Als syntaktische Grundlage habe ich TOML genommen von diesem Bürger.

Weil er (TOML) einerseits menschlich editierbar. Andererseits wird es 1:1 in eine der gängigeren Syntaxen übersetzt: XML, JSON, YAML.
Darüber hinaus ist die Implementierung, die ich von „github.com/BurntSushi/toml“ verwendet habe, zwar nicht die modernste (immer noch 1.4-Syntax), aber syntaktisch mit demselben („integrierten“) JSON kompatibel.

Das heißt, wenn Sie möchten, können Sie einfach sagen „Gehen Sie mit Ihrem TOML durch den Wald, ich möchte XXX“ und den Code mit nur einer Zeile „flicken“.

Wenn Sie also einige Fenster zum Konfigurieren von xswitcher schreiben möchten (Ich bin mir nicht sicher) „Mit dieser verdammten Konfiguration von Ihnen“ sind keine Probleme zu erwarten.

Für alle anderen basiert die Syntax auf „key = value“ (und buchstäblich ein paar kompliziertere Optionen, wie = [some, that, array]) Schätze ich
intuitiv bequem.
Das Interessante ist das "verbrannt" etwa zur gleichen Zeit (ca. 2013). Nur ist der Autor von TOML im Gegensatz zu mir in angemessenem Umfang vorgegangen.

Daher fällt es mir jetzt leichter, die Implementierung an meine Bedürfnisse anzupassen und nicht umgekehrt.

Im Allgemeinen verwenden wir TOML (sehr ähnlich dem alten Windows INI). Und wir haben eine Konfiguration, in der wir beschreiben, wie eine Reihe von Haken abhängig vom Satz der neuesten Scancodes der Tastatur angebracht werden. Unten sehen Sie Stück für Stück, was bisher passiert ist. Und eine Erklärung, warum ich mich so entschieden habe.

0. Grundlegende Abstraktionen

  • Codebezeichnungen scannen. Daran muss unbedingt etwas getan werden, da einfach digitale Codes absolut nicht für Menschen lesbar sind (das ist nur meine Meinung). loloswitcher).
    Ich habe „ecodes.go“ aus „golang-evdev“ herausgeschüttelt (ich war zu faul, mir die Originalquelle anzusehen, obwohl der Autor dies recht kulturell angedeutet hat). Ich habe (vorerst) ein wenig korrigiert, was ziemlich beängstigend war. Wie „LEFTBRACE“ → „L_BRACE“.
  • Darüber hinaus führte er das Konzept der „Staatsschlüssel“ ein. Da die verwendete reguläre Grammatik keine langen Passagen zulässt. (Aber es ermöglicht Ihnen eine Überprüfung mit minimalem Aufwand. Wenn Sie nur die „direkte“ Aufzeichnung verwenden.)
  • Es wird einen eingebauten „Deduplizierer“ für das geben, was gepresst wird. Somit wird der Zustand „repeat“=2 geschrieben ein Zeiten.

1. Abschnitt „Vorlagen“.

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

Woraus besteht ein menschliches Sprachwort mit phonetischer Notation? (entweder eine Frage von Graphemen, auch bekannt als „Hieroglyphen“)? Eine Art schreckliches „Blatt“. Deshalb führe ich gleich das Konzept der „Vorlage“ ein.

2. Was tun, wenn auf etwas geklickt wird (ein anderer Scancode ist angekommen)

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

Insgesamt gibt es 768 Codes. (Aber „nur für den Fall“ habe ich einfangende „Überraschungen“ in den xswitcher-Code eingefügt).
Darin habe ich beschrieben, wie das Array mit Links zu Funktionen gefüllt wird, „was zu tun ist“. In Golang ist das so (plötzlich) Es stellte sich als praktisch und offensichtlich heraus.

  • Ich habe vor, „Drop“ an dieser Stelle auf ein Minimum zu reduzieren. Für eine flexiblere Abwicklung (ich zeige es weiter unten).

3. Tabelle mit Fensterklassen

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

Die Zeilen der Tabelle stehen in doppelten eckigen Klammern mit ihrem Namen. Es hätte von Anfang an nicht einfacher sein können. Abhängig vom aktuell aktiven Fenster können Sie folgende Optionen auswählen:

  • Ihr eigener Satz von „Hotkeys“ „Aktionen = …“. Wenn nicht/leer, tun Sie nichts.
  • Schalter „MouseClickDrops“ – was tun, wenn ein Mausklick erkannt wird. Da es an der Stelle, an der xswitcher aktiviert wird, keine Details darüber gibt, „wo sie klicken“, setzen wir den Puffer standardmäßig zurück. In Terminals (z. B.) ist dies jedoch nicht erforderlich (allgemein).

4. Eine (oder mehrere) Klicksequenzen lösen den einen oder anderen Haken aus

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

Haken werden in zwei Typen unterteilt. Eingebaut, mit „sprechenden“ Namen (NewWord, NewSentence, Compose) und programmierbar.

Programmierbare Namen beginnen mit „Aktion“. Weil TOML v1.4, Namen mit Punkten müssen in Anführungszeichen stehen.

Jeder Abschnitt sollte im Folgenden beschrieben werden mit dem gleichen Namen.

Um die Leute nicht mit „nackten“ Stammgästen (aus Erfahrung ihrer schreibenvielleicht einer von zehn professionell), implemetiere ich sofort zusätzliche Syntax.

  • „AUS:“ (oder „EIN:“) vor regexp (regulärer Ausdruck) erfordern, dass die folgenden Tasten losgelassen (oder gedrückt) werden.
    Als nächstes werde ich einen „unfairen“ regulären Ausdruck erstellen. Mit separater Stückkontrolle zwischen den Rohren „|“. Um die Anzahl der Datensätze wie „[LR]_SHIFT“ zu reduzieren (wo dies offensichtlich nicht notwendig ist).
  • „SEQ:“ Wenn die vorherige Bedingung erfüllt ist (oder fehlt), prüfen wir anhand eines „normalen“ regulären Ausdrucks. Für Einzelheiten sende ich sofort die „regexp“-Bibliothek an ^W. Weil ich mir immer noch nicht die Mühe gemacht habe, den Grad der Kompatibilität mit meinem Lieblings-PCRE („Perl-kompatibel“) herauszufinden.
  • Der Ausdruck wird in das Formular geschrieben „BUTTON_1: CODE1, BUTTON_2: CODE2“ usw., in der Reihenfolge, in der die Scancodes empfangen werden.
  • Der Scheck wird immer an das Ende der Sequenz „geschmiegt“., daher ist es nicht nötig, „$“ an das Ende anzuhängen.
  • Alle Prüfungen in einer Zeile werden nacheinander durchgeführt und werden durch „Ich“ zusammengefasst. Da der Wert jedoch als Array beschrieben wird, können Sie nach dem Komma eine alternative Prüfung schreiben. Wenn dies aus irgendeinem Grund erforderlich ist.
  • Wert „SeqLength = 8“ begrenzt die Größe des Puffers, anhand dessen alle Prüfungen durchgeführt werden. Weil Ich bin (bis jetzt) ​​noch nie in meinem Leben auf endlose Ressourcen gestoßen.

5. Setzen der im vorherigen Abschnitt beschriebenen Haken

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

Die Hauptsache hier ist „Aktion = [Array]“. Ähnlich wie im vorherigen Abschnitt gibt es eine begrenzte Anzahl integrierter Aktionen. Und die Möglichkeit des Andockens ist grundsätzlich nicht eingeschränkt (Schreiben Sie „Action.XXX“ und seien Sie nicht zu faul, einen weiteren Abschnitt dafür zu schreiben).
Insbesondere gliedert sich das erneute Eintippen eines Wortes im korrigierten Layout in zwei Teile: „Ändern Sie das Layout wie dort angegeben“ и „retype“ („RetypeWord“).

Die restlichen Parameter werden in das „Wörterbuch“ geschrieben („Karte“ in Golang) Für eine bestimmte Aktion hängt ihre Liste davon ab, was in „Aktion“ geschrieben steht.

In einem Heap können mehrere unterschiedliche Aktionen beschrieben werden (Abschnitte). Oder Sie können es auseinanderziehen. Wie ich oben gezeigt habe.

Ich habe sofort die Aktion „Exec“ eingestellt, um das externe Skript auszuführen. Mit der Option, den aufgezeichneten Puffer in stdin zu verschieben.

  • „Wait = 1“ – warten Sie, bis der laufende Vorgang abgeschlossen ist.
  • Wahrscheinlich möchten Sie „auf den Haufen“ zusätzliche Menschen in die Umgebung bringen. Informationen wie den Namen der Fensterklasse, von der es abgefangen wurde.
    „Möchten Sie Ihren Hundeführer verbinden? Hier müssen Sie hin.“

Puh (ausgeatmet). Es scheint, als hätte ich nichts vergessen.

Hoppla! Ja, ich habe es nicht vergessen...
Wo ist die Startkonfiguration? Im Hardcode? Ungefähr so:

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

Wo habe ich vergessen/einen Fehler gemacht? (Ohne das geht es nicht)Ich hoffe wirklich, dass aufmerksame Leser nicht zu faul sind, die Nase zu stecken.

Good luck!

Source: habr.com

Kommentar hinzufügen