linux 在应用层创建虚拟hid设备
主要功能
应用层发送鼠标数据给驱动,驱动会解析完,传到input子系统,这样,其他的应用程序就能通用了。
具体步骤
应用层通过调用uhid驱动,来让uhid驱动创建一个虚拟的hid设备,然后,应用层向uhid发送hid报告描述符,用于生成具体的hid设备。例如,报告描述符包含鼠标描述符的话,就会创建一个input设备,报告描述符包含鼠标和键盘描述符的话,就会创建两个input设备,分别代表鼠标和键盘。接着,应用层发送hid报告数据给uhid驱动,虚拟hid解析完,就会将数据转发到input子系统。
使用步骤
- 使能uhid驱动
在内核配置文件中加入:CONFIG_UHID=y。该驱动源文件的具体路径为drivers/hid/uhid.c。
成功并烧写到系统的话,在/dev/目录下,会多出一个uhid文件。 - 编写应用程序
下面的代码,会创建一个鼠标,一个键盘设备,并且向hid发送鼠标数据,input子系统会报告事件的产生。
【uhid_control.h】
#pragma once
struct MT_event {
int state;
int x; // 0-7808
int y; // 0-4480
};
struct Keyboard_Ext {
bool LeftControl;
bool LeftShift;
bool LeftAlt;
bool Left_GUI;
bool RightControl;
bool RightShift;
bool RightAlt;
bool Right_GUI;
};
void uhid_create(char *descriptor, int size);
void uhid_create_default(bool mouse_enable, bool keyboard_enable);
void uhid_send_report(const char *data, int size);
void uhid_send_default_mouse(bool left_btn, bool right_btn, bool middle_btn, int x, int y, int wheel);
void uhid_send_default_keyboard(struct Keyboard_Ext ext, int key[6]);
void uhid_close(void);
void uhid_test(void);
void multi_touch_create(void);
void multi_touch_set(struct MT_event *event, int count);
void multi_touch_close(void);
void multi_touch_test(void);
【uhid_control.cpp】
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <ctype.h>
#include <time.h>
#include <sys/time.h>
#include <errno.h>
#include <string>
#include <linux/uhid.h>
#include "uhid_control.h"
static int uhid_fd;
void uhid_create(char *descriptor, int size)
{
if (access("/dev/uhid", F_OK) != 0)
return;
uhid_fd = open("/dev/uhid", O_RDWR);
if (uhid_fd > 0)
{
int ret;
struct uhid_event event;
event.type = UHID_CREATE2;
strcpy((char*)event.u.create2.name, "uibc");
strcpy((char*)event.u.create2.phys, "uibc_phys");
strcpy((char*)event.u.create2.uniq, "uibc_uniq");
event.u.create2.vendor = 0x1233;
event.u.create2.product = 0x3455;
event.u.create2.version = 0x5677;
event.u.create2.rd_size = size;
memcpy(event.u.create2.rd_data, descriptor, size);
ret = write(uhid_fd, &event, sizeof(event));
usleep(10000);
}
}
void uhid_create_default(bool mouse_enable, bool keyboard_enable)
{
int size = 0;
char descriptor[4096];
// 描述符在线分析: https://www.usbzh.com/tool/usb.html
// hid附录 E.10
char standard_mouse_descriptor[] = "\x05\x01\x09\x02\xa1\x01\x09\x01\xa1\x00" \
"\x05\x09\x19\x01\x29\x03\x15\x00\x25\x01\x95\x03\x75\x01\x81\x02" \
"\x95\x01\x75\x05\x81\x01\x05\x01\x09\x30\x09\x31\x15\x81\x25\x7f" \
"\x75\x08\x95\x02\x81\x06\xc0\xc0";
// hid附录 E.6
char standard_keyboard_descriptor[] = "\x05\x01\x09\x06\xa1\x01\x05\x07\x19\xe0" \
"\x29\xe7\x15\x00\x25\x01\x75\x01\x95\x08\x81\x02\x95\x01\x75\x08" \
"\x81\x01\x95\x05\x75\x01\x05\x08\x19\x01\x29\x05\x91\x02\x95\x01" \
"\x75\x03\x91\x01\x95\x06\x75\x08\x15\x00\x25\x65\x05\x07\x19\x00" \
"\x29\x65\x81\x00\xc0";
// 带report_id的鼠标描述符
char mouse_descriptor[] = "\x05\x01\x09\x02\xa1\x01\x85" \
"\x28\x09\x01\xa1\x00\x05\x09\x19\x01\x29\x08\x15\x00\x25\x01\x95" \
"\x08\x75\x01\x81\x02\x05\x01\x09\x30\x09\x31\x09\x38\x0a\x38\x02" \
"\x15\x81\x25\x7f\x75\x08\x95\x04\x81\x06\xc0\xc0";
// 带report_id的键盘描述符
char keyboard_descriptor[] = "\x05\x01\x09\x06\xa1\x01\x85" \
"\x29\x05\x07\x19\xe0\x29\xe7\x15\x00\x25\x01\x75\x01\x95\x08\x81" \
"\x02\x95\x01\x75\x08\x81\x03\x95\x05\x75\x01\x05\x08\x19\x01\x29" \
"\x05\x91\x02\x95\x01\x75\x03\x91\x03\x95\x06\x75\x08\x15\x00\x25" \
"\x65\x05\x07\x19\x00\x29\x65\x81\x00\xc0";
// standard_mouse_descriptor 和 standard_keyboard_descriptor 不可同时使用,
// 因为他们没有Report ID,无法区别输入数据是属于鼠标还是键盘
if (mouse_enable)
{
memcpy(&descriptor[size], mouse_descriptor, sizeof(mouse_descriptor) - 1);
size += sizeof(mouse_descriptor) - 1;
}
if (keyboard_enable)
{
memcpy(&descriptor[size], keyboard_descriptor, sizeof(keyboard_descriptor) - 1);
size += sizeof(keyboard_descriptor) - 1;
}
if (size > 0)
uhid_create(descriptor, size);
}
void uhid_send_report(const char *data, int size)
{
int ret;
struct uhid_event event;
if (uhid_fd > 0 && size <= 4096)
{
memset(&event, 0, sizeof(event));
event.type = UHID_INPUT2;
event.u.input2.size = size;
memcpy(event.u.input2.data, data, size);
ret = write(uhid_fd, &event, sizeof(event));
}
}
void uhid_send_default_mouse(bool left_btn, bool right_btn, bool middle_btn, int x, int y, int wheel)
{
char report[5] = { 0x28 };
report[1] += left_btn ? 0x01 : 0;
report[1] += right_btn ? 0x02 : 0;
report[1] += middle_btn ? 0x04 : 0;
report[2] = x;
report[3] = y;
report[4] = wheel;
uhid_send_report(report, 5);
}
void uhid_send_default_keyboard(struct Keyboard_Ext ext, int key[6])
{
char report[10] = { 0x29 };
report[1] += ext.LeftControl? 0x01 : 0;
report[1] += ext.LeftShift ? 0x02 : 0;
report[1] += ext.LeftAlt ? 0x04 : 0;
report[1] += ext.Left_GUI ? 0x08 : 0;
report[1] += ext.RightControl?0x10 : 0;
report[1] += ext.RightShift ? 0x20 : 0;
report[1] += ext.RightAlt ? 0x40 : 0;
report[1] += ext.Right_GUI ? 0x80 : 0;
report[4] = key[0];
report[5] = key[1];
report[6] = key[2];
report[7] = key[3];
report[8] = key[4];
report[9] = key[5];
uhid_send_report(report, 10);
}
void uhid_close(void)
{
if (uhid_fd > 0)
close(uhid_fd);
uhid_fd = 0;
}
void uhid_test(void)
{
struct Keyboard_Ext ext = {};
int key[6] = {};
// 创建虚拟鼠标键盘设备
uhid_create_default(true, true);
// 打开input子系统调试工具
system("evtest /dev/input/event4&"); // 鼠标input设备,具体数字需根据实际进行修改
system("evtest /dev/input/event5&"); // 键盘input设备,具体数字需根据实际进行修改
usleep(500000);
// 按下鼠标左键
uhid_send_default_mouse(1, 0, 0, 10, 20, 30);
usleep(100000);
// 松开鼠标左键
uhid_send_default_mouse(0, 0, 0, 0, 0, 0);
usleep(100000);
printf("\n\n\n");
// 按下键盘上的a
ext.LeftControl = 1;
key[0] = 4; // 具体的键值,需查询hid usage规范pdf
uhid_send_default_keyboard(ext, key);
usleep(100000);
// 松开键盘上的a
ext.LeftControl = 0;
key[0] = 0;
uhid_send_default_keyboard(ext, key);
usleep(100000);
uhid_close();
system("killall evtest");
}
static int mt_fd;
static struct timeval time_start;
static struct MT_event mt_event[10];
void multi_touch_create(void)
{
if (access("/dev/uhid", F_OK) != 0)
return;
mt_fd = open("/dev/uhid", O_RDWR);
if (mt_fd > 0)
{
int ret;
struct uhid_event event;
event.type = UHID_CREATE2;
strcpy((char*)event.u.create2.name, "uibc_mt");
strcpy((char*)event.u.create2.phys, "uibc_mt_phys");
strcpy((char*)event.u.create2.uniq, "uibc_mt_uniq");
event.u.create2.vendor = 0x1230;
event.u.create2.product = 0x3450;
event.u.create2.version = 0x5670;
// 十点触控描述符
char multi_touch_descriptor[] =
"\x05\x0D\x09\x04\xA1\x01\x85\x04\x09\x22\xA1\x02\x05\x0D\x95\x01" \
"\x75\x06\x09\x51\x15\x00\x25\x3F\x81\x02\x09\x42\x25\x01\x75\x01" \
"\x95\x01\x81\x02\x75\x01\x95\x01\x81\x03\x05\x01\x75\x10\x55\x0E" \
"\x65\x11\x09\x30\x26\x80\x1E\x35\x00\x46\x75\x0D\x81\x42\x09\x31" \
"\x26\x80\x11\x46\x8A\x07\x81\x42\xC0\x05\x0D\x09\x22\xA1\x02\x05" \
"\x0D\x95\x01\x75\x06\x09\x51\x15\x00\x25\x3F\x81\x02\x09\x42\x25" \
"\x01\x75\x01\x95\x01\x81\x02\x75\x01\x95\x01\x81\x03\x05\x01\x75" \
"\x10\x55\x0E\x65\x11\x09\x30\x26\x80\x1E\x35\x00\x46\x75\x0D\x81" \
"\x42\x09\x31\x26\x80\x11\x46\x8A\x07\x81\x42\xC0\x05\x0D\x09\x22" \
"\xA1\x02\x05\x0D\x95\x01\x75\x06\x09\x51\x15\x00\x25\x3F\x81\x02" \
"\x09\x42\x25\x01\x75\x01\x95\x01\x81\x02\x75\x01\x95\x01\x81\x03" \
"\x05\x01\x75\x10\x55\x0E\x65\x11\x09\x30\x26\x80\x1E\x35\x00\x46" \
"\x75\x0D\x81\x42\x09\x31\x26\x80\x11\x46\x8A\x07\x81\x42\xC0\x05" \
"\x0D\x09\x22\xA1\x02\x05\x0D\x95\x01\x75\x06\x09\x51\x15\x00\x25" \
"\x3F\x81\x02\x09\x42\x25\x01\x75\x01\x95\x01\x81\x02\x75\x01\x95" \
"\x01\x81\x03\x05\x01\x75\x10\x55\x0E\x65\x11\x09\x30\x26\x80\x1E" \
"\x35\x00\x46\x75\x0D\x81\x42\x09\x31\x26\x80\x11\x46\x8A\x07\x81" \
"\x42\xC0\x05\x0D\x09\x22\xA1\x02\x05\x0D\x95\x01\x75\x06\x09\x51" \
"\x15\x00\x25\x3F\x81\x02\x09\x42\x25\x01\x75\x01\x95\x01\x81\x02" \
"\x75\x01\x95\x01\x81\x03\x05\x01\x75\x10\x55\x0E\x65\x11\x09\x30" \
"\x26\x80\x1E\x35\x00\x46\x75\x0D\x81\x42\x09\x31\x26\x80\x11\x46" \
"\x8A\x07\x81\x42\xC0\x05\x0D\x09\x22\xA1\x02\x05\x0D\x95\x01\x75" \
"\x06\x09\x51\x15\x00\x25\x3F\x81\x02\x09\x42\x25\x01\x75\x01\x95" \
"\x01\x81\x02\x75\x01\x95\x01\x81\x03\x05\x01\x75\x10\x55\x0E\x65" \
"\x11\x09\x30\x26\x80\x1E\x35\x00\x46\x75\x0D\x81\x42\x09\x31\x26" \
"\x80\x11\x46\x8A\x07\x81\x42\xC0\x05\x0D\x09\x22\xA1\x02\x05\x0D" \
"\x95\x01\x75\x06\x09\x51\x15\x00\x25\x3F\x81\x02\x09\x42\x25\x01" \
"\x75\x01\x95\x01\x81\x02\x75\x01\x95\x01\x81\x03\x05\x01\x75\x10" \
"\x55\x0E\x65\x11\x09\x30\x26\x80\x1E\x35\x00\x46\x75\x0D\x81\x42" \
"\x09\x31\x26\x80\x11\x46\x8A\x07\x81\x42\xC0\x05\x0D\x09\x22\xA1" \
"\x02\x05\x0D\x95\x01\x75\x06\x09\x51\x15\x00\x25\x3F\x81\x02\x09" \
"\x42\x25\x01\x75\x01\x95\x01\x81\x02\x75\x01\x95\x01\x81\x03\x05" \
"\x01\x75\x10\x55\x0E\x65\x11\x09\x30\x26\x80\x1E\x35\x00\x46\x75" \
"\x0D\x81\x42\x09\x31\x26\x80\x11\x46\x8A\x07\x81\x42\xC0\x05\x0D" \
"\x09\x22\xA1\x02\x05\x0D\x95\x01\x75\x06\x09\x51\x15\x00\x25\x3F" \
"\x81\x02\x09\x42\x25\x01\x75\x01\x95\x01\x81\x02\x75\x01\x95\x01" \
"\x81\x03\x05\x01\x75\x10\x55\x0E\x65\x11\x09\x30\x26\x80\x1E\x35" \
"\x00\x46\x75\x0D\x81\x42\x09\x31\x26\x80\x11\x46\x8A\x07\x81\x42" \
"\xC0\x05\x0D\x09\x22\xA1\x02\x05\x0D\x95\x01\x75\x06\x09\x51\x15" \
"\x00\x25\x3F\x81\x02\x09\x42\x25\x01\x75\x01\x95\x01\x81\x02\x75" \
"\x01\x95\x01\x81\x03\x05\x01\x75\x10\x55\x0E\x65\x11\x09\x30\x26" \
"\x80\x1E\x35\x00\x46\x75\x0D\x81\x42\x09\x31\x26\x80\x11\x46\x8A" \
"\x07\x81\x42\xC0\x05\x0D\x09\x56\x55\x00\x65\x00\x27\xFF\xFF\xFF" \
"\x7F\x95\x01\x75\x20\x81\x02\x09\x54\x25\x7F\x95\x01\x75\x08\x81" \
"\x02\xC0";
event.u.create2.rd_size = sizeof(multi_touch_descriptor) - 1;
memcpy(event.u.create2.rd_data, multi_touch_descriptor, sizeof(multi_touch_descriptor) - 1);
ret = write(mt_fd, &event, sizeof(event));
usleep(10000);
}
}
void multi_touch_set(struct MT_event *event, int count)
{
int ret;
long usec;
char report[56] = { 0x04 };
struct uhid_event uevent;
for (int i = 0; i < 10 && i < count; i++)
{
unsigned char s;
s = event[i].state ? 0x40 : 00;
report[i * 5 + 1] = i + s;
report[i * 5 + 2] = event[i].x & 0xff;
report[i * 5 + 3] = (event[i].x >> 8) & 0xff;
report[i * 5 + 4] = event[i].y & 0xff;
report[i * 5 + 5] = (event[i].y >> 8) & 0xff;
}
if (time_start.tv_sec == 0 && time_start.tv_usec == 0)
{
gettimeofday(&time_start, NULL);
usec = 1000000 * time_start.tv_sec + time_start.tv_usec;
usec = 0;
}
else
{
struct timeval end;
gettimeofday(&end, NULL);
usec = 1000000 * (end.tv_sec - time_start.tv_sec) + (end.tv_usec - time_start.tv_usec);
}
usec = usec / 100;
report[51] = usec & 0xff;
report[52] = (usec >> 8) & 0xff;
report[53] = (usec >> 16) & 0xff;
report[54] = (usec >> 24) & 0xff;
report[55] = count;
memset(&uevent, 0, sizeof(event));
uevent.type = UHID_INPUT2;
uevent.u.input2.size = 56;
memcpy(uevent.u.input2.data, report, 56);
ret = write(mt_fd, &uevent, sizeof(uevent));
}
void multi_touch_close(void)
{
if (mt_fd > 0)
close(mt_fd);
mt_fd = 0;
}
static void multi_touch_replay(void)
{
char feature[] =
"\x06\x0A\xFC\x28\xFE\x84\x40\xCB\x9A\x87\x0D\xBE\x57\x3C\xB6\x70" \
"\x09\x88\x07\x97\x2D\x2B\xE3\x38\x34\xB6\x6C\xED\xB0\xF7\xE5\x9C" \
"\xF6\xC2\x2E\x84\x1B\xE8\xB4\x51\x78\x43\x1F\x28\x4B\x7C\x2D\x53" \
"\xAF\xFC\x47\x70\x1B\x59\x6F\x74\x43\xC4\xF3\x47\x18\x53\x1A\xA2" \
"\xA1\x71\xC7\x95\x0E\x31\x55\x21\xD3\xB5\x1E\xE9\x0C\xBA\xEC\xB8" \
"\x89\x19\x3E\xB3\xAF\x75\x81\x9D\x53\xB9\x41\x57\xF4\x6D\x39\x25" \
"\x29\x7C\x87\xD9\xB4\x98\x45\x7D\xA7\x26\x9C\x65\x3B\x85\x68\x89" \
"\xD7\x3B\xBD\xFF\x14\x67\xF2\x2B\xF0\x2A\x41\x54\xF0\xFD\x2C\x66" \
"\x7C\xF8\xC0\x8F\x33\x13\x03\xF1\xD3\xC1\x0B\x89\xD9\x1B\x62\xCD" \
"\x51\xB7\x80\xB8\xAF\x3A\x10\xC1\x8A\x5B\xE8\x8A\x56\xF0\x8C\xAA" \
"\xFA\x35\xE9\x42\xC4\xD8\x55\xC3\x38\xCC\x2B\x53\x5C\x69\x52\xD5" \
"\xC8\x73\x02\x38\x7C\x73\xB6\x41\xE7\xFF\x05\xD8\x2B\x79\x9A\xE2" \
"\x34\x60\x8F\xA3\x32\x1F\x09\x78\x62\xBC\x80\xE3\x0F\xBD\x65\x20" \
"\x08\x13\xC1\xE2\xEE\x53\x2D\x86\x7E\xA7\x5A\xC5\xD3\x7D\x98\xBE" \
"\x31\x48\x1F\xFB\xDA\xAF\xA2\xA8\x6A\x89\xD6\xBF\xF2\xD3\x32\x2A" \
"\x9A\xE4\xCF\x17\xB7\xB8\xF4\xE1\x33\x08\x24\x8B\xC4\x43\xA5\xE5" \
"\x24\xC2";
// 单个手指触摸按下
char a1[] =
"\x04\x40\xAC\x15\x0B\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\xD0\x5C\x00\x00\x01";
// 单个手指触摸移动到另一个位置
char a2[] =
"\x04\x40\xAC\x15\x0B\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x88\x5E\x00\x00\x01";
// 单个手指松开
char a3[] =
"\x04\x00\xAC\x15\x0B\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \
"\x00\x00\x00\xE0\x5E\x00\x00\x01";
multi_touch_create();
system("evtest /dev/input/event4&");
usleep(500000);
{
int ret;
struct uhid_event event;
sleep(1);
printf("\n\n");
memset(&event, 0, sizeof(event));
event.type = UHID_INPUT2;
event.u.input2.size = sizeof(a1) - 1;
memcpy(event.u.input2.data, a1, sizeof(a1) - 1);
ret = write(mt_fd, &event, sizeof(event));
printf("\n[write ret = %d]\n", ret);
}
{
int ret;
struct uhid_event event;
sleep(1);
printf("\n\n");
memset(&event, 0, sizeof(event));
event.type = UHID_INPUT2;
event.u.input2.size = sizeof(a2) - 1;
memcpy(event.u.input2.data, a2, sizeof(a2) - 1);
ret = write(mt_fd, &event, sizeof(event));
printf("\n[write ret = %d]\n", ret);
}
{
int ret;
struct uhid_event event;
sleep(1);
printf("\n\n");
memset(&event, 0, sizeof(event));
event.type = UHID_INPUT2;
event.u.input2.size = sizeof(a3) - 1;
memcpy(event.u.input2.data, a3, sizeof(a3) - 1);
ret = write(mt_fd, &event, sizeof(event));
printf("\n[write ret = %d]\n", ret);
}
multi_touch_close();
system("killall evtest");
}
void multi_touch_test(void)
{
multi_touch_create();
system("evtest /dev/input/event4&");
usleep(500000);
memset(mt_event, 0, sizeof(mt_event));
mt_event[0].state = 1;
mt_event[0].x = 160;
mt_event[0].y = 160;
multi_touch_set(mt_event, 1);
sleep(1);
printf("\n\n");
memset(mt_event, 0, sizeof(mt_event));
mt_event[0].state = 1;
mt_event[0].x = 7900;
multi_touch_set(mt_event, 1);
sleep(1);
printf("\n\n");
memset(mt_event, 0, sizeof(mt_event));
mt_event[0].state = 1;
mt_event[0].x = 480;
multi_touch_set(mt_event, 1);
sleep(1);
printf("\n\n");
memset(mt_event, 0, sizeof(mt_event));
mt_event[0].state = 0;
mt_event[0].x = 640;
multi_touch_set(mt_event, 1);
sleep(1);
multi_touch_close();
system("killall evtest");
}
int main(int argc, char **argv)
{
if(1)
{
// 创建鼠标键盘
uhid_create_default(true, true);
// 延迟999s,用户这时可以用cat /proc/bus/input/devices查看,是否多出两个input设备
sleep(999);
// 删除鼠标键盘
uhid_close();
}
else
{
// 创建并测试鼠标键盘
uhid_test();
}
return 0;
}
测试结果
1.执行cat /proc/bus/input/devices
会发现多了这两个input设备 。
2.创建并测试鼠标键盘时,应用程序会发送鼠标和键盘数据给hid,我们会发现input有数据上报。 图片中上半部分为鼠标事件,下半部分为键盘事件。