前言

继上一篇 keyd 入门文章后,关于 Kanata 的文章也来了(比计划要快)。如果你的需求复杂,或要求跨平台,我都强烈建议你迁移到 Kanata 上。它可以看作 Rust 版的 Kmonad。而 Kmonad 又可以看作软件版的 QMK。它们基本包含了 QMK 的全部高级设计和映射定制能力,甚至更加强大。

这是一篇探索 Kanata 安装、配置的新手向教程,过程十分简单!

以上提到的 QMK 是一个知名的键盘开源固件,大量客制化和商业键盘产品都基于它。

键盘

本文将以一个 61 键的小键盘为例,它长这样:

61 键盘布局

如果你第一次见到键位如此少的键盘,不必惊讶。在客制化圈子这类键盘很常见。它没有 F1-F12 功能键、没有方向键。没有导航/编辑键区。连删除键都没有。

在如此精简的布局下自定义就非常有必要的。配置得好是也是十分心应手的,不会差全尺寸键盘太多。如果你使用的是 108 键位的全尺寸键盘,本文仍然对你会有帮助。例如许多时候左手方便完成的事情,不麻烦右手就是更好的(因为右手得离开鼠标)。

Kanata

Kanata 是本文的主角,不同的平台上用不同的后端处理输入以实现键盘映射的自定义。在 Windows 上它基于 LLHOOK 机制或 Interception 驱动。在 Linux 上基于 uinput。在 macoOS 上使用 Karabiner 驱动。不同的后端,安装/配置略有所不同,但这种差异的总体占比极小。

在 Linux 上是最完美的,因为 uinput 提供了完整的用户空间输入设备模拟能力。

Kanata 支持“层”、“层映射”、“和弦”、“宏”等功能,各种精巧的动作,可以用“变量”、“别名”等特性优化配置。它的配置格式相比于 keyd 稍微有些门槛。后文将循序渐进的介绍。

下载

在此之前,请确保已经安装好了 Kanata。在 NixOS、Arch Linux、Void Linux 这类非常新颖的 Linux 系统中,直接安装 kanata 包即可使用。在 Ubuntu 这类可能过旧的系统中,可自行编译或跟 Windows 那样下载预编译的二进制。

请从这个页面找到适合你的系统和硬件架构的版本(展开 Assets)。例如 Windows 用户可将 windows-binaries-x64.zip 下载后解压到本地,其中的 kanata_windows_tty_winIOv2_x64.exe 程序就是我们将要运行的目标。

运行

打开终端软件(或 cmd),输入程序文件的路径加 --cfg <配置文件路径> 参数来运行它。这里是一个最简配置的例子:

(defcfg
  ;; 让未写进 defsrc 的键正常工作
  process-unmapped-keys yes
)

(defsrc)

将它命名为 kanata.kbd,保存到喜欢的路径中(例如 D:\Config\kanata\)。现在我们这样启动 Kanata:

D:\Kanata\kanata_windows_tty_winIOv2_x64.exe --cfg D:\Config\kanata\kanata.kbd

在 Linux 上用命令行启动时,可能需要 sudo 运行。从最小化权限章节了解如何免去 sudo。

如果没有输出 error 相关的日志,那么说明 Kanata 已经成功运行了。但此时它没有任何效果,因为我们的配置是空的。

Kanata 通常作为后台服务运行,我将在单独的自启动章节介绍。为了尽快进入配置教程,目前我们在命令行中前台运行它即可。当配置修改后,手动重启进程让其生效。

配置语法

Kanata 的配置不是 .ini 这类简单的键值对风格,它类似于 Lisp 的 S-表达式。所以我说它的门槛略高一点,毕竟 Lisp 是编程语言。但不用怕,Kanata 的配置并非编程代码。S-表示式由括号包裹,括号内是元素列表。第一个值通常表示命令/功能,后续的值表示参数。例如:

(defcfg process-unmapped-keys yes)

