在遵照 Web 的 VNC 应用程序中帮衬两种键盘布局

作者:杏彩彩票app下载

基于 Web 的 KVM 管理工具(比如 Kimchi 和 Ovirt)可帮助用户轻松创建和管理虚拟机 (VM),甚至是从移动设备创建和管理虚拟机。这些工具依靠远程桌面共享技术,比如 虚拟网络计算 (VNC),而使用 VNC 的技术需要一个基于 Web 的 VNC 客户端,比如 noVNC。

源地址:

Javascript中有3个事件句柄在对应键盘的输入状态:keydown、keypress和keyup。
分别对应的意思是:按键被按下(按下按键但还没有抬起)、点击按键(按下并抬起按键)、按键抬起(按键抬起之后)
按键的分类
按键可以分为“实键”和“虚键”
实键可以理解为我们能够看到并打印出来的按键,如字母“A”、数字“1”、字符“?”等等
虚键就是那些无法打印出来起到控制作用的按键,如“Ctrl”、“Alt”、“Shift”、“方向键”等等
IE在处理虚键时有个特例:虚键不会产生keypress事件,必须使用keydown或keyup来捕获
按键码和字符码
按键码是计算机用来识别不同按键的编码,每一个按键都有按键码
字符码是可被打印的实键特有的,对应了键盘上显示的字符
按键码可以使用String.fromCharCode()转换为字符码
按键码和和字符码的对应表可以在本文最后找到。
获取实键

VNC 最初的目的是使物理 PC 能够从远程进行访问。因为虚拟化不是 VNC 关注的问题,所以在将 VNC 用于 VM 时,需要经过特殊处理才能解释和操作击键。Web 技术也带来了额外的挑战:Web 应用程序必须解决浏览器支持上的差异,否则仅能用于某些选定的浏览器。Web 应用程序只能通过浏览器 API 访问 PC 硬件,而桌面应用程序能够更直接地进行访问。

 

复制代码 代码如下:

本文旨在帮助 JavaScript 开发人员理解和解决相关挑战,让基于 Web 的 VNC 客户端(或其他任何面临同样问题的基于 Web 的硬件模拟器)能够准确地响应从多种键盘布局生成的击键信息。我首先将解释桌面操作系统如何处理键盘信号。然后,您将学习 RFB(VNC 使用的协议)如何将击键信息从 VNC 客户端发送到 VNC 服务器,还将了解此过程在虚拟化场景中涉及到哪些问题,以及 QEMU 社区如何为桌面 VNC 客户端解决这些问题。然后,我将介绍如何使用一个相对较新的浏览器 API 为基于 Web 的 VNC 客户端实现 QEMU 解决方案。

虚拟键码保存在WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN和WM_SYSKEYUP消息的wParam参数中。此代码标识按下或释放的键。

function getKeyCode(e) {
var keyCode = 0;
var e = e || window.event;
keyCode = e.keyCode || e.which || e.charCode;
alert(keyCode);
}

操作系统如何处理击键

键盘是一种硬件设备,对于每个按下或释放的键,它都会发送一个信号。这些信号称为扫描码,由一个或多个字节组成,用于唯一地标识按下或释放实体键的操作。

IBM 在 IBM XT 中设立了第一个扫描码标准。大部分制造商都遵循 XT 标准来确保设备与 IBM 硬件兼容。但是,扫描码不是一种容易供应用程序使用的好的键盘表示,因为不同的键盘类型可能使用不同的扫描码。举例而言,USB 键盘遵循与 XT 标准不同的扫描码标准。

哈,又是「虚拟」,您喜欢这个词吗?虚拟指的是假定存在于思想中而不是现实世界中的一些事物,也只有熟练使用DOS汇编语言编写应用程序的程序写作者才有可能指出,为什么对Windows键盘处理如此基本的键码是虚拟的而不是真实的。

稍微解释下
1. e为Firefox等标准浏览器支持的JS隐藏变量,表示一个“事件”;IE系列中没有“e”,而是用window.event来表示“事件”;所以var e = e || window.event;就表示:获取当前正在发生的事件。
2. e.keyCode、e.which、e.charCode都代表获取按键码,但不同的浏览器支持不同的写法
获取虚键

键码

为了使应用程序能够处理任何类型的键盘,操作系统将扫描码转换为与不依赖于键盘的键码。例如,在 PS2 键盘中按 Q,会得到与在 USB 键盘中按 Q 相同的键码。得益于从扫描码到键码的转换(键盘驱动程序 的第一个任务),应用程序不需要处理所有已知的键盘类型。

