输入设备应用编程
输入类设备编程介绍
什么是输入设备
- 输入设备(input 设备),如鼠标、键盘、触摸屏等,允许用户与系统交互
input 子系统
Linux系统通过input子系统管理多种输入设备
Input子系统提供统一的框架和接口,屏蔽硬件差异
注册成功的输入设备在/dev/input目录下生成eventX设备节点
通过读取这些设备节点获取输入设备的数据
读取数据的流程
应用程序打开对应的设备文件(如/dev/input/event0)
发起读操作,若无数据则进入休眠状态(阻塞I/O)
有数据时,应用程序被唤醒并读取数据
应用程序对获取的数据进行解析
无数据可读时,应用程序休眠,直到有触摸或其他输入产生数据,才被唤醒继续操作
应用程序如何解析数据
数据结构: 应用程序通过读取操作获取的数据是struct input_event结构体类型,该结构包含时间(time), 类型(type), 具体事件(code), 和值(value)
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};type
- 数据同步 - 同步事件的目的 - 同步事件(EV_SYN)用于告知应用程序,一个输入动作的所有数据已经完整上报 - 读取输入数据的挑战 - 由于一次read操作只能读取一个struct input_event,对于包含多个数据点的输入动作(如触摸屏操作),需要多次read才能获得完整信息 - 同步类事件中包含了多种不同的事件 - - 完成数据读取的标记 - 应用程序通过接收到的同步事件(通常是SYN_REPORT,value值为0)来识别一组数据是否完全接收,确保了数据的完整性和准确性 - 同步事件的普遍性 - 所有输入设备在上报完一组数据后,都需要上报一个同步事件,以确保数据的完整上报和正确处理
- 在<linux/input.h>头文件中
code
按键类事件
相对位移事件
绝对位移事件,触摸屏设备是一种绝对位移设备
<linux/input.h>头文件(定义在
input-event-codes.h 头文件中,该头文件被<linux/input.h>所包含了)
读取 struct input_event 数据
程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
int main(int argc, char *argv[])
{
struct input_event in_ev = {0};
int fd = -1;
/* 校验传参 */
if (2 != argc) {
fprintf(stderr, "usage: %s <input-dev>\n", argv[0]);
exit(-1);
}
/* 打开文件 */
if (0 > (fd = open(argv[1], O_RDONLY))) {
perror("open error");
exit(-1);
}
for ( ; ; ) {
/* 循环读取数据 */
//计算 struct input_event 结构体的大小(以字节为单位)
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);
}
}
1 开始:程序的入口点
2 检查命令行参数数量,如果不等于2,则打印用法信息并退出
3 尝试打开指定的输入设备文件,如果失败,则打印错误信息并退出
4 进入无限循环,尝试读取输入事件数据,如果读取失败,则打印错误信息并退出
- 如果读取成功,则打印事件的详细信息
5 循环回到步骤4,继续读取下一个事件
注意
阻塞式 I/O 使用:程序采用阻塞式 I/O 方式读取设备文件,意味着在没有数据可读时,read 调用会被暂停(阻塞)
唤醒机制:只有当设备文件有数据可读时,read 调用才会被唤醒并继续执行
设备文件特性:与普通文件不同,读写设备文件时不需要设置读写位置的偏移量
在开发板上验证
ALPHA 和 Mini 开发板上都有一个用户按键 KEY0,作为典型的输入设备
该按键为 GPIO 按键,驱动基于 input 子系统,在 /dev/input 目录下有设备节点
可通过 /proc/bus/input/devices 文件查看系统中所有输入设备的信息
- 查看/proc/bus/input/devices 文件
程序运行后,终端会打印按键按下和松开的信息
短按
短按按键 KEY0
第一行打印信息:按键 KEY_VOLUMEDOWN 被按下
type=1,表示上报的是按键事件 EV_KEY,
code=114,打开 input-event-codes.h 头文件进行查找,可以发现 code=114 对应的是键盘上的 KEY_VOLUMEDOWN 按键,这个是 ALPHA/Mini 开发板出厂系统已经配置好的,
value=1表示按键按下
第二行打印信息:上报 EV_SYN 同步事件(type=0)中的 SYN_REPORT 事件(code=0),表示本轮数据已经完整、报告同步
第三行打印信息:按键 KEY_VOLUMEDOWN 被松开(type=1, code=114, value=0)
第四行打印信息:再次上报同步事件
长按
长按按键 KEY0
长按按键 KEY0 时,终端会打印 value=2,表示长按状态
按键应用编程
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
int main(int argc, char *argv[])
{
struct input_event in_ev = {0};
int fd = -1;
int value = -1;
/* 校验传参 */
if (2 != argc) {
fprintf(stderr, "usage: %s <input-dev>\n", argv[0]);
exit(-1);
}
/* 打开文件 */
if (0 > (fd = open(argv[1], O_RDONLY))) {
perror("open error");
exit(-1);
}
for ( ; ; ) {
/* 循环读取数据 */
if (sizeof(struct input_event) !=
read(fd, &in_ev, sizeof(struct input_event))) {
perror("read error");
exit(-1);
}
if (EV_KEY == in_ev.type) { //检查读取的事件是否为按键事件(EV_KEY)
switch (in_ev.value) { //根据按键事件的值(in_ev.value),打印按键的状态
case 0:
printf("code<%d>: 松开\n", in_ev.code);
break;
case 1:
printf("code<%d>: 按下\n", in_ev.code);
break;
case 2:
printf("code<%d>: 长按\n", in_ev.code);
break;
}
}
}
}
程序
开始:程序的入口点
校验传参:检查命令行参数是否正确
打开文件:以只读模式打开输入设备文件
无限循环:进入无限循环,持续监听输入设备
读取数据:调用 read() 函数读取输入设备上报的数据
判断事件类型:检查读取到的事件类型
是按键事件?:判断事件类型是否为按键事件(EV_KEY)
判断按键状态:如果是按键事件,根据 value 值判断按键状态(松开、按下、长按)
打印按键状态:根据按键状态打印相应信息
继续循环:继续下一次循环
在开发板测试
测试 KEY0
- 按下 KEY0 或松开 KEY0 以及长按情况下,终端会打印出相应的信息
测试键盘上的按键
插入 USB 键盘终端打印信息
使用命令"cat /proc/bus/input/devices",在打印信息中找到键盘设备的信息
可以根据 code 值查询到对应的按键(通过 input-event-codes.h 头文件),如 code=30 对应的是键盘上的字母 A 键
触摸屏应用编程
解析触摸屏设备上报的数据
触摸屏设备是一个绝对位移设备,可以上报绝对位移事件,绝对位移事件如下:
单点触摸和多点触摸
触摸屏分为多点触摸设备和单点触摸设备
单点触摸设备只支持一个触摸点,每轮数据包含一个触摸点信息
- 上报事件为 ABS_XXX,包含 ABS_X(X 轴坐标),ABS_Y(Y 轴坐标),可能还包括 ABS_Z(Z 轴坐标)、ABS_PRESSURE(按压力大小)
多点触摸设备支持多个触摸点,每轮数据可能包含多个触摸点信息
- 上报事件为 ABS_MT_XXX,包含 ABS_MT_POSITION_X(X 轴坐标)、ABS_MT_POSITION_Y(Y 轴坐标)
触摸屏设备还可以上报按键类事件和同步类事件
同步事件告知应用层数据是否完整
按键类事件如 BTN_TOUCH(code=0x14a,330)用于描述按下和松开触摸屏,不支持长按状态(value 不会等于 2)
多点触摸设备只有第一个点按下时上报 BTN_TOUCH 事件(value=1),最后一个点离开时上报(value=0)
单点触摸设备–事件上报的顺序
BTN_TOUCH
ABS_X
ABS_Y
SYN_REPORT
滑动
ABS_X
ABS_Y
SYN_REPORT
松开
BTN_TOUCH
SYN_REPORT
- 点击触摸屏时,上报顺序为:BTN_TOUCH(value=1),ABS_X,ABS_Y,SYN_REPORT
- 滑动时,上报顺序为:ABS_X,ABS_Y,SYN_REPORT,不包括 BTN_TOUCH 事件
- 松开时,上报顺序为:BTN_TOUCH(value=0),SYN_REPORT
- 不同设备上报的信息量可能不同,但所有数据在 SYN_REPORT 同步事件之前上报给应用层
多点触摸设备–事件上报的顺序
多点触摸设备上报一轮完整数据中可能包含多个触摸点的信息
- 例如,5点触摸设备会更新5个触摸点的信息,并将其上报给应用层
在Linux内核中,多点触摸设备使用多点触摸(MT)协议上报触摸点数据
MT协议分为Type A和Type B两种类型
Type A协议使用较少,几乎被淘汰
重点介绍Type B协议
MT 协议之 Type B 协议
Type B协议适用于能够追踪并区分触摸点的设备,如开发板配套的触摸屏
通过ABS_MT_SLOT事件上报各个触摸点的更新信息,slot是触摸点的编号
- 每个触摸点分配一个slot,用于传递该触摸点的变化
Type B协议还使用ABS_MT_TRACKING_ID事件,用于触摸点的创建、替换和销毁
- ABS_MT_TRACKING_ID事件的value表示触摸点的ID,ID>=0表示有效触摸点,ID=-1表示触摸点被移除
Type B协议减少数据发送,只上报发生变更的数据
slot与ID
slot是硬件上的概念,ID是软件上的概念,用于区分触摸点
slot按时间顺序分配,ID用于区分触摸点的生命周期
Type B 协议下多点触摸设备上报数据的流程列举
ABS_MT_SLOT 0
ABS_MT_TRACKING_ID 10
ABS_MT_POSITION_X
ABS_MT_POSITION_Y
ABS_MT_SLOT 1
ABS_MT_TRACKING_ID 11
ABS_MT_POSITION_X
ABS_MT_POSITION_Y
SYN_REPORTABS_MT_SLOT 0:选择slot 0,表示当前更新的是触摸点0的数据
ABS_MT_TRACKING_ID 10:为slot 0分配一个新的ID为10的触摸点,表示这是一个新创建的触摸点
ABS_MT_POSITION_X:上报触摸点0的X轴坐标
ABS_MT_POSITION_Y:上报触摸点0的Y轴坐标
ABS_MT_SLOT 1:选择slot 1,表示当前更新的是触摸点1的数据
…
最后,通过SYN_REPORT事件通知应用层,当前这一轮触摸点数据的上报已经完成,所有信息已经同步到应用层
触摸屏上报数据分析
保证开发板上已经连接了 LCD 屏
使用命令"cat /proc/bus/input/devices",确定触摸屏对应的设备节点
一个手指点击触摸屏先不松开
第一行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_TRACKING_ID(code=57)事件,并且 value 值等于 78,也就是 ID,表示创建一个新的触摸点,手指按下
第二行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_POSITION_X(code=53)事件,其 value
对应的便是触摸点的 X 坐标;第三行上报了 ABS_MT_POSITION_Y(code=54)事件,其 value 值对应的便是触摸点 Y 坐标,所以由此可知该触摸点的坐标为(372, 381)第四行上报了按键类事件 EV_KEY(type=1)中的 BTN_TOUCH(code=330),value 值等于 1,表示
这是触摸屏上最先产生的触摸点(slot=0、也就是触摸点 0)第五行和第六行分别上报了绝对位移事件 EV_ABS(type=3)中的 ABS_X(code=0)和 ABS_Y(code=1),
其 value 分别对应的是触摸点的 X 坐标和 Y 坐标。多点触摸设备也会通过 ABS_X、ABS_Y 事件上报触摸点的 X、Y 坐标,但通常只有触摸点 0 支持,所以可以把多点触摸设备当成单点触摸设备来使用最后一行:上报 SYN_REPORT 事件,表示此次触摸点的信息上报完毕
在第一个触摸点的基础上,增加第二个触摸点
1~7 行不再解释,第八行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_SLOT 事件(code=47),
表示目前要更新 slot=1 所关联的触摸点(也就是触摸点 1)对应的信息第九行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_TRACKING_ID 事件(code=57),ID=79,
这是之前没有出现过的 ID,表示这是一个新的触摸点第十、十一行分别上报了 ABS_MT_POSITION_X 和 ABS_MT_POSITION_Y 事件
最后一行上报同步事件(type=0、code=0),告知应用层数据完整
当手指松开时,触摸点就会被销毁,上报 ABS_MT_TRACKING_ID 事件,并将 value 设置为-1(ID)
获取触摸屏的信息
ioctl()函数
获取触摸屏信息:通过 ioctl() 函数获取触摸屏支持的最大触摸点数、X、Y 坐标范围等信息
- ioctl()是一个文件 I/O 操作的杂物箱,可以处理的事情非常杂、不统一,一般用于操作特殊文件或设备文件
ioctl() 函数原型:
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, …);fd:文件描述符
request:请求指令,与操作对象相关
在 input.h 头文件有EVIOC 相关的宏定义
EVIOCG(get)开头的表示获取信息,EVIOCS(set)开头表示设置
EVIOCGABS 宏:用于获取触摸屏 slot 的取值范围
#define EVIOCGABS(abs) _IOR(‘E’, 0x40 + (abs), struct input_absinfo)
abs:表示 ABS_XXX 绝对位移事件
第三个参数为 struct input_absinfo *,获取的信息写入该结构体
- struct input_absinfo {
__s32 value; //最新的报告值
__s32 minimum; //最小值
__s32 maximum; //最大值
__s32 fuzz;
__s32 flat;
__s32 resolution;
};
- struct input_absinfo {
可变参函数,第三个参数根据 request 决定
譬如使用 EVIOCGNAME 宏获取设备名称
- char name[100];
ioctl(fd, EVIOCGNAME(sizeof(name)), name);
- char name[100];
获取触摸屏支持的最大触摸点数
- struct input_absinfo info;
int max_slots; //最大触摸点数
if (0 > ioctl(fd, EVIOCGABS(ABS_MT_SLOT), &info))
perror(“ioctl error”);
max_slots = info.maximum + 1 - info.minimum;
- struct input_absinfo info;
程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
int main(int argc, char *argv[])
{
struct input_absinfo info;
int fd = -1;
int max_slots;
/* 校验传参 */
if (2 != argc) {
fprintf(stderr, "usage: %s <input-dev>\n", argv[0]);
exit(EXIT_FAILURE);
}
/* 打开文件 */
if (0 > (fd = open(argv[1], O_RDONLY))) {
perror("open error");
exit(EXIT_FAILURE);
}
/* 获取slot信息 */
if (0 > ioctl(fd, EVIOCGABS(ABS_MT_SLOT), &info)) {
perror("ioctl error");
close(fd);
exit(EXIT_FAILURE);
}
max_slots = info.maximum + 1 - info.minimum;
printf("max_slots: %d\n", max_slots);
/* 关闭、退出 */
close(fd);
exit(EXIT_SUCCESS);
}
- 开始:程序的入口点
- 校验传参:检查命令行参数是否正确
- 打开文件:以只读模式打开输入设备文件
- 获取触摸屏支持的最大触摸点数
- 获取slot信息:使用 ioctl 函数获取设备的 ABS_MT_SLOT 信息,并检查是否成功
- 计算max_slots:计算最大 slot 数量
- 打印max_slots:打印最大 slot 数量
- 关闭文件:关闭文件描述符
- 退出程序
运行结果
- 这个屏是一个 5 点触摸屏
单点触摸应用程序
- 程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
int main(int argc, char *argv[])
{
struct input_event in_ev;
int x, y; //触摸点x和y坐标
int down; //用于记录BTN_TOUCH事件的value,1表示按下,0表示松开,-1表示移动
int valid; //用于记录数据是否有效(我们关注的信息发生更新表示有效,1表示有效,0表示无效)
int fd = -1;
/* 校验传参 */
if (2 != argc) {
fprintf(stderr, "usage: %s <input-dev>\n", argv[0]);
exit(EXIT_FAILURE);
}
/* 打开文件 */
if (0 > (fd = open(argv[1], O_RDONLY))) {
perror("open error");
exit(EXIT_FAILURE);
}
x = y = 0; //初始化x和y坐标值
down = -1; //初始化<移动>
valid = 0;//初始化<无效>
for ( ; ; ) {
/* 循环读取数据 */
if (sizeof(struct input_event) !=
read(fd, &in_ev, sizeof(struct input_event))) {
perror("read error");
exit(EXIT_FAILURE);
}
switch (in_ev.type) { // 判断事件类型
case EV_KEY: //按键事件
if (BTN_TOUCH == in_ev.code) { // 判断按键代码是否为 BTN_TOUCH触摸屏上的触摸事件
down = in_ev.value; // 更新 down 值,记录按下 (1) 或松开 (0)
valid = 1; // 标记数据有效
}
break;
case EV_ABS: //绝对位移事件
switch (in_ev.code) { // 判断事件代码
case ABS_X: //X坐标
x = in_ev.value;
valid = 1;
break;
case ABS_Y: //Y坐标
y = in_ev.value;
valid = 1;
break;
}
break;
case EV_SYN: //同步事件
if (SYN_REPORT == in_ev.code) { // 判断同步事件代码是否为 SYN_REPORT
//SYN_REPORT 是一个特定的同步事件代码,用于表示当前一组输入事件的结束。
if (valid) {//判断是否有效
switch (down) {//判断状态
case 1:
printf("按下(%d, %d)\n", x, y);
break;
case 0:
printf("松开\n");
break;
case -1:
printf("移动(%d, %d)\n", x, y);
break;
}
valid = 0; //重置valid
down = -1; //重置down
}
}
break;
}
}
}
- 开始:程序的入口点
- 校验传参:检查命令行参数是否正确
- 打开文件:以只读模式打开输入设备文件
- 初始化变量
- 初始化 x、y 坐标值为 0
- 初始化 down 为 -1,表示移动状态
- 初始化 valid 为 0,表示无效数据
- 无限循环读取输入事件数据
- 读取触摸屏数据
- 从输入设备读取一个 input_event 结构体大小的数据
- 判断读取成功与否
- 如果读取失败,打印错误信息并退出
- 解析数据
- 根据事件类型 EV_KEY、EV_ABS 和 EV_SYN 进行不同处理
- 按键事件(EV_KEY)
- 判断按键代码是否为 BTN_TOUCH
- 更新 down 和 valid
- 绝对位移事件(EV_ABS)
- 判断事件代码是否为 ABS_X 或 ABS_Y
- 更新 x 或 y 坐标,并标记 valid
- 同步事件(EV_SYN)
- 判断同步事件代码是否为 SYN_REPORT
- 如果数据有效,则根据 down 的值打印相应的状态信息,并重置 valid 和 down
运行结果
- 用一个手指按下触摸屏、松开以及滑动操作
多点触摸应用程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <linux/input.h>
/* 用于描述MT多点触摸每一个触摸点的信息 */
struct ts_mt {
int x; //X坐标
int y; //Y坐标
int id; //对应ABS_MT_TRACKING_ID
int valid; //数据有效标志位(=1表示触摸点信息发生更新)
};
/* 一个触摸点的x坐标和y坐标 */
struct tp_xy {
int x;
int y;
};
//读取触摸屏的数据
static int ts_read(const int fd, const int max_slots,
struct ts_mt *mt)
{
struct input_event in_ev;
static int slot = 0;//用于保存上一个slot
static struct tp_xy xy[12] = {0};//用于保存上一次的x和y坐标值,假设触摸屏支持的最大触摸点数不会超过12
int i;
/* 对缓冲区初始化操作 */
memset(mt, 0x0, max_slots * sizeof(struct ts_mt)); //清零
//memset标准库函数,用于将一块内存区域设置为指定的值
//max_slots 是数组的大小,表示触摸点的最大数量
for (i = 0; i < max_slots; i++)
mt[i].id = -2;//将id初始化为-2, id=-1表示触摸点删除, id>=0表示创建
for ( ; ; ) {
if (sizeof(struct input_event) !=
read(fd, &in_ev, sizeof(struct input_event))) {
perror("read error");
return -1;
}
switch (in_ev.type) {
case EV_ABS: //处理绝对位置事件
switch (in_ev.code) {
case ABS_MT_SLOT:
slot = in_ev.value;
break;
case ABS_MT_POSITION_X:
xy[slot].x = in_ev.value;
mt[slot].valid = 1;
break;
case ABS_MT_POSITION_Y:
xy[slot].y = in_ev.value;
mt[slot].valid = 1;
break;
case ABS_MT_TRACKING_ID:
mt[slot].id = in_ev.value;
mt[slot].valid = 1;
break;
}
break;
//case EV_KEY://按键事件对单点触摸应用比较有用
// break;
case EV_SYN: //处理同步事件
if (SYN_REPORT == in_ev.code) {
//将所有有效触摸点的 x 和 y 坐标更新到 mt 数组中
for (i = 0; i < max_slots; i++) {
mt[i].x = xy[i].x;
mt[i].y = xy[i].y;
}
}
return 0;
}
}
}
int main(int argc, char *argv[])
{
struct input_absinfo slot;
struct ts_mt *mt = NULL;
int max_slots;
int fd;
int i;
/* 参数校验 */
if (2 != argc) {
fprintf(stderr,"usage: %s <input_dev>\n", argv[0]);
exit(EXIT_FAILURE);
}
/* 打开文件 */
fd = open(argv[1], O_RDONLY);
if (0 > fd) {
perror("open error");
exit(EXIT_FAILURE);
}
/* 获取触摸屏支持的最大触摸点数 */
if (0 > ioctl(fd, EVIOCGABS(ABS_MT_SLOT), &slot)) {
perror("ioctl error");
close(fd);
exit(EXIT_FAILURE);
}
max_slots = slot.maximum + 1 - slot.minimum;
printf("max_slots: %d\n", max_slots);
/* 申请内存空间并清零 */
mt = calloc(max_slots, sizeof(struct ts_mt));
//calloc 是 C 标准库函数,用于动态分配内存。与 malloc 类似,但不同之处在于 calloc 会将分配的内存初始化为全零
//计算出总共需要分配的内存字节数,并返回一个指向这块内存的指针
/* 读数据 */
for ( ; ; ) {
if (0 > ts_read(fd, max_slots, mt))
break;
for (i = 0; i < max_slots; i++) {
if (mt[i].valid) {//判断每一个触摸点信息是否发生更新(关注的信息发生更新)
if (0 <= mt[i].id)
printf("slot<%d>, 按下(%d, %d)\n", i, mt[i].x, mt[i].y);
else if (-1 == mt[i].id)
printf("slot<%d>, 松开\n", i);
else
printf("slot<%d>, 移动(%d, %d)\n", i, mt[i].x, mt[i].y);
}
}
}
/* 关闭设备、退出 */
close(fd);
free(mt);
exit(EXIT_FAILURE);
}
代码
开始:程序的入口点
校验传参:检查命令行参数是否正确
打开文件:以只读模式打开输入设备文件
获取触摸屏支持的最大触摸点数
获取slot信息:使用 ioctl 函数获取设备的 ABS_MT_SLOT 信息,并检查是否成功
计算max_slots:计算最大 slot 数量
打印max_slots:打印最大 slot 数量
申请内存空间:为 mt 指针申请内存,并初始化为零
无限循环读数据:进入无限循环,调用 ts_read 函数读取触摸屏数据
ts_read():读取触摸屏数据并存储在 mt 数组中
初始化缓冲区:使用 memset 初始化 mt 数组为零
初始化 mt 数组:将 mt 数组中的每个元素的 id 初始化为 -2
无限循环读取事件:进入无限循环,读取输入事件
读取事件:从文件描述符 fd 读取输入事件
检查读取结果:检查读取是否成功,如果失败则返回 -1
处理事件类型:根据事件类型进行处理
处理 EV_ABS 事件:如果是绝对位置事件,进一步处理
处理 ABS_MT_SLOT:更新当前的 slot
处理 ABS_MT_POSITION_X:更新 xy 数组中的 x 坐标,并设置 mt 数组的 valid 标志
处理 ABS_MT_POSITION_Y:更新 xy 数组中的 y 坐标,并设置 mt 数组的 valid 标志
处理 ABS_MT_TRACKING_ID:更新 mt 数组中的 id,并设置 valid 标志
处理 EV_SYN 事件:如果是同步事件,进一步处理
- 更新 mt 数组:将 xy 数组中的坐标更新到 mt 数组中
返回 0:返回 0 表示成功
处理触摸点数据:在内部循环中处理每个触摸点的数据
打印触摸点信息:根据触摸点的状态(按下、松开、移动)打印相应的信息
关闭文件描述符,释放内存
退出程序
运行结果
- 每一个不同的 slot 表示不同的触摸点