一.输入设备介绍
输入设备其实就是能够产生输入事件的设备就称为输入设备,常见的输入设备包括鼠标、键盘、触摸屏、按钮等等,它们都能够产生输入事件,产生输入数据给计算机系统。
对于输入设备的应用编程其主要是获取输入设备上报的数据、输入设备当前状态等,譬如获取触摸屏当前触摸点的X、Y 轴位置信息以及触摸屏当前处于按下还是松开状态。
input 子系统:
输入设备种类非常多,每种设备上报的数据类型又不一样,那么Linux 系统如何管理呢?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 函数进行读取输入设备数据的读取,每一次 read 操作获取的都是 一个 struct input_event 结构体类型数据,struct input_event 是 Linux 内核中用于描述输入事 件的结构体类型。它定义在 头文件中,用于在内核和用户空间之间传递输入 事件的信息。
结构体 struct input_event 定义如下:
struct input_event {
struct timeval time;
__u16 type;// 类型
__u16 code;// 具体事件
__s32 value;// 对应的取值
};
type:type 用于描述发生了哪一种类型的事件(对事件的分类),Linux 系统所支持的输 入事件类型如下所示:
#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
#define EV_SND 0x12
#define EV_REP 0x14
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)
以上这些宏定义同样在头文件中,所以在应用程序中需要包含该头文件;一 种输入设备 通常可以产生多种不同类型的事件,譬如点击鼠标按键(左键、右键,或鼠标上 的其它按键)时会上报按键类事件,移动鼠标时则会上报相对位移类事件。
code:code 表示该类事件中的哪一个具体事件,以上列举的每一种事件类型中都包含了 一系列具体事件,譬如一个键盘上通常有很多按键,而 code 变量则告知应用程序是哪一个按 键发生了输入事件。每一种事件类型都包含多种不同的事件,以按键类事件为例,对应的具体 事件如下所示:
#define KEY_RESERVED 0
#define KEY_ESC 1//ESC 键
#define KEY_1 2//数字 1 键
#define KEY_2 3//数字 2 键
#define KEY_3 4//数字 3 键
#define KEY_4 5//数字 4 键
#define KEY_5 6//数字 5 键
#define KEY_6 7//数字 6 键
#define KEY_7 8//数字 7 键
#define KEY_8 9//数字 8 键
#define KEY_9 10 //数字 9 键
#define KEY_0 11 //数字 0 键
#define KEY_MINUS 12 //减号键
#define KEY_EQUAL 13 //加号键
#define KEY_BACKSPACE 14 //回退键
对于其他输入事件的 code 值,可以查看 input-event-codes.h 头文件(该头文件被 所包含)。
value:内核每次上报事件都会向应用层发送一个数据 value,对 value 值的解释随着 code 的变化而变化。譬如对于按键事件来说,如果 value 等于 1,则表示按键按下;value 等于 0 表示按键松开,如果 value 等于 2 则表示按键长按。而在绝对位移事件中(type=3),如果 code=0(触摸点 X 坐标 ABS_X),那么 value 值就等于触摸点的 X 轴坐标值;如果 code=1 (触摸点 Y 坐标 ABS_Y),此时 value 值便等于触摸点的 Y 轴坐标值。
按键应用编程:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <linux/input.h>
int main(int argc, char *argv[])
{
int fd, ret;
struct input_event in_ev = {0}; //初始化 input_event 结构体
fd = open(argv[1], O_RDONLY); //打开输入设备文件
if (fd < 0)
{ //文件打开失败
printf("文件打开失败\n");
return -1;
}
while (1)
{
ret = read(fd, &in_ev, sizeof(struct input_event)); //读取数据
if (ret < 0)
{ //读取失败
printf("读取数据失败\n");
}
// 打印读取到的事件信息
printf("type:%d code:%d value:%d\n", in_ev.type, in_ev.code, in_ev.value);
if (EV_KEY == in_ev.type)
{ //检测到按键事件
switch (in_ev.value)
{
case 0: //松开按键
printf("code<%d>: 松开", in_ev.code);
break;
case 1: //按下按键
printf("code<%d>: 按下", in_ev.code);
break;
case 2: //长按按键
printf("code<%d>: 长按", in_ev.code);
break;
}
// 根据按键码打印按键功能
switch (in_ev.code)
{
case KEY_MENU:
printf(": HOME键\n");
break;
case KEY_BACK:
printf(": BACK键\n");
break;
case KEY_VOLUMEUP:
printf(": 音量增加键\n");
break;
case KEY_VOLUMEDOWN:
printf(": 音量减小键\n");
break;
// 其他按键码对应的功能在这里添加
default:
printf(": 未知键\n");
break;
}
}
}
close(fd); //关闭输入设备文件
return 0;
}