Android 使用uInput模拟手柄设备

背景

前文说到需求,需要在系统应用里模拟手柄摇杆和按键。之前已经通过模拟KeyEvent实现按键,模拟MotionEvent实现摇杆大致实现了这个功能,但是遇到新的问题,有些游戏不识别按键,尤其是多人云游戏。

解决方案

大致了解之后, KeyEvent和MotionEvent里有个deviceId字段,这个字段是用来表示不同的设备的。比如多人游戏,a手柄和b手柄的deviceId是不同的,游戏根据这个来区分不同的人做的操作,游戏可能是根据这个deviceId是否存在并且可以匹配,如果不存在则可能不处理这个事件。

  1. 第一想法是虚构一个deviceId,游戏都是通过InputDevice.getDeviceIds 来获取全部的,于是我反射了这个类的mInputDevices,但最后效果还是不可以,游戏还是无法识别。

  2. 搜索到Linux提供了/dev/uinput 可以用来虚拟外接设备。从底层虚拟输入设备。声明这个设备支持了摇杆和一些按键,操控实际的手柄。

#include "com_test_CommandDispatcher.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <linux/uinput.h>
#include <linux/input.h>

#include "android/log.h"
static const char *TAG="uinput";
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO,  TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)

#define KEY_CUSTOM_UP 0x20
#define KEY_CUSTOM_DOWN 0x30


static struct uinput_user_dev uinput_dev;
static int uinput_fd;

extern "C"
JNIEXPORT jint JNICALL
        Java_com_test_CommandDispatcher_createUinput(
                JNIEnv* env,
jobject /* this */,jstring name) {
    int i;
    int ret = 0;
    const char *strContent = env->GetStringUTFChars(name, JNI_FALSE);

    uinput_fd = open("/dev/uinput", O_RDWR | O_NDELAY);
    if(uinput_fd < 0){
        LOGE("%s:%d\n", __func__, __LINE__);
        return -1;//error process.
    }

    //to set uinput dev
    memset(&uinput_dev, 0, sizeof(struct uinput_user_dev));
    strcpy(uinput_dev.name,strContent);
    env->ReleaseStringUTFChars(name, strContent);

    uinput_dev.id.version = 1;
    //BUS_VIRTUAL   BUS_USB时对于游戏来说这个设备是真实存在通过usb连接的
    uinput_dev.id.bustype = BUS_USB;
    uinput_dev.id.vendor = 0x1;
    uinput_dev.id.product = 0x1;
    //min 和 max必须不同
    uinput_dev.absmin[ABS_X] = -1;
    uinput_dev.absmax[ABS_X] = 1;
    uinput_dev.absmin[ABS_Y] = -1;
    uinput_dev.absmax[ABS_Y] = 1;
    uinput_dev.absmin[ABS_Z] = -1;
    uinput_dev.absmax[ABS_Z] = 1;
    uinput_dev.absmin[ABS_RZ] = -1;
    uinput_dev.absmax[ABS_RZ] = 1;
    uinput_dev.absmin[ABS_THROTTLE] = -1;
    uinput_dev.absmax[ABS_THROTTLE] = 1;
    uinput_dev.absmin[ABS_BRAKE] = -1;
    uinput_dev.absmax[ABS_BRAKE] = 1;
    uinput_dev.absmin[ABS_HAT0X] = -1;
    uinput_dev.absmax[ABS_HAT0X] = 1;
    uinput_dev.absmin[ABS_HAT0Y] = -1;
    uinput_dev.absmax[ABS_HAT0Y] = 1;


    ioctl(uinput_fd, UI_SET_EVBIT, EV_SYN);

    ioctl(uinput_fd, UI_SET_EVBIT, EV_KEY);
    ioctl(uinput_fd, UI_SET_EVBIT, EV_MSC);

    for(i = 0; i < 256; i++){
        ioctl(uinput_fd, UI_SET_KEYBIT, i);
    }

    ioctl(uinput_fd, UI_SET_KEYBIT, BTN_THUMBL);
    ioctl(uinput_fd, UI_SET_KEYBIT, BTN_THUMBR);
    ioctl(uinput_fd, UI_SET_KEYBIT, BTN_X);
    ioctl(uinput_fd, UI_SET_KEYBIT, BTN_A);
    ioctl(uinput_fd, UI_SET_KEYBIT, BTN_Y);
    ioctl(uinput_fd, UI_SET_KEYBIT, BTN_B);
    ioctl(uinput_fd, UI_SET_KEYBIT, BTN_RIGHT);
    ioctl(uinput_fd, UI_SET_KEYBIT, BTN_LEFT);
    ioctl(uinput_fd, UI_SET_KEYBIT, BTN_SELECT);
    ioctl(uinput_fd, UI_SET_KEYBIT, BTN_START);
    ioctl(uinput_fd, UI_SET_KEYBIT, BTN_DPAD_LEFT);
    ioctl(uinput_fd, UI_SET_KEYBIT, BTN_DPAD_RIGHT);
    ioctl(uinput_fd, UI_SET_KEYBIT, BTN_DPAD_UP);
    ioctl(uinput_fd, UI_SET_KEYBIT, BTN_DPAD_DOWN);


    ioctl(uinput_fd, UI_SET_EVBIT, EV_ABS);
    ioctl(uinput_fd, UI_SET_ABSBIT, ABS_HAT0X);
    ioctl(uinput_fd, UI_SET_ABSBIT, ABS_HAT0Y);
    ioctl(uinput_fd, UI_SET_ABSBIT, ABS_X);
    ioctl(uinput_fd, UI_SET_ABSBIT, ABS_Y);
    ioctl(uinput_fd, UI_SET_ABSBIT, ABS_Z);
    ioctl(uinput_fd, UI_SET_ABSBIT, ABS_RZ);
    ioctl(uinput_fd, UI_SET_ABSBIT, ABS_THROTTLE);
    ioctl(uinput_fd, UI_SET_ABSBIT, ABS_BRAKE);


    ioctl(uinput_fd, UI_SET_MSCBIT, KEY_CUSTOM_UP);
    ioctl(uinput_fd, UI_SET_MSCBIT, KEY_CUSTOM_DOWN);

    ret = write(uinput_fd, &uinput_dev, sizeof(struct uinput_user_dev));
    if(ret < 0){
        LOGE("%s:%d\n", __func__, __LINE__);
        return ret;//error process.
    }


    ret = ioctl(uinput_fd, UI_DEV_CREATE);
    if(ret < 0){
        LOGE("%s:%d\n", __func__, __LINE__);
        close(uinput_fd);
        return ret;//error process.
    }
    return 1;
}

JNIEXPORT void JNICALL Java_com_test_CommandDispatcher_closeUinput
  (JNIEnv *, jobject) {
        close(uinput_fd);
}

createUinput 传入的参数就是设备名,调用方法后,
通过InputDevice.getDeviceIds()拿到所有的deviceId,
通过InputDevice.getDevice(deviceId) 拿到device,
判断device.getName和设备名一致时,device.getId()就是deviceId.
后续模拟事件都使用该id即可。

其他

这种在native层通过写/dev/uinput虚拟出设备, 然后在Android层模拟各种事件实现其实是比较取巧的办法,虚拟出设备后,其实是可以往这个设备节点写数据,达到模拟输入的效果的。参考下面第二个文档。

参考文档

使用UInput模拟系统输入设备–键盘,鼠标,触摸屏

android 使用uinput模拟输入设备

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值