哈喽,大家好,我是程序员秘书LittleG。
前言
uinput
即Userspace Input
,uinput 的实现是基于 Linux input子系统(Input Subsystem),允许用户空间程序创建虚拟的输入设备并向内核发送输入事件,比如键盘敲击、鼠标移动等,就像这些事件来自真实的物理设备一样。对于开发自定义输入设备驱动、自动化测试、游戏控制模拟以及各种人机交互实验等场景非常有用。
可能的使用场景有:
自动化测试:模拟用户操作,如点击、滑动等,进行UI自动化测试。在自动化测试过程中,可以模拟用户的输入操作来测试软件的功能。
自定义输入设备:为没有驱动支持的新奇设备创建虚拟驱动。
游戏控制模拟:模拟游戏手柄或其他特殊控制器。一些游戏玩家可能会使用脚本来自动执行一些复杂的操作。
辅助工具开发:如屏幕键盘、宏命令工具等。实现一些辅助工具(如屏幕阅读器、残疾人辅助工具等)可能需要模拟输入设备来与操作系统或其他应用程序进行交互。
uinput的使用场景非常广泛,包括但不限于以上场景。下面就学习看下原理和如何使用。
实现原理
uinput
通过在内核和用户空间之间创建一个特殊的设备文件(通常是 /dev/uinput
)来工作。用户空间程序可以打开这个文件,并向其中写入数据来模拟输入事件。这些数据会被内核的输入子系统接收,并像真实的输入设备产生的事件一样进行处理。
具体来说,uinput
的实现包括以下几个步骤:
创建 uinput 设备:用户空间程序通过 ioctl
调用 UINPUT_CREATE_DEVICE
请求来创建一个 uinput
设备。这个请求需要指定模拟的输入设备的类型和属性。
设置输入事件:用户空间程序通过写入 /dev/uinput
设备文件来设置要模拟的输入事件。这些事件可以是键盘按键、鼠标移动、触摸屏触摸等。
启用 uinput 设备:在设置完所有需要的输入事件后,用户空间程序通过 ioctl
调用 UINPUT_START_DEVICE
请求来启用 uinput
设备。此时,内核的输入子系统会开始处理这些模拟的输入事件。
发送输入事件:一旦 uinput
设备被启用,用户空间程序就可以通过写入 /dev/uinput
设备文件来发送输入事件了。这些事件会被内核的输入子系统接收,并像真实的输入设备产生的事件一样进行处理。
销毁 uinput 设备:当用户空间程序不再需要模拟输入设备时,它可以通过 ioctl
调用 UINPUT_DESTROY_DEVICE
请求来销毁 uinput
设备。
补充说明
关于配置设备属性
在创建虚拟设备之前,需要通过ioctl
调用配置设备的各种属性,比如设备名称、ID(厂商ID、产品ID、版本等)、支持的事件类型(EV_KEY、EV_REL、EV_ABS等)和具体的事件代码(如BTN_LEFT、KEY_A、REL_X等)。
关于发送事件
一旦设备被创建,就可以通过写入input_event
结构体到文件描述符来模拟输入事件。每个事件包括类型(EV_KEY、EV_REL等)、代码(按键、轴等)、值(按键状态、移动距离等)以及一个同步事件(EV_SYN)来标记事件包的结束。
使用举例
下面是一个简单的使用例子,通过uinput
创建虚拟键盘并发送一次键击事件:
#include <fcntl.h>
#include <linux/input.h>
#include <linux/uinput.h>
#include <stdio.h>
#include <unistd.h>
int main() {
int fd;
struct uinput_user_dev uidev;
struct input_event ev;
/* 打开uinput设备 */
fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
if (fd < 0) {
perror("Error opening /dev/uinput");
return 1;
}
/* 配置虚拟设备 */
memset(&uidev, 0, sizeof(uidev));
strncpy(uidev.name, "Virtual Keyboard", UINPUT_MAX_NAME_SIZE);
uidev.id.bustype = BUS_USB;
uidev.id.vendor = 0x1234;
uidev.id.product = 0x5678;
uidev.id.version = 1;
/* 设置支持的事件类型 */
ioctl(fd, UI_SET_EVBIT, EV_KEY);
ioctl(fd, UI_SET_KEYBIT, KEY_A);
/* 创建虚拟设备 */
write(fd, &uidev, sizeof(uidev));
ioctl(fd, UI_DEV_CREATE);
/* 发送按键事件 */
memset(&ev, 0, sizeof(ev));
ev.type = EV_KEY;
ev.code = KEY_A;
ev.value = 1; // 按下
write(fd, &ev, sizeof(ev));
ev.value = 0; // 抬起
write(fd, &ev, sizeof(ev));
/* 发送同步事件 */
memset(&ev, 0, sizeof(ev));
ev.type = EV_SYN;
ev.code = SYN_REPORT;
ev.value = 0;
write(fd, &ev, sizeof(ev));
/* 销毁设备 */
ioctl(fd, UI_DEV_DESTROY);
close(fd);
return 0;
}
说明:以上代码中,创建了一个名为"Virtual Keyboard"的虚拟键盘设备,发送了一个"A"键的按下与释放事件,并在最后销毁了虚拟设备。
注意,调试运行时,可能会存在相应的权限问题,可以使用root登录运行或chmod修改/dev/uinput
权限。
下期见~