操作系统有很多问题出现在触摸事件上,而平时自己手上只有鼠标控制,突发奇想,如果我将鼠标事件虚拟成触摸,那么我复现问题,调试代码不是很方便么。
uinput是用户空间的输入设备驱动,它提供了一种功能将内核的输入设备搬到应用层来模拟。内核通过如下配置打开
CONFIG_INPUT_UINPUT=y
鉴于我们的系统目前默认是x11环境,所以我们需要使用xlib的XQueryPointer函数,此函数可以获取X下的鼠标指针位置,XQueryPointer的原型如下
Bool XQueryPointer(Display *display, Window w, Window *root_return, Window *child_return, int *root_x_return, int *root_y_return, int *win_x_return, int *win_y_return, unsigned int *mask_return);
根据上面的信息,如果我们声明uinput是一个输入设备,然后通过XQueryPointer拿到鼠标在X的位置后,将其传给uinput,那么我们在系统中就白白获得了一个虚假的触摸设备。代码如下
#include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <fcntl.h> #include <unistd.h> #include <linux/uinput.h> #include <X11/Xlib.h> void emit(int fd, int type, int code, int val) { struct input_event ie; ie.type = type; ie.code = code; ie.value = val; ie.time.tv_sec = 0; ie.time.tv_usec = 0; write(fd, &ie, sizeof(ie)); } int main(void) { struct uinput_setup usetup; struct uinput_abs_setup uabs; int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); ioctl(fd, UI_SET_EVBIT, EV_KEY); ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH); ioctl(fd, UI_SET_EVBIT, EV_REL); ioctl(fd, UI_SET_RELBIT, REL_X); ioctl(fd, UI_SET_RELBIT, REL_Y); ioctl(fd, UI_SET_EVBIT, EV_ABS); ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_X); ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y); ioctl(fd, UI_SET_ABSBIT, ABS_MT_SLOT); ioctl(fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID); memset(&usetup, 0, sizeof(usetup)); usetup.id.bustype = BUS_USB; usetup.id.vendor = 0x1234; usetup.id.product = 0x5678; strcpy(usetup.name, "kylin virtual touch device"); ioctl (fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT); memset(&uabs, 0, sizeof(uabs)); uabs.code = ABS_MT_SLOT; uabs.absinfo.minimum = 0; uabs.absinfo.maximum = 9; ioctl(fd, UI_ABS_SETUP, &uabs); memset(&uabs, 0, sizeof(uabs)); uabs.code = ABS_MT_POSITION_X; uabs.absinfo.minimum = 0; uabs.absinfo.maximum = 1920; uabs.absinfo.resolution= 76; ioctl(fd, UI_ABS_SETUP, &uabs); memset(&uabs, 0, sizeof(uabs)); uabs.code = ABS_MT_POSITION_Y; uabs.absinfo.minimum = 0; uabs.absinfo.maximum = 1200; uabs.absinfo.resolution= 106; ioctl(fd, UI_ABS_SETUP, &uabs); ioctl(fd, UI_DEV_SETUP, &usetup); ioctl(fd, UI_DEV_CREATE); Display *display; Window root; Window child; int root_x, root_y, win_x, win_y; unsigned int mask; display = XOpenDisplay(NULL); if (display == NULL) { return 1; } root = DefaultRootWindow(display); /* * On UI_DEV_CREATE the kernel will create the device node for this * device. We are inserting a pause here so that userspace has time * to detect, initialize the new device, and can start listening to * the event, otherwise it will not notice the event we are about * to send. This pause is only needed in our example code! */ sleep(1); int mouse_fd = open("/dev/input/event4", O_RDONLY); if (mouse_fd < 0) { ioctl(fd, UI_DEV_DESTROY); close(fd); return EXIT_FAILURE; } while (1) { struct input_event ie; read(mouse_fd, &ie, sizeof(ie)); if (ie.type == EV_REL && (ie.code == REL_X || ie.code == REL_Y)) { continue; } switch (ie.code){ case BTN_LEFT: if(ie.value==1){ XQueryPointer(display, root, &root, &child, &root_x, &root_y, &win_x, &win_y, &mask); emit(fd, EV_ABS, ABS_MT_TRACKING_ID, 1); emit(fd, EV_ABS, ABS_MT_POSITION_X, root_x); emit(fd, EV_ABS, ABS_MT_POSITION_Y, root_y); emit(fd, EV_KEY, BTN_TOUCH, 1); emit(fd, EV_SYN, SYN_REPORT, 0); }else{ emit(fd, EV_ABS, ABS_MT_TRACKING_ID, -1); emit(fd, EV_KEY, BTN_TOUCH, 0); emit(fd, EV_SYN, SYN_REPORT, 0); } default: break; } } /* * Give userspace some time to read the events before we destroy the * device with UI_DEV_DESTOY. */ sleep(1); XCloseDisplay(display); ioctl(fd, UI_DEV_DESTROY); close(fd); return 0; }
值得注意的是,因为我们插入的鼠标的事件是不太确定的,所以需要提前手动查询一下,如下
先通过lsusb查看鼠标设备
# lsusb ...... Bus 001 Device 003: ID 25a7:fa61 Compx 2.4G Receiver
然后通过proc下的信息找到对应的事件
# cat /proc/bus/input/devices ...... I: Bus=0003 Vendor=25a7 Product=fa61 Version=0110 N: Name="Compx 2.4G Receiver Mouse" P: Phys=usb-fc800000.usb-1.2/input1 S: Sysfs=/devices/platform/fc800000.usb/usb1/1-1/1-1.2/1-1.2:1.1/0003:25A7:FA61.0002/input/input4 U: Uniq= H: Handlers=event4 B: PROP=0 B: EV=17 B: KEY=1f0000 0 0 0 0 B: REL=1943 B: MSC=10
此时我们知道事件是event4,那么代码填入的就是event4如下
int mouse_fd = open("/dev/input/event4", O_RDONLY);
此时编译上述代码如下
gcc mouse2touch.c -lX11 -pthread -o mouse2touch
如果我们在机器内运行,因为会XOpenDisplay,所以需要DISPLAY的环境变量设置正确,默认可以设置如下
export DISPLAY=:0
此时直接运行即可
root@kylin:~# ./mouse2touch
注意此程序是daemon进程,会while 1监听事件,如果突然退出需要检查是否有其他异常
当程序运行后,我们看到内核会有如下日志
input: kylin virtual touch device as /devices/virtual/input/input28
然后我们查看事件也能在/proc/bus/input/devices存在如下
I: Bus=0003 Vendor=1234 Product=5678 Version=0000 N: Name="kylin virtual touch device" P: Phys= S: Sysfs=/devices/virtual/input/input28 U: Uniq= H: Handlers=event18 B: PROP=2 B: EV=f B: KEY=400 0 0 0 0 0 B: REL=3 B: ABS=260800000000000
同样的Xorg也能正常识别,所以Xorg也有如下日志
tail -f /var/log/Xorg.0.log (II) event18 - kylin virtual touch device: is tagged by udev as: Touchscreen (II) event18 - kylin virtual touch device: device is a touch device
正好对应上了。此时我们点击鼠标,可以看到有触摸的特效出现
可以发现功能得到实现了,至此已经完全满足我在不需要触摸屏的前提下调试触摸的操作系统BUG了。
上面通过编写了一个小程序,实现了鼠标模拟触摸的情况,但是还是有一些小缺点的,主要如下
根据上面的信息,我简单的利用了uinput来辅助调试操作系统触摸问题,稍微扩展一下,我留个疑问,有兴趣可以思考一下。