这个表达式的含义是:声明 defcfg 配置,添加了 process-unmapped-keys 和 yes 两个参数。具体而言,我们用 defcfg 来定义全局配置。此处将 process-unmapped-keys 设置为 yes,让未写进 defsrc 的键也能正常工作。

元素本身也可以是一个列表(嵌套)。例如:

(defcfg
  linux-dev ("device1" "device2")
)

这里的 defcfg 仍然只有两个参数,参数成对出现以作为单个配置项的键和值。若直接展开 linux-dev 后面的列表,那么 device2 将被错误的当作下一个配置项的键名。所以我们有必要将它们用括号包裹,作为一个“整体”。

也就是说 device1 和 device2 构成了一个纯粹的列表,作为 linux-dev 配置项的“值”。我们通常将成对的构成单个配置项作为独立的一行,例如:

(defcfg
  process-unmapped-keys yes       # <- 这是第一个全局配置项,让未写进 defsrc 的键正常工作
  linux-dev ("device1" "device2") # <- 这也是一个全局配置项,指定了 Linux 上注册的输入设备
)

被嵌套的列表有时候不是纯粹的值,例如它的第一个元素可能是功能而后续元素是该功能的参数。在配置嵌套动作时很常见。理解 S-表达式的嵌套很重要,某些复杂场景中嵌套可以发挥巨大的作用。

基本配置

仍以本教程初始的配置为例子:

(defcfg
  ;; 让未写进 defsrc 的键正常工作
  process-unmapped-keys yes
)

(defsrc)

一个最基础的配置,只能有也必须要有一个 defsrc(即便它是空的)。上面有介绍过 defcfg,这其实是一个可选配置。它是用于定义全局配置项的地方,有效参数必须是成对的结构(键和值)。

现在我们开始对 defsrc 动刀:

;; 向 defsrc 添加内容,声明输入按键的列表
(defsrc
  a s d f
)

;; 定义第一个层,将 defsrc 中的按键映射为大写
(deflayer start
  1 2 3 4
)

现在重启进程让配置生效,你会发现按下 A、S、D、F 键时,输出的却是 1、2、3、4。如注释所述,我们在 defsrc 中声明了需要映射的按键列表(“源”输入)。然后在 deflayer 中定义了与之一一对应的映射关系。注意 deflayer 的第一个参数 start,它并非映射的一部分,它指的是层的名称。

参数是否换行,都不影响 S-表达式结构的正确性。有些用空格,有些换行,是为了更方便我们组织配置。

就在刚才,我们已经简单的尝试了一个映射相关的配置。如上所示,映射的关键是“层”,它就好似“源”的目标输出一般。接下来我们将继续深入“层”的概念。

记得将 deflayer 整个删除,以恢复正常按键。

层

层(layer)是 Kanata 最重要的核心概念之一。它包含了与 defsrc 中声明的按键一一对应的映射动作的列表。每个层都有一个名字(例如 start),我们通过名字来激活它。稍等,上面章节的配置似乎没有任何激活操作?没错,因为配置文件中定义的第一个层会自动激活(作为初始层)。

我们延续基本配置章节的例子,对初始层小改的同时添加第二个层:

(deflayer start
  (layer-while-held second) 2 3 4 ;; 第一个按键的映射改成了激活 second 层的动作
)

(deflayer second                  ;; 第二个层,名字为 second
  _ w e r                         ;; 映射为新的字母
)

现在,我们按 S、D、F 键时,输出的仍然是 2、3、4。但一旦“按住” A 键,就会激活 second 层。此时再按下 S、D、F 键就会输出的 w、e、r。这是第二个层配置的映射,和第一个层不再相关。

这里还隐含了两个概念:

  • layer-while-held:一个按键动作(Action),可以让我们在按住时激活指定的层。
  • _ 符号:作为“透明键”,在此处会保持原键功能不变。

关于第一个概念,我们将在独立的短按/长按章节介绍。此处重点介绍与层相关的 _ 系列符号。其实很好理解,由于在初始层激活 second 层时 A 键已被按住了(占用了)。所以第二个层对 A 键没有映射的必要了。这里的 _ 类似于一个占位符,但不完全等同。它的映射实际上会“穿透”到下一层的对应位置上。

