目录
六、编写代码之读取 struct input_event 数据
一、输入设备
输入设备其实就是能够产生输入事件的 设备就称为输入设备,常见的输入设备包括鼠标、键盘、触摸屏、按钮等等,它们都能够产生输入事件,产 生输入数据给计算机系统。 对于输入设备的应用编程其主要是获取输入设备上报的数据、输入设备当前状态等,譬如获取触摸屏当 前触摸点的 X、Y 轴位置信息以及触摸屏当前处于按下还是松开状态
二、input 子系统
Linux 系统为了统一管理这些输入设备,实现了一套能够兼容所有输入设备的框架,那么这个框架就 是 input 子系统。基于 input 子系统开发的输入设备的驱动程序,input 子系统可以屏蔽硬件的差 异,向应用层提供一套统一的接口,方便开发。
基于 input 子系统注册成功的输入设备,都会在/dev/input 目录下生成对应的设备节点(设备文件),设 备节点名称通常为 eventX(X 表示一个数字编号 0、1、2、3 等),譬如/dev/input/event0、/dev/input/event1、 /dev/input/event2 等,通过读取这些设备节点可以获取输入设备上报的数据。
三、读取数据的流程
假如要读取触摸屏的数据,假设触摸屏设备对应的设备节点为/dev/input/event0,那么数据读取流程如下:
①、应用程序打开/dev/input/event0 设备文件;
②、应用程序发起读操作(譬如调用 read),如果当前并没有去触碰触摸屏是没有数据可读,则会进入休眠(阻塞 I/O 情况下);
③、 用手指触摸触摸屏或者在屏上滑动时,此时就会产生触摸数据,当有数据可读时,应用程序会被唤醒,读操作获取到数据返回;
④、应用程序对读取到的数据进行解析。
对于其它输入设备亦是如此,无数据可读时应用程序会进入休眠状态(阻塞式 I/O 方式下), 当有数据可读时才会被唤醒。
四、解析数据
每一次 read 操作获取的都是一个 struct input_event 结构体类型数据, 该结构体定义在<linux/input.h>头文件中,它的定义如下:
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
time 成员变量是一个 struct timeval 类型的变量, 内核会记录每个上报的事件其发生的时间,并通过变量 time 返回给应用程序,struct timeval 结构体如下
struct timeval {
long tv_sec; /* 秒 *
long tv_usec; /* 微秒 */ };
type: type 用于描述发生了哪一种类型的事件(对事件的分类),分类宏定义也是在<linux/input.h>头文件中,下面列出其中几种
#define EV_SYN 0x00 //同步类事件,用于同步事件
#define EV_KEY 0x01 //按键类事件
#define EV_REL 0x02 //相对位移类事件(譬如鼠标)
#define EV_ABS 0x03 //绝对位移类事件(譬如触摸屏)
#define EV_MSC 0x04 //其它杂类事件
一种输入设备通常可以产生多种不同类型的事件,譬如点击鼠标按键(左键、右键,或鼠标上的其它按键)时会上报按键类事件,移动鼠标时则会上报相对位移类事件。
code: code 表示该类事件中的哪一个具体事件, 以上列举的每一种事件类型中都包含了一系列具体事件, 譬如一个键盘上通常有很多按键, 譬如字母 A、 B、 C、 D 或者数字 1、 2、 3、 4 等, 而 code变量则告知应用程序是哪一个按键发生了输入事件
具体事件
下面仅是列出几个常见的,可以自行浏览<linux/input.h>头文件,这些宏其实是定义在
input-event-codes.h 头文件中,该头文件被<linux/input.h>所包含了
①按键类事件
#define KEY_RESERVED 0
#define KEY_ESC 1 //ESC 键
#define KEY_1 2 //数字 1 键
#define KEY_2 3 //数字 2 键
#define KEY_TAB 15 //TAB 键
#define KEY_Q 16 //字母 Q 键
#define KEY_W 17 //字母 W 键
.....
②相对位移事件
#define REL_X 0x00 //X 轴
#define REL_Y 0x01 //Y 轴
#define REL_Z 0x02 //Z 轴
#define REL_MISC 0x09
#define REL_MAX 0x0f
#define REL_CNT (REL_MAX+1)
.....
③绝对位移事件
触摸屏设备是一种绝对位移设备,它能够产生绝对位移事件; 譬如对于触摸屏来说,一个触摸点所包含的信息可能有多种,譬如触摸点的 X 轴坐标、 Y 轴坐标、 Z 轴坐标、按压力大小以及接触面积等, 所以 code变量告知应用程序当前上报的是触摸点的哪一种信息(X 坐标还是 Y 坐标、亦或者其它)
#define ABS_X 0x00 //X 轴
#define ABS_Y 0x01 //Y 轴
#define ABS_Z 0x02 //Z 轴
#define ABS_PRESSURE 0x18
#define ABS_DISTANCE 0x19
#define ABS_TILT_X 0x1a
#define ABS_TILT_Y 0x1b
....
value: 内核每次上报事件都会向应用层发送一个数据 value, 对 value 值的解释随着 code 的变化而变化。
譬如对于按键事件(type参数=1) 来说, 假如code的值=2(2代表是键盘上的数字键 1,也就是 KEY_1), 那么如果 value 等于 1,则表示 KEY_1 键按下; value 等于 0 表示 KEY_1 键松开,如果 value 等于 2则表示 KEY_1 键长按
再比如, 在绝对位移事件中(type参数=3),如果 code=0 (触摸点 X 坐标 ABS_X),那么 value 值就等于触摸点的 X 轴坐标值; 同理, 如果 code=1(触摸点 Y 坐标 ABS_Y),此时
value 值便等于触摸点的 Y 轴坐标值; 所以对 value 值的解释需要根据不同的 code 值而定
五、数据同步
上面类型中有同步事件类型 EV_SYN,同步事件用于实现同步操作、告知接收者本轮上报的数据已经完整。应用程序读取输入设备上报的数据时,一次 read 操作只能读取一个 struct input_event 类型数据
譬如对于触摸屏来说,一个触摸点的信息包含了 X 坐标、 Y 坐标以及其它信息, 对于这样情况,应用程序需要执行多次 read 操作才能把一个触摸点的信息全部读取出来, 这样才能得到触摸点的完整信息。
内核将本轮需要上报、发送给接收者的数据全部上报完毕后,接着会上报一个同步事件,以告知应用程序本轮数据已经完整、 可以进行同步了。
同步类事件中也包含了多种不同的事件,所有的输入设备都需要上报同步事件, 上报的同步事件通常是 SYN_REPORT, 而 value 值通常为 0
六、编写代码之读取 struct input_event 数据
对输入设备调用 read()会读取到一个 struct input_event 类型数据,下面利用按下按键来看一下获取到的数据
13行,先定义一个 struct input_event 类型并初始化成员值为0,16行判断传参是否正确,21行打开传递进来的文件路径,然后一直循环读取,28行,将读取到的数据存在结构体中并判断读取的数据量是否相等,最后打印成员的值
程序中使用了阻塞式 I/O 方式读取设备文件,所以当无数据可读时 read 调用会被阻塞,知道有数据可读时才会被唤醒。设备文件不同于普通文件,读写设备文件之前无需设置读写位置偏移量
代码如下
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <poll.h>
#include <linux/input.h>
int main(int argc,char *argv[])
{
struct input_event in_ev={0};
int fd = -1;
if(argc != 2)
{
fprintf(stderr,"usage:%s <input-dev>\r\n",argv[0]);
exit(-1);
}
if((fd = open(argv[1],O_RDONLY))<0)
{
perror("open error");
exit(-1);
}
for(;;)
{
if(sizeof(struct input_event) !=
read(fd,&in_ev,sizeof(struct input_event)))
{
perror("read error");
exit(-1);
}
printf("type:%d code:%d value:%d\n",
in_ev.type,in_ev.code,in_ev.value);
}
}
验证
在开发板上通过查看/proc/bus/input/devices 文件可以得知系统中注册的所有输入设备相关的信息,如下
根据自己开发板的实际情况选择设备测试,N后面的表示名字,H后面表示设备节点文件,比如这里使用的按键,通过看名字可以知道对应就是gpio_keys,那么H就是event1文件,驱动基于 input 子系统而实现的, 在/dev/input 目录下存在 KEY0 的设备节点,具体设备是event1,如下
把文件从ubantu传到开发板,从上面得知按键设备节点路径就是 /dev/input/event1,测试1如下
运行app时,把设备节点路径作为传参,接着按一下按键,就能看到结构体对应的数据打印出来了
现象解释
根据前面的介绍,第一行中 type 等于 1,表示上报的是按键事件 EV_KEY, code=114, 打开 input-event-codes.h 头文件进行查找,可以发现 cpde=114 对应的是键盘上的 KEY_VOLUMEDOWN 按键,如下,这个是开发板出厂系统配置好的
value=1 表示按键按下,所以整个第一行的意思就是按键 KEY_VOLUMEDOWN被按下。
第二行, 表示上报了 EV_SYN 同步类事件(type=0)中的 SYN_REPORT 事件(code=0), 表示本轮数据已经完整、报告同步
第三行, type 等于 1,表示按键类事件, code 等于 114、而value 等于 0,所以表示按键 KEY_VOLUMEDOWN被松开
第四行上报同步事件
所以整个上面 4 行的打印信息就是开发板上的 KEY0 按键被按下以及松开这个过程, 内核所上报的事件以及发送给应用层的数据 value
试试长按按键 KEY0, 按住不放, 如下所示:
可以看到上报按键事件时,对应的 value 等于 2,表示长按状态
七、编写按键APP
编写一个获取按键状态的APP, 判断按键当前是按下、松开或长按状态。
从上面可知,对于按键来说,如果是按下,则上报 KEY 事件时, value=1;如果是松开,则 value=0;如果是长按,则 value=2,代码和上面的差不多,简单修改一下
35行判断具体事件是不是按键事件EV_KEY,然后37行根据value值来打印具体按键状态
测试如下
除了测试开发板的按键,也可以用开发板接上一个键盘,驱动会自动设别到键盘的,然后查看该键盘对应的设备节点,再用app测试即可,和上面操作同理