Linux输入系统应用编程
什么是输入系统
Linux 输入系统是处理用户输入设备(如键盘、鼠标、触摸屏、游戏手柄等)的软件架构。在应用编程层面,它提供了与这些输入设备交互的接口。
主要组成部分
-
输入设备驱动层:直接与硬件交互的驱动程序
-
输入核心层:内核中的输入子系统核心
-
事件接口层:向用户空间提供的接口(主要是
/dev/input/
下的设备文件)
关键概念
输入设备:任何能产生输入事件的硬件设备
输入事件:描述用户输入动作的数据结构(如按键按下、鼠标移动等)
输入子系统:Linux内核中管理所有输入设备的框架
编程接口
开发者主要通过以下方式与输入系统交互:
-
设备文件:通常位于
/dev/input/
目录下,如:-
/dev/input/eventX
- 输入事件接口 -
/dev/input/mice
- 鼠标设备(传统接口)
-
-
系统调用:如
open()
,read()
,ioctl()
等 -
库支持:
-
libevdev - 直接与输入设备交互的库
-
Xlib/XCB - X Window系统的输入处理
-
Wayland协议 - 现代显示服务器的输入处理
-
典型编程流程
-
打开输入设备文件
-
读取输入事件(struct input_event)
-
解析并处理事件
-
关闭设备文件
输入事件结构
struct input_event {struct timeval time; // 时间戳__u16 type; // 事件类型(如EV_KEY, EV_REL等)__u16 code; // 事件代码(如KEY_A, REL_X等)__s32 value; // 事件值(如1表示按下,0表示释放)
};
电阻屏和电容屏
电阻屏
电阻屏原理
Linux 驱动程序中,会上报触点的 X、Y 数据,注意:这不是 LCD 的坐标值,需要 APP 再次处理才能转换为 LCD 坐标值。
电阻屏数据
对应的 input_event 结构体中,“type、code、value” 如下:
按下时:
EV_KEY BTN_TOUCH 1 /* 按下 /
EV_ABS ABS_PRESSURE 1 / 压力值,可以上报,也可以不报,可以是其他压力值 /
EV_ABS ABS_X x_value / X 坐标 /
EV_ABS ABS_Y x_value / Y 坐标 /
EV_SYNC 0 0 / 同步事件 */
松开时:
EV_ABS BTN_TOUCH 0 /* 松开 /
EV_ABS ABS_PRESSURE 0 / 压力值,可以上报,也可以不报 /
EV_SYNC 0 0 / 同步事件 */
电容屏
电容屏原理
电容屏数据
Type A
该类型不能分辨哪一个触电,已被淘汰
Type B
该类型的触摸屏能分辨是哪一个触点,上报数据时会先上报触点 ID,再上报它的数据。
具体例子如下,这是最简单的示例,使用场景分析来看看它上报的数据。
当有 2 个触点时(type, code, value):
EV_ABS ABS_MT_SLOT 0 // 这表示 “我要上报一个触点信息了”,用来分隔触点信息
EV_ABS ABS_MT_TRACKING_ID 45 // 这个触点的 ID 是 45
EV_ABS ABS_MT_POSITION_X x [0] // 触点 X 坐标
EV_ABS ABS_MT_POSITION_Y y [0] // 触点 Y 坐标
EV_ABS ABS_MT_SLOT 1 // 这表示 “我要上报一个触点信息了”,用来分隔触点信息
EV_ABS ABS_MT_TRACKING_ID 46 // 这个触点的 ID 是 46
EV_ABS ABS_MT_POSITION_X x [1] // 触点 X 坐标
EV_ABS ABS_MT_POSITION_Y y [1] // 触点 Y 坐标
EV_SYNC SYN_REPORT 0 // 全部数据上报完毕
当 ID 为 45 的触点正在移动时:
EV_ABS ABS_MT_SLOT 0 // 这表示 “我要上报一个触点信息了”,之前上报过 ID,就不用再上报 ID 了
EV_ABS ABS_MT_POSITION_X x [0] // 触点 X 坐标
EV_SYNC SYN_REPORT 0 // 全部数据上报完毕
松开 ID 为 45 的触点时 (在前面 slot 已经被设置为 0,这里这需要再重新设置 slot,slot 就像一个全局变量一样:如果它没变化的话,就无需再次设置):
// 刚刚设置了 ABS_MT_SLOT 为 0,它对应 ID 为 45,这里设置 ID 为 - 1 就表示 ID 为 45 的触点被松开了
EV_ABS ABS_MT_TRACKING_ID -1
EV_SYNC SYN_REPORT 0 // 全部数据上报完毕
最后,松开 ID 为 46 的触点:
EV_ABS ABS_MT_SLOT 1 // 这表示 “我要上报一个触点信息了”,在前面设置过 slot 1 的 ID 为 46
EV_ABS ABS_MT_TRACKING_ID -1 // ID 为 - 1,表示 slot 1 被松开,即 ID 为 46 的触点被松开
EV_SYNC SYN_REPORT 0 // 全部数据上报完毕
电容屏的实验数据
假设你的开发板上电容屏对应的设备节点是/dev/input/event0,执行以下命令
hexdump /dev/input/event0
然后用一个手指点击触摸屏,得到类似如下的数据:
0000000 878d 5e3d 6026 0001 0003 0039 0000 0000 ABS_MT_TRACKING_ID 0
0000010 878d 5e3d 6026 0001 0003 0035 0236 0000 ABS_MT_POSITION_X
0000020 878d 5e3d 6026 0001 0003 0036 0146 0000 ABS_MT_POSITION_Y
0000030 878d 5e3d 6026 0001 0003 0030 0015 0000 ABS_MT_TOUCH_MAJOR
0000040 878d 5e3d 6026 0001 0003 0032 0015 0000 ABS_MT_WIDTH_MAJOR
0000050 878d 5e3d 6026 0001 0001 014a 0001 0000 EV_KEY BTN_TOUCH 1
0000060 878d 5e3d 6026 0001 0003 0236 0000 ABS_X
0000070 878d 5e3d 6026 0001 0003 0146 0000 ABS_Y
0000080 878d 5e3d 6026 0001 0000 0000 0000 0000 EV_SYNC
0000090 878d 5e3d 5cd4 0002 0003 0039 ffff ffff ABS_MT_TRACKING_ID -1
00000a0 878d 5e3d 5cd4 0002 0001 014a 0000 0000 EV_KEY BTN_TOUCH 0
00000b0 878d 5e3d 5cd4 0002 0000 0000 0000 0000 EV_SYNC
在上面的数据中,为了兼容老程序,它也上报了 ABS_X、ABS_Y 数据,电阻触摸屏就是使用这类型的数据。所以基于电阻屏的程序,也可以用在电容屏上。
当用两个手指点击触摸屏,得到类似如下的数据:
0000000 8fb8 5e3d b2a9 0003 0003 002f 0000 0000 ABS_MT_SLOT 0
0000010 8fb8 5e3d b2a9 0003 0003 0039 0007 0000 ABS_MT_TRACKING_ID 7
0000020 8fb8 5e3d b2a9 0003 0003 0035 01d6 0000 ABS_MT_POSITION_X
0000030 8fb8 5e3d b2a9 0003 0003 0036 0e05 0000 ABS_MT_POSITION_Y
0000040 8fb8 5e3d b2a9 0003 0003 002f 0001 0000 ABS_MT_SLOT 1
0000050 8fb8 5e3d b2a9 0003 0003 0039 0008 0000 ABS_MT_TRACKING_ID 8
0000060 8fb8 5e3d b2a9 0003 0003 0035 0285 0000 ABS_MT_POSITION_X
0000070 8fb8 5e3d b2a9 0003 0003 0036 00fd 0000 ABS_MT_POSITION_Y
0000080 8fb8 5e3d b2a9 0003 0003 0030 0016 0000 ABS_MT_TOUCH_MAJOR
0000090 8fb8 5e3d b2a9 0003 0003 0032 0016 0000 ABS_MT_WIDTH_MAJOR
00000a0 8fb8 5e3d b2a9 0003 0001 014a 0001 0000 EV_KEY BTN_TOUCH 1
00000b0 8fb8 5e3d b2a9 0003 0003 0001 01d6 0000 ABS_X
00000c0 8fb8 5e3d b2a9 0003 0003 0001 0e05 0000 ABS_Y
00000d0 8fb8 5e3d b2a9 0003 0000 0000 0000 0000 EV_SYNC
00000e0 8fb8 5e3d 54c7 0004 0003 002f 0000 0000 ABS_MT_SLOT 0
00000f0 8fb8 5e3d 54c7 0004 0003 0039 ffff ffff ABS_MT_TRACKING_ID -1
0000100 8fb8 5e3d 54c7 0004 0003 002f 0001 0000 ABS_MT_SLOT 1
0000110 8fb8 5e3d 54c7 0004 0003 0039 ffff ffff ABS_MT_TRACKING_ID -1
0000120 8fb8 5e3d 54c7 0004 0001 014a 0000 0000 EV_KEY BTN_TOUCH 0
0000130 8fb8 5e3d 54c7 0004 0000 0000 0000 0000 EV_SYNC
为了兼容老程序,它也上报了 ABS_X、ABS_Y 数据,但是只上报第 1 个触点的数据。
tslib
tslib是一个触摸屏的开源库,可以使用它来访问触摸屏设备
tslib的主要代码如下:
src/ 接口函数ts_setup.cts_open.cts_config.c
plugins/ 插件 /modulelinear.cdejitter.cpthres.cinput-raw.c
tests/ 测试程序ts_test.cts_test_mt.cts_print.cts_print_mt.c
核心在于 “plugins” 目录里的 “插件”,或称为 “module”。这个目录下的每个文件都是一个 module,每个 module 都提供 2 个函数:read、read_mt,前者用于读取单点触摸屏的数据,后者用于读取多点触摸屏的数据。
编写基于tslib的测试程序
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>#include <linux/input.h>#include <sys/ioctl.h>#include <tslib.h>int distance(struct ts_sample_mt *point1, struct ts_sample_mt *point2)
{int x = point1->x - point2->x;int y = point1->y - point2->y;return x*x + y*y;
}int main(int argc, char **argv)
{struct tsdev *ts;int i;int ret;struct ts_sample_mt **samp_mt;struct ts_sample_mt **pre_samp_mt; int max_slots;int point_pressed[20];struct input_absinfo slot;int touch_cnt = 0;ts = ts_setup(NULL, 0);//打开配置设备if (!ts){printf("ts_setup err\n");return -1;}if (ioctl(ts_fd(ts), EVIOCGABS(ABS_MT_SLOT), &slot) < 0) {// 获取触摸屏支持的最大点数perror("ioctl EVIOGABS");ts_close(ts);return errno;}max_slots = slot.maximum + 1 - slot.minimum;// 计算最大槽位数(支持的最大触点数)// 分配当前采样和上一采样点的内存空间samp_mt = malloc(sizeof(struct ts_sample_mt *));if (!samp_mt) {ts_close(ts);return -ENOMEM;}samp_mt[0] = calloc(max_slots, sizeof(struct ts_sample_mt));if (!samp_mt[0]) {free(samp_mt);ts_close(ts);return -ENOMEM;}pre_samp_mt = malloc(sizeof(struct ts_sample_mt *));if (!pre_samp_mt) {ts_close(ts);return -ENOMEM;}pre_samp_mt[0] = calloc(max_slots, sizeof(struct ts_sample_mt));if (!pre_samp_mt[0]) {free(pre_samp_mt);ts_close(ts);return -ENOMEM;}for ( i = 0; i < max_slots; i++)pre_samp_mt[0][i].valid = 0;while (1){// 读取当前所有触点的状态ret = ts_read_mt(ts, samp_mt, max_slots, 1);if (ret < 0) {printf("ts_read_mt err\n");ts_close(ts);return -1;}// 更新有效触点数据到pre_samp_mt(保存上一帧数据)for (i = 0; i < max_slots; i++){if (samp_mt[0][i].valid){memcpy(&pre_samp_mt[0][i], &samp_mt[0][i], sizeof(struct ts_sample_mt));}}// 统计当前被按下的触点数量touch_cnt = 0;for (i = 0; i < max_slots; i++){if (pre_samp_mt[0][i].valid && pre_samp_mt[0][i].tracking_id != -1)point_pressed[touch_cnt++] = i;}// 如果有两个触点被按下,计算并输出它们之间的距离平方if (touch_cnt == 2){printf("distance: %08d\n", distance(&pre_samp_mt[0][point_pressed[0]], &pre_samp_mt[0][point_pressed[1]]));}}return 0;
}
ts_sample_mt结构体
struct ts_sample_mt { // 触摸点数据结构int x; // X坐标int y; // Y坐标int valid; // 数据是否有效int tracking_id; // 触点ID// ...其他字段
};
程序逻辑:
-
设备初始化:
-
ts_setup()
打开触摸屏设备 -
通过
ioctl
获取设备支持的最大触点数(max_slots
)
-
-
内存分配:
-
为当前帧(
samp_mt
)和上一帧(pre_samp_mt
)数据分配内存 -
使用
calloc
确保初始化为零
-
-
历史数据清零
for(i=0; i<max_slots; i++) pre_samp_mt[0][i].valid = 0;
主循环逻辑
-
数据采集:
-
ts_read_mt()
读取当前所有触点状态到samp_mt
-
-
数据更新:
-
将当前有效触点数据复制到
pre_samp_mt
(作为下一轮的"上一帧"数据)
-
-
触点统计:
-
遍历所有槽位,统计有效触点(
valid=1
且tracking_id!=-1
) -
记录被按下触点的索引到
point_pressed
数组
-
-
距离计算与输出:
-
当恰好两个触点被按下时:
-
计算两点间欧氏距离的平方
-
格式化输出:
distance: xxxxxxxx
-
-