加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

使用UInput模拟系统输入设备--键盘,鼠标,触摸屏

(2010-09-07 10:42:16)
标签:

uinput

linux

分类: linux

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

 

在最近的项目中,Sam需要将设备数据解析后以系统键盘鼠标的消息发送出去。选用UInput(关于UInput Driver以及编译,( http://blog.sina.com.cn/s/blog_602f87700100liyk.html)。

 

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

 

1. 打开UInput Device:

应用程序:

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

open(dev, O_WRONLY | O_NDELAY);

 

此时,在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; 

file->private_data = newdev;

 

 

2. 设置UInput Device:

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 是个input_dev 类型数据。 此时,它未初始化。

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

 

具体到CMD=UI_SET_EVBIT

uinput_set_bit(arg, evbit, EV_MAX);

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

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

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

 

注意:此处input device的evbit:

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

#define EV_SYN 0x00  同步事件

#define EV_KEY 0x01  按键事件

#define EV_REL 0x02  相对坐标事件(鼠标,轨迹球)

#define EV_ABS 0x03  绝对坐标事件 (触摸屏)

#define EV_MSC 0x04  杂项

#define EV_SW 0x05  开关状态

#define EV_LED 0x11  LED

#define EV_SND 0x12  声音事件

#define EV_REP 0x14  Repeat

#define EV_FF 0x15  力反馈

#define EV_PWR 0x16  电源事件

#define EV_FF_STATUS 0x17


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关心或者说会产生的消息。

根据上面2.1事件的不同,产生不同的消息。

每个event type都对应一组code. 例如:EV_KEY支持 键盘,鼠标左右键,中间键等。  EV_REL支持相对设备的移动.....

KEY_1  BTN_LEFT  REL_X REL_Y  ABS_X ABS_Y  ABS_MT_POSITION_X  ABS_MT_POSITION_Y ABS_MT_TOUCH_MAJOR ABS_MT_WIDTH_MAJOR

 

 

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;

 

 

5.创建Input Device:

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

ioctl(fd, UI_DEV_CREATE);

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

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

udev->state = UIST_CREATED;

 

6. 向Input Device发送Event:

struct input_event event = {0};
 gettimeofday(&event.time, NULL);

 event.type  = EV_KEY;
 event.code  = key;
 event.value = press ? 1:0;
 write(fd, &event, sizeof(event));

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

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.

 

 

总结:

使用UInput的步骤为:

1. 打开设备。

2. 使用ioctl() 配置设备。包括设置设备类型(rel = ioctl(fd, UI_SET_EVBIT, EV_KEY);),设置类型所支持的事件(rel = ioctl(fd, UI_SET_KEYBIT, BTN_FORWARD);)。

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

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

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

 

 

 

 

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 ffffffff ffffffff ffffffff ffffffff
B: REL=3
B: ABS=1000003

解释如下:

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

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

struct input_id {
 __u16 bustype;
 __u16 vendor;
 __u16 product;
 __u16 version;
};

 

EV=f

 



6. 触摸屏(ABS)相关信息:

单点触摸设备消息:

ABS_X, ABS_Y: 单点触摸点坐标。

ABS_PRESSURE: 触摸的压力值。

ABS_TOOL_WIDTH:触摸工具的宽度。


时间出发序列:

EV_KEY(BTN_TOUCH)

ABS_X

ABS_Y

ABS_PRESSURE

ABS_TOOL_WIDTH

SYM_REPORT




多点触摸设备消息:

ABS_MT_POSITION_X, ABS_MT_POSITION_Y :触摸面形心坐标。

手指按在触摸屏上时,接触面通常是个椭圆。此时,透过触摸屏,可以看到两个椭圆形区域,一个是手指本身靠在触摸屏上的区域,一个是手指与触摸屏紧密接触的区域。

ABS_MT_TOUCH_MAJOR:手指与触摸屏接触的区域(椭圆)的长直径。

ABS_MT_TOUCH_MINOR:手指与触摸屏接触的区域(椭圆)的短直径。

ABS_MT_WIDTH_MAJOR:手指本身的区域(椭圆)的长直径

ABS_MT_WIDTH_MINOR:手指本身的区域(椭圆)的短直径


ABS_MT_TOUCH_MAJOR肯定小于ABS_MT_WIDTH_MAJOR. 他们的比值,可以看成压力大小。

ABS_MT_PRESSURE:压力大小。



memset(&uinput_data_mt, 0, sizeof(uinput_data_mt));

strncpy(uinput_data_mt.name, "Uinput_MT", UINPUT_MAX_NAME_SIZE);

uinput_data_mt.id.version = 4;

uinput_data_mt.id.bustype = BUS_USB;

uinput_data_mt.absmin[ABS_MT_SLOT] = 0;

uinput_data_mt.absmax[ABS_MT_SLOT] = 9; // MT代表multi touch 多指触摸 最大手指的数量我们设置9

uinput_data_mt.absmin[ABS_MT_TOUCH_MAJOR] = 0;

uinput_data_mt.absmax[ABS_MT_TOUCH_MAJOR] = 15;

uinput_data_mt.absmin[ABS_MT_POSITION_X] = 0; // 屏幕最小的X尺寸

uinput_data_mt.absmax[ABS_MT_POSITION_X] = 1024; // 屏幕最大的X尺寸

uinput_data_mt.absmin[ABS_MT_POSITION_Y] = 0; // 屏幕最小的Y尺寸

uinput_data_mt.absmax[ABS_MT_POSITION_Y] = 1024; //屏幕最大的Y尺寸

uinput_data_mt.absmin[ABS_MT_TRACKING_ID] = 0;

uinput_data_mt.absmax[ABS_MT_TRACKING_ID] = 65535;//按键码ID累计叠加最大值

uinput_data_mt.absmin[ABS_MT_PRESSURE] = 0;   

uinput_data_mt.absmax[ABS_MT_PRESSURE] = 255;     //屏幕按下的压力值



多点触摸的设备的事件发生协议:

分A/B两种协议。


A协议序列中点单:

ABS_MT_TRACKING_ID

ABS_MT_TOUCH_MAJOR

ABS_MT_PRESSURE

ABS_MT_POSSITION_X

ABS_MT_POSSITION_Y

SYN_REPORT



后记:

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

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


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

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


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

然后才是创建Device.


 附录:

创建出的Input Device和发送消息的对应:

在Android下,有个getevent命令。可以看input device信息:

getevent -p

getevent -l /dev/input/event0


struct input_event {

 struct timeval time;


 __u16 type;

 __u16 code;

 __s32 value;

};

其中:type:就是EV_SYNC, EV_KEY, EV_REL等。

code: type中包含的event.

 KEY_1  BTN_LEFT  REL_X REL_Y  ABS_X ABS_Y  ABS_MT_POSITION_X  ABS_MT_POSITION_Y ABS_MT_TOUCH_MAJOR ABS_MT_WIDTH_MAJOR


value: event的值。如按下,抬起, X大小。



0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有