Linux下修改键盘映射
Linux下的修改键盘映射
最近买了一个chromebook,破解后安装了Linux。所有功能都很好,唯独键盘有些问题。了解过chromebook的都知道,它的键盘跟普通的笔记本键盘是不一样的。缺少Win键和F1~F12。其中Win键还好说,因为它有个Search键,可以当Win键使用,只是位置不一样而已。但是,F1~F12位置是各种功能键。平常还好说,如果写个程序啥的就非常不方便了。想按F5调试都不行。
于是,想着把F1~F12给重新映射回来。主要是两种方式,一个是通过xmodmap,另一个是通过Linux内核中input驱动的ioctl。
scancode和keycode
scancode
是硬件提供的扫描码。不同的键盘,其扫描码可能会不一样。所以,软件并不会使用键盘的扫描码。而是使用keycode
。由键盘驱动程序将scancode
翻译成keycode
。所以,想要改键盘映射,只需要改一下两者的映射关系就可以了。
首先,我们使用evtest
这个工具查看键盘的扫描码和键位码。在linux中,每个输入设备,都会有一个设备文件。物理是键盘、鼠标、触摸屏、触摸板等等。运行evtest
后,首先选择键盘的设备文件,通常会有keyboard字样。实在找不到还可以逐个尝试。哪个敲击键盘后有数据打印出来,哪个就是了。
在我这个笔记本上是/dev/input/event2
,于是,我选择2
。
然后,在按键盘时就会看到下面的输出:
Event: time 1711356056.489203, -------------- SYN_REPORT ------------
Event: time 1711356057.937835, type 4 (EV_MSC), code 4 (MSC_SCAN), value b0
Event: time 1711356057.937835, type 1 (EV_KEY), code 115 (KEY_VOLUMEUP), value 1
Event: time 1711356057.937835, -------------- SYN_REPORT ------------
Event: time 1711356058.053976, type 4 (EV_MSC), code 4 (MSC_SCAN), value b0
Event: time 1711356058.053976, type 1 (EV_KEY), code 115 (KEY_VOLUMEUP), value 0
Event: time 1711356058.053976, -------------- SYN_REPORT ------------
其中,
EV_MSC
这一行最后的就是扫描码EV_KEY
这一行中包含的键盘码。
例如,我这儿按下了一下“音量上调”按键,其中,扫描码是:b0
,键盘码是:115
通过这种方法我们就可以把所有想要映射掉的按键信息查到。一定要注意,每款硬件的扫描码是不一样的。
拿到这个信息后,接下来就是实现键盘映射了。
使用xmodmap修改键盘映射
xmodmap 是一个在 X 图形环境下用于修改键盘和鼠标按钮映射的工具。通过写一个配置文件即可实现键盘映射。具体的用法可以搜索一下。注意,这个支持x11,如果使用的wayland,就不用尝试了。
配置文件
首先,使用上面查询到的keycode,编写一个xmodmap的配置文件chrome_keyboard.map
。
例如:
keycode 166 = XF86Back F1 F1 F1 F1 F1 XF86Switch_VT_1
keycode 181 = XF86Reload F2 F2 F2 F2 F2 XF86Switch_VT_2
keycode 380 = F3 F3 F3 F3
keycode 128 = XF86LaunchA F3 F3 F3
keycode 107 = Print F4 F4 F4
keycode 232 = XF86MonBrightnessDown F5 F5 F5
keycode 233 = XF86MonBrightnessUp F6 F6 F6
keycode 237 = XF86KbdBrightnessDown F7 F7 F7
keycode 238 = XF86KbdBrightnessUp F8 F8 F8
keycode 172 = XF86AudioPlay F9 F9 F9
keycode 121 = XF86AudioMute F10 F10 F10
keycode 122 = XF86AudioLowerVolume F11 F11 F11
keycode 123 = XF86AudioRaiseVolume F12 F12 F12
keycode 150 = XF86Sleep Delete Delete Delete
keycode 133 = Super_L NoSymbol Super_L
keycode 111 = Up NoSymbol Prior
keycode 116 = Down NoSymbol Next
keycode 113 = Left NoSymbol Home
keycode 114 = Right NoSymbol End
keycode 22 = BackSpace BackSpace Delete Delete
clear control
keycode 37 = Control_L NoSymbol Control_L
! keycode 105 = Control_R NoSymbol Control_R
keycode 105 = Mode_switch NoSymbol NoSymbol
add control = Control_L
配置生效
然后,使用xmodmap chrome_keyboard.map
即可生效了。
规则是:
-
把右边的Ctrl键改成了Mode_switch
-
把
Mode_switch+功能键
改成了F1~F12、Delete,把Mode_switch+上下左右
改成了PageUp、PageDown、Home和End。
开机自动生效
接下来,就是怎么才能开机自动生效了。因为这个是X11的键盘映射方法,我们可以把它放在shell的启动文件中实现自启动。比如,把命令写入$HOME/.bashrc
或者$HOME/.zshrc
中。
实际使用时就会发现,可能经常会不生效。这是因为系统启动的时候也会改键盘映射。我们改过以后又被改回去了。于是,写了一个脚本,监控这个改动。脚本.chrome_keyboard.sh
内容如下:
#!/bin/bash
function change_key()
{
if [ -f $HOME/.chrome_keyboard.map ]; then
date >> $HOME/.chrome_keyboard.log
echo "xxxx $1" >> $HOME/.chrome_keyboard.log
xmodmap $HOME/.chrome_keyboard.map >> $HOME/.chrome_keyboard.log
echo "xxxx ret=$?" >> $HOME/.chrome_keyboard.log
fi
}
rm $HOME/.mykey.log
count=0
while [ $count -lt 120 ]; do
xmodmap -pke|grep 166|grep F1 >/dev/null 2>&1
if [ $? -ne 0 ]; then
change_key $count
fi
sleep 3
count=$(($count+1))
done
然后,在.bashrc
或者.zshrc
里面后台启动一下这个脚本即可。
.....
bash $HOME/.chrome_keyboard.sh
.....
按说,到这一步基本就完美了。然而事与愿违。在实际使用中发现,键盘偶尔会乱掉。比如本来是Left_Ctrl+Up
是PageUp
,结果变成了Up
是PageUp
,Left_Ctrl+Up
是Up
。这就很难受了。查了好久也没有查到原因,只好放弃。
使用Input更改键盘映射
我们前面提到硬件提供扫描码,软件使用键盘码。扫描码和键盘码之间的映射关系是有内核驱动完成的。而在Linux内核中,有一个input子系统,通过它,我们可以改变键盘映射的规则。在input子系统的ioctl中就提供了相关的功能。
其中,EVIOCSKEYCODE
和EVIOCSKEYCODE_V2
就是用来实现这个功能的。
程序源码
这个小程序的源码如下:
#include <errno.h>
#include <fcntl.h>
#include <linux/input.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static const char *kbd_event_file = "/dev/input/event2";
static struct
{
unsigned int scancode;
unsigned int keycode;
} keys[] = {
{0xea, KEY_F1}, // 后退 ==> F1
{0xe7, KEY_F2}, // 刷新 ==> F2
{0x91, KEY_F3}, // 全屏 ==> F3
{0x92, KEY_F4}, // 缩小 ==> F4
{0x93, KEY_F5}, // 截屏 ==> F5
{0x94, KEY_F6}, // 屏幕亮度降低 ==> F6
{0x95, KEY_F7}, // 屏幕亮度提升 ==> F7
{0x97, KEY_F8}, // 键盘亮度降低 ==> F8
{0x98, KEY_F9}, // 键盘亮度提升 ==> F9
{0x9a, KEY_F10}, // 播放 ==> F10
{0xa0, KEY_F11}, // 静音 ==> F11
{0xae, KEY_F12}, // 音量降低 ==> F12
{0xb0, KEY_DELETE}, // 音量提升 ==> Delete
};
int main()
{
int fd, nkeys, i;
unsigned int buf[2];
fd = open(kbd_event_file, O_RDWR);
if (fd < 0)
{
fprintf(stderr, "open: %s (%s)\n", kbd_event_file, strerror(errno));
return 1;
}
// 通过ioctl更改键盘映射
nkeys = sizeof(keys) / sizeof(keys[0]);
for (i = 0; i < nkeys; i++)
{
buf[0] = keys[i].scancode;
buf[1] = keys[i].keycode;
if (ioctl(fd, EVIOCSKEYCODE, buf) < 0)
{
fprintf(stderr, "ioctl: %x -> %u (%s)\n", buf[0], buf[1],
strerror(errno));
}
}
// 使用udevadm使键盘映射生效
{
char cmd[128];
sprintf(cmd, "udevadm trigger %s", kbd_event_file);
system(cmd);
}
return 0;
}
使用命令gcc chrome_keyboard.c -o chrome_keyboard
即可编译。
需要留意的是:
1、设备文件要根据自己机器上的情况修改。
2、scancode也需要根据自己设备的情况修改。
开机自启动
因为这个是基于内核实现的键盘映射,所以,可以通过系统服务来实现。
首先,将编译好的文件复制到系统目录。
cp chrome_keyboard /sbin
然后,编写一个service文件,chrome_keyboard.service
,内容如下:
[Unit]
Description=change chrome keyboard layout to pc, such as F1~F12
After=syslog.target network.target remote-fs.target nss-lookup.target
[Service]
Type=simple
ExecStart=/sbin/chrome_keyboard
# ExecReload=target_dir/restart.sh
# ExecStop=target_dir/shutdown.sh
SuccessExitStatus=0
[Install]
WantedBy=multi-user.target
最后,让服务生效。
cp chrome_keyboard.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable chrome_keyboard
systemctl start chrome_keyboard
你好,我的笔记本是京东的一款,右侧键盘上的home键按下会是d键,无法解决
这是我的按键事件,好像是一个键被映射到多个组合,如何才能解决,谢谢
Event: time 1728949357.659910, -------------- SYN_REPORT ------------
Event: time 1728949358.896339, type 4 (EV_MSC), code 4 (MSC_SCAN), value db
Event: time 1728949358.896339, type 1 (EV_KEY), code 125 (KEY_LEFTMETA), value 1
Event: time 1728949358.896339, -------------- SYN_REPORT ------------
Event: time 1728949358.899357, type 4 (EV_MSC), code 4 (MSC_SCAN), value 20
Event: time 1728949358.899357, type 1 (EV_KEY), code 32 (KEY_D), value 1
Event: time 1728949358.899357, -------------- SYN_REPORT ------------
dEvent: time 1728949358.997493, type 4 (EV_MSC), code 4 (MSC_SCAN), value 20
Event: time 1728949358.997493, type 1 (EV_KEY), code 32 (KEY_D), value 0
Event: time 1728949358.997493, -------------- SYN_REPORT ------------
Event: time 1728949359.006538, type 4 (EV_MSC), code 4 (MSC_SCAN), value db
Event: time 1728949359.006538, type 1 (EV_KEY), code 125 (KEY_LEFTMETA), value 0
Event: time 1728949359.006538, -------------- SYN_REPORT ------------
你可以看一下真正的d键是什么值,是不是也是32,如果一样的话,那在应用层就无解了,那是键盘驱动层有问题。如果不是32,可以按照我文中的方法自己写个小程序互换一下。