kmonad -- Using Capslock as both Esc and Ctrl
在使用vim的时候,我已经习惯了将esc和capslock互换位置,不得不说是真的爽。
而对于emacs派,则流行将left ctrl和capslock呼唤,这样按C-a, C-b, C-f等快捷键就不会那么痛苦。
去年我试过一个keymap软件(名字忘了)来实现vim流的映射, 但结果一言难尽 —— 在vscode里,互换就不会生效,还有其它的一些小bug, 不久就被我遗弃了。 最近发现了一个haskell写的keymap软件 – kmonad, 试了之后惊为天人,完美解决了曾经的bug, 可以实现全局映射。
临时写了一个笨拙的映射:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(defcfg
input (device-file "/dev/input/by-path/platform-i8042-serio-0-event-kbd")
output (uinput-sink "kmonad output")
fallthrough true
allow-cmd false
)
(defsrc
esc f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 pause prnt ins del
` 1 2 3 4 5 6 7 8 9 0 - = bspc home
tab q w e r t y u i o p [ ] ret pgup
caps a s d f g h j k l ; ' \ pgdn
lsft z x c v b n m , . / rsft up end
lctl lmet lalt spc ralt cmps rctl left down rght
)
(deflayer base
caps f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 pause prnt ins del
` 1 2 3 4 5 6 7 8 9 0 - = bspc home
tab q w e r t y u i o p [ ] ret pgup
esc a s d f g h j k l ; ' \ pgdn
lsft z x c v b n m , . / rsft up end
lctl lmet lalt spc ralt cmps rctl left down rght
)
其中defsrc和deflayer中的对象一一对应,这里我做的就是把esc和caps的映射结果互换。
然而当时我对kmonad的强大还一无所知。直到今天,我突然又有了交换Capslock和Left Ctrl的想法。倒不是因为用emacs, 而是在折腾tmux, 它的默认主键如果能绑定到Ctrl-a,然后用Capslock代替Ctrl,想想就是一件美事啊!
我开始幻想:Capslock这么好的位置,能不能让我同时实现两个映射呢?搜索了一番发现,kmonad竟然也能胜任这个工作,等不及了,直接开干!
这次阅读了官方文档的部分内容:
- fallthrough
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
fallthrough: `true` or `false`, defaults to `false` KMonad catches input events and tries to match them to various handlers. If it cannot match an event to any handler (for example, if it isn't included in the corresponding `defsrc` block, or if it is, but the current keymap does not map any buttons to it), then the event gets quietly ignored. If `fallthrough` is set to `true`, any unhandled events simply get reemitted. In more practical terms, this allows you to only specify the keys that you want to overwrite in your `defsrc' block. For example, the following configuration would rebind Caps Lock to Escape only when tapped. (defcfg input … output … fallthrough true) (defsrc caps) (deflayer my-layer (tap-next esc caps))当
fallthrough为false的时候,我们没有在defsrc等列表中定义的按键在失配后会直接被忽略;但是如果设置为true,则会继续pass, 这样就不用把所有的按键都写一遍了。 - tap-next, tap-hold, tap-hold-next
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#| --------------------------------------------------------------------------
Optional: Multi-use buttons
Perhaps one of the most useful features of KMonad, where a lot of work has
gone into, but also an area with many buttons that are ever so slightly
different. The naming and structuring of these buttons might change sometime
soon, but for now, this is what there is.
For the next section being able to talk about examples is going to be handy,
so consider the following scenario and mini-language that will be the same
between scenarios:
- We have some button `foo` that will be different between scenarios
- `foo` is bound to 'Esc' on the input keyboard
- the letters a s d f are bound to themselves
- Px signifies the press of button x on the keyboard
- Rx signifies the release of said button
- Tx signifies the sequential and near instantaneous press and release of x
- 100 signifies 100ms pass
So for example:
Tesc Ta:
tap of 'Esc' (triggering `foo`), tap of 'a' triggering `a`
Pesc 100 Ta Tb Resc:
press of 'Esc', 100ms pause, tap of 'a', tap of 'b', release of 'Esc'
The `tap-next` button takes 2 buttons, one for tapping, one for holding, and
combines them into a single button. When pressed, if the next event is its own
release, we tap the 'tapping' button. In all other cases we first press the
'holding' button then we handle the event. Then when the `tap-next` gets
released, we release the 'holding' button.
So, using our mini-language, we set foo to:
(tap-next x lsft)
Then:
Tesc -> x
Tesc Ta -> xa
Pesc Ta Resc -> A
Pesc Ta Tr Resc -> AR
The `tap-hold` button is very similar to `tap-next` (a theme, trust me). The
difference lies in how the decision is made whether to tap or hold. A
`tap-hold` waits for a particular timeout, if the `tap-hold` is released
anywhere before that moment we execute a tap immediately. If the timeout
occurs and the `tap-hold` is still held, we switch to holding mode.
The additional feature of a `tap-hold` is that it pauses event-processing
until it makes its decision and then rolls back processing when the decision
has been made.
So, again with the mini-language, we set foo to:
(tap-hold 200 x lsft) ;; Like tap-next, but with a 200ms timeout
Then:
Tesc -> x
Tesc Ta -> xa
Pesc 300 Ta -> A (the moment you press a)
Pesc Ta 300 -> A (after 200 ms)
Pesc Ta 100 Resc -> xa (both happening immediately on Resc)
The `tap-hold-next` button is a combination of the previous 2. Essentially,
think of it as a `tap-next` button, but it also switches to held after a
period of time. This is useful, because if you have a (tap-next ret ctl) for
example, and you press it thinking you want to press C-v, but then you change
your mind, you now cannot release the button without triggering a 'ret', that
you then have to backspace. With the `tap-hold-next` button, you simply
outwait the delay, and you're good. I see no benefit of `tap-next` over
`tap-hold-next` with a decent timeout value.
You can use the `:timeout-button` keyword to specify a button other than the
hold button which should be held when the timeout expires. For example, we
can construct a button which types one x when tapped, multiple x's when held,
and yet still acts as shift when another button is pressed before the timeout
expires. So, using the minilanguage and foo as:
(tap-hold-next 200 x lsft :timeout-button x)
Then:
Tesc -> Tx
Pesc 100 Ta -> A (the moment you press a)
Pesc 5000 Resc -> xxxxxxx (some number of auto-repeated x's)
(tap-next a b) 代表:单独按下映射到a,长按映射到b,显然这个功能就是我想要的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
(defcfg
input (device-file "/dev/input/by-path/platform-i8042-serio-0-event-kbd")
output (uinput-sink "my kmonad output")
fallthrough true
allow-cmd false
)
(defsrc
esc caps
)
(deflayer base
caps (tap-next esc lctl)
)
Perfect! 直接爽用capslock
当前kmonad正在运行的时候,直接尝试再次读取配置文件的时候会报错,这是因为设备被占用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20(venv13) woc@myarch:~ $ sudo kmonad ~/.config/kmonad/map_capslock.kbd [sudo] password for woc: kmonad: Could not perform IOCTL grab on: /dev/input/by-path/platform-i8042-serio-0-event-kbd (venv13) woc@myarch:~ $ ll /dev/input/by-path/platform-i8042-serio-0-event-kbd lrwxrwxrwx 1 root root 9 Nov 27 19:06 /dev/input/by-path/platform-i8042-serio-0-event-kbd -> ../event2 (venv13) woc@myarch:~ $ sudo fuser -v /dev/input/event2 USER PID ACCESS COMMAND /dev/input/event2: root 1 F.... systemd root 518 F.... systemd-logind woc 819 F.... Hyprland root 31888 f.... kmonad (venv13) woc@myarch:~ $ sudo lsof /dev/input/event2 lsof: WARNING: can't stat() fuse.portal file system /run/user/1000/doc Output information may be incomplete. COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME systemd 1 root 173u CHR 13,66 0t0 183 /dev/input/event2 systemd-l 518 root 24u CHR 13,66 0t0 183 /dev/input/event2 systemd-l 518 root 40u CHR 13,66 0t0 183 /dev/input/event2 Hyprland 819 woc 28u CHR 13,66 0t0 183 /dev/input/event2 kmonad 31888 root 56r CHR 13,66 0t0 183 /dev/input/event2重启kmonad服务解决(刚开始一直找
kmonad.service没找到,后来想起来是我自定义的service,不叫这个名字…)