扫描码与键码之间的转换是可逆的。任何键码都可以转换回生成它的准确的硬件扫描码。例如,在标准美国 102 键键盘上按下标为 Q 的键,不会解释为 Q 键被按下,而是解释为位于第三行第二列的键被按下

对于早期的程序写作者来说,真实的键码由实际键盘硬件产生。在Windows文件中将这些键码称为「扫描码(scan codes)」。在IBM兼容机种上,扫描码16是Q键,17是W键,18是E、19是R,20是T,21是Y等等。这时您会发现,扫描码是依据键盘的实际布局的。Windows开发者认为这些代码过于与设备相关了,于是他们试图通过定义所谓的虚拟键码,以便经由与设备无关的方式处理键盘。其中一些虚拟键码不能在IBM兼容机种上产生,但可能会在其它制造商生产的键盘中找到,或者在未来的键盘上找到。

复制代码 代码如下:

键符 (keysyms)

对应用程序而言,使用键码仍不是很理想,因为根据不同的键盘布局,同一个实体键可能表示不同的符号。例如,在法国键盘中,位于第三行第二列的键是 A,不是 Q。大部分应用程序(例如文本编辑器)都希望获知用户按下了 Q,而不是按下的键在布局中的位置。

键符 (keysym) 是在考虑键盘布局图 (keymap) 后从一次或多次按键/释放键的操作生成的符号。从键码到键符的转换是操作系统执行的最后一次转换,该操作会向应用程序提供准确的键符。

图 1 演示了一个兼容 XT 的键盘将一个从美国或法国键盘布局将按键信号发送到基于 Linux 的系统的转换顺序。

您使用的大多数虚拟键码的名称在WINUSER.H表头文件中都定义为以VK_开头。表6-2列出了这些名称和数值(十进制和十六进制),以及与虚拟键相对应的IBM兼容机种键盘上的键。下表也标出了Windows执行时是否需要这些键。下表还按数字顺序列出了虚拟键码。

function getKeyCode(e) {
var keyCode = 0;
var e = e || window.event;
if (e.ctrlKey) alert("ctrlKey pressed");
if (e.altKey) alert("altKey pressed");
if (e.shiftKey) alert("shiftKey pressed");
}

图 1. 按键信号如何从键盘发送到应用程序

图片 1

不同于扫描码到键码的转换,从键码到键符的转换是不可逆的,原因有两个。首先,这种转换需要知道用于生成键符的键盘布局图,而且不是所有场景都可以获得此信息。其次,无法知道使用了哪种键组合来创建键符。例如,A 的键符可通过按 Shift + a 或在锁定大写时按 a 来生成。这种模糊性是 QEMU 在使用 RFB 时遇到的问题的来源。

前四个虚拟键码中有三个指的是鼠标键:

字母和数字键的键码值(keyCode)

RFB 协议、QEMU/KVM 虚拟化和 VNC

RFB(远程帧缓冲区)是 VNC 用于远程访问 GUI 的协议。在该协议及其扩展协议中定义的多种 RFB 客户端到服务器消息类型中,本文关注的是 KeyEvent,也就是在按下或释放一个键时从 RFB 客户端发送到服务器的消息。图 2 显示了该消息格式。

表6-2

按键

图 2. RFB KeyEvent 客户端消息的格式

图片 2

点击查看大图

 

  • message-type 指定消息类型。KeyEvent 消息为类型 4。
  • down-flag 指定键的状态。如果按下该键,该值为 1;如果释放,该值为 0。
  • padding 是一个填充了 0 的 2 字节字段。
  • keysym 是按下或释放的键的键符。

当收到 KeyEvent 消息时,依据 down-flag 的值,RFB 服务器将按下或释放键时的键符复制到远程桌面中。在此消息中使用键符,是早期 QEMU 版本在用于虚拟化的 VNC 客户端/服务器上遇到设计问题的根源。

 

键码

首次尝试

QEMU 项目首次尝试引入 keymap 选项,以告诉 QEMU 生成键符的 VNC 客户端中使用了哪个键盘布局图。有了此信息,QEMU 就可以尝试从键符转换回键码;如果未指定键盘布局图,它会使用默认的美国布局。此方法不足以解决非美国键盘的 QEMU 问题。QEMU 需要能够支持 VNC 客户端使用的任何键盘布局(针对 100 多种语言的布局),而且从上一节中我们已经知道,不同的组合键可能生成相同的键符。

