使用UInput模拟系统键盘鼠标动作 UInput driver分析

本文深入探讨了UInput驱动的实现机制与使用方法,包括打开设备、设置设备、创建InputDevice以及向设备发送事件的过程。并通过实例展示了如何在用户态程序中模拟系统级别的键盘和鼠标事件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当uinput driver已经insmod, 且node 已经建立后。即可使用它们传递系统输入设备消息。

<wbr></wbr>

1. 打开UInputDevice:

应用程序:

dev 为 UInput Node名:通常为/dev/uinput。

open(dev, O_WRONLY | O_NDELAY);

<wbr></wbr>

此时,在Kernel 层,对应的动作为:

static int uinput_open(struct inode *inode, struct file*file)

参数inode对应的是 主设备为10,子设备为223的node(即位用户态的dev)

参数file对应打开的文件。

动作:

创建了newdev-- uinput_device结构。

newdev->state =UIST_NEW_DEVICE;<wbr></wbr>

file->private_data = newdev;

<wbr></wbr>

<wbr></wbr>

2. 设置UInputDevice:

ioctl(fd, UI_SET_EVBIT, EV_KEY);

此时,在Kernel 层,对应的动作为:

static long uinput_ioctl(struct file *file, unsigned int cmd,unsigned long arg)

参数file对应打开的文件。

参数cmd 对应用户态ioctl参数2。UI_SET_EVBIT

参数arg对应用户态ioctl参数3。EV_KEY

动作:

2.1 将driver参数传递过来。

udev = file->private_data;

udev->dev<wbr>是个<strong>input_dev</strong>类型数据。<wbr>此时,它未初始化。</wbr></wbr>

如果udev->dev为空,则使用uinput_allocate_device(udev);申请input_dev结构

<wbr></wbr>

具体到CMD=UI_SET_EVBIT

uinput_set_bit(arg, evbit, EV_MAX);

首先判断newdev->state为UIST_CREATED,则返回错误码。

这就说明:设置bit,需要在create input device之前。

具体动作为:udev->dev->evbit设为EV_KEY.

<wbr></wbr>

注意:此处input device的evbit:

一个是evbit.表示设备所支持的动作.

#defineEV_KEY<wbr><wbr><wbr>0x01<wbr>// 按键<br> #defineEV_REL<wbr><wbr><wbr>0x02<wbr>// 释放</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr></wbr>

3.继续设置 Device:

ret = ioctl(fd, UI_SET_RELBIT, REL_X); //鼠标

ret = ioctl(fd, UI_SET_RELBIT, REL_Y);

ret = ioctl(fd, UI_SET_EVBIT, EV_ABS);
ret = ioctl(fd, UI_SET_ABSBIT, ABS_X);
ret = ioctl(fd, UI_SET_ABSBIT, ABS_Y);
ret = ioctl(fd, UI_SET_ABSBIT, ABS_PRESSURE);

同上。设置了Keybit等。

这里就是设置了Input Device关心或者说会产生的消息。

<wbr></wbr>

<wbr></wbr>

4. 写入设备:

struct uinput_user_dev uinput;

uinput.id.version = 4;
uinput.id.bustype = BUS_USB;
uinput.absmin[ABS_X] = 0;
uinput.absmax[ABS_X] = 65535; //sam 把屏幕设为0-65535
uinput.absmin[ABS_Y] = 0;
uinput.absmax[ABS_Y] = 65535;
uinput.absmin[ABS_PRESSURE] = 0;
uinput.absmax[ABS_PRESSURE] = 0xfff;

ret = write(fd, &uinput, sizeof(uinput));

此时,在Kernel 层,对应的动作为:

此时Device status为UIST_NEW_DEVICE

并将udev->dev 这个input device 具体化。初始化该input_dev。

之后,改变状态:

udev->state =UIST_SETUP_COMPLETE;

<wbr></wbr>

<wbr></wbr>

5.创建InputDevice:

注意,此处是创建了Input Device。而不是UInput Device。

ioctl(fd, UI_DEV_CREATE);

<wbr><strong>此时,在Kernel 层,对应的动作为:</strong></wbr>

input_register_device(udev->dev);//向子系统注册该设备,之后中断时input_event()向子系统报告事件

udev->state = UIST_CREATED;

<wbr></wbr>

6. 向InputDevice发送Event:

struct input_event event = {0};
<wbr>gettimeofday(&amp;event.time,NULL);</wbr>

