HID键盘的学习

HID键盘的设备描述符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
char ReportDescriptor[63] = {
0x05, 0x01, // USAGE_PAGE (Generic Desktop) // 用途页为通用桌面设备
0x09, 0x06, // USAGE (Keyboard) // 用途为键盘
0xA1, 0x01, // COLLECTION (Application) // 表示应用集合
0x05, 0x07, // USAGE_PAGE (Keyboard) // 用途为键盘设备
0x19, 0xE0, // USAGE_MINIMUM (LeftControl) // 首字节的最小值为E0。首字节的组合按键
0x29, 0xE7, // USAGE_MAXIMUM (Right GUI) // 首字节的最大值为E7。组合按键取值范围
0x15, 0x00, // LOGICAL_MINIMUM (0) // 逻辑最小值0
0x25, 0x01, // LOGICAL_MAXIMUM (1) // 逻辑最大值1
0x75, 0x01, // REPORT_SIZE (1) // 表示每个修饰键大小为1bits
0x95, 0x08, // REPORT_COUNT (8) // 表示有8个这样的修饰键位
0x81, 0x02, // INPUT (Data,Var,Abs) // 输入第一字节的值(组合按键)
0x95, 0x01, // REPORT_COUNT (1) // 报告的个数为1,即总共有8个bits
0x75, 0x08, // REPORT_SIZE (8) // 报告的大小为8bit。
0x81, 0x03, // INPUT (Cnst,Var,Abs) // 输入第二字节空值(规范中保留所以0填充)
0x95, 0x05, // REPORT_COUNT (5) // 该报告的总个数为5个,即总共有5个bits
0x75, 0x01, // REPORT_SIZE (1) // 该报告每个大小为1bit
0x05, 0x08, // USAGE_PAGE (LEDs) // 用途为指示灯
0x19, 0x01, // USAGE_MINIMUM (Num Lock) // 该字节的最小值为01
0x29, 0x05, // USAGE_MAXIMUM (Kana) // 该字节的最大值为05
0x91, 0x02, // OUTPUT (Data,Var,Abs) // 该输出报告包含5bits的键盘指示灯状态信息
0x95, 0x01, // REPORT_COUNT (1) // 该报告的个数为1,即总共有3个bits用来补足
0x75, 0x03, // REPORT_SIZE (3) // 该报告的大小为3bit
0x91, 0x03, // OUTPUT (Cnst,Var,Abs) // 将上面的输出报告填充补足为一字节
0x95, 0x06, // REPORT_COUNT (6) // 该报告的个数为6个
0x75, 0x08, // REPORT_SIZE (8) // 该报告每个大小为8bit
0x15, 0x00, // LOGICAL_MINIMUM (0) // 逻辑最小值0
0x25, 0x65, // LOGICAL_MAXIMUM (101) // 逻辑最大值101
0x05, 0x07, // USAGE_PAGE (Keyboard) // 用途为键盘设备
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated)) // 该字节的最小值为00
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application) // 该字节的最大值为65
0x81, 0x00, // INPUT (Data,Ary,Abs) // 输入第三~八字节(即按键值)
0xC0 // END_COLLECTION // 应用集合结束
};

// 输入报告含8个字节,第1字节为特殊功能键,第2字节保留,第3~8字节为普通键值(取值范围为0~101)
// 输出报告共1个字节,其中 BIT0 ~ BIT5 各自对应着下面某盏指示灯状态,该字节剩余用0填充
// Num Lock、Caps Lock、Scroll Lock、Compose、Kana 这五盏指示灯的状态。灯亮为1,灯灭为0

| BIT 7 | BIT 6 | BIT 5 | BIT 4 | BIT 3 | BIT 2 | BIT 1 | BIT 0 |
| :-----: | :-----: | :-----: | :-----: | :-----: | :-----: | :-----: | :-----: |
| 0 | 0 | 0 | Kana | Compose | Scroll | Caps | Num |

HID键盘通过脚本创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/bin/bash

#modprobe libcomposite # 加入驱动