QEMU 是一个硬件模拟器。当您连接到在 QEMU 虚拟机中运行的 VNC 服务器时,服务器不会单纯地接收和显示击键;它会模拟它们,就像有人在虚拟机中的一个真实键盘上按键一样。结果,在收到 RFB KeyEvent 消息时,QEMU 会尝试着将已发送的键转换为生成该键的 XT 扫描码。但是,KeyEvent 消息发送的是键符。QEMU 曾经面临着如何根据键符利用已按下或已释放的键来获取实际 XT 扫描码的挑战。

在 QEMU 最初尝试解决此问题失败后(参见 “首次尝试” 边栏),GTK-VNC 和 QEMU 社区合作创建了 RFB 协议的一个官方扩展,该扩展添加了一条新的 KeyEvent 消息,其中不仅包含键符,还包含在 VNC 客户端中按下的键码。图 3 显示了该消息格式。

十进制

十六进制

WINUSER.H标识符

必需?

IBM兼容键盘

1

01

VK_LBUTTON

 

鼠标左键

2

02

VK_RBUTTON

 

鼠标右键

3

03

VK_CANCEL

ˇ

Ctrl-Break

4

04

VK_MBUTTON

 

鼠标中键

按键

图 3. QEMU 扩展 KeyEvent RFB 消息的格式

图片 3

点击查看大图

 

  • message-type 指定消息类型。扩展的 QEMU KeyEvent 消息为类型 255。
  • submessage-type 有一个一字节的默认值 0。
  • down-flag 指定键的状态。如果按下该键,该值为 1;如果释放,该值为 0。
  • keysym 是已按下或释放的键的键符。
  • keycode 是生成该键符的键码。

借助额外的 keycode 信息,QEMU 可将键码转换回扫描码并进行模拟。此能力还使 VNC 服务器不知道 VNC 客户端使用了哪个键盘布局图。只要客户端的键盘布局图与来宾操作系统(在虚拟机中运行的操作系统)中配置的键盘布局图相同,键盘就会按预期工作。

您永远都不会从键盘消息中获得这些鼠标键代码。在下一章可以看到,我们能够从鼠标消息中获得它们。VK_CANCEL代码是一个虚拟键码,它包括同时按下两个键(Ctrl-Break)。Windows应用程序通常不使用此键。

键码

Web 技术和击键处理

在桌面应用程序和 Web 应用程序之间的键盘事件处理差异,为全面实现基于 Web 的 VNC 客户端增加了一个复杂性层。该层是浏览器。桌面应用程序可更直接地访问底层硬件,而 Web 应用程序受到浏览器支持的限制。

表6-3中的键--Backspace、Tab、Enter、Escape和Spacebar-通常用于Windows程序。不过,Windows一般用字符消息(而不是键盘消息)来处理这些键。

按键

浏览器中的击键处理基本知识

现在出现的问题总数比 2000 年代初期更少,但浏览器之间仍然缺乏标准化。而且谈到键盘处理,差异可能很大。

浏览器提向 Web 应用程序提供了 3 种键盘事件:

  • keydown:按下一个键。
  • keyup:释放一个键:
  • keypressed:按下一个字符键。

keydownkeyup 事件与操作系统处理的键盘事件类似。keypressed 事件仅在生成键符时发生。Shift 或 Alt 等特殊键不会生成 keypressed 事件。Web 应用程序要可靠地获得生成的字符,则必须依靠 keypressed 事件。

每个事件拥有至少以下 3 个属性:

  • keyCode 属性指按下的键,不含修饰键,比如 Shift 或 Alt。当按下 a 键时,甚至在生成的键符为 A 时,keyCode 也是相同的。许多网站和 Web 教程会误导性地将此属性称为键的扫描码。
  • charCode 属性是键事件(如果有)生成的键符的 ASCII 码。
  • which 属性返回的值在大多数时候与 keyCode 相同,提供按下的键的 Unicode 值。

可以使用 Javascript 键事件测试脚本 页面查看在按下某个键时键盘事件有何行为。例如,按下左 Shift 键会得到:

keydown keyCode=16 which=16 charCode=0
keyup keyCode=16 which=16 charCode=0

按下 a 键会得到:

keydown keyCode=65 (A) which=65 (A) charCode=0
keypress keyCode=0 which=97 (a) charCode=97 (a)
keyup keyCode=65 (A) which=65 (A) charCode=0

