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

如何在树莓派上用C语言区分多USB键盘/鼠标的原始输入?

树莓派多USB输入设备监听与差异化功能实现(C语言)

核心思路

基于Linux Input子系统实现:所有USB键盘/鼠标都会被系统识别为/dev/input/eventX设备文件,我们可以直接打开这些文件读取原始输入事件,通过设备的厂商ID(Vendor ID)和产品ID(Product ID)区分不同设备,同时通过设备独占或udev规则屏蔽默认输入功能,最终实现差异化的宏按键/事件响应。


1. 识别目标输入设备

首先需要确定要监听的键盘/鼠标对应的设备文件及标识:

  • 用lsusb查看设备的Vendor/Product ID(格式为ID xxxx:yyyy)
  • 用evtest工具测试设备事件,确认设备对应的/dev/input/eventX路径
  • 在C代码中,可通过ioctl(fd, EVIOCGID, &id)获取input_id结构体(包含vendor、product字段),以此筛选目标设备

2. 屏蔽默认输入功能

两种可靠方案:

方案一:udev规则(永久生效)

创建/etc/udev/rules.d/99-custom-input.rules,添加规则让系统不将目标设备识别为普通输入设备:

SUBSYSTEM=="input", ATTRS{idVendor}=="xxxx", ATTRS{idProduct}=="yyyy", ENV{ID_INPUT_KEYBOARD}="", ENV{ID_INPUT_MOUSE}=""

替换xxxx/yyyy为你的设备ID,执行udevadm control --reload-rules && udevadm trigger生效。

方案二:程序内独占设备(临时生效)

打开设备后调用ioctl(fd, EVIOCGRAB, 1),独占设备阻止其他进程读取事件;程序退出时需调用ioctl(fd, EVIOCGRAB, 0)释放,避免设备被锁定。


3. C语言核心实现代码

完整示例框架

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/select.h>
#include <linux/input.h>

// 存储设备信息的结构体
typedef struct {
    int fd;
    char name[256];
    __u16 vendor_id;
    __u16 product_id;
} InputDevice;

InputDevice devices[4];
int device_count = 0;

// 程序退出时清理资源
void cleanup(int sig) {
    for (int i = 0; i < device_count; i++) {
        if (devices[i].fd != -1) {
            ioctl(devices[i].fd, EVIOCGRAB, 0);
            close(devices[i].fd);
        }
    }
    exit(0);
}

// 枚举并筛选目标输入设备
void enumerate_target_devices() {
    char dev_path[64];
    // 遍历前10个event设备(可根据实际情况调整)
    for (int i = 0; i < 10; i++) {
        snprintf(dev_path, sizeof(dev_path), "/dev/input/event%d", i);
        int fd = open(dev_path, O_RDONLY | O_NONBLOCK);
        if (fd == -1) continue;

        input_id dev_id;
        if (ioctl(fd, EVIOCGID, &dev_id) == -1) {
            close(fd);
            continue;
        }

        // 替换为你要监听的设备Vendor/Product ID
        if ((dev_id.vendor == 0x1234 && dev_id.product == 0x5678) ||
            (dev_id.vendor == 0xabcd && dev_id.product == 0xef01)) {
            
            char dev_name[256];
            ioctl(fd, EVIOCGNAME(sizeof(dev_name)), dev_name);
            
            devices[device_count].fd = fd;
            strncpy(devices[device_count].name, dev_name, sizeof(devices[device_count].name)-1);
            devices[device_count].vendor_id = dev_id.vendor;
            devices[device_count].product_id = dev_id.product;
            device_count++;

            // 独占设备,屏蔽默认输入
            if (ioctl(fd, EVIOCGRAB, 1) == -1) {
                perror("Failed to grab device");
                close(fd);
                device_count--;
            }

            if (device_count >= 4) break;
        } else {
            close(fd);
        }
    }
}

int main() {
    // 注册信号处理,确保异常退出时清理资源
    signal(SIGINT, cleanup);
    signal(SIGTERM, cleanup);

    enumerate_target_devices();
    if (device_count == 0) {
        fprintf(stderr, "No target input devices found\n");
        return 1;
    }

    printf("Found %d target devices:\n", device_count);
    for (int i = 0; i < device_count; i++) {
        printf("  [%d] %s (Vendor: 0x%04X, Product: 0x%04X)\n",
               i, devices[i].name, devices[i].vendor_id, devices[i].product_id);
    }

    fd_set read_fds;
    struct input_event ev;

    while (1) {
        FD_ZERO(&read_fds);
        int max_fd = 0;
        for (int i = 0; i < device_count; i++) {
            FD_SET(devices[i].fd, &read_fds);
            if (devices[i].fd > max_fd) max_fd = devices[i].fd;
        }

        // 监听所有设备的事件
        int ret = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
        if (ret == -1) {
            perror("select failed");
            cleanup(0);
        }

        // 处理每个设备的事件
        for (int i = 0; i < device_count; i++) {
            if (FD_ISSET(devices[i].fd, &read_fds)) {
                ssize_t bytes_read = read(devices[i].fd, &ev, sizeof(ev));
                if (bytes_read == sizeof(ev)) {
                    // 按键事件处理
                    if (ev.type == EV_KEY) {
                        printf("Device %d: Key %d (%s)\n",
                               i, ev.code, ev.value ? "PRESSED" : "RELEASED");
                        
                        // 差异化宏功能示例
                        if (devices[i].vendor_id == 0x1234 && devices[i].product_id == 0x5678) {
                            if (ev.code == KEY_A && ev.value == 1) {
                                system("echo 'Macro executed: Device 1 - Key A pressed'");
                            }
                        } else if (devices[i].vendor_id == 0xabcd && devices[i].product_id == 0xef01) {
                            if (ev.code == KEY_B && ev.value == 1) {
                                system("echo 'Macro executed: Device 2 - Key B pressed'");
                            }
                        }
                    }
                    // 鼠标相对移动事件处理
                    else if (ev.type == EV_REL) {
                        if (ev.code == REL_X || ev.code == REL_Y) {
                            printf("Device %d: Mouse %s moved %d\n",
                                   i, ev.code == REL_X ? "X" : "Y", ev.value);
                            // 鼠标事件差异化逻辑可在此添加
                        }
                    }
                }
            }
        }
    }

    cleanup(0);
    return 0;
}

编译与运行

  • 编译:gcc input_monitor.c -o input_monitor
  • 运行(需root权限):sudo ./input_monitor
  • 后台运行并记录日志:sudo ./input_monitor > /var/log/input_monitor.log 2>&1 &

4. SSH控制与服务化

后台进程管理

通过SSH连接树莓派后,可使用以下命令控制程序:

  • 查看进程:ps aux | grep input_monitor
  • 停止进程:sudo kill <PID>

开机自启(systemd服务)

创建/etc/systemd/system/input-monitor.service:

[Unit]
Description=Custom Input Device Monitor
After=multi-user.target

[Service]
ExecStart=/usr/local/bin/input_monitor
Restart=always
User=root

[Install]
WantedBy=multi-user.target

启用服务:

sudo systemctl enable --now input-monitor.service

通过SSH控制服务:

sudo systemctl start input-monitor
sudo systemctl stop input-monitor
sudo systemctl restart input-monitor

注意事项

  1. 必须以root权限运行程序,否则无法访问/dev/input设备和使用EVIOCGRAB
  2. 若程序异常崩溃导致设备被锁定,可重新插拔设备或执行sudo udevadm trigger释放
  3. 可通过evtest工具获取按键对应的KEY_*常量值,用于宏功能匹配

内容的提问来源于stack exchange,提问作者John Allsup

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 19:48:23