<wbr>event.type<wbr>= EV_KEY;<br><wbr>event.code<wbr>= key;<br><wbr>event.value = press ? 1:0;<br><wbr>write(fd, &amp;event,sizeof(event));</wbr></wbr></wbr></wbr></wbr></wbr>

<wbr><strong>此时,在Kernel 层,对应的动作为:</strong></wbr>

static ssize_t uinput_write(struct file *file, const char __user*buffer, size_t count, loff_t *ppos)

因为此时state为UIST_CREATED

input_event(udev->dev, ev.type, ev.code,ev.value);

发送event.

<wbr></wbr>

<wbr></wbr>

总结:

使用UInput的步骤为:

1. 打开设备。

2. 使用ioctl() 配置设备。

3. 使用write() 将input device信息设置好。

4. 使用ioctl(UI_DEV_CREATE)创建Input Device。(即使用write设置的)

5. 再使用write() 写入event.

<wbr></wbr>

<wbr></wbr>

<wbr></wbr>

<wbr></wbr>

UInput添加的Input Device在/proc的反应:

#cat /proc/bus/input/device

I: Bus=0003 Vendor=0000 Product=0000 Version=0004
N: Name="uinput"
P: Phys=
S: Sysfs=/class/input/input6
H: Handlers=event1 mouse1
B: EV=f
B: KEY=400 0 670000 ffff ffffffff ffffffff ffffffff ffffffffffffffff ffffffff ffffffff
B: REL=3
B: ABS=1000003

解释如下:

Bus=0003 Vendor=0000 Product=0000Version=0004
这是在第一次write时设置的:

uinp.id.version = 4;
uinp.id.bustype = BUS_USB;

struct input_id {
<wbr>__u16 bustype;<br><wbr>__u16 vendor;<br><wbr>__u16 product;<br><wbr>__u16 version;<br> };</wbr></wbr></wbr></wbr>

<wbr></wbr>

EV=f

<wbr></wbr>


后记:

后来的工作中,Sam又看到Hi3716C中,如何使用Driver将红外遥控器模拟成一个Keyboard.

http://blog.sina.com.cn/s/blog_602f877001019wtx.html


其实原理非常类似. 都需要指出支持什么Type的Event.

注3:不同类型的InputEvent:
#define EV_SYN<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr>0x00<wbr><wbr><wbr><wbr><wbr>表示设备支持所有的事件</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>
#define EV_KEY<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr>0x01<wbr><wbr><wbr><wbr><wbr>键盘或者按键,表示一个键码<wbr><wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>
#define EV_REL<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr>0x02<wbr><wbr><wbr><wbr><wbr>鼠标设备,表示一个相对的光标位置结果</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>
#define EV_ABS<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr>0x03<wbr><wbr><wbr><wbr><wbr>手写板产生的值,其是一个绝对整数值<wbr><wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>
#define EV_MSC<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr>0x04<wbr><wbr><wbr><wbr><wbr>其他类型<wbr><wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>
#define EV_LED<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr>0x11<wbr><wbr><wbr><wbr><wbr>LED灯设备</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>
#define EV_SND<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr>0x12<wbr><wbr><wbr><wbr><wbr>蜂鸣器,输入声音<wbr><wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>
#define EV_REP<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr>0x14<wbr><wbr><wbr><wbr><wbr>允许重复按键类型<wbr><wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>
#define EV_PWR<wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr><wbr>0x16<wbr><wbr><wbr><wbr><wbr>电源管理事件<wbr><wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)


也要指出每种Type的Event中又分别支持什么具体值.

然后才是创建Device.


作者:Sam (甄峰)sam_code@hotmail.com

<wbr></wbr>

Sam在开发中需要在用户态程序中模拟系统Keyboard,Mouse event. 开始想用之前在2.4Kernel中开发的一个Virtual InputDriver,在改写为2.6Kernel模式后,连续尝试了2个嵌入式平台,发现竟然都缺乏必要的内核符号。突然想起之前在哪看到过InputUser level driver这样的东西。赶快问了问Google老师。发现它就是UInput。(发现TCC8900所使用的IRRemote Controller Driver与Sam很多年前在S3C2440A上开发的IR Remote ControllerDriver做法几乎完全一致,嘿嘿)

<wbr></wbr>

赶快以Module形式编译uinput.

先察看drivers/input/misc/Kconfig,看到INPUT_UINPUT(User level driversupport)前提为INPUT_MISC。

于是在make menuconfig中

Device Drivers<wbr>---&gt;</wbr>

Input device support<wbr>---&gt;</wbr>

[*]<wbr><wbr>Miscellaneousdevices<wbr>---&gt;</wbr></wbr></wbr>