# 定义USB产品的VID和PID
mkdir -p /sys/kernel/config/usb_gadget/g1 # 在usb_gadget 中创建我们设备的文件夹
cd /sys/kernel/config/usb_gadget/g1 # 当我们创建完这个文件夹之后,系统自动的在这个文件夹中创建usb相关的内容 ,这些内容需要由创建者自己填写。
echo "0x046d" > idVendor # Logitech, Inc.
echo "0xc53f" > idProduct # USB Receiver
echo "0x0100" > bcdDevice # v1.0.0
echo "0x0200" > bcdUSB # USB2

mkdir -p strings/0x409 # 实例化英语语言ID(0x409是USB language ID 美国英语)
echo "831409-0000" > strings/0x409/serialnumber
echo "Logitech" > strings/0x409/manufacturer # 将序列号和制造商等信息字符串写入内核
echo "Logitech USB Receiver" > strings/0x409/product

# 创建HID鼠标设备功能的描述 /dev/hidg0
# mkdir -p functions/hid.mouse
# echo 0 > functions/hid.mouse/subclass # 0 No subclass
# echo 2 > functions/hid.mouse/protocol # 2 Mouse
# echo 4 > functions/hid.mouse/report_length # 鼠标报告的长度
# echo -ne \\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\\x03\\x05\\x01\\x09\\x30\\x09\\x31\\x09\\x38\\x15\\x81\\x25\\x7f\\x75\\x08\\x95\\x03\\x81\\x06\\xc0\\xc0 > functions/hid.mouse/report_desc

# 创建HID键盘设备功能的描述 /dev/hidg1
mkdir -p functions/hid.keyboard
echo 1 > functions/hid.keyboard/subclass # 1 Boot Interface Subclass
echo 1 > functions/hid.keyboard/protocol # 1 Keyboard
echo 8 > functions/hid.keyboard/report_length # 键盘报告的长度
echo -ne \\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\\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 > functions/hid.keyboard/report_desc

# 创建一个USB Configuration配置实例并定义配置描述符使用的字符串
mkdir -p configs/c.1/strings/0x409
echo 250 > configs/c.1/MaxPower # 当设备采用总线供电时,设备可从主机提取的最大功率
#echo 0x80 > configs/c.1/bmAttributes # 配置是否支持远程唤醒功能,以及设备是总线供电还是自供电
#echo "Config 1" > configs/c.1/strings/0x409/configuration
# bmAttributes:一个字节大小,BIT7:保留,必须为1。BIT6:1表示设备是自己供电,0表示是总线供电。BIT5:1表示支持远程唤醒。BIT4~BIT0:保留,必须为0
# bMaxPower:总线供电时的最大电流,单位以2mA为基准,例如0x32为50*2=100mA。USB设备可以从USB总线上获得最大的电流为500mA(所以最大值为250)

# 关联配置和功能的文件夹和启用设备
# ln -s functions/hid.mouse configs/c.1 # 捆绑功能 Function 实例到配置 Configuration
ln -s functions/hid.keyboard configs/c.1 # 当我们执行完这段命令后,系统就自动的帮我们创建了hid设备
ls /sys/class/udc > UDC # 将gadget驱动注册到UDC上,插上USB线到电脑上,电脑就会枚举USB设备

HID键盘数据上报格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 字节(BYTE)是计算机数据处理的最小单位,一个字节是八个比特(bit)
# HID键盘发送给PC的数据每次为八个字节,这八个字节的定义分别是:

BYTE1 --
|--bit7: Right GUI # 是否按下,按下为1
|--bit6: Right Alt # 是否按下,按下为1
|--bit5: Right Shift # 是否按下,按下为1
|--bit4: Right Control # 是否按下,按下为1
|--bit3: Left GUI # 是否按下,按下为1
|--bit2: Left Alt # 是否按下,按下为1
|--bit1: Left Shift # 是否按下,按下为1
|--bit0: Left Control # 是否按下,按下为1
BYTE2 -- # 暂不清楚,有的地方说是保留位
BYTE3 ~ BYTE8 -- # 这六个为普通按键HID码的16进制