按住 a 键不放会得到:

keydown keyCode=65 (A) which=65 (A) charCode=0
keypress keyCode=0 which=97 (a) charCode=97 (a)
keydown keyCode=65 (A) which=65 (A) charCode=0
keypress keyCode=0 which=97 (a) charCode=97 (a)
keydown keyCode=65 (A) which=65 (A) charCode=0
keypress keyCode=0 which=97 (a) charCode=97 (a)
keyup keyCode=65 (A) which=65 (A) charCode=0

这种对键盘事件的浏览器支持使实现 VNC Web 客户端成为可能。一些 VNC 客户端项目已开始试验解决多键盘布局问题。但 noVNC 项目没有实现 QEMU VNC 扩展来处理该问题,所以在 2015 年,我们决定尝试一下。毫无疑问,我曾认为解决该问题仅需使用 keyCode(浏览器提供的所谓的扫描码)并将其放在 QEMU 扩展的 KeyEvent 消息中。哪里可能出错了?

表6-3

键码

keyCode,所谓的扫描码

在 noVNC 中使用 keyCode 属性实现 QEMU 扩展,没有解决键盘布局问题。我了解到,尽管 keyCode 属性拥有定位行为,但它依赖于布局,因此无法在 QEMU KeyEvent 消息中用作键码。

下面的简单试验展示了不同布局中的 keyCode 属性的行为。我们再次使用 Javascript 键事件测试脚本 页面来展示键盘事件,以下是在美国布局键盘中按下 q 键时的输出:

keydown keyCode=81 (Q) which=81 (Q) charCode=0
keypress keyCode=0 which=113 (q) charCode=113 (q)
keyup keyCode=81 (Q) which=81 (Q) charCode=0

将布局更改为法国,以下是同一个键的输出:

keydown  keyCode=65  (A)  which=65  (A)  charCode=0 
keypress keyCode=0        which=97  (a)  charCode=97  (a)
keyup    keyCode=65  (A)  which=65  (A)  charCode=0

请注意,当布局发生更改时,keyCode 值从 81 变为了 65。在法国 AZERTY 布局键盘中,第三行第二个键是 a,keyCode 反映了这一布局变化。

在我尝试在 noVNC 项目中实现 QEMU 扩展时,浏览器的 JavaScript 中没有描述物理位置的属性 — a 键的不依赖于布局的键码。所以,我必须暂时搁置这项工作。

 

按键

KeyboardEvent.code 成为了救星

2016 年初,Chrome 浏览器稳定版 48 中包含一个名为 code 的新 KeyboardEvent 属性。(Firefox 之前已引入此属性,Opera 随后也提供了它。)Mozilla Developer Network 对此属性进行了如下描述:

KeyboardEvent.code 包含一个标识所按下的实体键的字符串。该值不受当前键盘布局或修饰键状态的影响,所以特定的键将始终返回相同的值。

借助这个新属性,我可以继续并完成我的实现。

十进制

十六进制

WINUSER.H标识符

必需?

IBM兼容键盘

8

08

VK_BACK

ˇ

Backspace

9

09

VK_TAB

ˇ

Tab

12

0C

VK_CLEAR

 

Num Lock关闭时的数字键盘5

13

0D

VK_RETURN

ˇ

Enter (或者另一个)

16

10

VK_SHIFT

ˇ

Shift (或者另一个)

17

11

VK_CONTROL

ˇ

Ctrl (或者另一个)

18

12

VK_MENU

ˇ

Alt (或者另一个)

19

13

VK_PAUSE

 

Pause

20

14

VK_CAPITAL

ˇ

Caps Lock

27

1B

VK_ESCAPE

ˇ

Esc

32

20

VK_SPACE

ˇ

Spacebar

键码

有效的实现

另外,Windows程序通常不需要监视Shift、Ctrl或Alt键的状态。

A

浏览器支持状况

截至编写本文时,Chrome、Firefox、Firefox for Android 和 Opera 都支持 KeyboardEvent.code 属性。Microsoft Edge 路线图 将此 API 列入 “考虑” 范围。对于任何不支持 KeyboardEvent.code 的浏览器,VNC Web 客户端必须禁用 QEMU VNC 扩展,退而使用默认的 RFB KeyEvent 消息,或者找到另一种方法来使用可用的 KeyboardEvent 属性获取不依赖于布局的实体键。

