适用于 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.

我在哪里忘记/犯了错误? (没有这个就不行),真心希望细心的读者不要懒得戳鼻子。

祝你好运!

来源: habr.com

添加评论