# HID键盘数据上报示例
01 00 00 00 00 00 00 00 # 按下 L_Ctrl 键
01 00 06 00 00 00 00 00 # 按下 L_Ctrl + C 键
02 00 1B 00 00 00 00 00 # 按下 L_Shift + X 键
04 00 3B 00 00 00 00 00 # 按下 L_Alt + F2 键
02 00 04 05 00 00 00 00 # 同时按下了 L_Shift + A + B 三个键

# 键盘只能上报6个按键值,如果超过6个那么6个按键值将都是1,但特殊按键还是有效的
20 00 01 01 01 01 01 01 # 按下 R_Shift 且另外6个按键也被按下

# 键盘上报数据中,哪个按键先被按下,就先记录他的按键值
00 00 04 05 00 00 00 00 # 按下键盘 A B 的USB上报数据值
00 00 04 05 06 00 00 00 # 保持按下键盘 A B ,继续按下 C 的上报值
00 00 05 06 00 00 00 00 # 松开 A,但保持 B C 不松开的上报值
00 00 00 00 00 00 00 00 # 松开所有按键

组合键在第一字节的值

BIT 7 BIT 6 BIT 5 BIT 4 BIT 3 BIT 2 BIT 1 BIT 0
R_Win R_Alt R_Shift R_Ctrl L_Win L_Alt L_Shift L_Ctrl
1
2
3
4
5
6
7
8
9
# 第一个字节(BYTE1)组合按键的值。最右边的是第一个比特(BIT0),左边是尾 右边是头
L_Ctrl 按下:0 0 0 0 0 0 0 1 该二进制:1 的十进制值为:1
L_Shift按下:0 0 0 0 0 0 1 0 该二进制:10 的十进制值为:2
L_Alt 按下:0 0 0 0 0 1 0 0 该二进制:100 的十进制值为:4
L_Win 按下:0 0 0 0 1 0 0 0 该二进制:1000 的十进制值为:8
R_Ctrl 按下:0 0 0 1 0 0 0 0 该二进制: 10000 的十进制值为:16
R_Shift按下:0 0 1 0 0 0 0 0 该二进制: 100000 的十进制值为:32
R_Alt 按下:0 1 0 0 0 0 0 0 该二进制: 1000000 的十进制值为:64
R_Win 按下:1 0 0 0 0 0 0 0 该二进制: 10000000 的十进制值为:128
组合按键 HID码值 (10 [16]进制) 组合按键 HID码值 (10 [16]进制)
L_Ctrl 1 [0X01] R_Ctrl 16 [0X10]
L_Shift 2 [0X02] R_Shift 32 [0X20]
L_Alt 4 [0X04] R_Alt 64 [0X40]
L_Win 8 [0X08] R_Win 128 [0X80]

部分编程语言操作示例

  1. Shell下操作示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# -n:不自动换行		-e:解释转义字符
# \0NN 插入NN( 八进制) 所代表的ASCII字符
# \xAA 插入AA(十六进制)所代表的ASCII数字

# 第一行为按下a键,第二行为松开所有按键。即输入a
echo -ne "\x00\x00\x04\x00\x00\x00\x00\x00" > /dev/hidg0
echo -ne "\x00\x00\x00\x00\x00\x00\x00\x00" > /dev/hidg0

# 把ESC按键按下松开动作结合起来
echo -ne "\0\0\x29\0\0\0\0\0\0\0\0\0\0\0\0\0" > /dev/hidg0

# 分别单独输入 t e r m 字母
echo -ne "\0\0\x17\0\0\0\0\0\0\0\0\0\0\0\0\0" > /dev/hidg0
echo -ne "\0\0\x8\0\0\0\0\0\0\0\0\0\0\0\0\0" > /dev/hidg0
echo -ne "\0\0\x15\0\0\0\0\0\0\0\0\0\0\0\0\0" > /dev/hidg0
echo -ne "\0\0\x10\0\0\0\0\0\0\0\0\0\0\0\0\0" > /dev/hidg0

# 按下并松开ENTER键
echo -ne "\0\0\x28\0\0\0\0\0\0\0\0\0\0\0\0\0" > /dev/hidg0

