输入设备应用编程——通过设备文件读取按键事件

        本章学习输入设备的应用编程,首先要知道什么是输入设备?输入设备其实就是能够产生输入事件的设备就称为输入设备,常见的输入设备包括鼠标、键盘、触摸屏、按钮等等,它们都能够产生输入事件,产生输入数据给计算机系统。

        对于输入设备的应用编程其主要是获取输入设备上报的数据、输入设备当前状态等,譬如获取触摸屏当前触摸点的 X、Y 轴位置信息以及触摸屏当前处于按下还是松开状态。

输入类设备编程介绍

什么是输入设备?

        先来了解什么是输入设备(也称为 input 设备),常见的输入设备有鼠标、键盘、手柄、触摸屏、遥控器、电脑画图板等,用户通过输入设备与系统进行交互。

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 操作获取的都是一个 struct input_event 结构体类型数据,该结构体定义在头文件中,它的定义如下:

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

        结构体中的 time 成员变量是一个 struct timeval 类型的变量,该结构体在前面给大家介绍过,内核会记录每个上报的事件其发生的时间,并通过变量 time 返回给应用程序。时间参数通常不是那么重要,而其它 3 个成员变量 type、code、value 更为重要。

        type:type 用于描述发生了哪一种类型的事件(对事件的分类),Linux 系统所支持的输入事件类型如下所示:

/*
* Event types
*/
#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 表示该类事件中的哪一个具体事件,以上列举的每一种事件类型中都包含了一系列具体事件,譬如一个键盘上通常有很多按键,譬如字母 A、B、C、D 或者数字 1、2、3、4 等,而 code 变量则告知应用程序是哪一个按键发生了输入事件。每一种事件类型都包含多种不同的事件,譬如按键类事件:

#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 KEY_E 18 //字母 E 键
#define KEY_R 19 //字母 R 键
……

        相对位移事件

#define REL_X 0x00 //X 轴
#define REL_Y 0x01 //Y 轴
#define REL_Z 0x02 //Z 轴
#define REL_RX 0x03
#define REL_RY 0x04
#define REL_RZ 0x05
#define REL_HWHEEL 0x06
#define REL_DIAL 0x07
#define REL_WHEEL 0x08
#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_RX 0x03
#define ABS_RY 0x04
#define ABS_RZ 0x05
#define ABS_THROTTLE 0x06
#define ABS_RUDDER 0x07
#define ABS_WHEEL 0x08
#define ABS_GAS 0x09
#define ABS_BRAKE 0x0a
#define ABS_HAT0X 0x10
#define ABS_HAT0Y 0x11
#define ABS_HAT1X 0x12
#define ABS_HAT1Y 0x13
#define ABS_HAT2X 0x14
#define ABS_HAT2Y 0x15
#define ABS_HAT3X 0x16
#define ABS_HAT3Y 0x17
#define ABS_PRESSURE 0x18
#define ABS_DISTANCE 0x19
#define ABS_TILT_X 0x1a
#define ABS_TILT_Y 0x1b
#define ABS_TOOL_WIDTH 0x1c
......

        除了以上列举出来的之外,还有很多,大家可以自己浏览头文件(这些宏其实是定义在 input-event-codes.h 头文件中,该头文件被所包含了)关于这些具体的事件,后面再给大家进行介绍。

        value:内核每次上报事件都会向应用层发送一个数据 value,对 value 值的解释随着 code 的变化而变化。譬如对于按键事件(type=1)来说,如果 code=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 操作才能把一个触摸点的信息全部读取出来,这样才能得到触摸点的完整信息。

        那么应用程序如何得知本轮已经读取到完整的数据了呢?其实这就是通过同步事件来实现的,内核将本轮需要上报、发送给接收者的数据全部上报完毕后,接着会上报一个同步事件,以告知应用程序本轮数据已经完整、可以进行同步了。

        同步类事件中也包含了多种不同的事件,如下所示:

/*
* Synchronization events.
*/
#define SYN_REPORT 0
#define SYN_CONFIG 1
#define SYN_MT_REPORT 2
#define SYN_DROPPED 3
#define SYN_MAX 0xf
#define SYN_CNT (SYN_MAX+1)

        所以的输入设备都需要上报同步事件,上报的同步事件通常是 SYN_REPORT,而 value 值通常为 0。