这是进一步使用 _ 的例子:

(deflayer second                  ;; 第二个层,名字为 second
  _ w _ _                         ;; 映射第二个键为新字母
)

现在激活 second 层时,只有 S 键被映射为 w 了。D、F 键仍然输出 3、4,因为它们被 _ 穿透回到了 start 层上。注意:层与层之间是覆盖的关系,当前激活的层在最上。最底层就是 defsrc 中所声明按键的原始功能。其次是初始层。所以上面的例子穿透到了初始层的映射上。

有关层的内容我已不想再讲更多了,一方面是为节省篇幅,更重要的是我并不使用层。没错,层很重要,但它有个替代品。在进入下一个章节前,我想告诉你们我为何很少使用层。这是一个例子:

(defsrc
  grv  1    2    3    4    5    6    7    8    9    0    -    =    bspc
  tab  q    w    e    r    t    y    u    i    o    p    [    ]    \
  caps a    s    d    f    g    h    j    k    l    ;    '    ret
  lsft z    x    c    v    b    n    m    ,    .    /    rsft
  lctl lmet lalt           spc            ralt rmet rctl
)

(deflayer dvorak
  grv  1    2    3    4    5    6    7    8    9    0    [    ]    bspc
  tab  '    ,    .    p    y    f    g    c    r    l    /    =    \
  caps a    o    e    u    i    d    h    t    n    s    -    ret
  lsft ;    q    j    k    x    b    m    w    v    z    rsft
  lctl lmet lalt           spc            ralt rmet rctl
)

上面是层的典型用法。将几乎全部按键,按照布局排布出来。将一个完整布局,映射为另一种(如 dvorak,即德沃夏克)。它类似于 keyd 的“布局”概念。当我们不需要修改那么全的映射时,当我们有多个层时,就会带来模板式重复(如果只是映射少量按键,会有大量的 _)。

对于修改布局,它很有用。但少量改键加多层的玩法,它强制完整的「输入-输出」对应就显得死板。所以接下来将要介绍的“层映射”可能是更实用的,当我们不需要如此全面的修改按键映射时。

层映射

层映射(layermap)是定义层的另一种方法,它完全忽略 defsrc,直接将「输入-输出」的映射关系写在一起。这是一个将 WASD 映射为方向键的例子:

(deflayermap (arrows)
  w       up
  a       left
  s       down
  d       right
)

我们现在不再需要此前定义的 deflayer 了,把它们删掉。此外 defsrc 中也不再需要声明任何按键,留空它的同时将 process-unmapped-keys 配置为 yes。

在大多数键盘操控的游戏里,WASD 通常是默认的移动键(类似于真正的方向键)。这是因为方向键在键盘右侧,而我们的右手要使用鼠标。让我们用 Caps Lock 这个黄金位置的“搭档”激活它:

(deflayermap (base)
  caps    (tap-hold-press 180 180 caps (layer-while-held arrows))
)

上述配置的 tap-hold-press 动作有 4 个参数,在后续章节中我们会单独介绍它。简单来说,它会在短按时输出 Caps Lock 原键功能,长按时激活 arrows 层。

方向键映射

现在按住 Caps Lock 键激活我们的 arrows 层,在这个层里 WASD 键被重新映射为方向键。几乎任意小巧的键盘,我们都能用一只左手轻易的完成方向键操作。

短按/长按

在层映射章节中,我们用过一个名为 tap-hold-press 的按键动作。它是 tap-hold 的变体,是最常用的高级动作之一。在该配置中,我们长按以激活 arrows 层。如何理解它呢?很简单,我们将 tap-hold 拆开来看:它是 tap(轻按)和 hold(长按)的组合。这里我们主要用它配置长按动作以执行激活层的操作。

