uinput模拟触摸屏双指放大与缩小

  • Post author:
  • Post category:其他




前言

有时为了获取某种事件,需要使用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模拟触摸屏双指放大与缩小的内容就讲解到这里了,运行程序时看到窗口画面放大缩小,是不是很有趣呢。



版权声明:本文为qq_33594636原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。