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