使用uinput创建虚拟设备节点,生成虚拟设备。
开始之前首先要了解查看Linux下USB设备的命令:
root@ubuntu:~# cat /proc/bus/input/devices
I: Bus=0019 Vendor=0000 Product=0001 Version=0000
N: Name="Power Button"
P: Phys=LNXPWRBN/button/input0
S: Sysfs=/devices/LNXSYSTM:00/LNXPWRBN:00/input/input0
U: Uniq=
H: Handlers=kbd event0
B: PROP=0
B: EV=3
B: KEY=10000000000000 0
I: Bus=0011 Vendor=0001 Product=0001 Version=ab41
N: Name="AT Translated Set 2 keyboard"
P: Phys=isa0060/serio0/input0
S: Sysfs=/devices/platform/i8042/serio0/input/input1
U: Uniq=
H: Handlers=sysrq kbd event1 leds
B: PROP=0
B: EV=120013
B: KEY=402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe
B: MSC=10
B: LED=7
I: Bus=0011 Vendor=0002 Product=0013 Version=0006
N: Name="VirtualPS/2 VMware VMMouse"
P: Phys=isa0060/serio1/input1
S: Sysfs=/devices/platform/i8042/serio1/input/input4
U: Uniq=
H: Handlers=mouse0 event2
B: PROP=0
B: EV=b
B: KEY=70000 0 0 0 0
B: ABS=3
I: Bus=0011 Vendor=0002 Product=0013 Version=0006
N: Name="VirtualPS/2 VMware VMMouse"
P: Phys=isa0060/serio1/input0
S: Sysfs=/devices/platform/i8042/serio1/input/input3
U: Uniq=
H: Handlers=mouse1 event3
B: PROP=1
B: EV=7
B: KEY=30000 0 0 0 0
B: REL=103
I: Bus=0003 Vendor=0e0f Product=0003 Version=0110
N: Name="VMware VMware Virtual USB Mouse"
P: Phys=usb-0000:02:00.0-1/input0
S: Sysfs=/devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-1/2-1:1.0/0003:0E0F:0003.0001/input/input5
U: Uniq=
H: Handlers=mouse2 event4
B: PROP=0
B: EV=17
B: KEY=ff0000 0 0 0 0
B: REL=903
B: MSC=10
I: Bus=0003 Vendor=093a Product=2510 Version=0001
N: Name="Virtual Mouse"
P: Phys=mouse41547/41547
S: Sysfs=/devices/virtual/input/input47
U: Uniq=
H: Handlers=mouse3 event5
B: PROP=0
B: EV=17
B: KEY=ff0000 0 0 0 0
B: REL=903
B: MSC=10
在我的虚拟机下运行结果是这样的;event0-event4这5个设备节点是这样的(虽然我也不太清楚为什么会默认生成这5个节点),event5是我的程序生成的一个虚拟鼠标的节点。
使用libinput record命令输入对应的节点可以查看到当前设备存在哪些键值:
root@ubuntu:~# libinput record
Available devices:
/dev/input/event0: Power Button
/dev/input/event1: AT Translated Set 2 keyboard
/dev/input/event2: VirtualPS/2 VMware VMMouse
/dev/input/event3: VirtualPS/2 VMware VMMouse
/dev/input/event4: VMware VMware Virtual USB Mouse
/dev/input/event5: Virtual Mouse
Select the device event number: 5
Recording to 'stdout'.
version: 1
ndevices: 1
libinput:
version: "1.15.5"
git: "unknown"
system:
os: "ubuntu:20.04"
kernel: "5.11.0-40-generic"
dmi: "dmi:bvnPhoenixTechnologiesLTD:bvr6.00:bd11/12/2020:br4.6:efr0.0:svnVMware,Inc.:pnVMwareVirtualPlatform:pvrNone:sku:rvnIntelCorporation:rn440BXDesktopReferencePlatform:rvrNone:cvnNoEnclosure:ct1:cvrN/A:"
devices:
- node: /dev/input/event5
evdev:
# Name: Virtual Mouse
# ID: bus 0x3 vendor 0x93a product 0x2510 version 0x1
# Supported Events:
# Event type 0 (EV_SYN)
# Event type 1 (EV_KEY)
# Event code 272 (BTN_LEFT)
# Event code 273 (BTN_RIGHT)
# Event code 274 (BTN_MIDDLE)
# Event code 275 (BTN_SIDE)
# Event code 276 (BTN_EXTRA)
# Event code 277 (BTN_FORWARD)
# Event code 278 (BTN_BACK)
# Event code 279 (BTN_TASK)
# Event type 2 (EV_REL)
# Event code 0 (REL_X)
# Event code 1 (REL_Y)
# Event code 8 (REL_WHEEL)
# Event code 11 (REL_WHEEL_HI_RES)
# Event type 4 (EV_MSC)
# Event code 4 (MSC_SCAN)
# Properties:
name: "Virtual Mouse"
id: [3, 2362, 9488, 1]
codes:
0: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] # EV_SYN
1: [272, 273, 274, 275, 276, 277, 278, 279] # EV_KEY
2: [0, 1, 8, 11] # EV_REL
4: [4] # EV_MSC
properties: []
udev:
properties:
- ID_INPUT=1
- ID_INPUT_MOUSE=1
- LIBINPUT_DEVICE_GROUP=3/93a/2510:mouse41547/41547
quirks:
events:
libinput reocrd可以记录来自设备的内核事件(比如说鼠标按下的事件),当然,在这里我们只是查看一下当前设备的一些属性。我们可以看到在这个虚拟鼠标中有四种事件类型,分别是EV_SYN,EV_KEY,EV_REL,EV_MSC。
这里有事件类型及对应的意思
inux 驱动程序开发中输入子系统总共能产生哪些事件类型(EV_KEY,EV_ABS,EV_REL) - maxiongying - 博客园
了解了这些之后就进入代码:
//代码中用到的一些头文件
#include <cstdio>
#include <iostream>
#include <fcntl.h>
#include <string>
#include <vector>
#include <cstring>
#include <unistd.h>
#include "linux/input.h"
#include "linux/uinput.h"
1、创建文件句柄:
要创建虚拟设备,首先要需要创建一个文件句柄fd,用来对设备进行读写操作,使用open()函数以写入模式和不可阻断的方式打开文件这里的O_NONBLOCK方式是无论有没有数据读取或等待,都会立即返回进程之中。详细的文件打开方式可参考此链接:
C语言open()函数:打开文件函数_C语言中文网 (biancheng.net)
(这里的打开方式很重要,我试过不加后面的方式,会导致写入失败。)
int32_t fd = -1;
fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
if (fd < 0) {
printf("Failed to open uinput %s", __func__);
return false;
}
在这里我们需要使用open ()函数打开"/dev/uinput"路径,当open()函数打开失败时会返回-1,程序结束。
2、配置虚拟设备基本参数
在uinput中,uinput_user_dev结构体可以完成对设备基本信息的设置,例如设备名称、连接方式、产品ID等信息,具体可查看uinput.h文件:
/* uinput.h中定义了如下内容:
#define UINPUT_MAX_NAME_SIZE 80
struct uinput_user_dev {
char name[UINPUT_MAX_NAME_SIZE]; ------>用户定义生成虚拟设备的名称
struct input_id id; ------>生成描述虚拟设备的一些参数信息(连接方式、厂商ID、产品ID、版本)
__u32 ff_effects_max;
__s32 absmax[ABS_CNT]; ------>最大绝对坐标值
__s32 absmin[ABS_CNT]; ------>最小绝对坐标值
__s32 absfuzz[ABS_CNT]; ------>坐标的分辨率
__s32 absflat[ABS_CNT]; ------>坐标的基准值
}; */
uinput_user_dev dev{};
std::string deviceName = "Test Mouse";
strncpy(dev.name, deviceName.c_str(), deviceName.size());
dev.id.bustype = BUS_USB;
dev.id.vendor = 0x93a; // 这里是仿写真实鼠标的值
dev.id.product = 0x2510; // 这里是仿写真实鼠标的值
dev.id.version = 1;
定义一个结构体变量用来存放我们需要设置的信息,这里以虚拟鼠标为例,设置设备名称为"Test Mouse",连接方式选择USB模式,更多模式input.h文件中有定义,这里的厂商ID和产品ID我们设置为跟真实鼠标设备保持一致,版本号为1,因为鼠标中不存在ABS事件,所以对ABS的数据可以不进行设置。
3、设置其他相关信息
linux - Explain EV in /proc/bus/input/devices data - Unix & Linux Stack Exchange
这里有对设备信息在/proc/bus/input/devices中的解释,我们要设置一些PHYS:
/*根据cat /proc/bus/input/devices设置一些相关信息*/
std::string phys = "This is a mouse";
if (ioctl(fd, UI_SET_PHYS, phys.c_str()) < 0) { // 设置PHYS
printf("Fail to set PHYS! %s", __func__);
return -1; // 设置PHYS失败.
}
这是系统层次结构中设备的物理路径,经过查看,每个设备对应的都是不同的,即使是两个相同的设备这里也是有区分的,所以如果在生成多个同样类型的虚拟设备的时候需要对这个进行区分,在这里我们随便对他进行设置一下(因为我感觉这个不是很重要)。
4、设置事件及对应的键值
/*设置虚拟鼠标包含的事件类型*/
const std::vector<uint32_t> eventType{ EV_KEY, EV_REL, EV_MSC };
for (auto it : eventType) {
if (ioctl(fd, UI_SET_EVBIT, it) < 0) { // 循环设置事件类型
printf("%s ioctl failed", __func__); // 设置事件类型失败
return -1;
}
}
/*设置虚拟鼠标包含的EV_KEY事件*/
const std::vector<uint32_t> keys{
BTN_LEFT, BTN_RIGHT, BTN_MIDDLE, BTN_SIDE, BTN_EXTRA, BTN_FORWARD, BTN_BACK, BTN_TASK
};
for (auto it : keys) {
if (ioctl(fd, UI_SET_KEYBIT, it) < 0) { // 循环设置KEY事件
printf("%s ioctl failed", __func__); // 设置KEY事件失败
return -1;
}
}
/*设置虚拟鼠标包含的REL事件*/
const std::vector<uint32_t> rels{ REL_X, REL_Y, REL_WHEEL, REL_WHEEL_HI_RES };
for (auto it : rels) {
if (ioctl(fd, UI_SET_RELBIT, it) < 0) { // 循环设置REL事件
printf("%s ioctl failed", __func__); // 设置REL事件失败
return -1;
}
}
/*设置虚拟鼠标包含的MSC事件*/
if (ioctl(fd, UI_SET_MSCBIT, MSC_SCAN) < 0) {
printf("%s ioctl failed", __func__); // 设置MSC事件失败
return -1;
}
一般情况下一个设备会有多种事件类型以及一种事件会有多个键值,首先用ioctl()函数对事件类型进行设置,在我们的虚拟鼠标中存在EV_KEY, EV_REL, EV_MSC三种事件,然后再分别对三种事件设置对应的键值,这里应该很好理解,最终的成品就是我们在libinput record中查到的一些键值属性。
5、数据写入文件句柄
在第二步的时候我们将设备的一些基本信息存储到了结构体当中,这里我们同样需要将信息写入文件句柄当中,这里我们用到write()函数:
/*将设备信息写入到文件标识符fd中*/
if (write(fd, &dev, sizeof(dev)) < 0) {
printf("Unable to set input device info: %s", __func__); // 设置设备信息失败
return -1;
}
6、创建设备
完成了对设备基本信息以及事件键值的写入后,最后就要创建设备:
/*创建设备*/
if (ioctl(fd, UI_DEV_CREATE) < 0) {
printf("Unable to create input device : %s", __func__);
return -1;
}
while (true) {
usleep(1500000); // 加入睡眠、使进程保持运行
}
return 0;
通过ioctl()创建出设备,当程序运行时,如果创建完成后程序结束,那创建的设备也会随着程序的结束而销毁,所以这里需要让程序保持运行,才能保证设备一直存在。
7、销毁设备
在这个程序中可以不用到,但是这里写一下代码:
if (fd_ >= 0) {
ioctl(fd_, UI_DEV_DESTROY);
close(fd_);
fd_ = -1;
}
同样是通过ioctl()函数来进行控制。
8、编译执行
root@ubuntu:~/projects/ConsoleApplication1/bin/x64/Debug# ll
总用量 124
drwxr-xr-x 2 root root 4096 Nov 16 06:26 ./
drwxr-xr-x 3 root root 4096 Nov 16 06:26 ../
-rwxr-xr-x 1 root root 114976 Nov 16 06:26 CreateVirtualMouse.out*
root@ubuntu:~/projects/ConsoleApplication1/bin/x64/Debug# ./CreateVirtualMouse.out &
[1] 57873
root@ubuntu:~/projects/ConsoleApplication1/bin/x64/Debug#
这里我们将编译好的可执行文件后台运行;再次查看设备信息:
root@ubuntu:~/projects/ConsoleApplication1/bin/x64/Debug# cat /proc/bus/input/devices
#
#这里就不展示event0-4了
#
I: Bus=0003 Vendor=093a Product=2510 Version=0001
N: Name="Test Mouse"
P: Phys=This is a mouse
S: Sysfs=/devices/virtual/input/input50
U: Uniq=
H: Handlers=mouse3 event5
B: PROP=0
B: EV=17
B: KEY=ff0000 0 0 0 0
B: REL=903
B: MSC=10
root@ubuntu:~/projects/ConsoleApplication1/bin/x64/Debug#
root@ubuntu:~/projects/ConsoleApplication1/bin/x64/Debug# libinput record
Available devices:
/dev/input/event0: Power Button
/dev/input/event1: AT Translated Set 2 keyboard
/dev/input/event2: VirtualPS/2 VMware VMMouse
/dev/input/event3: VirtualPS/2 VMware VMMouse
/dev/input/event4: VMware VMware Virtual USB Mouse
/dev/input/event5: Test Mouse
Select the device event number: 5
Recording to 'stdout'.
version: 1
ndevices: 1
libinput:
version: "1.15.5"
git: "unknown"
system:
os: "ubuntu:20.04"
kernel: "5.11.0-40-generic"
dmi: "dmi:bvnPhoenixTechnologiesLTD:bvr6.00:bd11/12/2020:br4.6:efr0.0:svnVMware,Inc.:pnVMwareVirtualPlatform:pvrNone:sku:rvnIntelCorporation:rn440BXDesktopReferencePlatform:rvrNone:cvnNoEnclosure:ct1:cvrN/A:"
devices:
- node: /dev/input/event5
evdev:
# Name: Test Mouse
# ID: bus 0x3 vendor 0x93a product 0x2510 version 0x1
# Supported Events:
# Event type 0 (EV_SYN)
# Event type 1 (EV_KEY)
# Event code 272 (BTN_LEFT)
# Event code 273 (BTN_RIGHT)
# Event code 274 (BTN_MIDDLE)
# Event code 275 (BTN_SIDE)
# Event code 276 (BTN_EXTRA)
# Event code 277 (BTN_FORWARD)
# Event code 278 (BTN_BACK)
# Event code 279 (BTN_TASK)
# Event type 2 (EV_REL)
# Event code 0 (REL_X)
# Event code 1 (REL_Y)
# Event code 8 (REL_WHEEL)
# Event code 11 (REL_WHEEL_HI_RES)
# Event type 4 (EV_MSC)
# Event code 4 (MSC_SCAN)
# Properties:
name: "Test Mouse"
id: [3, 2362, 9488, 1]
codes:
0: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] # EV_SYN
1: [272, 273, 274, 275, 276, 277, 278, 279] # EV_KEY
2: [0, 1, 8, 11] # EV_REL
4: [4] # EV_MSC
properties: []
udev:
properties:
- ID_INPUT=1
- ID_INPUT_MOUSE=1
- LIBINPUT_DEVICE_GROUP=3/93a/2510:This is a mouse
quirks:
events:
可以看到在上面设置的东西在这里都体现出来了,键值也都设置成功,已经可以进行使用了。
简单的虚拟设备已经创建成功,本文内容基本都是自己总结出来了,希望大佬们指出不足之处。
PS:
uinput创建其他设备可查看这里: