如何在树莓派上用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
注意事项
- 必须以root权限运行程序,否则无法访问
/dev/input设备和使用EVIOCGRAB - 若程序异常崩溃导致设备被锁定,可重新插拔设备或执行
sudo udevadm trigger释放 - 可通过
evtest工具获取按键对应的KEY_*常量值,用于宏功能匹配
内容的提问来源于stack exchange,提问作者John Allsup
相关产品推荐
相关产品推荐

