Linux Input 子系统用户空间使用
概述
Linux 使用 Input 子系统统一管理各种输入设备,例如:
按键
键盘
鼠标
触摸屏
传感器不同硬件产生的数据格式可能不同,但经过 Input 子系统处理后,会统一转换成 input event(输入事件),并通过:
/dev/input/eventX提供给用户程序读取。
整体关系:
输入设备
↓
设备驱动
↓
Linux Input 子系统
↓
/dev/input/eventX
↓
应用程序查看输入设备
查看 event 设备
ls /dev/input/常见结果:
event0
event1
event2每个 eventX 对应一个注册到 Input 子系统的输入设备。
查看设备对应关系
可以查看:
cat /proc/bus/input/devices例如:
N: Name="20cc000.snvs:snvs-powerkey"
H: Handlers=kbd event0
N: Name="goodix-ts"
H: Handlers=event1
N: Name="gpio_keys@0"
H: Handlers=kbd event2通过:
Name可以判断是什么设备。
通过:
Handlers可以找到对应的 eventX。
例如:
event0 → 电源按键
event1 → 触摸屏
event2 → GPIO 按键读取输入设备
用户程序可以像读取普通设备文件一样读取:
/dev/input/eventX基本流程:
open()
↓
read()
↓
解析 struct input_event
↓
继续 read()例如打开:
int fd = open("/dev/input/event0", O_RDONLY);然后读取:
struct input_event ev;
read(fd, &ev, sizeof(ev));阻塞读取
默认情况下:
read(fd, &ev, sizeof(ev));属于阻塞读取。
如果当前没有新的输入事件:
read()
↓
进程阻塞等待当用户:
按下按键
移动鼠标
点击触摸屏产生新的输入事件后:
输入设备产生事件
↓
Input 子系统上报
↓
read() 返回
↓
应用程序解析事件因此程序通常会循环调用 read():
read
↓
等待事件
↓
事件发生
↓
解析事件
↓
再次 readstruct input_event
Input 子系统向用户空间提供的核心数据结构是:
struct input_event程序需要包含:
#include <linux/input.h>其核心字段可以理解为:
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};主要关注:
| 字段 | 作用 |
|---|---|
time | 事件发生时间 |
type | 事件类型 |
code | 具体事件编号 |
value | 事件值 |
可以简单理解成:
type → 发生了什么类型的事件
code → 具体是哪一个按键 / 坐标轴
value → 事件具体发生了什么变化type:事件类型
type 表示当前事件属于哪一种类型。
常见类型:
EV_KEY → 按键事件
EV_REL → 相对坐标事件
EV_ABS → 绝对坐标事件
EV_SYN → 同步事件例如:
if (ev.type == EV_KEY) {
// 这是一个按键事件
}code:事件编号
code 表示具体是哪个输入元素产生了事件。
对于按键事件,例如:
KEY_ESC
KEY_1
KEY_2
KEY_ENTER
KEY_POWER例如:
if (ev.code == KEY_ENTER) {
// 回车键
}因此:
type = EV_KEY
code = KEY_ENTER表示:
发生了一个“回车键”事件value:事件值
对于 EV_KEY 按键事件:
| value | 含义 |
|---|---|
0 | 按键松开 |
1 | 按键按下 |
2 | 按键自动重复 |
例如:
type = EV_KEY
code = KEY_ENTER
value = 1表示:
回车键按下而:
type = EV_KEY
code = KEY_ENTER
value = 0表示:
回车键松开因此判断一个按键事件通常需要同时判断:
type + code + value按键事件示例
假设用户按下回车键。
Input 子系统可能上报:
type = EV_KEY
code = KEY_ENTER
value = 1表示:
回车键按下用户松开后:
type = EV_KEY
code = KEY_ENTER
value = 0表示:
回车键松开完整过程:
按下回车键
↓
EV_KEY + KEY_ENTER + 1
松开回车键
↓
EV_KEY + KEY_ENTER + 0C 程序读取事件
下面程序读取并打印 /dev/input/event0 上报的事件:
#include <fcntl.h>
#include <linux/input.h>
#include <stdio.h>
#include <unistd.h>
int main(void)
{
int fd;
ssize_t ret;
struct input_event ev;
fd = open("/dev/input/event0", O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
while (1) {
ret = read(fd, &ev, sizeof(ev));
if (ret < 0) {
perror("read");
break;
}
if (ret != sizeof(ev))
continue;
printf("type=%u code=%u value=%d\n",
ev.type,
ev.code,
ev.value);
}
close(fd);
return 0;
}运行后操作输入设备,可以看到类似:
type=1 code=28 value=1
type=0 code=0 value=0
type=1 code=28 value=0
type=0 code=0 value=0其中:
type = 1对应:
EV_KEY而:
code = 28对应:
KEY_ENTER判断指定按键
例如只处理回车键:
if (ev.type == EV_KEY && ev.code == KEY_ENTER) {
if (ev.value == 1) {
printf("KEY_ENTER pressed\n");
} else if (ev.value == 0) {
printf("KEY_ENTER released\n");
}
}判断逻辑:
type == EV_KEY
↓
确定是按键事件
code == KEY_ENTER
↓
确定是回车键
value
├── 1 → 按下
├── 0 → 松开
└── 2 → 自动重复EV_SYN
读取 Input 事件时,经常还能看到:
EV_SYN例如:
type=0 code=0 value=0EV_SYN 用来表示一组输入事件的同步边界。
因此读取输入设备时出现:
EV_KEY
EV_SYN是正常现象。
例如一次按键上报可能类似:
EV_KEY KEY_ENTER 1
EV_SYN SYN_REPORT 0可以理解为:
回车键按下
↓
这一组事件上报完成注意 read() 的返回数据
示例中通常为了方便理解,以:
read(fd, &ev, sizeof(ev));一次读取一个:
struct input_event来演示。
实际使用时需要注意:
read()返回的是字节数。只要提供的缓冲区足够大,一次read()也可以读取多个struct input_event,因此不要把 Input 接口理解成“系统强制一次 read 只能返回一个事件”。
入门阶段使用:
struct input_event ev;
read(fd, &ev, sizeof(ev));即可。
常用命令
查看输入设备节点:
ls -l /dev/input/查看输入设备信息:
cat /proc/bus/input/devices快速查找 event:
grep -E "Name|Handlers" /proc/bus/input/devices例如:
N: Name="goodix-ts"
H: Handlers=event1即可知道:
goodix-ts → /dev/input/event1使用流程
实际使用 Input 设备时,可以按照下面的步骤:
1. 查看设备
↓
cat /proc/bus/input/devices
2. 找到 Handlers
↓
event0 / event1 / event2
3. 打开设备
↓
open("/dev/input/eventX")
4. 读取数据
↓
read()
5. 解析 struct input_event
↓
type + code + value
6. 根据事件执行业务逻辑小结
Linux Input 子系统的核心作用是统一不同输入设备的事件格式:
按键 / 键盘 / 鼠标 / 触摸屏
↓
Input 子系统
↓
/dev/input/eventX
↓
struct input_event
↓
应用程序用户空间最需要掌握的是:
/dev/input/eventX
struct input_event
type
code
value对于按键事件:
type = EV_KEY
code = 哪个按键
value = 按下 / 松开 / 重复其中:
value = 1 → 按下
value = 0 → 松开
value = 2 → 自动重复基本使用流程就是:
找到 eventX
↓
open()
↓
read()
↓
解析 type / code / value
↓
处理输入事件