读取 struct input_event 数据

        根据前面的介绍可知,对输入设备调用 read()会读取到一个 struct input_event 类型数据,接下来编写应用程序,将读取到的 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 ( ; ; ) {
        /* 循环读取数据 */
        if (sizeof(struct input_event) !=
            read(fd, &in_ev, sizeof(struct input_event))) {
            error("read error");
            exit(-1);
        }
    printf("type:%d code:%d value:%d\n",in_ev.type, in_ev.code, in_ev.value);
    }
}

        执行程序时需要传入参数,这个参数就是对应的输入设备的设备节点(设备文件),程序中会对传参进行校验。程序中首先调用 open()函数打开设备文件,之后在 for 循环中调用 read()函数读取文件,将读取到的数据存放在 struct input_event 结构体对象中,之后将结构体对象中的各个成员变量打印出来。注意,程序中使用了阻塞式 I/O 方式读取设备文件,所以当无数据可读时 read 调用会被阻塞,知道有数据可读时才会被唤醒!

        Tips:设备文件不同于普通文件,读写设备文件之前无需设置读写位置偏移量。 使用交叉编译工具编译上述代码得到可执行文件 testApp:

        作者使用的是正点原子ALPHA IXM6ULL开发板,板载按键KEY_0提供给用户一个 GPIO 按键,在出厂系统中,该按键驱动基于 input 子系统而实现,所以在/dev/input 目录下存在 KEY0 的设备节点,可以通过查看/proc/bus/input/devices 文件得知,查看该文件可以获取到系统中注册的所有输入设备相关的信息,如下所示:

        接下来我们使用这个按键进行测试,执行下面的命令: 

        程序运行后,执行按下 KEY0、松开 KEY0 等操作终端将会打印出相应的信息,如上所示。

        打开 input-event-codes.h 头文件进行查找,有

        type = 1 code = 114 value =1            KEY_VOLUMEDOWN事件按键按下

        type = 0 code = 0                               SYN_REPORT 事件

        type = 1 code = 114 value =0             KEY_VOLUMEDOWN事件按键松开

         所以整个上面 4 行的打印信息就是开发板上的 KEY0 按键被按下以及松开这个过程,内核所上报的事件以及发送给应用层的数据 value。 

应用程序编写

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

 测试结果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
