frameworks 之getEvent指令
通过 getEvent 可以识别按键基本命令和里面的关键信息
涉及到的类如下
- system/core/toolbox/toolbox.c
- system/core/toolbox/tools.h
- system/core/toolbox/getevent.c
指令解析
通过 getEvent -h 可打印出相关的指令帮助,对应的指令注释如下。输出的数字都为16进制。
houji:/ $ getevent -help
Usage: getevent [-t] [-n] [-s switchmask] [-S] [-v [mask]] [-d] [-p] [-i] [-l] [-q] [-c count] [-r] [device]
-t: show time stamps // 打印时间戳
-n: don't print newlines // 不换行
-s: print switch states for given bits // 打印指定位的开关状态
-S: print all switch states // 打印所有开关状态
-v: verbosity mask (errs=1, dev=2, name=4, info=8, vers=16, pos. events=32, props=64) // 根据 mask 值显示相关信息,执行后会一直显示上报数据,详细掩码(错误=1,dev=2,名称=4,信息=8,错误=16,位置事件=32,道具=64)默认显示 dev| name| info| vers = 30
-d: show HID descriptor, if available // 如果设备可用,显示设备隐藏的描述信息
-p: show possible events (errs, dev, name, pos. events) // 显示设备支持的事件类型和编码方式
-i: show all device info and possible events // 显示设备的所有信息和支持的事件,比 -p 显示更多信息
-l: label event types and names in plain text // 以文本形式输出事件类型和名称,比 -t 更清楚直观
-q: quiet (clear verbosity mask) // 以文本形式输出事件类型和名称,比 -t 更清楚直观
-c: print given number of events then exit // 打印固定数量的事件并退出
-r: print rate events are received // 显示事件上报速率
其中 -v 打印输入设备的详细信息,默认为 默认显示 dev| name| info| vers = 30。
emulator_x86_64:/ # getevent -v
add device 1: /dev/input/event0
bus: 0019
vendor 0000
product 0001
version 0000
name: "Power Button"
location: "LNXPWRBN/button/input0"
id: ""
version: 1.0.1
-l 打印的信息 分别对应 type code value。
emulator_x86_64:/ # getevent -l
/dev/input/event3: EV_ABS ABS_MT_TRACKING_ID 00000000
/dev/input/event3: EV_ABS ABS_MT_POSITION_X 000031af
/dev/input/event3: EV_ABS ABS_MT_POSITION_Y 00004447
type 值分为 3种类型
- EV_SYN 表示同步事件 有开始(不必要)和结束,对应的 code为 分为以下几点
对应的code:。
0004:代表一个事件开始(不必要)
0005:代表一个事件开始(不必要)
SYN_REPORT:代表一个事件的结束 (必要)
看到 SYN_REPORT 代表一个事件系列的结束 - EV_ABS 表示绝对坐标类型事件
对应的coe 如下
ABS_MT_SLOT:代表不同手指,它的value代表手指id,当有多指触碰的时候会出现。
ABS_MT_TRACKING_ID: 代表对应的ID,每次都会在上一次基础+1,一个非负数的表示一次接触。当为-1时候,表示这是一个无用的slot(可表示为这一次结束的结束,抬起, value 为 ffffffff)。
ABS_MT_POSITION_X,ABS_MT_POSITION_Y: 相对于屏幕中心的x,y坐标
ABS_MT_TOUCH_MAJORL: 接触部分的长轴长度。相当于椭圆的长轴,按压的接触长度。
ABS_MT_TOUCH_MINOR:接触部分的短轴长度。相当于椭圆的短轴
ABS_MT_PRESSURE:代表按下压力,有的设备不一定有 - EV_KEY 表示按键类型事件(包含手指触摸)
对应的coe 如下
BTN_TOUCH: 触碰按键。value 是DOWN或者UP
TN_TOOL_FINGER:按键的是finger,并且其值也是DOWN或者UP
KEY_POWER…: 各种硬按键名称
示例以及解释如下
[ 1959159.438822] /dev/input/event7: EV_ABS ABS_MT_SLOT 00000000 //第1个手指
[ 1959159.438822] /dev/input/event7: EV_ABS ABS_MT_TRACKING_ID 0005cfc3 //第1个手指对应的TRACKING_ID
[ 1959159.438822] /dev/input/event7: EV_KEY BTN_TOUCH DOWN //触摸按下事件
[ 1959159.438822] /dev/input/event7: EV_KEY BTN_TOOL_FINGER DOWN //触摸按下
[ 1959159.438822] /dev/input/event7: EV_ABS ABS_MT_POSITION_X 00007346 //对应x坐标
[ 1959159.438822] /dev/input/event7: EV_ABS ABS_MT_POSITION_Y 00034396 //对应y坐标
[ 1959159.438822] /dev/input/event7: EV_SYN SYN_REPORT 00000000 //事件结束
[ 1959160.905443] /dev/input/event7: EV_ABS ABS_MT_POSITION_X 00007382 //第1个手指的坐标
[ 1959160.905443] /dev/input/event7: EV_ABS ABS_MT_POSITION_Y 000343c8 //第1个手指的坐标
[ 1959160.905443] /dev/input/event7: EV_ABS ABS_MT_SLOT 00000001 //第2个手指
[ 1959160.905443] /dev/input/event7: EV_ABS ABS_MT_TRACKING_ID 0005cfc4 //第2个手指对应的TRACKING_ID
[ 1959160.905443] /dev/input/event7: EV_ABS ABS_MT_POSITION_X 00014ae6 //第2个手指的坐标
[ 1959160.905443] /dev/input/event7: EV_ABS ABS_MT_POSITION_Y 00030962 //事件结束
[ 1959160.905443] /dev/input/event7: EV_SYN SYN_REPORT 00000000 rate 123
[ 1959160.913862] /dev/input/event7: EV_ABS ABS_MT_SLOT 00000000 //切换到第1个手指的坐标
[ 1959160.913862] /dev/input/event7: EV_ABS ABS_MT_POSITION_X 00007396
[ 1959160.913862] /dev/input/event7: EV_ABS ABS_MT_POSITION_Y 000343d2
[ 1959160.913862] /dev/input/event7: EV_SYN SYN_REPORT 00000000 rate 118
[ 1959160.922049] /dev/input/event7: EV_ABS ABS_MT_POSITION_X 000073aa //第1个手指的坐标束
[ 1959160.922049] /dev/input/event7: EV_ABS ABS_MT_POSITION_Y 000343e6
[ 1959160.922049] /dev/input/event7: EV_SYN SYN_REPORT 00000000 rate 122
[ 1959162.928588] /dev/input/event7: EV_ABS ABS_MT_TRACKING_ID ffffffff //为负数,表示第1个手指结束抬起
[ 1959162.928588] /dev/input/event7: EV_SYN SYN_REPORT 00000000 rate 0 //事件结束
[ 1959164.133957] /dev/input/event7: EV_ABS ABS_MT_SLOT 00000001 //第二个手指
[ 1959164.133957] /dev/input/event7: EV_ABS ABS_MT_TRACKING_ID ffffffff // 为负数表示第二个手指抬起
[ 1959164.133957] /dev/input/event7: EV_KEY BTN_TOUCH UP
[ 1959164.133957] /dev/input/event7: EV_KEY BTN_TOOL_FINGER UP
[ 1959164.133957] /dev/input/event7: EV_SYN SYN_REPORT 00000000 rate 0 //事件结
参数可以组合使用,一次性查看需要的触摸屏信息,常用的命令组合为 getevent -ltr
emulator_x86_64:/ # getevent -ltr
[ 1384.965200] /dev/input/event3: EV_ABS ABS_MT_TRACKING_ID 00000000
源码追溯
可以执行, 则表示 system/bin 有对应的脚本。所以可以通过执行对应的打印 查看文件。通过命令 ls -l | grep event 过滤对应的关键字可以看到 该事件通过 软链接(前面开头为l开头即为软链接)。链接到 toolbox。
emulator_x86_64:/system/bin # ls -l | grep event
lrwxr-xr-x 1 root shell 7 2024-08-17 22:51 getevent -> toolbox
-rwxr-xr-x 1 root shell 3925 2024-03-18 00:26 mm_events
lrwxr-xr-x 1 root shell 6 2024-08-17 22:51 sendevent -> toybox
lrwxr-xr-x 1 root shell 4 2024-08-17 22:51 ueventd -> init
回到源码文件,一般工具类 在 system 目录下,可以先通过该目录查找。执行 find -name toolbox。可以看到在该**./core/toolbox** 文件夹下。
跳转到该文件夹下,查看对应的文件打印,查看对应的Bp文件。包含以下文件,可以看出入口为
toolbox.c 文件夹。
cc_defaults {
...
srcs: [
"toolbox.c",
"getevent.c",
"getprop.cpp",
"modprobe.cpp",
"setprop.cpp",
"start.cpp",
],
...
}
对应的 main 入口如下,该方法通过读取参数,遍历数组 ,然后判断名称和命令是否相等,相等就执行对应的 func 方法。对应 tools[] 数组 在文件前面定义,定义了名词和执行的方法。
会根据 name 拼接对应的执行方法。如 getEvent 拼接完为 getevent_main。相关数组定义在 tools.h 头文件里面。
// system/core/toolbox/tools.c
#define TOOL(name) int name##_main(int, char**); // 拼接对应的执行方法名称
#include "tools.h"
#undef TOOL
// 定义对应的数组,数组内容来自 tools.h定义
static struct {
const char* name;
int (*func)(int, char**);
} tools[] = {
#define TOOL(name) { #name, name##_main },
#include "tools.h"
#undef TOOL
{ 0, 0 },
};
int main(int argc, char** argv) {
// Let's assume that none of this code handles broken pipes. At least ls,
// ps, and top were broken (though I'd previously added this fix locally
// to top). We exit rather than use SIG_IGN because tools like top will
// just keep on writing to nowhere forever if we don't stop them.
signal(SIGPIPE, SIGPIPE_handler);
// 获取第一个参数,遍历数组,判断是否和名称相等,相等执行对应的方法。
char* cmd = strrchr(argv[0], '/');
char* name = cmd ? (cmd + 1) : argv[0];
for (size_t i = 0; tools[i].name; i++) {
if (!strcmp(tools[i].name, name)) {
return tools[i].func(argc, argv);
}
}
printf("%s: no such tool\n", argv[0]);
return 127;
}
头文件定义
// system/core/toolbox/tools.h
TOOL(getevent)
TOOL(getprop)
TOOL(modprobe)
TOOL(setprop)
TOOL(start)
TOOL(stop)
TOOL(toolbox)
所以运行脚本 执行的方法 在 getevent.c 下的 getevent_main方法下。
源码解析
1.解析参数
查看对应的方法 main 方法,可以看到 第一步 通过 getopt 解析对应的参数
部分参数通过 或 记录到 print_flags 中。
opterr = 0;
do {
c = getopt(argc, argv, "tns:Sv::dpilqc:rh"); // 解析对应的参数
if (c == EOF)
break;
switch (c) {
case 't':
get_time = 1;
break;
case 'n':
newline = "";
break;
case 's':
get_switch = strtoul(optarg, NULL, 0);
if(dont_block == -1)
dont_block = 1;
break;
case 'S':
get_switch = ~0;
if(dont_block == -1)
dont_block = 1;
break;
case 'v':
if(optarg)
print_flags |= strtoul(optarg, NULL, 0);
else
print_flags |= PRINT_DEVICE | PRINT_DEVICE_NAME | PRINT_DEVICE_INFO | PRINT_VERSION;
print_flags_set = 1;
break;
case 'd':
print_flags |= PRINT_HID_DESCRIPTOR;
break;
case 'p':
print_flags |= PRINT_DEVICE_ERRORS | PRINT_DEVICE
| PRINT_DEVICE_NAME | PRINT_POSSIBLE_EVENTS | PRINT_INPUT_PROPS;
print_flags_set = 1;
if(dont_block == -1)
dont_block = 1;
break;
case 'i':
print_flags |= PRINT_ALL_INFO;
print_flags_set = 1;
if(dont_block == -1)
dont_block = 1;
break;
case 'l':
print_flags |= PRINT_LABELS;
break;
case 'q':
print_flags_set = 1;
break;
case 'c':
event_count = atoi(optarg); //解析对应参数值
dont_block = 0;
break;
case 'r':
sync_rate = 1;
break;
case '?':
fprintf(stderr, "%s: invalid option -%c\n",
argv[0], optopt);
case 'h':
usage(argv[0]); // 打印帮助
exit(1);
}
} while (1);
if(dont_block == -1)
dont_block = 0;
if (optind + 1 == argc) {
device = argv[optind];
optind++;
}
if (optind != argc) {
usage(argv[0]);
exit(1);
}
2.初始化ufds数组
第二步,会通过 device 判断是否 观察单个或者全部 。如如果我们 执行的命令后面加了对应的设备,则只会观察对应的设备 如 getevent -ltr /dev/input/event1。其中 inotify_init 获取了 dev/input 文件夹对应的监听。 然后通过 inotify_add_watch。进行删除和添加的监听。
nfds = 1;
ufds = calloc(1, sizeof(ufds[0]));
ufds[0].fd = inotify_init();
ufds[0].events = POLLIN;
// 打印单个设备对应信息
if(device) {
if(!print_flags_set)
print_flags |= PRINT_DEVICE_ERRORS;
// 通过 open 方法打开对应的驱动,并通过 ioctl 获取对应的信息,并放到对应的 ufds 数组
res = open_device(device, print_flags);
if(res < 0) {
return 1;
}
} else { // 打印全部
if(!print_flags_set)
print_flags |= PRINT_DEVICE_ERRORS | PRINT_DEVICE | PRINT_DEVICE_NAME;
print_device = 1;
// 添加删除和添加对应文件夹的监听
res = inotify_add_watch(ufds[0].fd, device_path, IN_DELETE | IN_CREATE);
if(res < 0) {
fprintf(stderr, "could not add watch for %s, %s\n", device_path, strerror(errno));
return 1;
}
// 遍历该文件夹 dev/input 文件夹下所有的
res = scan_dir(device_path, print_flags);
if(res < 0) {
fprintf(stderr, "scan dir failed for %s\n", device_path);
return 1;
}
}
没指定对应的设备的会 将所有添加到数组中,通过 scan_dir 方法。遍历文件夹下。先通过 opendir,打开文件夹,然后 通过 readdir进行遍历。遍历到符合的 在通过 open_device 将fd 添加到 数组中。
static int scan_dir(const char *dirname, int print_flags)
{
char devname[PATH_MAX];
char *filename;
DIR *dir;
struct dirent *de;
// // 打开对应的文件夹
dir = opendir(dirname);
if(dir == NULL)
return -1;
strcpy(devname, dirname);
filename = devname + strlen(devname);
*filename++ = '/';
while((de = readdir(dir))) {
if(de->d_name[0] == '.' &&
(de->d_name[1] == '\0' ||
(de->d_name[1] == '.' && de->d_name[2] == '\0')))
continue;
strcpy(filename, de->d_name);
// 将fd 添加到 数组
open_device(devname, print_flags);
}
closedir(dir);
return 0;
}
最终都是通过 open_device 添加到数组,查看对应的方法,可以看到该方法通过 open 打开对应的节点,在通过 ioctl 获取对应的信息,并根据 设置的 print_flags 按需打印对应信息。最终在存放到 ufds 数组中。
static int open_device(const char *device, int print_flags)
{
int version;
int fd;
int clkid = CLOCK_MONOTONIC;
struct pollfd *new_ufds;
char **new_device_names;
char name[80];
char location[80];
char idstr[80];
struct input_id id;
// 打开这个节点
fd = open(device, O_RDONLY | O_CLOEXEC);
if(fd < 0) {
if(print_flags & PRINT_DEVICE_ERRORS)
fprintf(stderr, "could not open %s, %s\n", device, strerror(errno));
return -1;
}
// 获取相关的信息
if(ioctl(fd, EVIOCGVERSION, &version)) {
if(print_flags & PRINT_DEVICE_ERRORS)
fprintf(stderr, "could not get driver version for %s, %s\n", device, strerror(errno));
return -1;
}
if(ioctl(fd, EVIOCGID, &id)) {
if(print_flags & PRINT_DEVICE_ERRORS)
fprintf(stderr, "could not get driver id for %s, %s\n", device, strerror(errno));
return -1;
}
name[sizeof(name) - 1] = '\0';
location[sizeof(location) - 1] = '\0';
idstr[sizeof(idstr) - 1] = '\0';
if(ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name) < 1) {
//fprintf(stderr, "could not get device name for %s, %s\n", device, strerror(errno));
name[0] = '\0';
}
if(ioctl(fd, EVIOCGPHYS(sizeof(location) - 1), &location) < 1) {
//fprintf(stderr, "could not get location for %s, %s\n", device, strerror(errno));
location[0] = '\0';
}
if(ioctl(fd, EVIOCGUNIQ(sizeof(idstr) - 1), &idstr) < 1) {
//fprintf(stderr, "could not get idstring for %s, %s\n", device, strerror(errno));
idstr[0] = '\0';
}
if (ioctl(fd, EVIOCSCLOCKID, &clkid) != 0) {
fprintf(stderr, "Can't enable monotonic clock reporting: %s\n", strerror(errno));
// a non-fatal error
}
new_ufds = realloc(ufds, sizeof(ufds[0]) * (nfds + 1));
if(new_ufds == NULL) {
fprintf(stderr, "out of memory\n");
return -1;
}
ufds = new_ufds;
new_device_names = realloc(device_names, sizeof(device_names[0]) * (nfds + 1));
if(new_device_names == NULL) {
fprintf(stderr, "out of memory\n");
return -1;
}
device_names = new_device_names;
if(print_flags & PRINT_DEVICE)
printf("add device %d: %s\n", nfds, device);
if(print_flags & PRINT_DEVICE_INFO)
printf(" bus: %04x\n"
" vendor %04x\n"
" product %04x\n"
" version %04x\n",
id.bustype, id.vendor, id.product, id.version);
if(print_flags & PRINT_DEVICE_NAME)
printf(" name: \"%s\"\n", name);
if(print_flags & PRINT_DEVICE_INFO)
printf(" location: \"%s\"\n"
" id: \"%s\"\n", location, idstr);
if(print_flags & PRINT_VERSION)
printf(" version: %d.%d.%d\n",
version >> 16, (version >> 8) & 0xff, version & 0xff);
if(print_flags & PRINT_POSSIBLE_EVENTS) {
print_possible_events(fd, print_flags);
}
if(print_flags & PRINT_INPUT_PROPS) {
print_input_props(fd);
}
if(print_flags & PRINT_HID_DESCRIPTOR) {
print_hid_descriptor(id.bustype, id.vendor, id.product);
}
// 将 fd 存放到ufds 数组中
ufds[nfds].fd = fd;
ufds[nfds].events = POLLIN;
// 放到 device_names 数组中
device_names[nfds] = strdup(device);
nfds++;
return 0;
}
3.添加到poll 并做对应处理
添加完数组后,通过 poll 等待消息的监听。poll 和 epoll 是一样的。但是 epoll 更加高效。因为 epoll 是监听到消息 塞到 events 数组并返回 数量。后续遍历变化的即可。但是 poll 是无论如何都要遍历全部数组 找到对应的变化,没 epoll 高效。
当有新消息来后,则做对应的处理。数组第一个为文件夹 ,所以对文件夹做单独的处理。
通过 read_notify 方法。添加对应添加和删除,添加则调用 open_device,删除则调用 close_device 移除对应的监听。
因为第0个已处理,所以下面的遍历从下标1开始。遍历到变化时候, 通过 read 读取对应信息到 变量 event 中(input_event),最后根据设置的 falg 打印相对于的信息格式。
while(1) {
//int pollres =
// 通过 poll 等待消息的监听
poll(ufds, nfds, -1);
//printf("poll %d, returned %d\n", nfds, pollres);
// 第1个是文件夹的变化,对文件夹内容的监听做单独的处理
if(ufds[0].revents & POLLIN) {
read_notify(device_path, ufds[0].fd, print_flags);
}
// 因为第0个已处理,所以下面的遍历从下标1开始
for(i = 1; i < nfds; i++) {
if(ufds[i].revents) {
// 判断到有变化
if(ufds[i].revents & POLLIN) {
// 读取对应的内容到变量 event中,为 input_event 结构体
res = read(ufds[i].fd, &event, sizeof(event));
if(res < (int)sizeof(event)) {
fprintf(stderr, "could not get event\n");
return 1;
}
if(get_time) {
printf("[%8ld.%06ld] ", event.time.tv_sec, event.time.tv_usec);
}
if(print_device)
printf("%s: ", device_names[i]);
// 传进去 值,根据 对应的 print_flags 做打印
print_event(event.type, event.code, event.value, print_flags);
if(sync_rate && event.type == 0 && event.code == 0) {
int64_t now = event.time.tv_sec * 1000000LL + event.time.tv_usec;
if(last_sync_time)
printf(" rate %lld", 1000000LL / (now - last_sync_time));
last_sync_time = now;
}
printf("%s", newline);
if(event_count && --event_count == 0)
return 0;
}
}
}
}