適用於 Linux 的 Xswitcher 佈局校正器:第二步

以前的出版物 (xswitcher 處於「概念驗證」階段)收到了相當多的建設性回饋 (這很好),我繼續利用空閒時間開發該專案。 現在我想花你的一點... 第二步較不常見:提議/討論配置設計。

適用於 Linux 的 Xswitcher 佈局校正器:第二步

不知怎的,事實證明,普通程式設計師發現設定所有這些控制項非常無聊。

為了不毫無根據,裡面是我正在處理的例子。
整體而言,Apache Kafka 和 ZooKeeper 構思出色(且實作良好)。
- 配置? 但這很無聊! 愚蠢的 xml(因為它是“開箱即用的”)。
- 哦,你也想要 ACL 嗎? 但實在是太無聊了! Tap-blooper...類似的東西。

但在我的作品中卻恰恰相反。 正確的 (唉,幾乎從來沒有第一次) 建立的模型可以讓您輕鬆自然地繼續下去 (幾乎) 組裝一個圖表。

我最近看到一篇關於 Habré 的文章,講述了資料科學家的辛勤工作...
事實證明,這一刻對他們來說是徹底實現了。 在我的實踐中,正如他們所說,“輕型版本”。 多卷模型、經驗豐富且準備好 OOP 的程式設計師等。 ——這一切都會在稍後起飛時出現。 但設計師需要從此時此地開始。

進入正題吧。 我以 TOML 作為語法基礎 來自這位公民.

因為他 (TOML) 一方面,人工可編輯。 另一方面,它被 1:1 轉換為任何更常見的語法:XML、JSON、YAML。
此外,我使用的“github.com/BurntSushi/toml”實現雖然不是最時尚的(仍然是 1.4 語法),但在語法上與相同(“內置”)JSON 兼容。

也就是說,如果您願意,您可以簡單地說“用您的 TOML 穿過樹林,我想要 XXX”,然後僅用一行“修補”程式碼。

因此,如果你想寫一些windows來設定xswitcher (我不知道) 「用你這個該死的配置」預計不會有任何問題。

對於所有其他的,語法基於“key = value” (實際上是幾個更複雜的選項,例如 = [some, that, array]) 我猜
直觀方便。
有趣的是 “燒焦” 大約在同一時間(2013 年左右)。 只是,與我不同的是,TOML 的作者參與的規模適當。

因此,現在我更容易調整其實現以適合自己,而不是相反。

一般來說,我們採用 TOML(與舊的 Windows INI 非常相似)。 我們有一個配置,其中描述瞭如何根據鍵盤上最新的掃描代碼集附加一系列掛鉤。 以下逐一介紹迄今為止所發生的事情。 並解釋了我為什麼這樣決定。

0. 基本抽象

  • 掃碼指定。 肯定需要對此採取一些措施,因為簡單的數字代碼絕對不是人類可讀的(這只是我的觀點) 洛洛切換者).
    我從“golang-evdev”中剔除了“ecodes.go”(我懶得看原始來源,儘管作者在文化上表明了這一點)。 我(暫時)糾正了一些非常可怕的事情。 就像“LEFTBRACE”→“L_BRACE”。
  • 此外,他還引入了「狀態密鑰」的概念。 由於使用的常規語法不允許長段落。 (但它允許您以最小的開銷進行檢查。如果您僅使用“直接”記錄。)
  • 將會有一個內建的「重複資料刪除器」來記錄所按下的內容。 因此,狀態「repeat」=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])"

帶有註音符號的人類語言單字由什麼組成? (要么是字素問題,又稱“象形文字”)? 某種可怕的「床單」。 因此,我立即引入“模板”的概念。

2.點擊後怎麼辦(又一個掃碼到了)

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

共有768個代碼。 (但“以防萬一”,我在 xswitcher 程式碼中插入了捕捉“驚喜”的內容)。
在裡面我描述了用指向函數“做什麼”的連結填充數組。 在 golang 中這是 (突然) 事實證明它既方便又明顯。

  • 我打算把這個地方的「Drop」降到最低。 有利於更靈活的處理(我將在下面展示)。

3. 帶有視窗類別的表

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

表的行包含在雙方括號中及其名稱。 立刻就變得再容易不過了。 根據目前活動窗口,您可以選擇以下選項:

  • 您自己的一組“熱鍵”“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)" ]

鉤子分為兩種。 內置,具有“會說話”的名稱(NewWord、NewSentence、Compose)且可編程。

可程式名稱以“Action”開頭。 因為TOML v1.4,帶點的名稱必須用引號引起來。

每個部分應在下面進行描述 同名.

為了不讓人們對「裸體」常客感到震驚(根據經驗,他們的 也許十分之一 專業人士),我立即實作附加語法。

  • 「關:」(或「開:」) before regexp(正規表示式)要求釋放(或按下)以下按鈕。
    接下來我將製作一個「不公平」的正規表示式。 單獨檢查管道“|”之間的部分。 為了減少像“[LR]_SHIFT”這樣的記錄數量(這顯然是不必要的)。
  • “序列:” 如果滿足(或不存在)前面的條件,則我們檢查「正常」正規表示式。 有關詳細信息,我立即將“regexp”庫發送給^W。 因為我還沒有費心去找出與我最喜歡的pcre的兼容程度(“perl兼容”)。
  • 表達式寫成以下形式 “BUTTON_1:代碼 1,BUTTON_2:代碼 2” 等等,按照掃描碼接收的順序。
  • 檢查始終“緊貼”到序列的末尾,所以不需要在尾部加上“$”。
  • 一行中的所有檢查都依序執行 並由「我」組合而成。 但由於該值被描述為數組,因此您可以在逗號後編寫替代檢查。 如果出於某種原因需要這樣做。
  • “序列長度 = 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.

這裡最主要的是 “動作=[陣列]”。 與上一節類似,有一組有限的內建操作。 且原則上對接的可能性不受限制 (寫“Action.XXX”,不要再寫一段).
具體來說,在修正後的佈局中重新輸入一個單字分為兩部分: “更改那裡指定的佈局” и 「重新輸入」(「重新輸入單字」).

其餘參數寫入“字典” (golang 中的「地圖」) 對於給定的操作,它們的清單取決於「操作」中寫入的內容。

多個不同的動作可以在一個堆中描述 (部分)。 或者你可以把它拆開。 正如我上面所展示的。

我立即設定“Exec”操作來執行外部腳本。 可以選擇將記錄的緩衝區推入標準輸入。

  • “Wait = 1” — 等待正在運行的進程完成。
  • 也許,當「到堆」時,您會想要在環境中放置更多的人。 訊息,例如從中截獲的視窗類別的名稱。
    「你想連接你的處理程序嗎? 這就是你需要去的地方。”

呼(呼氣)。 看來我什麼都沒忘記。

哎呀! 是的,我沒有忘記…
啟動配置在哪裡? 用硬代碼? 像那樣:

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

我在哪裡忘記/犯了錯? (沒有這個就不行),真心希望細心的讀者不要懶得戳鼻子。

祝你好運!

來源: www.habr.com

添加評論