将具体的配置 tap-hold-press 180 180 caps (layer-while-held arrows) 拆分开:

  • tap-hold-press:动作的名称。
  • 180:第一个参数,代表某个超时时间。
  • 180:第二个参数,代表另一个超时时间。
  • caps:第三个参数,代表短按时的动作(或按键)。
  • (layer-while-held arrows):第四个参数,代表长按时的动作。

我们暂时忽略两个超时参数,从短按和长按的动作配置讲起。首先是短按的动作配置成了 caps,也就是输出 Caps Lock 的功能(在这里是原键)。这个非常容易理解,无需额外解释。然后是长按的动作配置,它是一个嵌套的 S-表达式。它执行了 layer-while-held 动作,用于激活指定的层。而 arrows 就是它的参数,即被激活层的名字。

所以此配置的意思就是:短按 Caps Lock 键时输出原键功能,长按时激活 arrows 层。

这里就体现了一次 S-表达式的动作嵌套,层激活被嵌套进了短按/长按里。

那么两个超时参数呢?在开始之前,我们不得不先提及键盘的一个原生功能。当我们长按 A 键时,默认会得到 aaaaaa... 这种重复输出。那么问题来了,我们用 tap-hold 将长按配置为另一个动作了,岂不是把这种默认行为覆盖了?是的没错,但第一个超时参数就是为此需求准备的。

第一个参数值的单位是毫秒,此处是 180。它表示:当你在 180 毫秒内,二次按下这个键并按住时,就会重复输出短按动作,而不再触发长按动作。例如:

(deflayermap (base)
  a       (tap-hold 180 180 a b)
)

通过以上讲解,我们能轻易的明白。它配置的是:短按 A 键输出 A 键原有功能,长按时输出 B 键功能。现在,当我们正常长按 A 时,它会持续的输出 bbbbbb...。但如果我们第一次按下 A 键后松开然后 180 毫秒内再次按下 A 键并按住,它就会重复输出 aaaaaa...。

很好理解,多次短按本就是在表达想重复的意图。

那么,第二个超时参数是什么意思呢?它更容易理解,此时间用于判定我们是长按还是短按。超出 180 毫秒,就触发长按。低于这个时间抬起了按键,就触发短按动作。

最后,我们使用的是 tap-hold-press 这个变体,和基础 tap-hold 有什么差异?简单来说,它更适合用于修饰键的长按类型。我们可以想一下:

当我们按住 Ctrl 时,按下另一个键应立即触发组合键功能。而不必要等待一个固定的超时时间。类似的,当我们长按 Caps Lock 这样的键激活另一个层时,我们也应该期待另一个键立即触发动作。通常对 A、S、D、F 这类直接参与打字的键,固定的超时才有必要。因为快速打字时是有可能同时按下多个字母键的,但它们一般不用于修饰和组合。

简单来说 tap-hold-press 变体多了一个额外的「提前触发」长按动作的契机。

按键的触发模式还有更多类型,例如:一次性动作、连点动作、fork 动作等等。本文暂未介绍。

别名和变量

在上面的配置中,我们将输入按键和输出动作直接写在 deflayermap 中。当这类配置越来越多,越来越杂时,会非常不直观。或者我们要在不同的按键/不同的层多次使用某个相同操作时,怎么复用呢?这就是别名(alias)的用途了。将上面的操作提取到别名中:

;; 声明别名
(defalias
  cap-arrows  (tap-hold-press 180 180 caps (layer-while-held arrows))
)

(deflayermap (base)
  caps        @cap-arrows
)

现在这套操作被我们命名为 cap-arrows 放在 defalias 中。在 deflayermap 中通过前缀 @ 符号来引用它。这可以让复杂操作具备语义化的名称,也能让我们在其它地方复用它。

再假设我们的配置中有多个 tap-hold 动作,但部分时间参数是一样的。如果每一个都重复一次 180,一旦发生修改就不好统一管理。我们可以定义变量(var),然后在参数位置引用变量:

;; 定义变量
(defvar
  repress-timeout 180
  hold-timeout    180
)