# 松开所有按键
echo -ne "\0\0\0\0\0\0\0\0" > /dev/hidg0
  1. Python下操作示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/env python3
NULL_CHAR = chr(0)

def write_report(report):
with open('/dev/hidg0', 'rb+') as fd:
fd.write(report.encode())

# Press a # HID键盘码的十进制即可
write_report(NULL_CHAR*2+chr(4)+NULL_CHAR*5)
# Release keys
write_report(NULL_CHAR*8)
# Press SHIFT + a = A
write_report(chr(32)+NULL_CHAR+chr(4)+NULL_CHAR*5)

# Press z
write_report(NULL_CHAR*2+chr(29)+NULL_CHAR*5)
# Release keys
write_report(NULL_CHAR*8)
# Press SHIFT + z = Z
write_report(chr(32)+NULL_CHAR+chr(29)+NULL_CHAR*5)

# RETURN/ENTER key
write_report(NULL_CHAR*2+chr(40)+NULL_CHAR*5)

# Release all keys
write_report(NULL_CHAR*8)
  1. JavaScript下操作示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env node
var fs = require("fs");

// Buffer类用来创建一个专门存放二进制数据的缓存区
// 创建一个长度为 8 用零填充的 Buffer,该值的作用是松开键盘按键
const Rest_Key = Buffer.alloc(8);

// 缓存区内可以使用十进制或十六进制的HID键盘码,该值的作用是按下Z键
// var Hid_Key = Buffer.from([0, 0, 29, 0, 0, 0, 0, 0]);
var Hid_Key = Buffer.from([0x0, 0x0, 0x1D, 0x0, 0x0, 0x0, 0x0, 0x0]);
var Hid_Device = '/dev/hidg0'; // 指定Gadget Hid设备路径

// fs中如果不指定encoding编码格式,默认以buffer格式读取
function WriteReport(Hid_Device, Hid_Report) {
fs.writeFile(Hid_Device, Hid_Report, function(err) {
if (err) {
return console.error(err);
}
});
}

WriteReport(Hid_Device, Hid_Key); // 按下 Z键
WriteReport(Hid_Device, Rest_Key); // 松开按键

HID键盘输入数据查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 首先通过 cat /proc/bus/input/devices 查看各个设备的描述
# 然后可根据关键字 keyboard或mouse 之类的定位到 event* 节点
# 然后直接 cat /dev/input/event* 来查看输入设备是否有数据输入
# 根据 N: Name= 为设备名,H: Handlers= 中 event* 节点可找到指定设备

I: Bus=0003 Vendor=2207 Product=3588 Version=0101
N: Name="rockchip Rockchip HID Gadget"
P: Phys=usb-ff100000.usb-1/input0
S: Sysfs=/devices/platform/ff100000.usb/usb1/1-1/1-1:1.0/0003:2207:3588.0001/input/input2
U: Uniq=
H: Handlers=kbd leds event2
B: PROP=0
B: EV=120013
B: KEY=e080ffdf01cfffff fffffffffffffffe
B: MSC=10
B: LED=1f

I: Bus=0003 Vendor=2207 Product=3588 Version=0101
N: Name="rockchip Rockchip HID Gadget"
P: Phys=usb-ff100000.usb-1/input1
S: Sysfs=/devices/platform/ff100000.usb/usb1/1-1/1-1:1.1/0003:2207:3588.0002/input/input3
U: Uniq=
H: Handlers=event3
B: PROP=0
B: EV=17
B: KEY=70000 0 0 0 0
B: REL=903
B: MSC=10

I: Bus=0003 Vendor=046d Product=c53f Version=0111
N: Name="Logitech USB Receiver Mouse"
P: Phys=usb-xhci-hcd.4.auto-1/input1
S: Sysfs=/devices/platform/usbdrd/fe500000.dwc3/xhci-hcd.4.auto/usb3/3-1/3-1:1.1/0003:046D:C53F.0004/input/input5
U: Uniq=
H: Handlers=event5
B: PROP=0
B: EV=17
B: KEY=ffff0000 0 0 0 0
B: REL=1943
B: MSC=10


