1. 键盘报文体验

一个键盘对于用户的体验是,用户按按键A他能看到字母A会在主机上显示出来。那这是如何实现的?

其实很简单,只要键盘发送下面的两个报文给主机,字母A就能在主机上显示出来。

(1)表1:字母A按下的报文

Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x00 0x04 0x00 0x00 0x00

(2)表2:字母A松开的报文

Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x00 0x00 0x00 0x00 0x00

报文的原则很简单,就是它把字母A所以对应的usage发给主机,主机就能显示出字母A. 以上表1按下报文Byte 1的值就是字母A的usage, 这个在USB官网的HID usage table(https://usb.org/sites/default/files/hut1_22.pdf)里面有定义。因为这个是USB标准所定义的,现在所有的操作系统都会认识这个usage, 于是主机会把0x04翻译成字母A.

有人要问了,那看起来只要发一个报文就可以输出A了,为什么还要再发一个松开的报文?

大家在用键盘的时候应该有这个体验,我们要输入一个字母A, 会按下A键然后松开。如果你一直按着字母A, 那么在主机上会看到一直会有字母A输出,下到你松开按键A.

当键盘只给主机发送一个A键盘按下的报文(表1),那么主机会一直输出字母A,这自然不是我们想到的。因此我们在发送完按下的报文后,还要发送松开的报文告诉主机说按键已经松开了。表2中把Byte 1的值改为0x00就是告诉主机按键已松开。

 

2. 按键实验

我们有了之上的知识后,那么比如我现在想要输入其他的字母要怎么做?

第一步我们要找到HID usage table(https://usb.org/sites/default/files/hut1_22.pdf),然后找到其他字母对应的usage并发出去。

上面我截取了一段键盘按键的usage, N的usage为0x11, i的usage为0x0C, c的usage为0x06, e的usage为0x08. 所以如果我要让主机输出单词Nice, 键盘必须要发出下面4组报文(report).

(1)发送N的报文

Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x00 0x11 0x00 0x00 0x00
Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x00 0x00 0x00 0x00 0x00

(2)发送i的报文

Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x00 0x0C 0x00 0x00 0x00
Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x00 0x00 0x00 0x00 0x00

(3)发送c的报文

Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x00 0x06 0x00 0x00 0x00
Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x00 0x00 0x00 0x00 0x00

(4)发送e的报文

Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x00 0x08 0x00 0x00 0x00
Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x00 0x00 0x00 0x00 0x00

以此类推,我们查找到26个字母对应的usage, 并让键盘发出去,就可以让主机输出26个字母出来。

3. 报文格式解析

细心的同学可能会有个疑问:我用一个字节就可以完成26个字母的发送,还要其他4个字节来做什么?

第一个字节Byte 0下面会讲到,这里先跳过。 第2到第5个字母的地位是等同的。也就是说发送按键A,下面的4种方式的效果是一样的。

Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x00 0x04 0x00 0x00 0x00
Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x00 0x00 0x04 0x00 0x00
Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x00 0x00 0x00 0x04 0x00
Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x00 0x00 0x00 0x00 0x04

可能大家还是蒙的,你说这么多还是没有解释原因呀。现在解释正式开始。

上面说的所有情况都是用户一个按键一个按键按住松开的情况,那如果我4个按键一起按住一起松开,那键盘该怎么发送报文?

还是以Nice这个单词为例,假如我同时按住Nice然后松开,键盘应该发送如下的报文。

(1) 4个按键按住的报文

Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x00 0x11 0x0C 0x06 0x08

(2) 4个按键松开的报文

Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x00 0x00 0x00 0x00 0x00

可能有人要说了,如果我有同时按键10个按键的情况,那这4个字节不够用看呀,怎么办呢?其实也很简单,你只要把报文扩展为11个字节就可以了,下面两个报文就是同时输出: ABCDEFGHIJ的例子.

Byte 0 Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6 Byte 7 Byte 8 Byte 9 Byte 10
0x00 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D
Byte 0 Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6 Byte 7 Byte 8 Byte 9 Byte 10
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

4. Qwerty Key vs Modifier Key

在继续往讲之前,先来认识一下qwerty key和modifier key这两个概念。

上面介绍的26个字母的按键,都属于qwerty key. 这个qwerty这个词,你看一下手头键盘的第一行字母的排列,是不是觉得创造这个词的人很偷懒。

那什么是modifier key呢?"Ctrl"、"Alt"、"Shift"、"Win"这些按键统称为modifier key. 除了这些按键,键盘上的其他按键都称为qwerty key.

5. Modifier报文格式

上面留了个悬念,就是没有解释byte 0的作用。其实byte 0就是用来表示modifier按键的。 我们希望的Modifier报文的格式如下:

bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
R Win R Shift R Alt R Ctrl L Win L Shift L Alt L Ctrl

对于一个全键盘会有左右边的modifier,因此我们定义byte 0的高4bit表示右边的modifier, 低4bit表示左边的modifier.

有了报文格式之后,我们就可以尝试来发一下modifier的报文。比如我们要发送左边的Ctrl,那应该发:

Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x01 0x00 0x00 0x00 0x00
Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x00 0x00 0x00 0x00 0x00

几个modifier的组合应该怎么发呢?比如: L Ctrl + L Alt + R Win

Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x83 0x00 0x00 0x00 0x00
Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x00 0x00 0x00 0x00 0x00

6. 组合按键报文

我们现在来看看,qwerty key和modifier key的组合。比如程序员经常使用的组合键:Ctrl + C, Ctrl + V.

(1) Ctrl + C 报文

Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x01 0x06 0x00 0x00 0x00
Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x00 0x00 0x00 0x00 0x00

(2) Ctrl + V 报文

Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x01 0x19 0x00 0x00 0x00
Byte 0 Byte 1 Byte 2 Byte 3 Byte 4
0x00 0x00 0x00 0x00 0x00

7. 总结

这篇主要讲了键盘报文的分类与格式,以及如何根据要求发送相应的报文,让主机输出相应的qwerty key和modifier key.

大家应该会接着问,那主机为什么知道我这些报文的格式?那肯定是主机要提前知道我们发的报文的格式,那么问题就变成了:在发送报文前我们要怎么通知主机,让它知道我们报文的格式。

我们下次再继。。。。。。