16. 输入设备应用编程

本文详细介绍了Linux系统中输入设备编程,包括输入设备的基本概念、input子系统的管理、structinput_event数据结构的读取,以及按键和触摸屏应用的编程示例,重点讲解了TypeB协议在多点触摸设备中的事件上报顺序和数据解析。
摘要由CSDN通过智能技术生成

1. 输入类设备编程介绍

1.1 什么是输入设备

常见的输入设备有鼠标、键盘、触摸屏、遥控器等,用户通过输入设备与系统进行交互。

1.2 input 子系统

Linux 为了管理这些输入设备,实现了一套能够兼容所有输入设备的框架,这个框架就是 input 子系统,可以屏蔽硬件的差异,向应用层提供一套统一的接口。基于子系统注册成功的输入设备,都会在 /dev/input 目录下生成对应的设备节点,名称通常为 eventX,通过读取这些设备节点可以获取输入设备上报的数据。

1.3 读取数据的流程

假设读取的设备对应的节点为 event0:

  1. 打开 /dev/input/event0 设备文件
  2. 发起读操作,如果没有数据可读则会进入休眠(阻塞 IO 情况下)
  3. 当有数据可读时,应用程序会被唤醒,读操作获取到数据返回
  4. 应用程序对读取到的数据进行解析

1.4 应用程序如何解析数据

每一次 read 操作获取的都是一个 struct input_event 结构体类型数据

struct input_event
{
	struct timeval time;
	__u16 type;
	__u16 code;
	__s32 value;
};

主要关注以下三个:

  • type: 用于描述发生了哪一中类型的事件,事件分类具体可以查看 <linux/input.h>头文件
  • code: 表示该类事件中的哪一个具体事件
  • value: 内核每次上报事件都会向应用层发送一个数据 value,对 value 值的解释随 code 变化而变化。

数据同步:
事件中有一个同步事件 EV_SYN,用于实现同步操作,告知接收者本轮上报的数据已经完整。应用程序读取输入设备上报的数据时,一次 read 只能读取一个 struct input_event 类型数据,但是如果读取触摸屏数据时,有横纵坐标,需要多次 read。内核将本轮需要上报、发送给接收者的数据全部上报完毕后,接着会上报一个同步事件,以告知应用程序本轮数据已经完整,可以进行同步了。同步事件也包含多种不同的事件:
在这里插入图片描述
所有的输入设备都需要上报同步事件,上报的同步事件通常是 SYN_REPORT,而 value 值通常为 0.

2. 读取 struct input_event 数据

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>

int main(int argc,char *argv[])	// 执行文件时需要传入参数,参数就是输入设备文件
{
	if(argc!=2)
	{
		fprintf(stderr,"usage:%s input-dev\n",argv[0]);
		return -1;
	}
	struct input_event in_ev={0};
	int fd=open(argv[1],O_RDONLY);
	for(;;)
	{
		// 循环读取数据
		if(sizeof(struct input_event)!=read(fd,&in_ev,sizeof(struct input_event)))
		{
			perror("read");
			return -1;
		}
		printf("type:%d code:%d value:%d\n",in_ev.type,in_ev.code,in_ev.value);
	}
}

3. 按键应用编程

获取按键状态,以字母 A 为例,上报 KEY_A 事件时,value=1;如果是松开,value=0;如果是长按,value=2

int main(int argc,char *argv[])
{
	if(argc!=2)
	{
		fprintf(stderr,"usage:%s input-dev\n",argv[0]);
		return -1;
	}
	struct input_event in_ev={0};
	int fd=open(argv[1],O_RDONLY);
	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)
		{
			switch (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;
			}
		}	
	}
}

4. 触摸屏应用编程

4.1 解析触摸屏设备上报的数据

