从未知扫描码到有用按键:在 Linux (NixOS) 上映射神秘按键

你是否遇到过这种情况:你的键盘上有一个按键(尤其是笔记本电脑上的特殊功能键),但无论你怎么按,系统都毫无反应?标准的按键测试工具,如 evtestwev,似乎也完全忽略了它的存在。这可能令人沮丧,但别担心,通常有办法解决!

最近我就遇到了这样的问题(小米笔记本上的”小爱”键),通过一系列步骤成功让这个“沉默”的按键恢复了功能。

症状

evtest

sudo evtest /dev/input/by-path/platform-i8042-serio-0-event-kbd

1
2
3
Event: time 1743306850.355718, -------------- SYN_REPORT ------------
Event: time 1743306850.417179, type 4 (EV_MSC), code 4 (MSC_SCAN), value f2
Event: time 1743306850.417179, -------------- SYN_REPORT ------------

wev

毫无反应

倾听内核的声音 (dmesg)

当一个按键按下后系统没有明显反应时,底层的内核驱动可能已经注意到了什么。检查内核环形缓冲区日志是我们的首要任务。打开终端,输入:

1
sudo dmesg -w

或者,如果你使用 systemd journal:

1
sudo journalctl -f

然后,按下那个没有反应的按键。如果幸运的话,你可能会看到类似下面的信息(这正是我当时看到的):

1
2
[ 1443.346931] atkbd serio0: Unknown key released (translated set 2, code 0xf2 on isa0060/serio0).
[ 1443.346937] atkbd serio0: Use 'setkeycodes e072 <keycode>' to make it known.
  • atkbd serio0: 表明这是来自 AT/PS2 键盘驱动(atkbd)在主键盘端口(serio0)的信息。
  • Unknown key pressed/released: 内核驱动收到了按键按下和释放的信号,但它不认识这个信号代表哪个键。
  • code 0xf2: 这是硬件发送的原始**扫描码 (scancode)**。
  • Use 'setkeycodes e072 <keycode>' to make it known.: 这是关键线索!内核直接告诉我们,这个未知的按键对应的扫描码是 e0720xf2 经过处理后的形式),并且我们可以使用 setkeycodes 命令来给它分配一个 Linux 内核**键码 (keycode)**。

Scancode & Keycode & Keysym

扫描码 (Scancode)

最底层、最原始的信号,由你的键盘硬件直接产生。当你按下或松开一个键时,键盘会发送一个或多个代表该物理按键位置或动作(按下/松开)的数字代码。这个代码是硬件相关的,不同的键盘型号对于同一个物理按键可能发送不同的扫描码。内核的底层键盘驱动程序首先接收并处理这些原始扫描码。

sudo showkey -s可以观测

键码 (Keycode)

内核键盘驱动接收到原始的扫描码后,会查询一个内部映射表,将其翻译成一个标准的、数字形式的键码。键码是 Linux 内核用来识别一个特定 物理按键 的抽象标识符,与该按键当前的功能或字符无关(例如,它代表“空格键这个物理按键”,而不是“空格字符”)。这个键码层级相对稳定,不直接受键盘布局影响,是 setkeycodes 命令操作的对象,也是 evtest 工具显示的代码。

sudo evtest /dev/input/eventX可以观测

内核头文件: include/uapi/linux/input-event-codes.h

按键符号 (Keysym)

最高层级的表示,代表按键的实际 含义 或 功能。图形环境中的 XKB 系统(或类似系统)接收来自内核的键码,然后根据当前选择的键盘布局(如美式、法式)以及是否按下了 Shift、Ctrl、Alt 等修饰键,将键码翻译成一个按键符号。

wev观测

参考 xkbcommon-keysyms.h header

选择一个合适的键码

我们需要为扫描码 e072 选择一个目标键码。这个键码决定了系统将如何“看待”这个按键。为了避免与现有功能冲突,并方便后续自定义,最好选择一个相对“空闲”或“特殊”的键码。

以下是一些不错的选择:

  1. 可编程/通用功能键: KEY_PROG1 (键码 148), KEY_PROG2 (149), KEY_PROG3 (202) 等。这些键码设计上就是用于用户自定义的。
  2. 扩展功能键: KEY_F13 (键码 183) 到 KEY_F24 (194)。大多数键盘没有这些物理按键,因此它们的键码通常是空闲的。

笔者使用了 KEY_PROG1 (148) 。

临时映射与测试

现在,我们可以使用内核提示的 setkeycodes 命令进行临时映射。打开终端,执行(这里以映射到 KEY_PROG1 为例):

1
sudo setkeycodes e072 148

映射完成后,我们需要验证它是否生效。使用 evtest 监控你的键盘设备(你需要先找到键盘对应的 eventX 设备,通常路径中包含 kbd):

1
2
3
4
# 找到你的键盘 event 设备路径,例如 event3
ls /dev/input/by-path/*kbd*
# 运行 evtest
sudo evtest /dev/input/by-path/platform-i8042-serio-0-event-kbd

现在按下那个之前无效的按键。如果一切顺利,你应该能在 evtest 的输出中看到类似这样的事件:

1
2
3
4
5
6
7
Event: time 1743311277.269163, -------------- SYN_REPORT ------------
Event: time 1743311278.704083, type 4 (EV_MSC), code 4 (MSC_SCAN), value f2
Event: time 1743311278.704083, type 1 (EV_KEY), code 148 (KEY_PROG1), value 1
Event: time 1743311278.704083, -------------- SYN_REPORT ------------
Event: time 1743311278.860473, type 4 (EV_MSC), code 4 (MSC_SCAN), value f2
Event: time 1743311278.860473, type 1 (EV_KEY), code 148 (KEY_PROG1), value 0
Event: time 1743311278.860473, -------------- SYN_REPORT ------------

这表明内核现在已经能够识别这个按键了!

永久化设置 (NixOS)

setkeycodes 命令的效果是临时的,重启后就会失效。我们需要将这个设置固化到 NixOS 的配置中。有两种主要方法:

使用 udev 规则

这是健壮的方式,它会在系统检测到你的键盘时自动应用设置。编辑你的 /etc/nixos/configuration.nix 文件:

1
2
3
4
5
6
7
8
9
10
11
12
{ config, pkgs, ... }:

{
# ... 其他配置 ...

services.udev.extraRules = ''
# 将扫描码 e072 映射到键码 148 (KEY_PROG1)
ACTION=="add", SUBSYSTEM=="input", KERNEL=="event*", ENV{ID_PATH}=="platform-i8042-serio-0", RUN+="${pkgs.kbd}/bin/setkeycodes e072 148"
'';

# ... 其他配置 ...
}
  • ${pkgs.kbd}/bin/setkeycodes 使用 Nix 管理的 setkeycodes 程序路径。

rebuild switch以后记得 重启电脑

最终验证

重启后,再次使用 evtest 检查那个特殊按键,确认它依然能够被正确识别为你映射的键码。

使用你的新按键!

现在,这个按键在内核层面已经被赋予了生命(一个键码)。接下来,XKB 系统(无论在 Xorg 还是 Wayland 下)会根据你的键盘布局将这个内核键码翻译成一个**按键符号 (Keysym)**,例如 F13XF86Launch1

由于应用/WM(Hyprland)读取的是Keysym,可以使用 xev (Xorg) 或 wev (Wayland) 来查看按下这个键时产生的具体 Keysym

1
bind = , XF86Launch1, exec, notify-send "Have a good day(^-^)"

从未知扫描码到有用按键:在 Linux (NixOS) 上映射神秘按键
https://20040702.xyz/2025/03/30/keycode/
作者
Seeker
发布于
2025年3月30日
许可协议