手机中的屏幕触摸事件是通过驱动将事件上报到/dev/input设备上,然后被input模块读取发送到APP
如果我没有物理的屏幕但我想发出触摸事件怎么办?通过Linux的uinput模块就可以不需要写驱动代码就能模拟一块触摸屏,当然我们也可以模拟出虚拟鼠标和键盘
本文讨论的是模拟触摸屏,鼠标和键盘比较easy
前提准备:getevent命令使用
1通过adb shell getevent -p 查看设备的输入设备
/dev/input/event0...n就是输入设备 KEY一般对应的是键值Keycode 一般指设备上的物理按键和虚拟按键ABS代表绝对值得意思
2:adb shell getevent -l /dev/input/event1 获得该设备底层发送的事件,我们看一下手机屏幕被触摸后getevent获得的事件
我们可以看到 有一系列的按键码被发出组合成onActionDown-->onActionMove-->onActionUp事件流
到此我们要做的就是创建一个/dev/input/eventX代表虚拟屏幕, 然后发出按键码形成触摸事件送到input模块发给APP
一:通过uinput创建虚拟屏幕
//创建触摸屏 通过systemproperty保存fd
static void createTouchScreen()
{
static int uinp_fd;
struct uinput_user_dev uinp;
struct input_event event;
uinp_fd = open("/dev/uinput", O_WRONLY|O_NONBLOCK);
if(uinp_fd == 0) {
ALOGD("Unable to open /dev/uinput\n");
return;
}
// configure touch device event properties
memset(&uinp, 0, sizeof(uinp));
//设备的别名
strncpy(uinp.name, "ShaoTouchScreen", UINPUT_MAX_NAME_SIZE);
uinp.id.version = 4;
uinp.id.bustype = BUS_USB;
uinp.absmin[ABS_MT_SLOT] = 0;
uinp.absmax[ABS_MT_SLOT] = 9; // MT代表multi touch 多指触摸 最大手指的数量我们设置9
uinp.absmin[ABS_MT_TOUCH_MAJOR] = 0;
uinp.absmax[ABS_MT_TOUCH_MAJOR] = 15;
uinp.absmin[ABS_MT_POSITION_X] = 0; // 屏幕最小的X尺寸
uinp.absmax[ABS_MT_POSITION_X] = 1020; // 屏幕最大的X尺寸
uinp.absmin[ABS_MT_POSITION_Y] = 0; // 屏幕最小的Y尺寸
uinp.absmax[ABS_MT_POSITION_Y] = 1020; //屏幕最大的Y尺寸
uinp.absmin[ABS_MT_TRACKING_ID] = 0;
uinp.absmax[ABS_MT_TRACKING_ID] = 65535;//按键码ID累计叠加最大值
uinp.absmin[ABS_MT_PRESSURE] = 0;
uinp.absmax[ABS_MT_PRESSURE] = 255; //屏幕按下的压力值
// Setup the uinput device
ioctl(uinp_fd, UI_SET_EVBIT, EV_KEY); //该设备支持按键
ioctl(uinp_fd, UI_SET_EVBIT, EV_REL); //支持鼠标
// Touch
ioctl (uinp_fd, UI_SET_EVBIT, EV_ABS); //支持触摸
ioctl (uinp_fd, UI_SET_ABSBIT, ABS_MT_SLOT);
ioctl (uinp_fd, UI_SET_ABSBIT, ABS_MT_TOUCH_MAJOR);
ioctl (uinp_fd, UI_SET_ABSBIT, ABS_MT_POSITION_X);
ioctl (uinp_fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y);
ioctl (uinp_fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID);
ioctl (uinp_fd, UI_SET_ABSBIT, ABS_MT_PRESSURE);
ioctl (uinp_fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT);
char str[20];
memset(str,0,sizeof(str));
sprintf(str,"%d",uinp_fd);
property_set("nvr_touch_screen_device",str);
ALOGD("nvr touch screen device strfd = %s , id = %d\n",str ,uinp_fd);
/* Create input device into input sub-system */
write(uinp_fd, &uinp, sizeof(uinp));
ioctl(uinp_fd, UI_DEV_CREATE);
}
我们来看一下设备有没有我们创建的触摸屏ShaoTouchScreen
到这设别已经创建完成,接下来我们需要发出touch事件,MULTI_TOUCH 支持多点触摸,我们这边模拟的是MultiTouch下单点触摸
#include <jni.h>
#include <stdint.h>
#include <cutils/log.h>
#include <linux/uinput.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <cutils/properties.h>
#define ACTION_DOWN 0
#define ACTION_UP 1
#define ACTION_MOVE 2
#define API_EXPORT __attribute__((visibility("default")))
static int global_tracking_id = 1;
namespace shao{
extern long jptr(void *native_dtr) {
return reinterpret_cast<intptr_t>(native_dtr);
};
extern void *native(long ptr) {
return reinterpret_cast<void *>(ptr);
};
}
using namespace nvr;
extern "C"{
static bool device_writeEvent(int fd, uint16_t type, uint16_t keycode, int32_t value) {
struct input_event ev;
memset(&ev, 0, sizeof(struct input_event));
ev.type = type;
ev.code = keycode;
ev.value = value;
if (write(fd, &ev, sizeof(struct input_event)) < 0) {
char * mesg = strerror(errno);
ALOGD("nibiru uinput errormag info :%s\n",mesg);
return false;
}
return true;
}
static void execute_sleep(int duration_msec)
{
usleep(duration_msec*1000);
}
//startX startY 代表触摸down的坐标 endX 和 endY代表Up的坐标
//如果startX = startY 同时 endX = endY ,没有actionMove事件产生只有actionDown和actionUp事件
API_EXPORT void nvr_execute_touch(int fd,int startX,int startY,int endX,int endY)
{
//actionDown事件
device_writeEvent(fd, EV_ABS, ABS_MT_TRACKING_ID, global_tracking_id++);
device_writeEvent(fd, EV_ABS, ABS_MT_POSITION_X, startX);
device_writeEvent(fd, EV_ABS, ABS_MT_POSITION_Y, startY);
device_writeEvent(fd, EV_ABS, ABS_MT_PRESSURE, 60);
device_writeEvent(fd, EV_ABS, ABS_MT_TOUCH_MAJOR, 5);
device_writeEvent(fd, EV_SYN, SYN_REPORT, 0);
//action_move事件
device_writeEvent(fd, EV_ABS, ABS_MT_POSITION_X, endX);
device_writeEvent(fd, EV_ABS, ABS_MT_POSITION_Y, endY);
device_writeEvent(fd, EV_SYN, SYN_REPORT, 0);
//action_up事件
device_writeEvent(fd, EV_ABS, ABS_MT_TRACKING_ID, -1);
//事件发送完毕需要sync
device_writeEvent(fd, EV_SYN, SYN_REPORT, 0);
ALOGD(" one touch operation send end");
}
API_EXPORT void sendScreenTouch(int startX,int startY,int endX,int endY)
{
char package_status[PROPERTY_VALUE_MAX]={0};
property_get("touch_screen_device",package_status,NULL);
int fd = atoi(package_status);
ALOGD("touch screen fd = %d\n",fd);
nvr_execute_touch(fd,startX,startY,endX,endY);
execute_sleep(20);
}
}
到这我们就可以模拟SingleTouch了,如果需要模拟多点触摸的话同样的思路,按键码可能不一致需要组合
到这一旦发送成功事件会被input模块识别,会对我们的X和Y坐标进行运算,这边将代码路径贴出来作为提示
涉及代码路径:\frameworks\native\services\inputflinger\inputreader.cpp
void MultiTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) {
size_t inCount = mMultiTouchMotionAccumulator.getSlotCount();
size_t outCount = 0;
BitSet32 newPointerIdBits;
for (size_t inIndex = 0; inIndex < inCount; inIndex++) {
const MultiTouchMotionAccumulator::Slot* inSlot =
mMultiTouchMotionAccumulator.getSlot(inIndex);
if (!inSlot->isInUse()) {
continue;
}
if (outCount >= MAX_POINTERS) {
#if DEBUG_POINTERS
ALOGD("MultiTouch device %s emitted more than maximum of %d pointers; "
"ignoring the rest.",
getDeviceName().string(), MAX_POINTERS);
#endif
break; // too many fingers!
}
RawPointerData::Pointer& outPointer = outState->rawPointerData.pointers[outCount];
outPointer.x = inSlot->getX();
outPointer.y = inSlot->getY();
outPointer.pressure = inSlot->getPressure();
outPointer.touchMajor = inSlot->getTouchMajor();
outPointer.touchMinor = inSlot->getTouchMinor();
outPointer.toolMajor = inSlot->getToolMajor();
outPointer.toolMinor = inSlot->getToolMinor();
outPointer.orientation = inSlot->getOrientation();
outPointer.distance = inSlot->getDistance();
outPointer.tiltX = 0;
outPointer.tiltY = 0;
ALOGD("shao fwk receive multi x = %d , y = %d\n", outPointer.x , outPointer.y);
outPointer.toolType = inSlot->getToolType();
if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
outPointer.toolType = mTouchButtonAccumulator.getToolType();
if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
}
}
MultiTouchInputMapper::syncTouch()函数里会拿到event数据
在 TouchInputMapper::cookPointerData() 函数里会根据屏幕方向来计算x,y坐标传给app
switch (mSurfaceOrientation) {
case DISPLAY_ORIENTATION_90:
//x = float(yTransformed - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
//y = float(mRawPointerAxes.x.maxValue - xTransformed) * mXScale + mXTranslate;
x = xTransformed;
y = yTransformed;
ALOGD("shao fwk hadlere 1 x = %f , y = %f \n",x ,y);
left = float(rawTop - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
right = float(rawBottom- mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
bottom = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale + mXTranslate;
top = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale + mXTranslate;
orientation -= M_PI_2;
if (mOrientedRanges.haveOrientation && orientation < mOrientedRanges.orientation.min) {
orientation += (mOrientedRanges.orientation.max - mOrientedRanges.orientation.min);
}
break;
case DISPLAY_ORIENTATION_180:
x = float(mRawPointerAxes.x.maxValue - xTransformed) * mXScale + mXTranslate;
y = float(mRawPointerAxes.y.maxValue - yTransformed) * mYScale + mYTranslate;
ALOGD("shao fwk hadlere 2 x = %f , y = %f\n",x ,y);
left = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale + mXTranslate;
right = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale + mXTranslate;
bottom = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale + mYTranslate;
top = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale + mYTranslate;
orientation -= M_PI;
if (mOrientedRanges.haveOrientation && orientation < mOrientedRanges.orientation.min) {
orientation += (mOrientedRanges.orientation.max - mOrientedRanges.orientation.min);
}
break;
case DISPLAY_ORIENTATION_270:
x = float(mRawPointerAxes.y.maxValue - yTransformed) * mYScale + mYTranslate;
y = float(xTransformed - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
ALOGD("shao fwk hadlere 3 x = %f , y = %f\n",x ,y);
left = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale + mYTranslate;
right = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale + mYTranslate;
bottom = float(rawRight - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
top = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
orientation += M_PI_2;
if (mOrientedRanges.haveOrientation && orientation > mOrientedRanges.orientation.max) {
orientation -= (mOrientedRanges.orientation.max - mOrientedRanges.orientation.min);
}
break;
default:
x = float(xTransformed - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
y = float(yTransformed - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
ALOGD("shao fwk hadlere 4 x = %f , y = %f\n",x ,y);
所以大家注意一下x y坐标的转换