VHDL语言100例 VHDL学习资料VHDL 编程要点VHDL编程心得体会: 100vhdl例子 VHDL 编程要注意问题.doc VHDL——按键消抖.doc VHDL电路简化.doc VHDL编程心得体会.pdf vhd开发的官方手册.pdf 第1例 带控制端口的加法器 第2例 无控制端口的加法器 第3例 乘法器 第4例 比较器 第5例 二路选择器 第6例 寄存器 第7例 移位寄存器 第8例 综合单元库 第9例 七值逻辑与基本数据类型 第10例 函数 第11例 七值逻辑线或分辨函数 第12例 转换函数 第13例 左移函数 第14例 七值逻辑程序包 第15例 四输入多路器 第16例 目标选择器 第17例 奇偶校验器 第18例 映射单元库及其使用举 第19例 循环边界常数化测试 第20例 保护保留字 第21例 进程死锁 第22例 振荡与死锁 第23例 振荡电路 第24例 分辨信号与分辨函数 第25例 信号驱动源 第26例 属性TRANSACTION和分辨信号 第27例 块保护及属性EVENT, 第28例 形式参数属性的测试 第29例 进程和并发语句 第30例 信号发送与接收 第31例 中断处理优先机制建模 第32例 过程限定 第33例 整数比较器及其测试 第34例 数据总线的读写 第35例 基于总线的数据通道 第36例 基于多路器的数据通道 第37例 四值逻辑函数 第38例 四值逻辑向量按位或运算 第39例 生成语句描述规则结构 第40例 带类属的译码器描述 第41例 带类属的测试平台 第42例 行为与结构的混合描述 第43例 四位移位寄存器 第44例 寄存/计数器 第45例 顺序过程调用 第46例 VHDL中generic缺省值的使用 第47例 无输入元件的模拟 第48例 测试激励向量的编写 第49例 delta延迟例释 第50例 惯性延迟分析 第51例 传输延迟驱动优先 第52例 多倍(次)分频器 第53例 三位计数器与测试平台 第54例 分秒计数显示器的行为描述6 第55例 地址计数器 第56例 指令预读计数器 第57例 加.c减.c乘指令的译码和操作 第58例 2-4译码器结构描述 第59例 2-4译码器行为描述 第60例 转换函数在元件例示中的应用 第61例 基于同一基类型的两分辨类型的赋值相容问题 第62例 最大公约数的计算 第63例 最大公约数七段显示器编码 第64例 交通灯控制器 第65例 空调系统有限状态自动机 第66例 FIR滤波器 第67例 五阶椭圆滤波器 第68例 闹钟系统的控制 第69例 闹钟系统的译码 第70例 闹钟系统的移位寄存器 第71例 闹钟系统的闹钟寄存器和时间计数器 第72例 闹钟系统的显示驱动器 第73例 闹钟系统的分频器 第74例 闹钟系统的整体组装 第75例 存储器 第76例 电机转速控制器 第77例 神经元计算机 第78例ccAm2901四位微处理器的ALU输入 第79例ccAm2901四位微处理器的ALU 第80例ccAm2901四位微处理器的RAM 第81例ccAm2901四位微处理器的寄存器 第82例ccAm2901四位微处理器的输出与移位 第83例ccAm2910四位微程序控制器中的多路选择器 第84例ccAm2910四位微程序控制器中的计数器/寄存器 第85例ccAm2910四位微程序控制器的指令计数器 第86例ccAm2910四位微程序控制器的堆栈 第87例 Am2910四位微程序控制器的指令译码器 第88例 可控制计数器 第89例 四位超前进位加法器 第90例 实现窗口搜索算法的并行系统(1)——协同处理器 第91例 实现窗口搜索算法的并行系统(2)——序列存储器 第92例 实现窗口搜索算法的并行系统(3)——字符串存储器 第93例 实现窗口搜索算法的并行系统(4)——顶层控制器 第94例 MB86901流水线行为描述组成框架 第95例 MB86901寄存器文件管理的描述 第96例 MB86901内ALU的行为描述 第97例 移位指令的行为描述 第98例 单周期指令的描述 第99例 多周期指令的描述 第100例 MB86901流水线行为模型
HDFS是Hadoop分布式文件系统,它提供了Java API来进行文件读写操作。在HDFS中,文件被分成多个块并存储在不同的节点上,因此需要使用分布式文件系统的API来进行文件读写操作。 HDFS Java API提供了以下几个类来进行文件读写操作: 1. FileSystem:表示一个文件系统对象,可以通过它来获取文件系统的配置信息、创建文件、删除文件等操作。 2. Path:表示一个文件或目录的路径。 3. FSDataInputStream:表示一个输入流,可以用来读取HDFS中的文件。 4. FSDataOutputStream:表示一个输出流,可以用来向HDFS中写入数据。 下面是一个简单的示例代码,演示如何使用HDFS Java API进行文件读写操作: ```java import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.FSDataInputStream; import org.apache.hadoop.fs.FSDataOutputStream; public class HdfsExample { public static void main(String[] args) throws Exception { // 创建一个Configuration对象,用于获取Hadoop配置信息 Configuration conf = new Configuration(); // 获取HDFS文件系统对象 FileSystem fs = FileSystem.get(conf); // 创建一个Path对象,表示要读取文件路径 Path inputPath = new Path("/input/test.txt"); // 创建一个FSDataInputStream对象,用于读取文件 FSDataInputStream in = fs.open(inputPath); // 读取文件内容 byte[] buffer = new byte[1024]; int len = in.read(buffer); while (len > 0) { System.out.write(buffer, 0, len); len = in.read(buffer); } // 关闭输入流 in.close(); // 创建一个Path对象,表示要写入的文件路径 Path outputPath = new Path("/output/test.txt"); // 创建一个FSDataOutputStream对象,用于写入文件 FSDataOutputStream out = fs.create(outputPath); // 写入文件内容 String content = "Hello, HDFS!"; out.write(content.getBytes()); // 关闭输出流 out.close(); } } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值