一个键盘对于用户的体验是,用户按按键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.
大家应该会接着问,那主机为什么知道我这些报文的格式?那肯定是主机要提前知道我们发的报文的格式,那么问题就变成了:在发送报文前我们要怎么通知主机,让它知道我们报文的格式。
我们下次再继。。。。。。