<M><wbr><wbr>User level driver support<wbr></wbr></wbr></wbr>

呵呵,现在才理解misc是各种其它非标准设备的意思。

#make

将其编译为uinput.ko

<wbr></wbr>

读uinput.c之前,最好先读 driver/char/misc.c

<wbr></wbr>

解析misc.c

<wbr></wbr>

1. init函数

subsys_initcall(misc_init);
insmod misc时,会调用misc_init()

misc_init()使用比较老的方式创建字符设备driver:

register_chrdev(MISC_MAJOR,"misc",&misc_fops)

主设备号为:MISC_JAJOR=10

driver名字为 misc,<wbr>在/proc/device中出现。</wbr>

driver处理程序为misc_fops.

<wbr></wbr>

2. driver处理程序:

static const struct file_operations misc_fops = {
<wbr>.owner<wbr><wbr>=THIS_MODULE,<br><wbr>.open<wbr><wbr>=misc_open,<br> };<br> 也就是说:它只赋值了open--misc_open()</wbr></wbr></wbr></wbr></wbr></wbr>

换句话说,当用户在使用系统调用open主设备号为10的device时,kernel会最终调用misc_open().

<wbr></wbr>

misc_open():

它首先察看全局链表misc_list成员中是否有次设备号与open()参数inode的次设备号相同。

如果有,则将链表中c->fops取出,并用它取代参数2 file中的fops.

并调用c->fops->open(inode, file)

注意:这里非常关键,因为misc是一系列driver的组合。各个driver所做工作完全不同,他们之间使用次设备号区分。

但Kernel只关心主设备号,也就是说,它只会把主设备号对应的file给对应出来。所以在open()时,需要使用次设备号作标记,替换对应driver的file(open()的参数2)的fops.

<wbr></wbr>

3.int misc_register(structmiscdevice * misc)

查全局链表misc_list内容。如果需要注册的参数misc的子设备号在链表misc_list中已经存在。则返回busy.

<wbr></wbr>

如果注册的参数misc的子设备号为MISC_DYNAMIC_MINOR(255,动态子设备号)。则从misc全局子设备号(misc_minors)中取出一个未用的来使用。并将misc_minors中对应子设备号标记为已用。

<wbr></wbr>

建立dev_t (主,次设备号)

<wbr></wbr>

并经参数misc加入到misc_list中去。<wbr></wbr>

<wbr></wbr>

这样,就很清楚misc的处理了。先使用misc_register()注册一个次设备号对应driver.并将其放入misc_list链表。当用户使用系统调用open主设备号为10的device时,则在misc_list寻找device(也就是node)对应的次设备号fops对应file的对应fops.

<wbr></wbr>

<wbr></wbr>

<wbr></wbr>

<wbr></wbr>

<wbr><strong><span style="font-size:24px">解析uinput.c</span></strong></wbr>

<wbr></wbr>

1. init函数

<wbr>module_init(uinput_init);<br> insmod uinput时,会调用uinput_init()</wbr>

它调用misc_register(&uinput_misc);

请注意参数uinput_misc。

static struct miscdevice uinput_misc = {
<wbr>.fops<wbr><wbr>=&amp;uinput_fops,<br><wbr>.minor<wbr><wbr>=UINPUT_MINOR, (223)<br><wbr>.name<wbr><wbr>=UINPUT_NAME,<wbr>(uinput)<br> };</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

注册后,当用户使用系统调用open主设备号为10,次设备号为223的node时,则使用uinput_fops取代file->fops.

且同时调用fops->open()

<wbr></wbr>

2. open函数:

创建结构体uinput_device 实体,并将file->private_data指向该实体。

<wbr></wbr>

struct uinput_device {
<wbr>struct input_dev<wbr>*dev;<br><wbr>structmutex<wbr><wbr>mutex;<br><wbr>enum uinput_state<wbr>state;<br><wbr>wait_queue_head_t<wbr>waitq;<br><wbr>unsignedchar<wbr><wbr>ready;<br><wbr>unsignedchar<wbr><wbr>head;<br><wbr>unsignedchar<wbr><wbr>tail;<br><wbr>structinput_event<wbr>buff[UINPUT_BUFFER_SIZE];</wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr>

<wbr>structuinput_request<wbr>*requests[UINPUT_NUM_REQUESTS];<br><wbr>wait_queue_head_t<wbr>requests_waitq;<br><wbr>spinlock_t<wbr><wbr>requests_lock;<br> };</wbr></wbr></wbr></wbr></wbr></wbr></wbr>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值