将扫描码映射到键码

出自 ArchWiki
(重定向自 Setkeycodes

本页面假定您已阅读 键盘输入,其中提供了更广泛的背景信息。

扫描码键码的映射在 Xorg 和 Linux 控制台之下的层级实现,这意味着对此映射的更改在两者中都有效。[1][2][3] 请注意,此方法只能用于简单的 1:1 按键重映射;有关允许在同一低级别进行更复杂重映射的程序,请参阅 输入重映射实用程序

有两种方法可以将扫描码映射到键码

首选方法是使用 udev,因为它使用硬件信息(这是一个非常可靠的来源)在数据库中选择键盘型号。这意味着如果您的键盘型号已在数据库中找到,则您的按键将被开箱即用地识别。

识别扫描码

您需要知道要重映射的按键的扫描码。有关详细信息,请参阅 键盘输入#识别扫描码

使用 udev

udev 提供了一个名为 hwdb 的内置函数,用于维护 /etc/udev/hwdb.bin 中的硬件数据库索引。该数据库从扩展名为 .hwdb 的文件编译而来,这些文件位于目录 /usr/lib/udev/hwdb.d//run/udev/hwdb.d//etc/udev/hwdb.d/ 中。默认的扫描码到键码映射文件是 /usr/lib/udev/hwdb.d/60-keyboard.hwdb。有关详细信息,请参阅 hwdb(7)

.hwdb 文件可以基于硬件 ID glob 模式将按键映射应用于一个或多个键盘。您可以通过以 root 用户身份运行 evemu-describe(1) 来获取设备识别信息。此命令由 evemu 软件包提供。

evdev: 前缀用于将硬件与映射块进行匹配。支持以下硬件匹配

  • 由 usb 内核 modalias 识别的通用输入设备(也包括 USB 键盘)
evdev:input:b<bus_id>v<vendor_id>p<product_id>e<version_id>-<input_modalias>
带有以下 4 位十六进制大写字段
  • <vendor_id><product_id><version_id>:供应商 ID、产品 ID 和版本 ID,与 lsusb 命令的输出匹配。
  • <bus_id> 是 4 位十六进制总线 ID,对于 usb 设备应为 0003。可能的 <bus_id> 值在 /usr/include/linux/input.h 中定义(您可以运行 awk '/BUS_/ {print $2, $3}' /usr/include/linux/input.h 来获取列表)。
  • <input_modalias> 是描述设备功能的任意长度的 input-modalias。其他字段足以唯一标识设备,因此您可以在此处使用 glob。
如果您当前已将设备插入计算机,则可以使用 sysfs 一次获取整个 modalias,如 #重映射特定设备 中所示。
  • 输入驱动程序设备名称和 DMI 数据匹配
    evdev:name:<input device name>:dmi:bvn*:bvr*:bd*:svn<vendor>:pn*
    其中 <input_device_name> 是驱动程序指定的名称设备,<vendor> 是内核 DMI modalias 导出的固件提供的字符串。

块主体中每行的格式为 KEYBOARD_KEY_<scancode>=<keycode><scancode> 的值是十六进制的,但不带前导 0x(即指定 a0 而不是 0xa0),而 <keycode> 的值是 /usr/include/linux/input-event-codes.h 中列出的小写键码名称字符串(请参阅 KEY_<KEYCODE> 变量),排序列表可在 [4] 中找到。无法在 <keycode> 中指定十进制值。

示例

重映射所有设备

假设我们要为所有 AT 键盘重映射几个常用按键

/etc/udev/hwdb.d/90-custom-keyboard.hwdb
evdev:atkbd:*
 KEYBOARD_KEY_10=suspend
 KEYBOARD_KEY_a0=search

重映射特定设备

假设我们要重映射当前碰巧已插入的设备。您应该已经有了 evdev 路径(例如 /dev/input/event17)以及扫描码(例如 caps lock 的 70039)。现在,使用事件编号,您可以查询 sysfs 以获取 modalias

cat /sys/class/input/event17/device/modalias
input:b0003v32ACp0012e0111-e0,1,4,1...

此设备可以使用以下 hwdb 规则进行匹配

/etc/udev/hwdb.d/90-remap.hwdb
evdev:input:b0003v32ACp0012e0111*
 KEYBOARD_KEY_70039=rightctrl # This example maps the 70039 scancode to the "rightctrl" keycode.

禁用按键

要阻止 Sleep 键,请将其绑定到“reserved”关键字。或者,您可以使用“unknown”将其映射到 NoSymbol 键。例如

/etc/udev/hwdb.d/90-block-sleep.hwdb
evdev:input:b0003v03F0p020C* # hp 5308 keyboard controller
 KEYBOARD_KEY_10082=reserved

更新硬件数据库索引

更改配置文件后,需要重建硬件数据库索引 hwdb.bin

  • 通过运行以下命令手动更新 hwdb.bin
# systemd-hwdb update
  • 通过使用 替换单元文件 注释掉 systemd-hwdb-update.service 中的 ConditionNeedsUpdate,在每次重启时自动更新
/etc/systemd/system/systemd-hwdb-update.service
#  This file is part of systemd.
.
.
#ConditionNeedsUpdate=/etc
.
.

systemd-hwdb-update.service 完成加载后,systemd-trigger.service 将从 hwdb.bin 重新加载更改。

  • systemd 升级后自动更新。

在每次升级 systemd 时,30-systemd-hwdb.hook 会通过以 root 用户身份运行 systemd-hwdb --usr update 来重建 hwdb.bin,因此我们无需担心。

重新加载硬件数据库索引

内核在启动过程中加载 hwdb.bin,重启系统将确保加载更新后的 hwdb.bin

使用 udevadm 可以通过运行以下命令从更新后的 hwdb.bin 加载新的按键映射

# udevadm trigger

请注意,使用 udevadm 仅加载添加或更改的按键映射,因此,如果我们从配置文件中删除映射,重建 hwdb.bin 并以 root 用户身份运行 udevadm trigger,则删除的映射仍由内核保留,至少在重启之前是这样。

查询数据库

您可以通过按键或运行 udevadm info 来检查您的配置是否已加载。对于上面示例中的 USB 键盘,这将输出我们配置的映射,如下所示

# udevadm info /dev/input/by-path/*-usb-*-kbd | grep KEYBOARD_KEY
E: KEYBOARD_KEY_70039=leftalt
E: KEYBOARD_KEY_700e2=leftctrl

使用 setkeycodes

setkeycodes 是一个将扫描码键码映射表加载到 Linux 内核的工具。其用法是

# setkeycodes scancode keycode ...

可以一次指定多个对。扫描码以十六进制给出,键码以十进制给出。

注意

此文章或章节的事实准确性存在争议。

原因: 在 Linux 6.7.8-zen1-1-zen 上,这似乎静默失败,而不是发出错误。(在 Talk:Map scancodes to keycodes 中讨论)

显然 setkeycodes 不适用于 USB 键盘 (Linux 3.14.44-1-lts)

# setkeycodes 45 30     # bind NumLock (0x45) to KEY_A (30) on AT keyboard
(successful)
# setkeycodes 70053 30  # bind NumLock (0x70053) to KEY_A (30) on USB keyboard
KDSETKEYCODE: Invalid argument
failed to set scancode 620d3 to keycode 31

如果使用此简单命令,更改将在重启后丢失。可以通过创建新服务使更改永久生效

/etc/systemd/system/setkeycodes.service
[Unit]
Description=Change keycodes at boot

[Service]
Type=oneshot
ExecStart=/usr/bin/setkeycodes [scancode] [keycode] [scancode] [keycode] [...]

[Install]
WantedBy=multi-user.target

启用 setkeycodes.service