扩展的 QEMU KeyEvent 消息已在多个桌面 VNC 客户端中良好地建立和实现。既然 KeyboardEvent.code 属性使恢复按下的实体键成为可能,那么 VNC Web 客户端就没有理由不采用相同方式实现该扩展。我为 noVNC 项目实现的解决方案可供任何基于 Web 的 VNC 客户端使用。

表6-4列出的前八个码可能是与VK_INSERT和VK_DELETE一起最常用的虚拟键码:

65

忽略 keypressed 事件

我在解决方案中选择了忽略 keypressed 事件。这些事件仅在一个或多个 keypressed 事件生成一个可读字符(一个键符)时触发。检测到来自支持 QEMU VNC 扩展的客户端的连接时,QEMU VNC 服务器会(在大多数时候,我稍后将讨论)忽略消息的 keysym 字段,仅依靠 keycode 字段在虚拟机中模拟 XT 扫描码。

表6-4

J

代码实现

我设计的完整、有效的实现可在 GitHub 上获得。

获取代码

在这里,我将重点介绍一些特别值得注意的细节。

 

74

如何将 KeyboardEvent.code 转换为 xt_scancode

KeyboardEvent.code 提供了键的物理位置,但未使用可直接用在 RFB 消息中的格式。以下是该属性的可能值的一个示例:

'Esc' key:  xt_scancode 0x0001 keyboardevent.code = "Escape"
Spacebar: xt_scancode 0x0039 keyboardevent.code = "Space"
'F1' key: xt_scancode 0x003B keyboardevent.code = "F1"

我的实现使用了这篇有关 KeyboardEvent.code 的 Mozilla Developer Network 文章 中提供的表,创建一个将 KeyboardEvent.code 值转换为相应的 xt_scancode 的哈希表,例如:

XT_scancode["Escape"] = 0x0001;
XT_scancode["Space"] = 0x0039;
XT_scancode["F1"] = 0x003B;

十进制

十六进制

WINUSER.H标识符

必需?

IBM兼容键盘

33

21

VK_PRIOR

ˇ

Page Up

34

22

VK_NEXT

ˇ

Page Down

35

23

VK_END

ˇ

End

36

24

VK_HOME

ˇ

Home

37

25

VK_LEFT

ˇ

左箭头

38

26

VK_UP

ˇ

上箭头

39

27

VK_RIGHT

ˇ

右箭头

40

28

VK_DOWN

ˇ

下箭头

41

29

VK_SELECT

   

42

2A

VK_PRINT

   

43

2B

VK_EXECUTE

   

44

2C

VK_SNAPSHOT

 

Print Screen

45

2D

VK_INSERT

ˇ

Insert

46

2E

VK_DELETE

ˇ

Delete

47

2F

VK_HELP

   

S

创建 QEMU RFB KeyEvent 消息

buff 视为一个大小为 12 的字节数组:

buff[offset] = 255; // msg-type
buff[offset + 1] = 0; // sub msg-type
 
buff[offset + 2] = (down >> 8);
buff[offset + 3] = down;
 
buff[offset + 4] = (keysym >> 24);
buff[offset + 5] = (keysym >> 16);
buff[offset + 6] = (keysym >> 8);
buff[offset + 7] = keysym;
 
var RFBkeycode = getRFBkeycode(keycode)
 
buff[offset + 8] = (RFBkeycode >> 24);
buff[offset + 9] = (RFBkeycode >> 16);
buff[offset + 10] = (RFBkeycode >> 8);
buff[offset + 11] = RFBkeycode;

数据结构与 图 3 类似,这绝非偶然。在本代码中,keycode 是从 keyboardevent.code 值转换得到的 xt_scancodekeysym 是一个 0 字段(大部分情况下如此)。

getRFBkeycode() 函数将 XT_scancode 转换为 QEMU VNC 扩展定义的格式:

function getRFBkeycode(xt_scancode) {
    var upperByte = (keycode >> 8);
    var lowerByte = (keycode & 0x00ff);
    if (upperByte === 0xe0 && lowerByte < 0x7f) {
        lowerByte = lowerByte | 0x80;
        return lowerByte;
    }
    return xt_scancode
}

注意,许多名称(例如VK_PRIOR和VK_NEXT)都与键上的标志不同,而且也与滚动条中的标识符不统一。Print Screen键在平时都被Windows应用程序所忽略。Windows本身响应此键时会将视讯显示的位图影本存放到剪贴板中。假使有键盘提供了VK_SELECT、VK_PRINT、VK_EXECUTE和VK_HELP,大概也没几个人看过那样的键盘。

