06、输入设备应用编程
📢本篇我们将讲解输入设备应用编程。
一、输入设备介绍
输入设备是用户用来操作计算机的工具,比如键盘、鼠标、触摸屏等。在Linux系统中,这些设备通过内核里的输入子系统来工作。这个子系统就像是中间桥梁,专门负责把设备收集的信息(比如按键或点击动作)传递给正在运行的程序。
在 Linux
中,每个输入设备都被表示为一个字符设备文件。这些文件通常位于 /dev/input
目录下,如下图所示:
二、input 子系统
Linux输入子系统负责管理所有输入设备,比如键盘、鼠标、触摸屏等。它的主要功能是把设备产生的操作(如按键、移动、触摸等)转化为计算机能识别的信号,并传递给应用程序。
具体流程是这样的:
- 设备连接后,系统会加载对应的驱动程序(比如鼠标用hid驱动)。驱动就像翻译官,把设备的原始信号转换成标准格式。
- 所有设备产生的事件(比如"按下A键"或"鼠标右移10格")会被收集到一个队列里,先到的事件先处理。
- 这些事件最终会传送到用户程序能读取的地方。开发者只要打开类似/dev/input/event0这样的文件,就能获取实时的设备操作信息。
整个过程就像邮局分拣信件:驱动负责把不同格式的"信件"(原始数据)翻译成统一格式,系统把所有"信件"按顺序排队,最后准确送到应用程序的"收件箱"里。
如下图所示:
三、读取输入设备
以下是更简洁易懂的改写版本:
读取输入设备数据的流程说明:
- 连接设备 应用程序通过打开系统文件(如/dev/input/event0)与输入设备建立连接。这里的event0是设备编号,不同设备编号不同。
- 读取数据 应用程序持续等待数据:
- 当你操作设备(比如按键盘、移动鼠标)时,程序会立刻读取到数据
- 如果没操作,程序会暂停等待,直到有新动作发生
- 解析数据 每次读取到的数据都是一个标准信息包,包含3个关键信息:
- 事件类型:说明是什么类型的设备(如键盘/鼠标/触摸屏)
- 事件代码:具体动作类型(如哪个键被按、鼠标移动方向)
- 事件值:动作的具体数值(如坐标值、按键状态)
- 处理动作 程序根据这三个信息判断:
- 哪个设备发生了动作
- 具体是什么动作
- 动作的具体参数(比如移动了5毫米或按下了A键)
- 结束操作 使用完毕后,程序关闭设备连接,释放系统资源。
::: 补充说明: 每次读取操作都会得到一个完整的"事件信息包",就像收到一个包含所有必要信息的快递包裹。这些信息包直接来自系统底层,应用程序通过解读包裹里的信息,就能知道用户在设备上做了什么操作。 :::
结构体 struct input_event
定义如下:
struct input_event {
struct timeval time;
__u16 type;// 类型
__u16 code;// 具体事件
__s32 value;// 对应的取值
};
2
3
4
5
6
下面对 struct input_event
结构体的三个字段及其相应的宏进行详细的讲解:
3.1、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)
2
3
4
5
6
7
8
9
10
11
12
13
14
这些宏定义都定义在 <linux/input.h>
头文件里,所以使用时要包含这个头文件。一个输入设备通常能产生多种事件类型,比如点击鼠标按键(左键、右键或其他按键)时会发送按键事件;而移动鼠标时,会发送位移事件。
3.2、code
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 //回退键
................................
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
对于其他输入事件的 code
值,可以查看 input-event-codes.h
头文件(该头文件被 <linux/input.h>
所包含)。
3.3、value
内核在发送事件时会传递一个数值(value)。这个数值的具体含义由事件类型(code)决定:
对于按键事件:
- value为1表示按键被按下
- value为0表示按键松开
- value为2表示按键长按
对于触摸屏坐标事件(类型为3):
- 当code为0(代表X轴坐标)时,value就是触摸点的横向位置
- 当code为1(代表Y轴坐标)时,value就是触摸点的纵向位置
简单来说,value的数值意义完全取决于它所属的事件类型。就像不同语言需要不同翻译一样,每个code都定义了value应该怎样解读。
四、按键应用编程案例
#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;
}
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66