键位映射问题#
vim 一直是我的主力编辑器,但其实我之前每天使用编辑器的时间并不是很多,并且每次的使用时间也不是很长,在最近编辑文件和写脚本的时间比较多,发现之前键盘映射的一些问题
由于 esc 键在最左上,如果使用 vim 的话需要频繁按 esc 键盘,这样的话左小拇指就会很累,手动的幅度也比较大,众所周知,vim 用户都很 lazy,为了手的动作幅度能够尽可能小,想出了各种各样的键位和快捷键的映射,我之前就是在编辑模式下把 jj 映射成了 esc,但是最近发现一个问题,由于 fcitx5 默认配置的剪切板 的快捷键是 ctrl+; 在 vim 中偶尔会误触按到 fcitx5 的剪切板,这个时候 jj 是没法当作 esc 来使用关闭掉剪切板的,而且使用 jj 来映射的时候,如果你只是想输入 j 字母的时候,vim 会等待一段时间来看你是想输入 j 字符还是要使用映射的键位,虽然这个时间是可以来调整的,但是还是会有视觉上的卡顿,所以,我还是打算放弃掉这个映射,来使用其他键来映射
我的想法是把 esc 映射 capslock, capslock 映射 ctrl, ctrl 映射 esc
通过 Google 搜索到的结果,很多人给出了使用 setxkbmap/xmodmap 来实现,但是这个方案也有各种各样的问题,并且这个只能在 xorg 中使用,如果在 console 中使用,还需要再为 console 单独配置
虽然这个方案被我放弃了,但是我再搜索这个方案的时候看到了依云老师关于键盘映射的解决方案,使用 hwdb 和可编程的键盘,我的 keychron k6 不支持编程,所以当然是使用 hwdb 来实现啦
关于 hwdb#
hwdb 是 udev 提供的一个内置函数,用于维护 /etc/udev/hwdb.bin 中的硬件数据库索引,我们可以通过自定义 hwdb 配置文件来实现更底层的键位映射,从而可以同时在 xorg / console 中生效,而且没有那么多的问题
hwdb 可以通过修改 scancodes 到 keycodes 的映射来实现键位映射
关于 scancodes 和 keycodes#
扫描码(scancode)是一个键的最小识别 ID。如果一个键没有扫描码值,我们无法做任何事,因为内核看不到它。
键位码(keycode)是一个键的第二级识别 ID,对应到一个函数。
符号(symbol)是一个键的第三级识别 ID,Xorg 通过该 ID 引用按键。
我们在键盘输入时,键盘发送键位的 scancode 给 内核的键盘驱动,除了可编程键盘外,键位的 scancode 一般来说都是固定的,然后 linux 内核将 scancode 映射到 keycode,然后根据你的键盘布局的键码映射到一个符号或键符,当然,这只是简要描述,只是为了让你了解之后我们要做的事情
改变 scancode 映射的 keycode#
安装 evtest 包
我们使用
evtest来获取当前要修改的键位的scancode,然后修改该键位映射的keycodeevtest这个包在大部分发行版上可能都没有安装,我使用的是Arch Linux,首先安装这个包sudo pacman -S evtest如果你使用的是
Debian和其衍生发行版sudo apt install evtest找到你的键盘设备
通过
cat /proc/bus/input/devices列出当前的输入设备,然后找到你的键盘设备一把都可以通过
Name字段来找到你的设备型号,以下是截取部分我的输出结果I: Bus=0003 Vendor=046d Product=4074 Version=0111 N: Name="Logitech G304" P: Phys=usb-0000:03:00.3-3/input2:1 S: Sysfs=/devices/pci0000:00/0000:00:08.1/0003:03:00.3/usb1/1-3/1-3:1.2/0003:046D:C53F.0004/0003:046D:4074.0005/input/input26 U: Uniq=4074-32-9b-8e-5f H: Handlers=sysrq kbd leds event11 mouse2 B: PROP=0 B: EV=120017 B: KEY=ffff0000 1000000000007 ff9f207ac14057ff febeffdfffefffff fffffffffffffffe B: REL=1943 B: MSC=10 B: LED=1f I: Bus=0005 Vendor=05ac Product=024f Version=011b N: Name="Keychron K6" P: Phys=80:30:49:4e:60:e8 S: Sysfs=/devices/pci0000:00/0000:00:08.1/0003:03:00.4/usb3/3-2/3-2:1.0/bluetooth/hci0/hci0:2/0005:05AC:024F.0007/input/input30 U: Uniq=dc:2c:26:05:c3:f8 H: Handlers=sysrq kbd leds event12 B: PROP=0 B: EV=12001f B: KEY=3f000303ff 0 10000 483ffff17aff32d bfd4444600000000 1 130ffb8b17d007 ffff7bfad9415fff ffbeffdfffefffff fffffffffffffffe B: REL=1040 B: ABS=10100000000 B: MSC=10 B: LED=1f可以看到第一个
Name的值是Logitech G304,是我的无线鼠标 罗技g304,第二个就是我的键盘设备,keychron k6我们只需要关注该键盘的
bus id, vendor id,product id , version id 和 event id,也就是第二段的第一行Bus=0005 Vendor=05ac Product=024f Version=011b,event id在 H: 开头的那一行,可以看到我的键盘event id是12获取到你要修改的键位的
scancode执行
sudo evtest /dev/input/event<id>#使用上一步的event id替换命令里的id然后分别按下你要修改的键,比如我按的
esc,capslock和ctrl,输出结果... ... ... Event: time 1636561197.770309, -------------- SYN_REPORT ------------ Event: time 1636561207.561382, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70029 Event: time 1636561207.561382, type 1 (EV_KEY), code 58 (KEY_CAPSLOCK), value 1 Event: time 1636561207.561382, -------------- SYN_REPORT ------------ Event: time 1636561207.561771, type 17 (EV_LED), code 1 (LED_CAPSL), value 1 Event: time 1636561207.561771, -------------- SYN_REPORT ------------ Event: time 1636561207.594132, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70029 Event: time 1636561207.594132, type 1 (EV_KEY), code 58 (KEY_CAPSLOCK), value 0 Event: time 1636561207.594132, -------------- SYN_REPORT ------------ Event: time 1636561208.907878, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70039 Event: time 1636561208.907878, type 1 (EV_KEY), code 29 (KEY_LEFTCTRL), value 1 Event: time 1636561208.907878, -------------- SYN_REPORT ------------ Event: time 1636561208.952822, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70039 Event: time 1636561208.952822, type 1 (EV_KEY), code 29 (KEY_LEFTCTRL), value 0 Event: time 1636561208.952822, -------------- SYN_REPORT ------------ Event: time 1636561209.819355, type 4 (EV_MSC), code 4 (MSC_SCAN), value 700e0 Event: time 1636561209.819355, type 1 (EV_KEY), code 1 (KEY_ESC), value 1 Event: time 1636561209.819355, -------------- SYN_REPORT ------------ Event: time 1636561209.875377, type 4 (EV_MSC), code 4 (MSC_SCAN), value 700e0 Event: time 1636561209.875377, type 1 (EV_KEY), code 1 (KEY_ESC), value 0 Event: time 1636561209.875377, -------------- SYN_REPORT ------------前两个
SYN_REPORT分别代表着按下esc键的按下和释放的信息第三,四个是
capslock的按下和释放的信息最后两个是
ctrl的按下和释放的信息从中可以获取到我的键盘的
esc键的scancode是70029,capslock是70039,ctrl是700e0创建一个 hwdb 配置文件
数据库是从位于
/usr/lib/udev/hwdb.d/、/run/udev/hwdb.d/和/etc/udev/hwdb.d/目录中的扩展名为.hwdb的文件编译而成的。 默认的 “扫描码到键码” 映射文件是/usr/lib/udev/hwdb.d/60-keyboard.hwdb我们不要去修改系统默认的键盘映射文件,我们在
/etc/udev/hwdb.d/新建一个格式为数字开头的后缀是.hwdb的文件,数字代表了执行顺序,我们这里随便给一个就可以,比如99_k6_keyboard.hwdb格式为
evdev:input:b<bus_id>v<vendor_id>p<product_id>e<version_id>-<modalias> KEYBOARD_KEY_<scancode>=<keycode> KEYBOARD_KEY_<scancode>=<keycode>其中
<bus_id>,<vendor_id>,<product_id>和<version_id>是我们之前第二步里面通过cat /proc/bus/input/devices获取到的,<bus_id>是4位数的十六进制总线ID,对于USB设备,应为0003,我的是蓝牙设备(k6 是蓝牙键盘),是 0005 ,不确定是否都一样,以你的实际输出结果为准,其他的都是 4 位十六进制大写(不够4位在前面补0)<modalias>是描述设备功能的任意长度的输入模态(input-modalias)注意一定要是大写,我之前就因为没有大写所以一直没有生效
<scan_code>的值是上一步通过sudo evtest /dev/input/event<id>获取的<keycode>的值是小写的键名名称字符串,就像在/usr/include/linux/input-event-codes.h中列的一样(参阅KEY_*<KEYCODE>*变量)我的配置文件为
evdev:input:b0005v05ACp024Fe011B* # 这里可以不用写这么多信息,可以使用通配符 * , 比如只写一个 evdev:input:b0005* 也是可以的,因为busid 是唯一的,不会重复的值 KEYBOARD_KEY_70029=capslock #70029 是 esc 的 scancode,这里让它映射为 capslock KEYBOARD_KEY_70039=leftctrl #70039 是 capslock 的 scancode,这里让它映射为 leftctrl KEYBOARD_KEY_700e0=esc #700e0 是 leftctrl 的 scancode,这里让它映射为 esc更新硬件数据库索引 && 重新加载硬件数据库索引
写好配置文件并保存后,更新硬件数据库索引
sudo systemd-hwdb update然后重新加载硬件数据库索引,映射会立即生效
sudo udevadm trigger检查按键映射
最好我们检查映射是否生效
udevadm info /dev/input/event<id> | grep KEYBOARD_KEY这个命令的输出结果会显示你的键盘映射结果,有这些输出就代表已经生效了,比如我的
E: KEYBOARD_KEY_70029=capslock E: KEYBOARD_KEY_70039=leftctrl E: KEYBOARD_KEY_700e0=esc
参考文档