libevdev按键映射按键粘滞问题排查与解决问询
解决按键映射中的粘滞问题及程序退出后按键即时恢复方案
问题背景
现有代码通过uinput创建虚拟输入设备,拦截真实设备事件并将Home键映射为'b'键,但存在两个关键问题:
- 执行
libevdev_grab设备抓取操作时,若有按键处于按住状态,会触发按键粘滞(比如命令行启动时的Enter键),即使添加sleep延迟仍可通过切换窗口按住按键复现,推测是按键抬起信号丢失,且libevdev_get_event_value无法检测这类粘滞状态。 - 程序终止后,曾粘滞的按键首次按下无响应,后续操作才恢复正常。
核心原因
- 抓取时状态不同步:调用
libevdev_grab时,内核会锁定设备当前的按键状态,若此时有按键按下,虚拟设备未同步该初始状态,当真实按键抬起时,虚拟设备收到抬起事件却无对应的按下事件记录,导致系统判定按键持续按下。 - 退出时未清理状态:程序终止时未主动同步并释放真实设备的按键状态,也未向系统发送残留按键的抬起事件,导致首次按下需要先抵消残留状态。
解决方案
- 抓取前同步所有按键状态:在执行
libevdev_grab前,遍历设备支持的所有按键,读取当前按下状态,向虚拟设备同步发送对应的按下事件,确保虚拟设备与真实设备状态一致。 - 退出时强制清理按键状态:在清理函数中,再次遍历所有按键,若检测到虚拟设备中仍有按下状态的按键,发送抬起事件,然后解除设备抓取,最后销毁虚拟设备。
- 优化事件同步逻辑:修正SYNC事件的处理方式,确保同步事件能正确转发,避免状态不一致。
修改后的代码
#include <csignal> #include <cstring> #include <fcntl.h> #include <functional> #include <iostream> #include <libevdev/libevdev-uinput.h> #include <libevdev/libevdev.h> #include <unistd.h> int fd; struct libevdev *dev; struct libevdev_uinput *uidev; void cleanup() { if (uidev) { // 遍历所有按键,发送抬起事件清理状态 for (int code = KEY_RESERVED; code <= KEY_MAX; code++) { if (libevdev_has_event_code(dev, EV_KEY, code)) { int val = libevdev_get_event_value(dev, EV_KEY, code); if (val == 1) { libevdev_uinput_write_event(uidev, EV_KEY, code, 0); } } } // 发送同步事件确认状态更新 libevdev_uinput_write_event(uidev, EV_SYN, SYN_REPORT, 0); // 解除设备抓取 if (dev) { libevdev_grab(dev, LIBEVDEV_UNGRAB); } libevdev_uinput_destroy(uidev); } if (dev) libevdev_free(dev); if (fd) close(fd); } void sigint_handler(int sig) { cleanup(); std::cout << "程序终止,已完成清理。" << std::endl; exit(0); } bool maybe_error(std::function<bool()> predicate, const std::string &msg, int err) { if (err == 0) return false; std::cerr << msg << ": " << strerror(-err) << std::endl; return true; } void maybe_error_exit(std::function<bool()> predicate, const std::string &msg, int err) { if (maybe_error(predicate, msg, err)) { cleanup(); exit(1); } } void sync_initial_key_state() { if (!dev || !uidev) return; // 遍历所有支持的按键,同步初始按下状态 for (int code = KEY_RESERVED; code <= KEY_MAX; code++) { if (libevdev_has_event_code(dev, EV_KEY, code)) { int val = libevdev_get_event_value(dev, EV_KEY, code); if (val == 1) { // 向虚拟设备发送按下事件 libevdev_uinput_write_event(uidev, EV_KEY, code, val); } } } // 发送同步事件确认状态 libevdev_uinput_write_event(uidev, EV_SYN, SYN_REPORT, 0); } int main(int argc, char **argv) { signal(SIGTERM, sigint_handler); signal(SIGINT, sigint_handler); fd = open("/dev/input/event4", O_RDONLY); maybe_error_exit([&](){ return fd < 0; }, "打开设备失败", errno); int err = libevdev_new_from_fd(fd, &dev); maybe_error_exit([&](){ return err != 0; }, "初始化libevdev失败", err); err = libevdev_uinput_create_from_device(dev, LIBEVDEV_UINPUT_OPEN_MANAGED, &uidev); maybe_error_exit([&](){ return err != 0; }, "创建uinput设备失败", err); sleep(3); std::cerr << "开始抓取设备" << std::endl; // 同步初始按键状态后再抓取设备 sync_initial_key_state(); err = libevdev_grab(dev, LIBEVDEV_GRAB); maybe_error_exit([&](){ return err != 0; }, "抓取设备失败", err); struct input_event ev; while (true) { err = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL | LIBEVDEV_READ_FLAG_BLOCKING, &ev); if (err == LIBEVDEV_READ_STATUS_SUCCESS) { if (ev.type == EV_KEY && ev.code == KEY_HOME) { ev.code = KEY_B; } libevdev_uinput_write_event(uidev, ev.type, ev.code, ev.value); // 每次事件后发送同步 if (ev.type == EV_SYN) { libevdev_uinput_write_event(uidev, EV_SYN, SYN_REPORT, 0); } } else if (err == LIBEVDEV_READ_STATUS_SYNC) { // 同步事件直接转发 do { libevdev_uinput_write_event(uidev, ev.type, ev.code, ev.value); err = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_SYNC, &ev); } while (err == LIBEVDEV_READ_STATUS_SYNC); libevdev_uinput_write_event(uidev, EV_SYN, SYN_REPORT, 0); } else if (err == -EAGAIN) { continue; } else { std::cerr << "读取事件失败: " << strerror(-err) << std::endl; break; } } cleanup(); return 0; }
关键修改点说明
- 同步初始状态:新增
sync_initial_key_state函数,在抓取设备前同步所有按下的按键状态到虚拟设备,避免抓取时状态断层。 - 退出清理逻辑:在
cleanup中遍历所有按键,发送残留按下状态的抬起事件,并解除设备抓取,确保系统状态完全恢复。 - 事件处理优化:修改事件读取标志为
LIBEVDEV_READ_FLAG_BLOCKING,并在SYNC事件处理时正确转发所有同步事件,保证状态同步。
内容的提问来源于stack exchange,提问作者yorklift
相关产品推荐
相关产品推荐