-----------------------------------------------------------
# 更好的办法是直接格式化读取HID输入设备
root@debian:/# hexdump -e '8/1 " %02X" "\n"' /dev/hidraw0
00 00 04 00 00 00 00 00 # 按下 A 键
00 00 00 00 00 00 00 00 # 松开 A 键

01 00 00 00 00 00 00 00 # 按下 LCtrl
01 00 06 00 00 00 00 00 # 按下 C 键
01 00 00 00 00 00 00 00 # 松开 C 键
00 00 00 00 00 00 00 00 # 松开 LCtrl

02 00 00 00 00 00 00 00 # 按下 LShift
02 00 1D 00 00 00 00 00 # 按下 Z 键
02 00 00 00 00 00 00 00 # 松开 Z 键
00 00 00 00 00 00 00 00 # 松开 LShift

常用HID规范的键盘码

按键名称 HID码值 (10 [16]进制) 按键名称 HID码值 (10 [16]进制)
A 4 [0X04] CapLck 57 [0X39]
B 5 [0X05] F1 58 [0X3A]
C 6 [0X06] F2 59 [0X3B]
D 7 [0X07] F3 60 [0X3C]
E 8 [0X08] F4 61 [0X3D]
F 9 [0X09] F5 62 [0X3E]
G 10 [0X0A] F6 63 [0X3F]
H 11 [0X0B] F7 64 [0X40]
I 12 [0X0C] F8 65 [0X41]
J 13 [0X0D] F9 66 [0X42]
K 14 [0X0E] F10 67 [0X43]
L 15 [0X0F] F11 68 [0X44]
M 16 [0X10] F12 69 [0X45]
N 17 [0X11] PrintScr 70 [0X46]
O 18 [0X12] Scroll 71 [0X47]
P 19 [0X13] Pause 72 [0X48]
Q 20 [0X14] Insert 73 [0X49]
R 21 [0X15] Home 74 [0X4A]
S 22 [0X16] PageUp 75 [0X4B]
T 23 [0X17] Delete 76 [0X4C]
U 24 [0X18] End 77 [0X4D]
V 25 [0X19] PageDn 78 [0X4E]
W 26 [0X1A] Right 79 [0X4F]
X 27 [0X1B] Left 80 [0X50]
Y 28 [0X1C] Down 81 [0X51]
Z 29 [0X1D] Up 82 [0X52]
1 30 [0X1E] NumLock 83 [0X53]
2 31 [0X1F] KEYPAD / 84 [0X54]
3 32 [0X20] KEYPAD * 85 [0X55]
4 33 [0X21] KEYPAD - 86 [0X56]
5 34 [0X22] KEYPAD + 87 [0X57]
6 35 [0X23] KEYPAD ENTER 88 [0X58]
7 36 [0X24] KEYPAD 1 89 [0X59]
8 37 [0X25] KEYPAD 2 90 [0X5A]
9 38 [0X26] KEYPAD 3 91 [0X5B]
0 39 [0X27] KEYPAD 4 92 [0X5C]
Enter 40 [0X28] KEYPAD 5 93 [0X5D]
ESC 41 [0X29] KEYPAD 6 94 [0X5E]
BackSpace 42 [0X2A] KEYPAD 7 95 [0X5F]
Tab 43 [0X2B] KEYPAD 8 96 [0X60]
Space 44 [0X2C] KEYPAD 9 97 [0X61]
- 45 [0X2D] KEYPAD 0 98 [0X62]
= 46 [0X2E] KEYPAD . 99 [0X63]
[ 47 [0X2F] ——– ———
] 48 [0X30] ——– ———
\ 49 [0X31] LCtrl 224 [0XE0]
\ 50 [0X32] LShift 225 [0XE1]
; 51 [0X33] LAlt 226 [0XE2]
52 [0X34] LWIN 227 [0XE3]
` 53 [0X35] RCtrl 228 [0XE4]
, 54 [0X36] RShift 229 [0XE5]
. 55 [0X37] RAlt 230 [0XE6]
/ 56 [0X38] RWIN 231 [0XE7]