uinput模拟触摸屏双指放大与缩小
前言
有时为了获取某种事件,需要使用uinput来创建一个虚拟的设备,通过先配置好设备属性,将预定好的input event序列写入/dev/uinput设备文件,便可在没有硬件设备的情况下获得所需设备的某个事件。
本文主要以分析代码的方式讲解如何利用uinput模拟触摸屏的双指放大与缩小,就像在手机上双指放大缩小高德地图那样。
另外:
需包含头文件 #include <linux/uinput.h> ,其他头文件根据需要添加
查看当前所有设备使用 sudo lsinput
实时获取某个设备发生的事件使用 sudo input-events 后接设备号
查看手机的输入设备 adb shell getevent -p
获取手机底层发送的事件,例如:adb shell getevent -l /dev/input/event2
涉及到文件的路径如下:
/usr/include/linux/input.h
/usr/include/linux/input-event-codes.h
/usr/include/linux/uinput.h
创建虚拟设备并配置属性
这部分的代码如下,最好将其写入独立的线程中,现在便开始分析
int fd, rc;
struct uinput_user_dev uidev;
...
int VirtualDevice::create_virtual_touchscreen()
{
//打开uinput文件
fd = open("/dev/uinput", O_WRONLY);
if(fd < 0) {
printf("Unable to open /dev/uinput\n");
return -1;
}
//配置设备属性
ioctl(fd, UI_SET_EVBIT, EV_ABS); //支持触摸
ioctl(fd, UI_SET_EVBIT, EV_SYN); //支持同步,用于report
//multitouch对应的属性,即input_event结构体中的type
ioctl(fd, UI_SET_ABSBIT, ABS_MT_SLOT); //支持几个触摸点
ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOUCH_MAJOR); //接触面直径大小,这里没用MINOR
ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_X); //x坐标
ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y); //y坐标
ioctl(fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID); //对某一次触摸的标记,按键码ID
ioctl(fd, UI_SET_ABSBIT, ABS_MT_PRESSURE); //触摸的压力
//对当前设备设置以上属性的数值范围
memset(&uidev, 0, sizeof(uidev));
snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "kydroid_touchscreen");
//以上 kydroid_touchscreen 为设备名
uidev.id.bustype = BUS_USB;
uidev.id.vendor = 0x1;
uidev.id.product = 0x1;
uidev.id.version = 1;
uidev.absmin[ABS_MT_POSITION_X] = 0;
uidev.absmax[ABS_MT_POSITION_X] = 1919;//坐标x的最大最小值,笔者的屏幕为显示器
uidev.absfuzz[ABS_MT_POSITION_X] = 0;
uidev.absflat[ABS_MT_POSITION_X] = 0;
uidev.absmin[ABS_MT_POSITION_Y] = 0;
uidev.absmax[ABS_MT_POSITION_Y] = 1199;//坐标y的最大最小值
uidev.absfuzz[ABS_MT_POSITION_Y] = 0;
uidev.absflat[ABS_MT_POSITION_Y] = 0;
uidev.absmin[ABS_MT_PRESSURE] = 0;
uidev.absmax[ABS_MT_PRESSURE] = 100;//触摸压力的最大最小值
uidev.absfuzz[ABS_MT_PRESSURE] = 0;
uidev.absflat[ABS_MT_PRESSURE] = 0;
uidev.absmax[ABS_MT_SLOT] = 9; //同时支持最多9个触点
uidev.absmax[ABS_MT_TOUCH_MAJOR] = 16; //与屏接触面的最大值
uidev.absmax[ABS_MT_TRACKING_ID] = 65535; //按键码ID累计叠加最大值
//利用以上属性创建设备
rc = write(fd, &uidev, sizeof(uidev));
if(rc != sizeof(uidev)){
printf("Unable to write uidev to fd\n");
return -1;
}
rc = ioctl(fd, UI_DEV_CREATE);
if(rc < 0){
printf("Unable to create UINPUT device.\n");
return -1;
}
while(1){
sleep(3); //使程序一直运行,否则退出后设备就不存在了
}
return 1;
}
写入单个事件
传入函数get_events_then_send()的参数依次为input_event结构体的type、code、value。
int VirtualDevice::get_events_then_send(int eventType, int eventCode, int eventValue)
{
struct input_event ev; //input_event定义在 /usr/include/linux/input.h
memset(&ev, 0, sizeof(struct input_event));
//将事件存入结构体
ev.type = eventType;
ev.code = eventCode;
ev.value = eventValue;
//写入到设备的事件文件/dev/uinput,在/dev/input/eventX文件中可查看
if (write(fd, &ev, sizeof(struct input_event)) < 0) {
char * mesg = strerror(errno);
printf("nibiru uinput errormag info :%s\n",mesg);
return -1;
}
return 1;
}
创建事件序列
可以先参考从手机中抓取出来的双指放大缩小的序列,命令为adb shell getevent -l /dev/input/event2,将后面的event2换成当前触摸屏对应的文件。下方代码模拟单次放大或缩小。
int id = 0;
...
void VirtualDevice::img_change(int type)
{
int x0 = 600, y0 = 600, x1 = 1200, y1 = 600; //两个触点落下的位置
//两个触点依次落下事件
usleep(20*1000);//事件间隔
id += 1;
this->get_events_then_send(EV_ABS, ABS_MT_SLOT, 0);
this->get_events_then_send(EV_ABS, ABS_MT_TRACKING_ID, id);
this->get_events_then_send(EV_ABS, ABS_MT_TOUCH_MAJOR, 4);
this->get_events_then_send(EV_ABS, ABS_MT_PRESSURE, 80);
this->get_events_then_send(EV_ABS, ABS_MT_POSITION_X, x0 );//落点x坐标
this->get_events_then_send(EV_ABS, ABS_MT_POSITION_Y, y0 );//落点y坐标
this->get_events_then_send(EV_SYN, SYN_REPORT, 0);//事件分批次上报,以使输出设备做出反应
usleep(1*1000);
id += 1;
this->get_events_then_send(EV_ABS, ABS_MT_SLOT, 1);
this->get_events_then_send(EV_ABS, ABS_MT_TRACKING_ID, id);
this->get_events_then_send(EV_ABS, ABS_MT_TOUCH_MAJOR, 5);
this->get_events_then_send(EV_ABS, ABS_MT_PRESSURE, 80);
this->get_events_then_send(EV_ABS, ABS_MT_POSITION_X, x1 );
this->get_events_then_send(EV_ABS, ABS_MT_POSITION_Y, y1 );
this->get_events_then_send(EV_SYN, SYN_REPORT, 0);
//两个触点分别左右移动实现放大或缩小
usleep(20*1000);
for (int i=0;i<50;i++){
if (type == 0){ //放大
x0 -= 3;
x1 += 3;
}else { //缩小
x0 += 3;
x1 -= 3;
}
//移动过后,两个触点所处的状态
usleep(40*1000);
this->get_events_then_send(EV_ABS, ABS_MT_SLOT, 0);
this->get_events_then_send(EV_ABS, ABS_MT_PRESSURE, 80);
this->get_events_then_send(EV_ABS, ABS_MT_POSITION_X, x0 );
this->get_events_then_send(EV_SYN, SYN_REPORT, 0);
this->get_events_then_send(EV_ABS, ABS_MT_SLOT, 1);
this->get_events_then_send(EV_ABS, ABS_MT_PRESSURE, 80);
this->get_events_then_send(EV_ABS, ABS_MT_POSITION_X, x1 );
this->get_events_then_send(EV_SYN, SYN_REPORT, 0);
}
//两指依次离开屏幕
usleep(10*1000);
this->get_events_then_send(EV_ABS, ABS_MT_SLOT, 1);
this->get_events_then_send(EV_ABS, ABS_MT_PRESSURE, 0);//压力变为0
this->get_events_then_send(EV_ABS, ABS_MT_POSITION_X, x1 );
this->get_events_then_send(EV_SYN, SYN_REPORT, 0);
this->get_events_then_send(EV_ABS, ABS_MT_TRACKING_ID, -1);//-1表示抬起
this->get_events_then_send(EV_SYN, SYN_REPORT, 0);
usleep(10*1000);
this->get_events_then_send(EV_ABS, ABS_MT_SLOT, 0);
this->get_events_then_send(EV_ABS, ABS_MT_PRESSURE, 0);
this->get_events_then_send(EV_ABS, ABS_MT_POSITION_X, x0 );
this->get_events_then_send(EV_SYN, SYN_REPORT, 0);
this->get_events_then_send(EV_ABS, ABS_MT_TRACKING_ID, -1);
this->get_events_then_send(EV_SYN, SYN_REPORT, 0);
}
注意:不同设备的事件序列可能不同,如果程序运行没有反应,可以更改一下上方的序列。
程序运行过程
新建线程执行create_virtual_touchscreen(),使设备一直存在。调用img_change(int type),其中type值为0时模拟放大,为1时模拟缩小。img_change()中按事件序列依次调用get_events_then_send()来写入事件,事件写入后输出设备会做出反应。这样根据type的值每执行一次img_change(),以高德地图为例,可以看到地图的放大或缩小。
uinput模拟触摸屏双指放大与缩小的内容就讲解到这里了,运行程序时看到窗口画面放大缩小,是不是很有趣呢。