原子学习笔记2——输入设备应用编程

一、输入类设备介绍

1、输入设备

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

2、input子系统

常见的输入设备有鼠标、键盘、触摸屏、遥控器、电脑画图板等,用户通过输入设备与系统进行交互。
基于 input 子系统注册成功的输入设备,都会在/dev/input 目录下生成对应的设备节点(设备文件),设备节点名称通常为 eventX(X 表示一个数字编号 0、1、2、3 等),譬如/dev/input/even 、/dev/input/event1、/dev/input/event2等,通过读取这些设备节点可以获取输入设备上报的数据。

3、读取数据的流程

如果我们要读取触摸屏的数据,假设触摸屏设备对应的设备节点为/dev/input/event0,那么数据读取流程如下:
①、应用程序打开/dev/input/event0 设备文件;
②、应用程序发起读操作(譬如调用 read),如果没有数据可读则会进入休眠(阻塞 I/O 情况下);
③、当有数据可读时,应用程序会被唤醒,读操作获取到数据返回;
④、应用程序对读取到的数据进行解析。

4、解析数据

应用程序打开输入设备对应的设备文件,向其发起读操作,其实每一次 read 操作获取的都是一个 struct input_event 结构体类型数据,该结构体定义在<linux/input.h>头文件中,它的定义如下:

struct input_event {struct timeval time;__u16 type;__u16 code;__s32 value;
};
  • type:type 用于描述发生了哪一种类型的事件(对事件的分类)。点击鼠标按键(左键、右键,或鼠标上的其它按键)时会上报按键类事件,移动鼠标时则会上报相对位移类事件。
  • code:code 表示该类事件中的哪一个具体事件,以上列举的每一种事件类型中都包含了一系列具体事件,譬如一个键盘上通常有很多按键,譬如字母 A、B、C、D 或者数字 1、2、3、4 等,而 code变量则告知应用程序是哪一个按键发生了输入事件。
  • value:内核每次上报事件都会向应用层发送一个数据 value,对 value 值的解释随着 code 的变化而变化。譬如对于按键事件(type=1)来说,如果 code=2(键盘上的数字键 1,也就是KEY_1),那么如果 value 等于 1,则表示 KEY_1 键按下;value 等于 0 表示 KEY_1 键松开

二、按键应用编程

编写按键应用程序,读取按键状态并将结果打印出来。
如果是按下,则上报 KEY_A 事件时,value=1;如果是松开,则 value=0;如果是长按,则 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) { //按键事件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;}}}
}

在这里插入图片描述
可以看出开发板下的设备节点是event1,结果如下:
在这里插入图片描述

三、触摸屏应用编程

触摸屏分为多点触摸设备和单点触摸设备。单点触摸设备只支持单点触摸,一轮(笔者把一个同步事件称为一轮)完整的数据只包含一个触摸点信息;多点触摸设备,一轮完整的数据可能包含多个触摸点信息。

1、获取触摸屏信息

首先介绍ioctl()函数,ioctl()是一个文件 I/O 操作的杂物箱,可以处理的事情非常杂、不统一,一般用于操作特殊文件或设备文件。

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

第一个参数 fd 对应文件描述符;第二个参数 request 与具体要操作的对象有关,没有统一值,表示向文件描述符请求相应的操作,也就是请求指令;此函数是一个可变参函数,第三个参数需要根据 request 参数来决定,配合 request 来使用。
下面代码用于获取触摸屏支持的最大触摸点数:

#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);
}

可以看到ioctl函数第二个参数

#define EVIOCGABS(abs)		_IOR('E', 0x40 + (abs), struct input_absinfo)

通过这个宏可以获取到触摸屏 slot(slot<0>表示触摸点 0、slot<1>表示触摸点 1、slot<2>表示触摸点 2,以此类推!)的取值范围,可以看到使用该宏需要传入一个 abs 参数,该参数表示为一个 ABS_XXX 绝对位移事件,譬如 EVIOCGABS(ABS_MT_SLOT)表示获取触摸屏的 slot 信息,此时 ioctl()函数的第三个参数是一个 struct input_absinfo *的指针,指向一个 struct input_absinfo 对象,调用 ioctl()会将获取到的信息写入到struct input_absinfo 对象中。struct input_absinfo 结构体如下所示:

struct input_absinfo {__s32 value; //最新的报告值__s32 minimum; //最小值__s32 maximum; //最大值__s32 fuzz;__s32 flat;__s32 resolution;
};

拷贝到开发板执行程序可以看到这个屏是这个屏是一个 5 点触摸屏
在这里插入图片描述

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;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) {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; //重置 validdown = -1; //重置 down}}break;}}
}

观察几个宏定义,通过input_event结构体的type成员判断事件类型

#define EV_SYN 0x00 //同步类事件,用于同步事件
#define EV_KEY 0x01 //按键类事件
#define EV_ABS 0x03 //绝对位移类事件(譬如触摸屏)

再通过code成员判断是时间类型中的哪种具体事件

#define BTN_TOUCH		0x14a
#define ABS_X			0x00  //X坐标
#define ABS_Y			0x01  //Y坐标

最后是数据同步
同步事件用于实现同步操作、告知接收者本轮上报的数据已经完整。应用程序读取输入设备上报的数据时,一次 read 操作只能读取一个 struct input_event 类型数据,对于触摸屏来说,一个触摸点的信息包含了 X 坐标、Y 坐标以及其它信息,对于这样情况,应用程序需要执行多次 read 操作才能把一个触摸点的信息全部读取出来,这样才能得到触摸点的完整信息。所以使用for循环一直读结构体的值。
内核将本轮需要上报、发送给接收者的数据全部上报完毕后,接着会上报一个同步事件,以告知应用程序本轮数据已经完整、可以进行同步了。

#define SYN_REPORT		0     //同步事件

输入设备都需要上报同步事件,上报的同步事件通常是 SYN_REPORT,而 value 值通常为 0。
打印信息如下:
在这里插入图片描述

3、多点触摸屏应用编程

编写一个打印各个触摸点信息的程序:

#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_IDint 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;//用于保存上一个 slotstatic struct tp_xy xy[12] = {0};//用于保存上一次的 x 和 y 坐标值,假设触摸屏支持的最大触摸点数不会超过 12int i;/* 对缓冲区初始化操作 */memset(mt, 0x0, max_slots * sizeof(struct ts_mt)); //清零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) {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));/* 读数据 */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);elseprintf("slot<%d>, 移动(%d, %d)\n", i, mt[i].x, mt[i].y);}}}/* 关闭设备、退出 */close(fd);free(mt);exit(EXIT_FAILURE);
}

可以看出程序先通过ioct()函数获取触摸屏支持的最大触摸点数,重点关注ts_read()函数,然后在for循环中打印出各个触摸点信息。
在 Linux 内核中,多点触摸设备使用多点触摸(MT)协议上报各个触摸点的数据,MT 协议分为两种类型:Type A 和 Type B,我们使用的是 Type B协议。
首先硬件能够为每一个识别到的触摸点与一个 slot 进行关联,这个 slot 就是一个编号,触摸点 0、触摸点 1、触摸点 2 等。底层驱动向应用层上报 ABS_MT_SLOT 事件,此事件会告诉接收者当前正在更新的是哪个触摸点的数据,ABS_MT_SLOT 事件中对应的 value 数据存放的便是一个 slot、以告知应用层当前正在更新 slot关联的触摸点对应的信息。
其次除了ABS_MT_SLOT 事 件 之 外 , Type B 协 议 还 会 使 用 到 ABS_MT_TRACTKING_ID 事 件 ,ABS_MT_TRACTKING_ID 事件则用于触摸点的创建、替换和销毁工作,ABS_MT_TRACTKING_ID 事件携带的数据 value 表示一个 ID,一个非负数的 ID(ID>=0)表示一个有效的触摸点,如果 ID 等于-1 表示该触摸点已经不存在、被移除了;一个以前不存在的 ID 表示这是一个新的触摸点。
然后得到X,Y的坐标值
最后进行数据同步
上报流程如下:

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_REPORT

可以看出5个触摸点信息如下:
在这里插入图片描述

4、鼠标应用编程

与触摸屏类似

#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 x, y; //触摸点 x 和 y 坐标int down; //用于记录事件的 value,1 表示按下,0 表示松开,-1 表示移动int valid; //用于记录数据是否有效(我们关注的信息发生更新表示有效,1 表示有效,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);}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(-1);}switch(in_ev.type){case EV_KEY:down = in_ev.value;valid = 1;break;case EV_REL://相对位移事件(鼠标)switch(in_ev.code){case REL_X:x = in_ev.value;valid = 1;break;case REL_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("按下\n");break;case 0:printf("松开\n");break;case -1:printf("移动(%d, %d)\n", x, y);break;}valid = 0;down = -1;}}break;}}
}

结果如下:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/286756.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java中线程状态的描述

多线程-基础方法的认识 截止目前线程的复习 Thread 类 创建Thread类的方法 继承Thread类,重写run方法实现Runnable接口,重写run方法使用匿名内部类继承Thread类,重写run方法使用匿名内部类实现Runnable接口,重写run方法使用Lambda表达式 run方法中的所有的代码是当前线程对…

两种方案实现等待线程池结束后执行后面的业务代码

使用场景 批量任务处理&#xff1a;当需要并发执行多个任务&#xff0c;然后等待所有任务执行完毕后进行下一步操作时&#xff0c;可以使用这两种方法来等待所有任务执行完毕。 线程池管理&#xff1a;在使用线程池执行任务时&#xff0c;有时需要等待所有任务执行完毕后再关闭…

零基础也能制作家装预约咨询小程序

近年来&#xff0c;随着互联网的快速发展&#xff0c;越来越多的消费者倾向于使用手机进行购物和咨询。然而&#xff0c;许多家装实体店却发现自己的客流量越来越少&#xff0c;急需一种新的方式来吸引顾客。而开发家装预约咨询小程序则成为了一种利用互联网技术来解决这一问题…

标准IO与文件IO

标准IO通过缓冲机制减少系统调用&#xff0c;实现更高的效率 全缓冲&#xff1a;当流的缓冲区无数据或无空间时才执行实际IO操作 行缓冲&#xff1a;当在输入和输出中遇到换行符&#xff08;\n&#xff09;时&#xff0c;进行IO操作 当流和一个终端关联时&#xff0c;典型的行缓…

python学习,2.简单的数据类型

1.了解数及运算 整数&#xff1a;1&#xff0c;2&#xff0c;3。 运算符&#xff1a;加减乘除&#xff0c;**(乘方) 浮点数&#xff1a;python将所有带小数点的数称为浮点数。 这一块和别的语言有些不一样&#xff0c; 像C&#xff0c;分为float&#xff0c;double&#x…

基于grpc从零开始搭建一个准生产分布式应用(7) - 01 - 附:GRPC拦截器源码

开始前必读&#xff1a;​​基于grpc从零开始搭建一个准生产分布式应用(0) - quickStart​​ 一、源码目录结构 二、GRPC拦截器源码 2.1、com.zd.baseframework.core.core.common.interceptor package com.zd.baseframework.core.core.common.interceptor;import com.zd.ba…

清华提出ViLa,揭秘 GPT-4V 在机器人视觉规划中的潜力

人类在面对简洁的语言指令时&#xff0c;可以根据上下文进行一连串的操作。对于“拿一罐可乐”的指令&#xff0c;若可乐近在眼前&#xff0c;下意识的反应会是迅速去拿&#xff1b;而当没看到可乐时&#xff0c;人们会主动去冰箱或储物柜中寻找。这种自适应的能力源于对场景的…

算法(2)——滑动窗口

前言&#xff1a; 步骤及算法模板&#xff1a; 确定两个指针变量&#xff0c;left0,right0; 进窗口&#xff1a; 判断&#xff1a; 出窗口 更新结果 接下来我们的所用滑动窗口解决问题都需要以上几个步骤。 一、长度最小的子数组 209. 长度最小的子数组 - 力扣&#xff08;L…

【重点】【前缀树|字典树】208.实现Trie(前缀树)

题目 前缀树介绍&#xff1a;https://blog.csdn.net/DeveloperFire/article/details/128861092 什么是前缀树 在计算机科学中&#xff0c;trie&#xff0c;又称前缀树或字典树&#xff0c;是一种有序树&#xff0c;用于保存关联数组&#xff0c;其中的键通常是字符串。与二叉查…

安卓开发学习---kotlin版---笔记(三)

网络 安卓主页的网络框架&#xff1a;OkHttp 在OkHttp的基础上进行封装的&#xff1a;Retrofit框架&#xff0c;更常使用 OkHttp学习 在使用网络请求的时候&#xff0c;先添加网络访问权限&#xff1a; <uses-permission android:name"android.permission.INTERNET&…

JavaScript 内存管理的秘密武器:垃圾回收(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

SQL进阶理论篇(十四):CBO优化器是如何计算代价的?

文章目录 简介能调整的代价模型的参数有哪些&#xff1f;mysql.server_costmysql.engine_cost 如何修改这些代价参数&#xff1f;代价模型具体是如何计算的参考文献 简介 大部分RDBMS都支持基于代价的优化器CBO&#xff0c;但其实CBO仍然存在缺陷&#xff08;比如参数配置的不…