触摸屏设备是一个绝对位移设备,可以上报绝对位移事件,绝对位移事件如下:
在这里插入图片描述
单点触摸和多点触摸:
单点触摸设备只支持单点触摸, 一轮完整的数据只包含一个触摸点信息;单点触摸设备以 ABS_XXX 事件承载、上报触摸点的信息,譬如 ABS_X(value 值对应的是 X 轴坐标值)、ABS_Y(value 值对应的是 Y 轴坐标值)等绝对位移事件,而有些设备可能还支持 Z 轴坐标(通过 ABS_Z 事件上报、 value 值对应的便是 Z 轴坐标值)、按压力大小(通过 ABS_PRESSURE 事件上报、 value 值对应的便是按压力大小) 以及接触面积等属性。 大部分的单点触摸设备都会上报 ABS_X 和 ABS_Y 事件,而其它绝对位移事件则根据具体的设备以及驱动的实现而定!
多点触摸设备可支持多点触摸,一轮完整的数据可能包含多个触摸点信息。 多点触摸设备则是以 ABS_MT_XXX(MT 是 Multi-touch, 意思为:多点触摸)事件承载、上报触摸点的信息,如 ABS_MT_POSITION_X(X 轴坐标)、ABS_MT_POSITION_Y(Y 轴坐标)等绝对位移事件。
触摸屏设备除了上报绝对位移事件之外,还可以上报按键类事件和同步类事件。同步事件很好理解,因为几乎每一个输入设备都会上报同步事件、告知应用层本轮数据是否完整;当手指点击触摸屏或手指从触摸屏离开时,此时就会上报按键类事件,用于描述按下触摸屏和松开触摸屏;具体的按键事件为 BTN_TOUCH(code=0x14a,也就是 330),当然,手指在触摸屏上滑动不会上报 BTN_TOUCH 事件。
Tips:BTN_TOUCH 事件不支持长按状态,故其 value 不会等于 2。 对于多点触摸设备来说,只有第一个点按下时上报 BTN_TOUCH 事件 value=1,当最后一个点离开触摸屏时上报 BTN_TOUCH 事件 value=0。

4.1.1 单点触摸设备——事件上报顺序

流程大致如下:

// 点击触摸屏时
BTN_TOUCH  // value=1
ABS_X
ABS_Y
SYN_REPORT

// 滑动
ABS_X
ABS_Y
SYN_REPORT

// 松开
BTN_TOUCH	// value=0
SYN_REPORT

4.1.2 多点触摸设备——事件上报顺序

在 Linux 内核中,多点触摸设备使用多点触摸协议(MT)上报各个触摸点的数据,MT 协议分为 TypeA 和 TypeB,前者几乎不用,介绍后一个。

4.1.2.1 TypeB 协议

该协议适用于能够追踪并区分触摸点的设备,是通过 ABS_MT_SLOT 事件上报各个触摸点信息的更新。这类设备通常在硬件上能够区分不同的触摸点,为每一个识别到的触摸点与一个 slot 进行关联,这个 slot 就是一个编号。底层驱动向应用层上报 ABS_MT_SLOT 事件,此事件会告诉接收者当前正在更新的是哪个触摸点的数据,ABS_MT_SLOT 事件中对应的 value 数据存放的便是一个 slot,以告知应用层当前正在更新 slot 关联的触摸点对应的信息。
每个识别出来的触摸点分配一个 slot,与该 slot 关联起来,利用这个 slot 来传递对应触点的变化。除了 ABS_MT_SLOT 事件之外,TypeB 协议还会使用到 ABS_MT_TRACTKING_ID 事件,用于触摸点的创建、替换和销毁工作,携带的 value 表示一个 ID,如果是非负数,表示一个有效的触摸点,如果是 -1,表示该触摸点已经不存在,如果是一个以前不存在的 ID 表示这是一个新的触摸点。
该协议可以减少发送到用户空间的数据,只有发生了变更的数据才会上报。

4.1.2.2 触摸屏上报数据分析

使用命令cat /proc/bus/input/devices确定触摸屏对应的设备节点。点击后先不要松开
在这里插入图片描述
首先第一行上报了绝对位移事件 EV_ABS(type=3)中的 ABS_MT_TRACKING_ID(code=57)事件,并且 value 值等于 78,也就是 ID,这个 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 支持,所以可以把多点触摸设备当成单点触摸设备来使用。
最后一行上报了同步类事件 EV_SYN(type=0)中的 SYN_REPORT(code=0)事件,表示此次触摸点的信息全部上报完毕。
在第一个触摸点的基础上,增加第二个触摸点,打印信息如下所示:
在这里插入图片描述
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)

4.2 获取触摸屏的信息

这里需要用到 ioctl() 函数

#include <sys/ioctl.h>
int ioctl(int fd,unsigned long request,...);

第二个参数在input.h头文件中 EVIOC 相关的宏中有定义,第三个参数则是根据第二个参数来确定。譬如获取设备名称:

char name[100];
ioctl(fd,EVIOCGNAME(sizeof(name)),name);

使用最多的是EVIOCGABS(abs),可以获取到触摸屏 slot 的取值范围,参数表示一个 ABS_XXX 绝对位移事件,譬如获取 slot 信息,第三个参数是 struct input_absinfo*

在这里插入图片描述
获取触摸屏支持的最大触摸点数

#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[])
{
	if (2 != argc) 
	{
		fprintf(stderr, "usage: %s <input-dev>\n", argv[0]);
		return -1;
	}
	struct input_absinfo info;
	int fd=open(argv[1],O_RDONLY);
	ioctl(fd,EVIOCGABS(ABS_MT_SLOT),&info);
	int max_slots=info.maximum+1-info.minimum;
	printf("最大触摸点数: %d\n",max_slots);
	close(fd);
	return 0;
}

4.3 单点触摸应用程序——获取一个触摸点的坐标

int main(int argc,char *argv[])
{
	if (2 != argc) 
	{
		fprintf(stderr, "usage: %s <input-dev>\n", argv[0]);
		return -1;
	}
	struct input_event in_ev;
	int x=0,y=0;
	int down=-1; 	// 记录 BTN_TOUCH 事件的 value,1 表示按下,0 表示松开,-1 表示移动
	int valid=0;	// 记录数据是否有效,更新表示有效,1 表示有效,0 表示无效
	struct input_absinfo info;
	int fd=open(argv[1],O_RDONLY);
	for(;;)
	{
		if(sizeof(struct input_event) != read(fd,&in_ev,sizeof(struct input_event)))
		{
			perror("read");
			return -1;
		}
		switch(in_ev.type)
		{
			case EV_KEY:	// 按键事件
				if(BTN_TOUCH == in_ev.code)
				{
					down = in_ev.value;
					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) 
				{
					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;
		}
	}
}

4.4 多点触摸应用程序

// 描述 MT 多点触摸每一个触摸点的信息
struct ts_mt
{
	int x;
	int y;
	int id;		// 对应 ABS_MT_TRACKING_ID
	int valid;	// 数据有效标志位
};
// 触摸点的坐标
struct tp_xy
{
	int x;
	int y;
};
int ts_read(const int fd,const int max_slots,struct ts_mt *mt)
{
	struct input_event in_ev;
	static int slot = 0;
	static struct tp_xy xy[12]={0};
	int i;

	// 对缓冲区进行初始化
	memset(mt,0x0,max_slots *sizeof(struct ts_mt));
	for(i=0;i<max_slots;i++)
	{
		mt[i].id=-2;	// 初始化为 -2,-1 表示触摸点删除,id>=0 表示创建
	}
	for(;;)
	{
		read(fd,&in_ev,sizeof(struct input_event));
		
		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_SYN: //同步事件
				if (SYN_REPORT == in_ev.code) 
				{
					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[])
{
	if (2 != argc) 
	{
		fprintf(stderr,"usage: %s <input_dev>\n", argv[0]);
		exit(EXIT_FAILURE);
	}
	struct input_absinfo slot;
	struct ts_mt *mt =NULL;
	int max_slots;
	int fd;
	int i;
	fd=open(argv[1],O_RDONLY);
	ioctl(fd,EVIOCGABS(ABS_MT_SLOT),&slot)
	max_slots=slot.maximum+1-slot.minimum;
	printf("max_slots:%d\n",max_slot);
	mt=calloc(max_slots,sizeof(struct ts_mt));
	// 读数据
	for(;;)
	{
		if(ts_read(fd,max_slots,mt)<0)
		{
			break;
		}
		for(i=0;i<max_slots;i++)
		{
			if(mt[i].valid)	// 判断每一个触摸点信息是否发生更新
			{
				if(mt[i].id>=0)
					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);
	return 0;
}

在单点触摸中,是通过BTN_TOUCH事件判断手指动作的,而多点触摸中,需要通过 id 来判断多个手指的动作。

  • 16
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值