首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

在基于 Web 的 VNC 应用程序中支持多种键盘布局-2

在基于 Web 的 VNC 应用程序中支持多种键盘布局-2

Web 技术和击键处理在桌面应用程序和 Web 应用程序之间的键盘事件处理差异,为全面实现基于 Web 的 VNC                客户端增加了一个复杂性层。该层是浏览器。桌面应用程序可更直接地访问底层硬件,而 Web 应用程序受到浏览器支持的限制。
浏览器中的击键处理基本知识现在出现的问题总数比 2000 年代初期更少,但浏览器之间仍然缺乏标准化。而且谈到键盘处理,差异可能很大。
浏览器提向 Web 应用程序提供了 3 种键盘事件:
  • keydown:按下一个键。
  • keyup:释放一个键:
  • keypressed:按下一个字符键。
keydown 和 keyup                事件与操作系统处理的键盘事件类似。keypressed 事件仅在生成键符时发生。Shift 或 Alt 等特殊键不会生成                keypressed 事件。Web 应用程序要可靠地获得生成的字符,则必须依靠                keypressed 事件。
每个事件拥有至少以下 3 个属性:
  • keyCode 属性指按下的键,不含修饰键,比如 Shift 或 Alt。当按下 a 键时,甚至在生成的键符为 A                    时,keyCode 也是相同的。许多网站和 Web 教程会误导性地将此属性称为键的扫描码。
  • charCode 属性是键事件(如果有)生成的键符的 ASCII 码。
  • which 属性返回的值在大多数时候与 keyCode 相同,提供按下的键的                    Unicode 值。
可以使用                 页面查看在按下某个键时键盘事件有何行为。例如,按下左 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                消息中。哪里可能出错了?
keyCode,所谓的扫描码在 noVNC 中使用 keyCode 属性实现 QEMU 扩展,没有解决键盘布局问题。我了解到,尽管                keyCode 属性拥有定位行为,但它依赖于布局,因此无法在 QEMU KeyEvent                消息中用作键码。
下面的简单试验展示了不同布局中的 keyCode 属性的行为。我们再次使用                 页面来展示键盘事件,以下是在美国布局键盘中按下 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 年初, 浏览器稳定版 48 中包含一个名为 code 的新                KeyboardEvent 属性。( 之前已引入此属性,Opera 随后也提供了它。)Mozilla Developer Network                对此属性进行了如下描述:
KeyboardEvent.code                包含一个标识所按下的实体键的字符串。该值不受当前键盘布局或修饰键状态的影响,所以特定的键将始终返回相同的值。

借助这个新属性,我可以继续并完成我的实现。
有效的实现浏览器支持状况截至编写本文时, 都支持                    KeyboardEvent.code 属性。Microsoft Edge  将此 API 列入 “考虑” 范围。对于任何不支持 KeyboardEvent.code                    的浏览器,VNC Web 客户端必须禁用 QEMU VNC 扩展,退而使用默认的 RFB KeyEvent                    消息,或者找到另一种方法来使用可用的 KeyboardEvent 属性获取不依赖于布局的实体键。

扩展的 QEMU KeyEvent 消息已在多个桌面 VNC 客户端中良好地建立和实现。既然                KeyboardEvent.code 属性使恢复按下的实体键成为可能,那么 VNC Web                客户端就没有理由不采用相同方式实现该扩展。我为 noVNC 项目实现的解决方案可供任何基于 Web 的 VNC 客户端使用。
忽略 keypressed 事件我在解决方案中选择了忽略 keypressed 事件。这些事件仅在一个或多个 keypressed                事件生成一个可读字符(一个键符)时触发。检测到来自支持 QEMU VNC 扩展的客户端的连接时,QEMU VNC                服务器会(在大多数时候,我稍后将讨论)忽略消息的 keysym 字段,仅依靠 keycode                字段在虚拟机中模拟 XT 扫描码。
代码实现我设计的完整、有效的实现可在 GitHub 上获得。

在这里,我将重点介绍一些特别值得注意的细节。
如何将 KeyboardEvent.code 转换为                xt_scancodeKeyboardEvent.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;




创建 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;




数据结构与  类似,这绝非偶然。在本代码中,keycode 是从                keyboardevent.code 值转换得到的                xt_scancode,keysym 是一个 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
}

返回列表