83

NumLock 的独特情况:键符发挥作用的时刻

我提到过键符基本上被忽略。在至少一种情况下,QEMU VNC 服务器考虑键符:当使用数字键盘 (Numpad) 中的键时。

在我的解决方案的第一个实现中(忽略 QEMU KeyEvent 消息的键符字段),出现了一种奇怪的行为:当按下任何多用途数字键盘键时,比如 0、1、2、3、4、6、7、8、9 或小数点(en_US 布局中的句点),即使虚拟机和客户端上的 NumLock 状态为 ON,QEMU VNC 服务器也会:

  • 将虚拟机的 NumLock 状态更改为 OFF(如果它为 ON
  • 按键

例如,在客户端和虚拟机上的 NumLock 状态为 ON 时按数字键盘键 8,会将虚拟机中的 NumLock 状态更改为 OFF,然后执行向上箭头键的操作。在 NumLock 状态为 OFF 时按数字键盘键 8 的行为才是符合预期的。

此问题可通过可靠方式利用客户端和虚拟机的 NumLock 状态来解决。但远程 QEMU VNC 服务器不可能知道客户端键盘的 NumLock 状态。服务器可以看到何时按下/释放 NumLock 键,但无从了解当前的 NumLock 状态,因为 QEMU VNC KeyEvent 消息未传递该信息。

经过在桌面 VNC 客户端上广泛测试后,我认识到在这些环境中发送了键符。尽管键码不会基于 NumLock 状态而发生更改,但键符会受到影响。结论是,QEMU VNC 服务器使用键符字段来猜测客户端的 NumLock 状态��并采取相应行动来尝试同步虚拟机状态。在实现中,发送的键符为 0 时,服务器将此解释为 “客户端的 NumLock 状态为 OFF”,强制将客户端 NumLock 状态更改为 OFF,然后发送按下的键码。

因为如果不发送键符,会默认为 NumLock 状态为 OFF,所以解决方案是仅在 NumLock 状态为 ON 时发送键符。

Windows也包括在主键盘上的字母和数字键的虚拟键码(数字键盘将单独处理)。

1

发送数字键盘的键符

生成键符的键盘事件是 keypressed 事件,我的解决方案忽略了该事件。那么如何将键符应用于 QEMU KeyEvent 消息?

幸运的是,确定键符不是一定需要 keypressed 事件。数字键盘在所有布局中都是标准的(否则,如果没有键盘布局图,QEMU VNC 服务器就无法猜测 NumLock 状态)。所以,数字键盘键的键符值可预先确定。

这就留下了一个问题,如果不使用 keypress 事件,如何区分数字键 7 用作 Home 键的情况和用作数字 7 的情况。我的实现使用了 KeyboardEvent.keyCode 属性(在 keydown 事件上设置)来进行区分,如下面的代码片段所示。

下面的函数接收一个键盘事件 evt,并将 KeyboardEvent.code 值与属于数字键盘的值相比较:

function isNumPadMultiKey(evt) {
    var numPadCodes = ["Numpad0", "Numpad1", "Numpad2",
        "Numpad3", "Numpad4", "Numpad5", "Numpad6",
        "Numpad7", "Numpad8", "Numpad9", "NumpadDecimal"];
    return (numPadCodes.indexOf(evt.code) !== -1);
}

我使用前面的函数来查看是否需要对某个指定的键盘事件进行任何特殊处理。

下面的函数接收一个键盘事件 evt,并将它的 keyboardevent.keyCode 属性与一个名为 numLockOnKeyCodes 的预定义值集相比较:

function getNumPadKeySym(evt) {
    var numLockOnKeySyms = {
        "Numpad0": 0xffb0, "Numpad1": 0xffb1, "Numpad2": 0xffb2,
        "Numpad3": 0xffb3, "Numpad4": 0xffb4, "Numpad5": 0xffb5,
        "Numpad6": 0xffb6, "Numpad7": 0xffb7, "Numpad8": 0xffb8,
        "Numpad9": 0xffb9, "NumpadDecimal": 0xffac
    };
    var numLockOnKeyCodes = [96, 97, 98, 99, 100, 101, 102,
        103, 104, 105, 108, 110];
 
    if (numLockOnKeyCodes.indexOf(evt.keyCode) !== -1) {
        return numLockOnKeySyms[evt.code];
    }
    return 0;

在 NumLock ON 状态下,numLockOnKeyCodes 值对应于数字键盘键 0 到 9 和小数点。如果 evt.keyCode 是这些值之一,那么该函数会返回 numLockOnKeySyms 提供的等效键符;否则,它会返回 0。

以下是在代码内调用这些函数的方式:

result.code = evt.code;
result.keysym = 0;
 
if (isNumPadMultiKey(evt)) {
    result.keysym = getNumPadKeySym(evt);
}

在此代码中,result 是在处理过程中传递的对象。这样,解决方案就可以确保正确处理 NumLock 键。

表6-5

49

AltGR 和 Windows

我在 Windows 10 上运行的所有支持的浏览器(Chrome、Firefox 和 Opera)中测试 noVNC 解决方案时出现了另一个异常:AltGR 修饰键在 Linux 虚拟机上未按预期工作。

通过调试代码,我发现, AltGR 键通过两条 KeyEvent 消息发送到 QEMU VNC 服务器,而不是一条消息。第一条消息是一个左 Ctrl 键;第二条消息是一个右 Alt 键 — 与您期望某人按下左 Ctrl 后立即按右 Alt 的效果相同。当客户端在 Linux PC 中运行时,发送 AltGR 键作为右 Alt。

出现此行为 是有历史原因的。长话短说:旧的美国键盘没有 AltGR 键,Windows 最初使用左 Ctrl + 右 Alt 来模拟它。此解决方案适合没有 AltGR 键的键盘,但在使用有 AltGR 的键盘时可能带来误导。

一个解决方案是记录此行为,并强制用户删除此默认映射。另一个是我选择的解决方案 — 用于处理 noVNC 中的行为。我的代码包含对按左 Ctrl 后按右 Alt 的组合的特殊处理:

if (state.length > 0 && state[state.length-1].code == 'ControlLeft') {
     if (evt.code !== 'AltRight') {
         next({code: 'ControlLeft', type: 'keydown', keysym: 0});
     } else {
         state.pop();
     }
}
             (...)
            if (evt.code !== 'ControlLeft') {
next(evt);
            }

此代码告诉 noVNC:在 keydown 事件中,如果 KeyboardEvent.code 等于 ControlLeft,则不要立即转发该事件。等待第二个 keydown 事件,并验证它的代码是否等于 AltRight,这意味着浏览器收到了一个左 Ctrl

  • 右 Alt 的组合,这可能意味着在 Windows 浏览器中按下了 AltGR 键。在这种情况下,丢弃左 Ctrl,仅转发右 Alt,这是 Linux 中的默认行为。这种处理使 AltGR 键能按预期工作,甚至在 Windows 浏览器中也是如此。

此方法的缺点是,即使用户合理地按下了左 Ctrl + 右 Alt 的组合,也不会转发该组合。我将此视为可接受的缺点,因为左 Ctrl + 右 Alt 不是一种常用的组合键(左 Ctrl + 左 Alt 和右 Ctrl + 右 Alt 容易键入得多)。适用性影响极小,而且用户不需要在 Windows 重新配置键盘布局图。

 

B

弃用的属性

我的实现的另一个已知缺陷,是一个用于处理 NumLock 问题的属性:

if (numLockOnKeyCodes.indexOf(evt.keyCode) !== -1) {

KeyboardEvent.keyCode(连同 whichcharCode,可在 “Web 技术和击键处理” 部分看到)自 2015 年以来已被 弃用。但是,当时在大部分浏览器中没有实现应在它们的位置使用的属性 KeyboardEvent.key(而且在编写本文时,所有 Safari 版本和 Chrome 移动版本仍不支持它)。所有这些弃用的属性被广泛用在 noVNC 和其他任何需要键盘控制的应用程序中。我不希望浏览器很快丢弃这些属性,但依靠一个弃用的属性不是推荐做法。我强烈建议受影响应用程序的开发人员将 keyCodewhichcharCode 重构为新的 KeyboardEvent.key API。

十进制

十六进制

WINUSER.H标识符

必需?

IBM兼容键盘

48-57

30-39

ˇ

主键盘上的0到9

65-90

41-5A

ˇ

A到Z

66

结束语

注意,数字和字母的虚拟键码是ASCII码。Windows程序几乎从不使用这些虚拟键码;实际上,程序使用的是ASCII码字符的字符消息。

K

桌面与 Web VNC 客户端

尽管更方便使用,但基于 Web 的 VNC 客户端比桌面客户端更慢。Kimchi 的一个社区要求是,使用名为 Virt Viewer 的 Linux VNC 客户端让桌面 VNC 客户端更容易连接到 Kimchi 管理的虚拟机。作为 Kimchi 项目的活跃贡献者,我开发了一个 Kimchi 特性,让用户可以选择使用 Virt Viewer 还是 noVNC 来连接到 Kimchi 虚拟机的 VNC 服务器。

调查 VNC Web 应用程序中的键盘布局问题,在 noVNC 项目中实现解决方案并处理未预见的问题,是一个虽艰苦但有益的过程。

Web 开发自早期的噩梦时代以来已有了很大改善。浏览器兼容性的提高使得大部分 Web 应用程序都只需编码一次,即可在所有主要浏览器中按预期运行。但当应用程序需要更高级的 API 时,比如键盘处理或者甚至移动设备加速计,问题就出现了。在这些 API 中,浏览器支持缓冲区,这直接影响了应使用相同代码库在多个设备上运行(借助响应式 Web 设计和 HTML5)的应用程序的开发。

在面临键盘布局问题时,VNC Web 客户端就会受到这类跨浏览器差异的影响。未实现 QEMU VNC KeyEvent 扩展的项目无法摆脱一些问题,比如如何在不知道使用的键盘布局图的情况下解释非美国键盘中的给定键符。除非 KeyboardEvent.code 属性可用于所有浏览器,否则实现该扩展的项目(正如我为 noVNC 所做的一样)需要支持两种不同的键盘处理模式。

本文永久更新链接地址:http://www.linuxidc.com/Linux/2016-12/137810.htm

图片 4

表6-6所示的代码是由Microsoft Natural Keyboard及其兼容键盘产生的:

75

表6-6

T

 

84

十进制

十六进制

WINUSER.H标识符

必需?

IBM兼容键盘

91

5B

VK_LWIN

 

左Windows键

92

5C

VK_RWIN

 

右Windows键

93

5D

VK_APPS

 

Applications键

2

Windows用VK_LWIN和VK_RWIN键打开「开始」菜单或者(在以前的版本中)启动「工作管理员程序」。这两个都可以用于登录或注销Windows(只在Microsoft Windows NT中有效),或者登录或注销网络(在Windows for Applications中)。应用程序能够通过显示辅助信息或者当成快捷方式键看待来处理application键。

50

表6-7所示的代码用于数字键盘上的键(如果有的话):

C

表6-7

67

 

L

十进制

十六进制

WINUSER.H标识符

必需?

IBM兼容键盘

96-105

60-69

VK_NUMPAD0到VK_ NUMPAD9

 

NumLock打开时数字键盘上的0到9

106

6A

VK_MULTIPLY

 

数字键盘上的*

107

6B

VK_ADD

 

数字键盘上的+

108

6C

VK_SEPARATOR

   

109

6D

VK_SUBTRACT

 

数字键盘上的-

110

6E

VK_DECIMAL

 

数字键盘上的.

111

6F

VK_DIVIDE

 

数字键盘上的/

76

最后,虽然多数的键盘都有12个功能键,但Windows只需要10个,而位旗标却有24个。另外,程序通常用功能键作为键盘快捷键,这样,它们通常不处理表6-8所示的按键:

U

表6-8

85

 

3

十进制

十六进制

WINUSER.H标识符

必需?

IBM兼容键盘

112-121

70-79

VK_F1到VK_F10

ˇ

功能键F1到F10

122-135

7A-87

VK_F11到VK_F24

 

功能键F11到F24

144

90

VK_NUMLOCK

 

Num Lock

145

91

VK_SCROLL

 

Scroll Lock

51

另外,还定义了一些其它虚拟键码,但它们只用于非标准键盘上的键,或者通常在大型主机终端机上使用的键。查看/ Platform SDK / User Interface Services / User Input / Virtual-Key Codes,可得到完整的列表。

D

68

M

77

V

86

4

52

E

69

N

78

W

87

5

53

F

70

O

79

X

88

6

54

G

71

P

80

Y

89

7

55

H

72

Q

81

Z

90

8

56

I

73

R

82

0

48

9

57

  

数字键盘上的键的键码值(keyCode)

功能键键码值(keyCode)

按键

键码

按键

键码

按键

键码

按键

键码

0

96

8

104

F1

112

本文由杏彩发布,转载请注明来源

关键词: