You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

libevdev按键映射按键粘滞问题排查与解决问询

解决按键映射中的粘滞问题及程序退出后按键即时恢复方案

问题背景

现有代码通过uinput创建虚拟输入设备,拦截真实设备事件并将Home键映射为'b'键,但存在两个关键问题:

  • 执行libevdev_grab设备抓取操作时,若有按键处于按住状态,会触发按键粘滞(比如命令行启动时的Enter键),即使添加sleep延迟仍可通过切换窗口按住按键复现,推测是按键抬起信号丢失,且libevdev_get_event_value无法检测这类粘滞状态。
  • 程序终止后,曾粘滞的按键首次按下无响应,后续操作才恢复正常。

核心原因

  1. 抓取时状态不同步:调用libevdev_grab时,内核会锁定设备当前的按键状态,若此时有按键按下,虚拟设备未同步该初始状态,当真实按键抬起时,虚拟设备收到抬起事件却无对应的按下事件记录,导致系统判定按键持续按下。
  2. 退出时未清理状态:程序终止时未主动同步并释放真实设备的按键状态,也未向系统发送残留按键的抬起事件,导致首次按下需要先抵消残留状态。

解决方案

  1. 抓取前同步所有按键状态:在执行libevdev_grab前,遍历设备支持的所有按键,读取当前按下状态,向虚拟设备同步发送对应的按下事件,确保虚拟设备与真实设备状态一致。
  2. 退出时强制清理按键状态:在清理函数中,再次遍历所有按键,若检测到虚拟设备中仍有按下状态的按键,发送抬起事件,然后解除设备抓取,最后销毁虚拟设备。
  3. 优化事件同步逻辑:修正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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 12:07:52