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

先前出版物 (xswitcher 處於概念驗證階段)已經收到了很多建設性的回饋 (這很好)之後,我繼續利用空閒時間來開發這個專案。 現在我想花一點你的錢… 第二步會有些不尋常:提出/討論配置設計。

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

不知何故,事實證明,普通程式設計師發現設置所有這些旋鈕非常無聊。

為了避免毫無根據,以下是我正在處理的一個例子。
Apache Kafka 和 ZooKeeper 整體來說構思巧妙(並且實現良好)。
- 配置?但這很無聊! Slappy XML(因為它是「開箱即用的」)。
- 哦,你還想要 ACL 嗎?但它太無聊了!啪嗒啪嗒……類似這樣的事。

但在我的工作中,情況卻恰恰相反。正確的 (唉,第一次幾乎不會發生這種情況) 建造的模型允許進一步輕鬆和放鬆 (好吧,差不多) 組裝電路。

我最近在 Habr 上看到一篇關於資料科學家艱苦工作的文章...
事實證明,這一刻對他們來說已經完全實現了。在我的實踐中,正如他們所說,這是“輕鬆版”。多卷模型、經驗豐富的 OOP 程式設計師等等 - 所有這些都將在以後出現,當/如果它起飛的話。但設計師需要從現在開始。

讓我們開始談正事吧。我以 TOML 作為語法基礎 來自這位公民.

因為他是 (TOML) 一方面,人類可編輯。另一方面,它以 1:1 的比例轉換為任何更常見的語法:XML、JSON、YAML。
此外,我從「github.com/BurntSushi/toml」使用的實作不是最受歡迎的(仍然是 1.4 語法),但它在語法上與相同(「內建」)JSON 相容。

也就是說,如果你願意,你可以簡單地說“去你的 TOML,我要 XXX”,然後用一行程式碼“修補”程式碼。

因此,如果你想寫一些視窗來設定 xswitcher (絕對不是我) 看起來“你的這個該死的配置”沒有任何問題。

對於所有其他情況,語法是基於“key = value”的 (實際上還有一些更複雜的選項,例如 = [some, array]) 我想
直觀方便。
有趣的是 “燒毀” 大約在同一時間(2013 年左右)。只是,與我不同的是,TOML 的作者對此進行了適當的探討。

這就是為什麼我現在可以更輕鬆地調整其實施方式以適合自己,而不是反過來。

一般來說,我們採用 TOML(與舊的 Windows INI 非常相似)。我們設定了一個配置,其中描述如何根據鍵盤的最新掃描碼集附加一系列鉤子。以下分部分列出了迄今為止所取得的成就。並解釋我為什麼做出這樣的決定。

0.基本抽象

  • 掃描代碼指定。肯定需要對此採取措施,因為數字代碼絕對不是人類可讀的(我在這裡是諷刺的)。 loloswitcher).
    從“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"

表格行及其名稱位於雙方括號中。 它馬上就變得簡單多了。 根據目前活動窗口,您可以選擇選項:

  • 您自己的一組熱鍵“操作=...”。如果沒有或為空 - 則不執行任何操作。
  • 「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,有句點的名稱必須放在引號中。

下面每個部分都應該有一個描述 同名.

為了不讓常客們因為「裸體」而感到震驚(根據經驗,他們 -那麼可能是十分之一 專業人士),我立即實現了額外的語法。

  • 「關閉:」(或「開啟:」) 在 regexp(正規表示式)之前需要釋放(或按下)以下按鈕。
    接下來我要做一個「不誠實」的正規表示式。對管道“|”之間的部分進行單獨檢查。為了減少“[LR]_SHIFT”類型的條目數量(顯然不需要)。
  • “序號:” 如果滿足(或不滿足)先前的條件,那麼我們根據「正常」正規表示式進行檢查。有關詳細信息,我立即將您發送到“regexp”庫中的^W。 因為我還沒有費心去弄清楚它與我最喜歡的 pcre(“perl 兼容”)的兼容程度。
  • 表達式為 “BUTTON_1:代碼1,BUTTON_2:代碼2” 等等,依照接收掃描碼的順序。
  • 檢查始終“按”到序列的末尾,所以不需要在尾部加上“$”。
  • 一行中的所有檢查均依序執行 並且由“AND”連接起來。但由於該值被描述為一個數組,因此您可以在逗號後寫入替代檢查。 如果由於某種原因需要它。
  • “序列長度 = 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”,不要懶得為它寫另一個部分).
具體來說,在更正後的佈局中重新輸入單字分為兩個部分: “更改那裡設定的佈局” и 「重新輸入」(“RetypeWord”).

其餘參數寫入“字典” (golang 中的「map」) 對於這個行動,他們的名單取決於「行動」中寫的內容。

在一個堆中可以描述幾種不同的操作 (章節)。或者你可以將其拖曳開。正如我上面所展示的。

我立即設定操作“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