(defalias
  cap-arrows      (tap-hold-press $repress-timeout $hold-timeout caps (layer-while-held arrows))
)

引用变量时使用 $ 前缀符号。现在我们只需要修改变量的值,就能统一修改所有引用它的参数了。同时,由于变量名具有语义化的作用,我们也能更清晰的知道这个参数的意义。

别名和变量都没有功能上的作用,它们主要用于优化配置:让配置更清晰,易于维护。它们的使用完全取决于个人习惯。对于复杂配置,合理的使用绝对比不用好。

和弦与组合

虽然和弦(chords)可能被部分人当作组合(combos),但严格来说它们是有区别的。和弦是无序的,可无需修饰键参与。而组合是有序的,通常需要修饰键参与。我们先来看和弦:

(defcfg
  concurrent-tap-hold   yes         ;; <- defchordsv2 需要它
  ;; 省略其它配置……
)

;; 定义全局和弦
(defchordsv2
  ;; 编辑(打字)快捷操作
  (d f)   bspc 50 all-released ()   ;; 删除左侧字符
  (j k)   del  50 all-released ()   ;; 删除右侧字符
)

上述配置中的 concurrent-tap-hold 配置是必要的,否则 defchordsv2 定义的和弦可能无法正常工作。

这里通过 defchordsv2 定义了两个和弦。当我们同时按下 D 和 F 键,会触发退格键(Backspace,简写为 bspc)功能删除左侧字符。同时按下 J 和 K 键触发删除键(Delete,简写为 del)功能删除右侧字符。如下:

ALT_HERE

因为 F̠ 和 J̠ 分别是左右手食指的定位键,相比于退格和删除键,它让删除操作不打断标准指法。一个用于删除左侧字符,一个用于删除右侧字符,也十分自然。

我们再来看看 defchordsv2 中的具体配置。每一个有效配置包含 5 个参数,将其中一个拆解:

  • (d f):被组合的按键列表。
  • bspc:触发的动作(此处为退格键名)。
  • 50:所有参与的键都被按下后,多久触发和弦(毫秒)。
  • all-released:释放时机,此处为所有参与键都释放后释放和弦。
  • ():禁用的层列表,此处留空(没有禁用任何层)。

这里使用的 defchordsv2 定义的和弦是全局的,和层无关。还有一个 defchords 可以定义和弦,可以按层定义。本章节暂未做介绍。

说完了和弦,我们再来说说组合。它和各种图形环境、桌面软件的快捷键概念类似。先按下修饰键(例如 Ctrl),然后按下另一个键,触发组合动作。它相比于和弦更为简洁:

;; 用 Caps Lock 模拟 Ctrl 常用组合操作
(deflayermap (cap-zxcv)
  z       C-z
  x       C-x
  c       C-c
  v       C-v
)

在这个例子中,我们定义了一个层映射。它把原生的 Ctrl 常用组合键(复制、剪切、粘贴、撤销)映射到 Caps Lock 键上。注意看,被映射的动作由 C 前缀和 - 符号连接具体按键。这里的 C 实际上就是修饰键 Ctrl 的缩写。完整的如下:

功能 左侧键 (缩写) 左侧键(说明) 右侧键 (缩写) 右侧键(说明)
Control C 左 Ctrl RC 右 Ctrl
Alt A 左 Alt RA / AG 右 Alt
Shift S 左 Shift RS 右 Shift
Meta M 左 Meta RM 右 Meta

这里有个认知要点要区分开。本章节我们定义的和弦,是“输入”(即我们按下的键)。而此处定义的组合是“输出”(即触发的动作)。以上配置中的输出组合,本质是对其它操作的复用(例如原生组合按键)。

若要修改输入组合(带修饰键)的映射,可以使用 defoverridesv2 来简单配置。本章节暂未介绍。

宏

Kanata 的宏(macro)类似于现代键盘或鼠标设备的宏功能,它可以把预先配置的动作序列重新“播放”出来。以下是一个实用例子:

;; 快捷生成成对符号的层
(deflayermap (pairs-symbols)
  apos    (macro S-apos S-apos left)  ;; 引号对
  9       (macro S-9 S-0 left)        ;; 括号对
  lbrc    (macro lbrc rbrc left)      ;; 中括号
)

在这个层中,我们按下撇号键(Apostrophe,简写为 apos)时,会触发一个宏。这个宏会依次输出 Shift + Apostrophe(第一个引号),然后再输出一次 Shift + Apostrophe(第二个引号),最后输出左箭头键(left)将光标移动到引号中间。其余两个也是类似的。它们可以简化输入成对符号的操作,类似于 VS Code 中的片段。

此时我们的按键所映射的不再是简单的激活、长按这一类动作,而是连续的动作序列。宏还可以在动作间插入延迟时间,让我们感受到动作执行的过程。例如:

(defalias  
  copyall-next-window (macro C-a C-c 500 A-tab C-v)  
)

若将按键映射到这个别名上,那么按下它时会先执行 Ctrl + A(全选)和 Ctrl + C(复制),然后等待 500 毫秒,再执行 Alt + Tab(切换窗口)和 Ctrl + V(粘贴)。这可以让我们快速在多个窗口间复制粘贴内容。由于我们等待了 500 毫秒,所以你会看到全选完成后要等一会儿才切换窗口。

宏还支持录制,以及鼠标控制。本教程暂未介绍这些内容。宏的作用不仅仅是“改键”,它可以简化许多过程。包括日常使用电脑的各种动作,游戏中的复杂操作等。

指定设备

默认 Kanata 会拦截检测到的所有键盘,无相关配置是直接可用的。但如果你有多个键盘且布局不同,那么就很有必要让特定配置只作用于特定的设备了。在 Linux 上,我们将键盘对应的 dev 设备路径写在 linux-dev 配置项中:

(defcfg  
  ;; 指定设备
  linux-dev (
    "/dev/input/by-path/pci-0000:00:00.0-usb-0:0:0.0-event-kbd"
    "/dev/input/by-path/pci-0000:00:00.0-usb-0:0:0.1-event-kbd"
  )
)

以上是一个真实案例匿名化处理后的配置。同一个设备文件有多种不同的路径,例如 /dev/input/eventX、/dev/input/by-id/xxx、/dev/input/by-path/xxx 等等。通常来说,by-id 和 by-path 这类路径是比较稳定的,不会因为设备发现顺序改变而改变。而 eventX 这类路径可能会变动,所以不建议使用。

这里是一个多行命令,可复制后直接在终端执行:

for id_link in /dev/input/by-id/*-kbd; do
    [ -e "$id_link" ] || continue
    event_node=$(readlink -f "$id_link")
    event_name=$(basename "$event_node")
    path_link=$(find /dev/input/by-path/ -lname "*$event_name" | head -n 1)

    echo -e "ID 路径\t\t: $id_link"
    echo -e "Path 路径\t: ${path_link:-'未找到对应 Path'}"
    echo -e "内核节点\t: $event_node\n"
done

它会列出所有键盘设备的 by-id 和 by-path 路径,其中 by-id 的路径能轻易分辨具体的设备(通常包含品牌或型号)。然后在配置中写 by-path 更加匿名化,分享配置时不暴露设备信息。

有时候你会看到一个键盘有多个设备,这是正常的,可全部添加到配置中。你还可能看到鼠标也被列举出来了,这是因为鼠标也有按键功能也会模拟成键盘输入设备,无视即可。

当你看到同一个键盘有多个设备文件,你通常有必要都添加到配置中。因为一些键盘为了实现全键无冲,会用多个逻辑输入节点来处理更多的按键组合。如果你不添加其它“看似无用”的设备文件,那么当你配置了 7 个或以上按键数量的和弦或组合键时,它可能永远不会被映射。

很遗憾,在 Windows 上无法指定设备。

自启动

在提供了 kanata 包的 Linux 发行版中,通常已经预设了服务脚本和配置文件路径。让服务自启动即可。否则你需要手动创建 Systemd 服务。如果是 NixOS,请添加这些配置:

{
  # 启用 Kanata(按键映射)
  services.kanata = {
    enable = true;
    keyboards = {
      "61" = {                             # <- 定义一个配置名,例如 "61"
        configFile = "/etc/kanata/61.kbd"; # <- 指定一个配置文件路径
      };
    };
  };
}

然后将配置文件写在 /etc/kanata/61.kbd 中即可(可能要手动创建目录)。生成的服务名称为 kanata-61,可通过 systemctl 命令管理它。

在 Windows 上,会稍微“丑陋”一点。你通常不能用 Linux 的思维将 Kanata 进程服务化管理。因为 Windows 标准服务有 Session 0 隔离,会导致即使 Kanata 作为服务运行了,它也无法拦截修改你的任何输入。说简单点就是:核心功能不工作。

解决办法很简单,让 Kanata 作为普通自启动程序。按下 Win + R 键,输入 shell:startup 打开启动文件夹(路径类似于 C:\Users\你的用户名\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup)。创建 kanata_windows_gui_winIOv2_x64.exe 文件的快捷方式,将快捷方式放在这个文件夹中即可:

ALT_HERE

注意以上截图,我们在快捷方式的属性中,编辑过目标。需要加上 --cfg 参数以及配置文件的路径。我将这个快捷方式命名为了 Kanata_GUI,因为我们的启动程序是带 GUI 的。当然实际上它没有任何窗口,仅创建一个托盘图标。右键托盘图标可以进行一些简单的管理,如重载配置,选择配置或退出。

相比于 Linux 的服务,这样自启动的 Windows 程序是无法在登录前就工作的。

最小化权限

本章节仅适用于 Linux。

一般情况下 Kanata 作为系统服务运行是最合适的,因为它可以在登录前就启动。但如果你要用 cmd 功能执行外部程序,或有其它令人不放心的理由,也是能让以它普通权限运行的。首先把自己添加到两个必要的组:

sudo usermod -aG input $USER
sudo usermod -aG uinput $USER

组不存在就先用 sudo groupadd uinput 命令创建。然后添加 udev 规则(在 /etc/udev/rules.d 或 /lib/udev/rules.d 目录):

KERNEL=="uinput", MODE="0660", GROUP="uinput", OPTIONS+="static_node=uinput"

现在以自己的用户权限(无需 sudo)启动的 Kanata 进程,也能访问 input 和 uinput 子系统注入事件了。接下来手动创建用户级别的 Systemd 服务即可(或用自己的方式自启动)。

我本人仍然认为这是没必要的。我不会使用它启动外部程序,我也不希望它作为用户服务运行。

意外情况

某些时候,错误的配置可能导致严重后果(例如键盘无法正常使用,或无法恢复配置)。当然,这属于极端情况。正常来讲,错误的配置要么无效,要么不影响下次修改。但一旦真的出现了极端情况,停止 Kanata 进程就是恢复键盘的后路。结束 Kanata 方法如下:

同时按下 Esc + Ctrl + Space(空格)键,即可让 Kanata 自动退出。一切配置都会失效。这时候你就可以轻易还原配置了。如果你发现 Kanata 进程仍然存在,例如它又自动启动了。可能是 init 的自动重启策略,按照这里的方法将它禁用。

允许 Kanata 进程关闭自己是有必要的,自以为是的重启策略反而造成问题。

结束语

这就是 Kanata 的入门教程了。由于篇幅太长,我花了数次更新才完成。仍然有一些十分有用的功能没有介绍到,高级的使用模式没有介绍。毕竟这个工具比 keyd 还是复杂不少。而此项目所谓的「配置指南」实际上类似于手册,适合查询功能说明而并不适合入门。本教程的例子并一定实用,它更多的是为了突出功能设计目的和用法。

我的 Kanata 配置:Hentioe/dotfiles/kanata/61.kbd

本文由作者按照CC BY 4.0进行授权
分享:
相关文章