summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPierre Ossman <ossman@cendio.se>2017-05-04 12:18:55 +0200
committerPierre Ossman <ossman@cendio.se>2017-05-04 12:18:55 +0200
commit545442afc35c649b40482c3e996ba4e1fc5354e4 (patch)
tree17d6dc770a0c9aaafcfd25348e13845d0c584f57
parent1c5702b0b5dbc6a2d8e4eb9afd909fc26e855310 (diff)
parent5a3e9d3da89d854131396aceca1881487abded26 (diff)
downloadnovnc-545442afc35c649b40482c3e996ba4e1fc5354e4.tar.gz
Merge branch 'keyboard' of https://github.com/CendioOssman/noVNC
-rw-r--r--app/error-handler.js3
-rw-r--r--app/ui.js14
-rw-r--r--core/input/devices.js222
-rw-r--r--core/input/domkeytable.js310
-rw-r--r--core/input/fixedkeys.js127
-rw-r--r--core/input/keysym.js234
-rw-r--r--core/input/keysymdef.js693
-rw-r--r--core/input/util.js725
-rw-r--r--core/input/vkeys.js116
-rw-r--r--core/input/xtscancodes.js2
-rw-r--r--core/rfb.js66
-rw-r--r--tests/input.html11
-rw-r--r--tests/test.helper.js386
-rw-r--r--tests/test.keyboard.js1091
-rw-r--r--tests/test.rfb.js27
-rw-r--r--tests/vnc_perf.html3
-rwxr-xr-xutils/genkeysymdef.js128
-rw-r--r--utils/parse.js103
18 files changed, 2410 insertions, 1851 deletions
diff --git a/app/error-handler.js b/app/error-handler.js
index 99c2bd3..640bb87 100644
--- a/app/error-handler.js
+++ b/app/error-handler.js
@@ -35,8 +35,7 @@
msg.appendChild(div);
}
- if (err &&
- (err.stack !== undefined)) {
+ if (err && (err.stack !== undefined)) {
div = document.createElement("div");
div.className = 'noVNC_stack';
div.appendChild(document.createTextNode(err.stack));
diff --git a/app/ui.js b/app/ui.js
index 91f056a..cdf1cc5 100644
--- a/app/ui.js
+++ b/app/ui.js
@@ -1516,10 +1516,10 @@ const UI = {
// Send the key events
for (i = 0; i < backspaces; i++) {
- UI.rfb.sendKey(KeyTable.XK_BackSpace);
+ UI.rfb.sendKey(KeyTable.XK_BackSpace, "Backspace");
}
for (i = newLen - inputs; i < newLen; i++) {
- UI.rfb.sendKey(keysyms.fromUnicode(newValue.charCodeAt(i)).keysym);
+ UI.rfb.sendKey(keysyms.lookup(newValue.charCodeAt(i)));
}
// Control the text content length in the keyboardinput element
@@ -1573,7 +1573,7 @@ const UI = {
},
sendEsc: function() {
- UI.rfb.sendKey(KeyTable.XK_Escape);
+ UI.rfb.sendKey(KeyTable.XK_Escape, "Escape");
},
sendTab: function() {
@@ -1583,10 +1583,10 @@ const UI = {
toggleCtrl: function() {
var btn = document.getElementById('noVNC_toggle_ctrl_button');
if (btn.classList.contains("noVNC_selected")) {
- UI.rfb.sendKey(KeyTable.XK_Control_L, false);
+ UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
btn.classList.remove("noVNC_selected");
} else {
- UI.rfb.sendKey(KeyTable.XK_Control_L, true);
+ UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
btn.classList.add("noVNC_selected");
}
},
@@ -1594,10 +1594,10 @@ const UI = {
toggleAlt: function() {
var btn = document.getElementById('noVNC_toggle_alt_button');
if (btn.classList.contains("noVNC_selected")) {
- UI.rfb.sendKey(KeyTable.XK_Alt_L, false);
+ UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
btn.classList.remove("noVNC_selected");
} else {
- UI.rfb.sendKey(KeyTable.XK_Alt_L, true);
+ UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
btn.classList.add("noVNC_selected");
}
},
diff --git a/core/input/devices.js b/core/input/devices.js
index 22653e3..1efcfb5 100644
--- a/core/input/devices.js
+++ b/core/input/devices.js
@@ -13,29 +13,22 @@ import { isTouchDevice } from '../util/browsers.js'
import { setCapture, releaseCapture, stopEvent, getPointerEvent } from '../util/events.js';
import { set_defaults, make_properties } from '../util/properties.js';
import * as KeyboardUtil from "./util.js";
+import KeyTable from "./keysym.js";
//
// Keyboard event handler
//
const Keyboard = function (defaults) {
- this._keyDownList = []; // List of depressed keys
+ this._keyDownList = {}; // List of depressed keys
// (even if they are happy)
+ this._pendingKey = null; // Key waiting for keypress
set_defaults(this, defaults, {
'target': document,
'focused': true
});
- // create the keyboard handler
- this._handler = new KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(),
- KeyboardUtil.VerifyCharModifier( /* jshint newcap: false */
- KeyboardUtil.TrackKeyState(
- KeyboardUtil.EscapeModifiers(this._handleRfbEvent.bind(this))
- )
- )
- ); /* jshint newcap: true */
-
// keep these here so we can refer to them later
this._eventHandlers = {
'keyup': this._handleKeyUp.bind(this),
@@ -45,58 +38,202 @@ const Keyboard = function (defaults) {
};
};
+function isMac() {
+ return navigator && !!(/mac/i).exec(navigator.platform);
+}
+function isWindows() {
+ return navigator && !!(/win/i).exec(navigator.platform);
+}
+
Keyboard.prototype = {
// private methods
- _handleRfbEvent: function (e) {
- if (this._onKeyPress) {
- Log.Debug("onKeyPress " + (e.type == 'keydown' ? "down" : "up") +
- ", keysym: " + e.keysym.keysym + "(" + e.keysym.keyname + ")");
- this._onKeyPress(e);
+ _sendKeyEvent: function (keysym, code, down) {
+ if (!this._onKeyEvent) {
+ return;
+ }
+
+ Log.Debug("onKeyEvent " + (down ? "down" : "up") +
+ ", keysym: " + keysym, ", code: " + code);
+
+ // Windows sends CtrlLeft+AltRight when you press
+ // AltGraph, which tends to confuse the hell out of
+ // remote systems. Fake a release of these keys until
+ // there is a way to detect AltGraph properly.
+ var fakeAltGraph = false;
+ if (down && isWindows()) {
+ if ((code !== 'ControlLeft') &&
+ (code !== 'AltRight') &&
+ ('ControlLeft' in this._keyDownList) &&
+ ('AltRight' in this._keyDownList)) {
+ fakeAltGraph = true;
+ this._onKeyEvent(this._keyDownList['AltRight'],
+ 'AltRight', false);
+ this._onKeyEvent(this._keyDownList['ControlLeft'],
+ 'ControlLeft', false);
+ }
+ }
+
+ this._onKeyEvent(keysym, code, down);
+
+ if (fakeAltGraph) {
+ this._onKeyEvent(this._keyDownList['ControlLeft'],
+ 'ControlLeft', true);
+ this._onKeyEvent(this._keyDownList['AltRight'],
+ 'AltRight', true);
}
},
- setQEMUVNCKeyboardHandler: function () {
- this._handler = new KeyboardUtil.QEMUKeyEventDecoder(KeyboardUtil.ModifierSync(),
- KeyboardUtil.TrackQEMUKeyState(
- this._handleRfbEvent.bind(this)
- )
- );
+ _getKeyCode: function (e) {
+ var code = KeyboardUtil.getKeycode(e);
+ if (code === 'Unidentified') {
+ // Unstable, but we don't have anything else to go on
+ // (don't use it for 'keypress' events thought since
+ // WebKit sets it to the same as charCode)
+ if (e.keyCode && (e.type !== 'keypress')) {
+ code = 'Platform' + e.keyCode;
+ }
+ }
+
+ return code;
},
_handleKeyDown: function (e) {
if (!this._focused) { return; }
- if (this._handler.keydown(e)) {
- // Suppress bubbling/default actions
+ var code = this._getKeyCode(e);
+ var keysym = KeyboardUtil.getKeysym(e);
+
+ // We cannot handle keys we cannot track, but we also need
+ // to deal with virtual keyboards which omit key info
+ if (code === 'Unidentified') {
+ if (keysym) {
+ // If it's a virtual keyboard then it should be
+ // sufficient to just send press and release right
+ // after each other
+ this._sendKeyEvent(keysym, 'Unidentified', true);
+ this._sendKeyEvent(keysym, 'Unidentified', false);
+ }
+
stopEvent(e);
- } else {
- // Allow the event to bubble and become a keyPress event which
- // will have the character code translated
+ return;
}
+
+ // Alt behaves more like AltGraph on macOS, so shuffle the
+ // keys around a bit to make things more sane for the remote
+ // server. This method is used by RealVNC and TigerVNC (and
+ // possibly others).
+ if (isMac()) {
+ switch (keysym) {
+ case KeyTable.XK_Super_L:
+ keysym = KeyTable.XK_Alt_L;
+ break;
+ case KeyTable.XK_Super_R:
+ keysym = KeyTable.XK_Super_L;
+ break;
+ case KeyTable.XK_Alt_L:
+ keysym = KeyTable.XK_Mode_switch;
+ break;
+ case KeyTable.XK_Alt_R:
+ keysym = KeyTable.XK_ISO_Level3_Shift;
+ break;
+ }
+ }
+
+ // Is this key already pressed? If so, then we must use the
+ // same keysym or we'll confuse the server
+ if (code in this._keyDownList) {
+ keysym = this._keyDownList[code];
+ }
+
+ // macOS doesn't send proper key events for modifiers, only
+ // state change events. That gets extra confusing for CapsLock
+ // which toggles on each press, but not on release. So pretend
+ // it was a quick press and release of the button.
+ if (isMac() && (code === 'CapsLock')) {
+ this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
+ this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
+ stopEvent(e);
+ return;
+ }
+
+ // If this is a legacy browser then we'll need to wait for
+ // a keypress event as well
+ if (!keysym) {
+ this._pendingKey = code;
+ return;
+ }
+
+ this._pendingKey = null;
+ stopEvent(e);
+
+ this._keyDownList[code] = keysym;
+
+ this._sendKeyEvent(keysym, code, true);
},
+ // Legacy event for browsers without code/key
_handleKeyPress: function (e) {
if (!this._focused) { return; }
- if (this._handler.keypress(e)) {
- // Suppress bubbling/default actions
- stopEvent(e);
+ stopEvent(e);
+
+ // Are we expecting a keypress?
+ if (this._pendingKey === null) {
+ return;
+ }
+
+ var code = this._getKeyCode(e);
+ var keysym = KeyboardUtil.getKeysym(e);
+
+ // The key we were waiting for?
+ if ((code !== 'Unidentified') && (code != this._pendingKey)) {
+ return;
+ }
+
+ code = this._pendingKey;
+ this._pendingKey = null;
+
+ if (!keysym) {
+ console.log('keypress with no keysym:', e);
+ return;
}
+
+ this._keyDownList[code] = keysym;
+
+ this._sendKeyEvent(keysym, code, true);
},
_handleKeyUp: function (e) {
if (!this._focused) { return; }
- if (this._handler.keyup(e)) {
- // Suppress bubbling/default actions
- stopEvent(e);
+ stopEvent(e);
+
+ var code = this._getKeyCode(e);
+
+ // See comment in _handleKeyDown()
+ if (isMac() && (code === 'CapsLock')) {
+ this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
+ this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
+ return;
+ }
+
+ // Do we really think this key is down?
+ if (!(code in this._keyDownList)) {
+ return;
}
+
+ this._sendKeyEvent(this._keyDownList[code], code, false);
+
+ delete this._keyDownList[code];
},
_allKeysUp: function () {
Log.Debug(">> Keyboard.allKeysUp");
- this._handler.releaseAll();
+ for (var code in this._keyDownList) {
+ this._sendKeyEvent(this._keyDownList[code], code, false);
+ };
+ this._keyDownList = {};
Log.Debug("<< Keyboard.allKeysUp");
},
@@ -130,17 +267,13 @@ Keyboard.prototype = {
//Log.Debug(">> Keyboard.ungrab");
},
-
- sync: function (e) {
- this._handler.syncModifiers(e);
- }
};
make_properties(Keyboard, [
['target', 'wo', 'dom'], // DOM element that captures keyboard input
['focused', 'rw', 'bool'], // Capture and send key events
- ['onKeyPress', 'rw', 'func'] // Handler for key press/release
+ ['onKeyEvent', 'rw', 'func'] // Handler for key press/release
]);
const Mouse = function (defaults) {
@@ -188,10 +321,6 @@ Mouse.prototype = {
_handleMouseButton: function (e, down) {
if (!this._focused) { return; }
- if (this._notify) {
- this._notify(e);
- }
-
var pos = this._getMousePosition(e);
var bmask;
@@ -258,10 +387,6 @@ Mouse.prototype = {
_handleMouseWheel: function (e) {
if (!this._focused) { return; }
- if (this._notify) {
- this._notify(e);
- }
-
var pos = this._getMousePosition(e);
if (this._onMouseButton) {
@@ -288,10 +413,6 @@ Mouse.prototype = {
_handleMouseMove: function (e) {
if (! this._focused) { return; }
- if (this._notify) {
- this._notify(e);
- }
-
var pos = this._getMousePosition(e);
if (this._onMouseMove) {
this._onMouseMove(pos.x, pos.y);
@@ -383,7 +504,6 @@ Mouse.prototype = {
make_properties(Mouse, [
['target', 'ro', 'dom'], // DOM element that captures mouse input
- ['notify', 'ro', 'func'], // Function to call to notify whenever a mouse event is received
['focused', 'rw', 'bool'], // Capture and send mouse clicks/movement
['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release
diff --git a/core/input/domkeytable.js b/core/input/domkeytable.js
new file mode 100644
index 0000000..6758c08
--- /dev/null
+++ b/core/input/domkeytable.js
@@ -0,0 +1,310 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2017 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
+ */
+
+import KeyTable from "./keysym.js"
+
+/*
+ * Mapping between HTML key values and VNC/X11 keysyms for "special"
+ * keys that cannot be handled via their Unicode codepoint.
+ *
+ * See https://www.w3.org/TR/uievents-key/ for possible values.
+ */
+
+var DOMKeyTable = {};
+
+function addStandard(key, standard)
+{
+ if (standard === undefined) throw "Undefined keysym for key \"" + key + "\"";
+ if (key in DOMKeyTable) throw "Duplicate entry for key \"" + key + "\"";
+ DOMKeyTable[key] = [standard, standard, standard, standard];
+}
+
+function addLeftRight(key, left, right)
+{
+ if (left === undefined) throw "Undefined keysym for key \"" + key + "\"";
+ if (right === undefined) throw "Undefined keysym for key \"" + key + "\"";
+ if (key in DOMKeyTable) throw "Duplicate entry for key \"" + key + "\"";
+ DOMKeyTable[key] = [left, left, right, left];
+}
+
+function addNumpad(key, standard, numpad)
+{
+ if (standard === undefined) throw "Undefined keysym for key \"" + key + "\"";
+ if (numpad === undefined) throw "Undefined keysym for key \"" + key + "\"";
+ if (key in DOMKeyTable) throw "Duplicate entry for key \"" + key + "\"";
+ DOMKeyTable[key] = [standard, standard, standard, numpad];
+}
+
+// 2.2. Modifier Keys
+
+addLeftRight("Alt", KeyTable.XK_Alt_L, KeyTable.XK_Alt_R);
+addStandard("AltGraph", KeyTable.XK_ISO_Level3_Shift);
+addStandard("CapsLock", KeyTable.XK_Caps_Lock);
+addLeftRight("Control", KeyTable.XK_Control_L, KeyTable.XK_Control_R);
+// - Fn
+// - FnLock
+addLeftRight("Hyper", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
+addLeftRight("Meta", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
+addStandard("NumLock", KeyTable.XK_Num_Lock);
+addStandard("ScrollLock", KeyTable.XK_Scroll_Lock);
+addLeftRight("Shift", KeyTable.XK_Shift_L, KeyTable.XK_Shift_R);
+addLeftRight("Super", KeyTable.XK_Super_L, KeyTable.XK_Super_R);
+// - Symbol
+// - SymbolLock
+
+// 2.3. Whitespace Keys
+
+addNumpad("Enter", KeyTable.XK_Return, KeyTable.XK_KP_Enter);
+addStandard("Tab", KeyTable.XK_Tab);
+addNumpad(" ", KeyTable.XK_space, KeyTable.XK_KP_Space);
+
+// 2.4. Navigation Keys
+
+addNumpad("ArrowDown", KeyTable.XK_Down, KeyTable.XK_KP_Down);
+addNumpad("ArrowUp", KeyTable.XK_Up, KeyTable.XK_KP_Up);
+addNumpad("ArrowLeft", KeyTable.XK_Left, KeyTable.XK_KP_Left);
+addNumpad("ArrowRight", KeyTable.XK_Right, KeyTable.XK_KP_Right);
+addNumpad("End", KeyTable.XK_End, KeyTable.XK_KP_End);
+addNumpad("Home", KeyTable.XK_Home, KeyTable.XK_KP_Home);
+addNumpad("PageDown", KeyTable.XK_Next, KeyTable.XK_KP_Next);
+addNumpad("PageUp", KeyTable.XK_Prior, KeyTable.XK_KP_Prior);
+
+// 2.5. Editing Keys
+
+addStandard("Backspace", KeyTable.XK_BackSpace);
+addStandard("Clear", KeyTable.XK_Clear);
+addStandard("Copy", KeyTable.XF86XK_Copy);
+// - CrSel
+addStandard("Cut", KeyTable.XF86XK_Cut);
+addNumpad("Delete", KeyTable.XK_Delete, KeyTable.XK_KP_Delete);
+// - EraseEof
+// - ExSel
+addNumpad("Insert", KeyTable.XK_Insert, KeyTable.XK_KP_Insert);
+addStandard("Paste", KeyTable.XF86XK_Paste);
+addStandard("Redo", KeyTable.XK_Redo);
+addStandard("Undo", KeyTable.XK_Undo);
+
+// 2.6. UI Keys
+
+// - Accept
+// - Again (could just be XK_Redo)
+// - Attn
+addStandard("Cancel", KeyTable.XK_Cancel);
+addStandard("ContextMenu", KeyTable.XK_Menu);
+addStandard("Escape", KeyTable.XK_Escape);
+addStandard("Execute", KeyTable.XK_Execute);
+addStandard("Find", KeyTable.XK_Find);
+addStandard("Help", KeyTable.XK_Help);
+addStandard("Pause", KeyTable.XK_Pause);
+// - Play
+// - Props
+addStandard("Select", KeyTable.XK_Select);
+addStandard("ZoomIn", KeyTable.XF86XK_ZoomIn);
+addStandard("ZoomOut", KeyTable.XF86XK_ZoomOut);
+
+// 2.7. Device Keys
+
+addStandard("BrightnessDown", KeyTable.XF86XK_MonBrightnessDown);
+addStandard("BrightnessUp", KeyTable.XF86XK_MonBrightnessUp);
+addStandard("Eject", KeyTable.XF86XK_Eject);
+addStandard("LogOff", KeyTable.XF86XK_LogOff);
+addStandard("Power", KeyTable.XF86XK_PowerOff);
+addStandard("PowerOff", KeyTable.XF86XK_PowerDown);
+addStandard("PrintScreen", KeyTable.XK_Print);
+addStandard("Hibernate", KeyTable.XF86XK_Hibernate);
+addStandard("Standby", KeyTable.XF86XK_Standby);
+addStandard("WakeUp", KeyTable.XF86XK_WakeUp);
+
+// 2.8. IME and Composition Keys
+
+addStandard("AllCandidates", KeyTable.XK_MultipleCandidate);
+addStandard("Alphanumeric", KeyTable.XK_Eisu_Shift); // could also be _Eisu_Toggle
+addStandard("CodeInput", KeyTable.XK_Codeinput);
+addStandard("Compose", KeyTable.XK_Multi_key);
+addStandard("Convert", KeyTable.XK_Henkan);
+// - Dead
+// - FinalMode
+addStandard("GroupFirst", KeyTable.XK_ISO_First_Group);
+addStandard("GroupLast", KeyTable.XK_ISO_Last_Group);
+addStandard("GroupNext", KeyTable.XK_ISO_Next_Group);
+addStandard("GroupPrevious", KeyTable.XK_ISO_Prev_Group);
+// - ModeChange (XK_Mode_switch is often used for AltGr)
+// - NextCandidate
+addStandard("NonConvert", KeyTable.XK_Muhenkan);
+addStandard("PreviousCandidate", KeyTable.XK_PreviousCandidate);
+// - Process
+addStandard("SingleCandidate", KeyTable.XK_SingleCandidate);
+addStandard("HangulMode", KeyTable.XK_Hangul);
+addStandard("HanjaMode", KeyTable.XK_Hangul_Hanja);
+addStandard("JunjuaMode", KeyTable.XK_Hangul_Jeonja);
+addStandard("Eisu", KeyTable.XK_Eisu_toggle);
+addStandard("Hankaku", KeyTable.XK_Hankaku);
+addStandard("Hiragana", KeyTable.XK_Hiragana);
+addStandard("HiraganaKatakana", KeyTable.XK_Hiragana_Katakana);
+addStandard("KanaMode", KeyTable.XK_Kana_Shift); // could also be _Kana_Lock
+addStandard("KanjiMode", KeyTable.XK_Kanji);
+addStandard("Katakana", KeyTable.XK_Katakana);
+addStandard("Romaji", KeyTable.XK_Romaji);
+addStandard("Zenkaku", KeyTable.XK_Zenkaku);
+addStandard("ZenkakuHanaku", KeyTable.XK_Zenkaku_Hankaku);
+
+// 2.9. General-Purpose Function Keys
+
+addStandard("F1", KeyTable.XK_F1);
+addStandard("F2", KeyTable.XK_F2);
+addStandard("F3", KeyTable.XK_F3);
+addStandard("F4", KeyTable.XK_F4);
+addStandard("F5", KeyTable.XK_F5);
+addStandard("F6", KeyTable.XK_F6);
+addStandard("F7", KeyTable.XK_F7);
+addStandard("F8", KeyTable.XK_F8);
+addStandard("F9", KeyTable.XK_F9);
+addStandard("F10", KeyTable.XK_F10);
+addStandard("F11", KeyTable.XK_F11);
+addStandard("F12", KeyTable.XK_F12);
+addStandard("F13", KeyTable.XK_F13);
+addStandard("F14", KeyTable.XK_F14);
+addStandard("F15", KeyTable.XK_F15);
+addStandard("F16", KeyTable.XK_F16);
+addStandard("F17", KeyTable.XK_F17);
+addStandard("F18", KeyTable.XK_F18);
+addStandard("F19", KeyTable.XK_F19);
+addStandard("F20", KeyTable.XK_F20);
+addStandard("F21", KeyTable.XK_F21);
+addStandard("F22", KeyTable.XK_F22);
+addStandard("F23", KeyTable.XK_F23);
+addStandard("F24", KeyTable.XK_F24);
+addStandard("F25", KeyTable.XK_F25);
+addStandard("F26", KeyTable.XK_F26);
+addStandard("F27", KeyTable.XK_F27);
+addStandard("F28", KeyTable.XK_F28);
+addStandard("F29", KeyTable.XK_F29);
+addStandard("F30", KeyTable.XK_F30);
+addStandard("F31", KeyTable.XK_F31);
+addStandard("F32", KeyTable.XK_F32);
+addStandard("F33", KeyTable.XK_F33);
+addStandard("F34", KeyTable.XK_F34);
+addStandard("F35", KeyTable.XK_F35);
+// - Soft1...
+
+// 2.10. Multimedia Keys
+
+// - ChannelDown
+// - ChannelUp
+addStandard("Close", KeyTable.XF86XK_Close);
+addStandard("MailForward", KeyTable.XF86XK_MailForward);
+addStandard("MailReply", KeyTable.XF86XK_Reply);
+addStandard("MainSend", KeyTable.XF86XK_Send);
+addStandard("MediaFastForward", KeyTable.XF86XK_AudioForward);
+addStandard("MediaPause", KeyTable.XF86XK_AudioPause);
+addStandard("MediaPlay", KeyTable.XF86XK_AudioPlay);
+addStandard("MediaRecord", KeyTable.XF86XK_AudioRecord);
+addStandard("MediaRewind", KeyTable.XF86XK_AudioRewind);
+addStandard("MediaStop", KeyTable.XF86XK_AudioStop);
+addStandard("MediaTrackNext", KeyTable.XF86XK_AudioNext);
+addStandard("MediaTrackPrevious", KeyTable.XF86XK_AudioPrev);
+addStandard("New", KeyTable.XF86XK_New);
+addStandard("Open", KeyTable.XF86XK_Open);
+addStandard("Print", KeyTable.XK_Print);
+addStandard("Save", KeyTable.XF86XK_Save);
+addStandard("SpellCheck", KeyTable.XF86XK_Spell);
+
+// 2.11. Multimedia Numpad Keys
+
+// - Key11
+// - Key12
+
+// 2.12. Audio Keys
+
+// - AudioBalanceLeft
+// - AudioBalanceRight
+// - AudioBassDown
+// - AudioBassBoostDown
+// - AudioBassBoostToggle
+// - AudioBassBoostUp
+// - AudioBassUp
+// - AudioFaderFront
+// - AudioFaderRear
+// - AudioSurroundModeNext
+// - AudioTrebleDown
+// - AudioTrebleUp
+addStandard("AudioVolumeDown", KeyTable.XF86XK_AudioLowerVolume);
+addStandard("AudioVolumeUp", KeyTable.XF86XK_AudioRaiseVolume);
+addStandard("AudioVolumeMute", KeyTable.XF86XK_AudioMute);
+// - MicrophoneToggle
+// - MicrophoneVolumeDown
+// - MicrophoneVolumeUp
+addStandard("MicrophoneVolumeMute", KeyTable.XF86XK_AudioMicMute);
+
+// 2.13. Speech Keys
+
+// - SpeechCorrectionList
+// - SpeechInputToggle
+
+// 2.14. Application Keys
+
+addStandard("LaunchCalculator", KeyTable.XF86XK_Calculator);
+addStandard("LaunchCalendar", KeyTable.XF86XK_Calendar);
+addStandard("LaunchMail", KeyTable.XF86XK_Mail);
+addStandard("LaunchMediaPlayer", KeyTable.XF86XK_AudioMedia);
+addStandard("LaunchMusicPlayer", KeyTable.XF86XK_Music);
+addStandard("LaunchMyComputer", KeyTable.XF86XK_MyComputer);
+addStandard("LaunchPhone", KeyTable.XF86XK_Phone);
+addStandard("LaunchScreenSaver", KeyTable.XF86XK_ScreenSaver);
+addStandard("LaunchSpreadsheet", KeyTable.XF86XK_Excel);
+addStandard("LaunchWebBrowser", KeyTable.XF86XK_WWW);
+addStandard("LaunchWebCam", KeyTable.XF86XK_WebCam);
+addStandard("LaunchWordProcessor", KeyTable.XF86XK_Word);
+
+// 2.15. Browser Keys
+
+addStandard("BrowserBack", KeyTable.XF86XK_Back);
+addStandard("BrowserFavorites", KeyTable.XF86XK_Favorites);
+addStandard("BrowserForward", KeyTable.XF86XK_Forward);
+addStandard("BrowserHome", KeyTable.XF86XK_HomePage);
+addStandard("BrowserRefresh", KeyTable.XF86XK_Refresh);
+addStandard("BrowserSearch", KeyTable.XF86XK_Search);
+addStandard("BrowserStop", KeyTable.XF86XK_Stop);
+
+// 2.16. Mobile Phone Keys
+
+// - A whole bunch...
+
+// 2.17. TV Keys
+
+// - A whole bunch...
+
+// 2.18. Media Controller Keys
+
+// - A whole bunch...
+addStandard("Dimmer", KeyTable.XF86XK_BrightnessAdjust);
+addStandard("MediaAudioTrack", KeyTable.XF86XK_AudioCycleTrack);
+addStandard("RandomToggle", KeyTable.XF86XK_AudioRandomPlay);
+addStandard("SplitScreenToggle", KeyTable.XF86XK_SplitScreen);
+addStandard("Subtitle", KeyTable.XF86XK_Subtitle);
+addStandard("VideoModeNext", KeyTable.XF86XK_Next_VMode);
+
+// Extra: Numpad
+
+addNumpad("=", KeyTable.XK_equal, KeyTable.XK_KP_Equal);
+addNumpad("+", KeyTable.XK_plus, KeyTable.XK_KP_Add);
+addNumpad("-", KeyTable.XK_minus, KeyTable.XK_KP_Subtract);
+addNumpad("*", KeyTable.XK_asterisk, KeyTable.XK_KP_Multiply);
+addNumpad("/", KeyTable.XK_slash, KeyTable.XK_KP_Divide);
+addNumpad(".", KeyTable.XK_period, KeyTable.XK_KP_Decimal);
+addNumpad(",", KeyTable.XK_comma, KeyTable.XK_KP_Separator);
+addNumpad("0", KeyTable.XK_0, KeyTable.XK_KP_0);
+addNumpad("1", KeyTable.XK_1, KeyTable.XK_KP_1);
+addNumpad("2", KeyTable.XK_2, KeyTable.XK_KP_2);
+addNumpad("3", KeyTable.XK_3, KeyTable.XK_KP_3);
+addNumpad("4", KeyTable.XK_4, KeyTable.XK_KP_4);
+addNumpad("5", KeyTable.XK_5, KeyTable.XK_KP_5);
+addNumpad("6", KeyTable.XK_6, KeyTable.XK_KP_6);
+addNumpad("7", KeyTable.XK_7, KeyTable.XK_KP_7);
+addNumpad("8", KeyTable.XK_8, KeyTable.XK_KP_8);
+addNumpad("9", KeyTable.XK_9, KeyTable.XK_KP_9);
+
+export default DOMKeyTable;
diff --git a/core/input/fixedkeys.js b/core/input/fixedkeys.js
new file mode 100644
index 0000000..6dd4222
--- /dev/null
+++ b/core/input/fixedkeys.js
@@ -0,0 +1,127 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2017 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
+ */
+
+/*
+ * Fallback mapping between HTML key codes (physical keys) and
+ * HTML key values. This only works for keys that don't vary
+ * between layouts. We also omit those who manage fine by mapping the
+ * Unicode representation.
+ *
+ * See https://www.w3.org/TR/uievents-code/ for possible codes.
+ * See https://www.w3.org/TR/uievents-key/ for possible values.
+ */
+
+export default {
+
+// 3.1.1.1. Writing System Keys
+
+ 'Backspace': 'Backspace',
+
+// 3.1.1.2. Functional Keys
+
+ 'AltLeft': 'Alt',
+ 'AltRight': 'Alt', // This could also be 'AltGraph'
+ 'CapsLock': 'CapsLock',
+ 'ContextMenu': 'ContextMenu',
+ 'ControlLeft': 'Control',
+ 'ControlRight': 'Control',
+ 'Enter': 'Enter',
+ 'MetaLeft': 'Meta',
+ 'MetaRight': 'Meta',
+ 'ShiftLeft': 'Shift',
+ 'ShiftRight': 'Shift',
+ 'Tab': 'Tab',
+ // FIXME: Japanese/Korean keys
+
+// 3.1.2. Control Pad Section
+
+ 'Delete': 'Delete',
+ 'End': 'End',
+ 'Help': 'Help',
+ 'Home': 'Home',
+ 'Insert': 'Insert',
+ 'PageDown': 'PageDown',
+ 'PageUp': 'PageUp',
+
+// 3.1.3. Arrow Pad Section
+
+ 'ArrowDown': 'ArrowDown',
+ 'ArrowLeft': 'ArrowLeft',
+ 'ArrowRight': 'ArrowRight',
+ 'ArrowUp': 'ArrowUp',
+
+// 3.1.4. Numpad Section
+
+ 'NumLock': 'NumLock',
+ 'NumpadBackspace': 'Backspace',
+ 'NumpadClear': 'Clear',
+
+// 3.1.5. Function Section
+
+ 'Escape': 'Escape',
+ 'F1': 'F1',
+ 'F2': 'F2',
+ 'F3': 'F3',
+ 'F4': 'F4',
+ 'F5': 'F5',
+ 'F6': 'F6',
+ 'F7': 'F7',
+ 'F8': 'F8',
+ 'F9': 'F9',
+ 'F10': 'F10',
+ 'F11': 'F11',
+ 'F12': 'F12',
+ 'F13': 'F13',
+ 'F14': 'F14',
+ 'F15': 'F15',
+ 'F16': 'F16',
+ 'F17': 'F17',
+ 'F18': 'F18',
+ 'F19': 'F19',
+ 'F20': 'F20',
+ 'F21': 'F21',
+ 'F22': 'F22',
+ 'F23': 'F23',
+ 'F24': 'F24',
+ 'F25': 'F25',
+ 'F26': 'F26',
+ 'F27': 'F27',
+ 'F28': 'F28',
+ 'F29': 'F29',
+ 'F30': 'F30',
+ 'F31': 'F31',
+ 'F32': 'F32',
+ 'F33': 'F33',
+ 'F34': 'F34',
+ 'F35': 'F35',
+ 'PrintScreen': 'PrintScreen',
+ 'ScrollLock': 'ScrollLock',
+ 'Pause': 'Pause',
+
+// 3.1.6. Media Keys
+
+ 'BrowserBack': 'BrowserBack',
+ 'BrowserFavorites': 'BrowserFavorites',
+ 'BrowserForward': 'BrowserForward',
+ 'BrowserHome': 'BrowserHome',
+ 'BrowserRefresh': 'BrowserRefresh',
+ 'BrowserSearch': 'BrowserSearch',
+ 'BrowserStop': 'BrowserStop',
+ 'Eject': 'Eject',
+ 'LaunchApp1': 'LaunchMyComputer',
+ 'LaunchApp2': 'LaunchCalendar',
+ 'LaunchMail': 'LaunchMail',
+ 'MediaPlayPause': 'MediaPlay',
+ 'MediaStop': 'MediaStop',
+ 'MediaTrackNext': 'MediaTrackNext',
+ 'MediaTrackPrevious': 'MediaTrackPrevious',
+ 'Power': 'Power',
+ 'Sleep': 'Sleep',
+ 'AudioVolumeDown': 'AudioVolumeDown',
+ 'AudioVolumeMute': 'AudioVolumeMute',
+ 'AudioVolumeUp': 'AudioVolumeUp',
+ 'WakeUp': 'WakeUp',
+};
diff --git a/core/input/keysym.js b/core/input/keysym.js
index f3a247f..ba58be6 100644
--- a/core/input/keysym.js
+++ b/core/input/keysym.js
@@ -12,6 +12,37 @@ export default {
XK_Escape: 0xff1b,
XK_Delete: 0xffff, /* Delete, rubout */
+ /* International & multi-key character composition */
+
+ XK_Multi_key: 0xff20, /* Multi-key character compose */
+ XK_Codeinput: 0xff37,
+ XK_SingleCandidate: 0xff3c,
+ XK_MultipleCandidate: 0xff3d,
+ XK_PreviousCandidate: 0xff3e,
+
+ /* Japanese keyboard support */
+
+ XK_Kanji: 0xff21, /* Kanji, Kanji convert */
+ XK_Muhenkan: 0xff22, /* Cancel Conversion */
+ XK_Henkan_Mode: 0xff23, /* Start/Stop Conversion */
+ XK_Henkan: 0xff23, /* Alias for Henkan_Mode */
+ XK_Romaji: 0xff24, /* to Romaji */
+ XK_Hiragana: 0xff25, /* to Hiragana */
+ XK_Katakana: 0xff26, /* to Katakana */
+ XK_Hiragana_Katakana: 0xff27, /* Hiragana/Katakana toggle */
+ XK_Zenkaku: 0xff28, /* to Zenkaku */
+ XK_Hankaku: 0xff29, /* to Hankaku */
+ XK_Zenkaku_Hankaku: 0xff2a, /* Zenkaku/Hankaku toggle */
+ XK_Touroku: 0xff2b, /* Add to Dictionary */
+ XK_Massyo: 0xff2c, /* Delete from Dictionary */
+ XK_Kana_Lock: 0xff2d, /* Kana Lock */
+ XK_Kana_Shift: 0xff2e, /* Kana Shift */
+ XK_Eisu_Shift: 0xff2f, /* Alphanumeric Shift */
+ XK_Eisu_toggle: 0xff30, /* Alphanumeric toggle */
+ XK_Kanji_Bangou: 0xff37, /* Codeinput */
+ XK_Zen_Koho: 0xff3d, /* Multiple/All Candidate(s) */
+ XK_Mae_Koho: 0xff3e, /* Previous Candidate */
+
/* Cursor control & motion */
XK_Home: 0xff50,
@@ -171,7 +202,17 @@ export default {
XK_Hyper_L: 0xffed, /* Left hyper */
XK_Hyper_R: 0xffee, /* Right hyper */
+ /*
+ * Keyboard (XKB) Extension function and modifier keys
+ * (from Appendix C of "The X Keyboard Extension: Protocol Specification")
+ * Byte 3 = 0xfe
+ */
+
XK_ISO_Level3_Shift: 0xfe03, /* AltGr */
+ XK_ISO_Next_Group: 0xfe08,
+ XK_ISO_Prev_Group: 0xfe0a,
+ XK_ISO_First_Group: 0xfe0c,
+ XK_ISO_Last_Group: 0xfe0e,
/*
* Latin 1
@@ -377,4 +418,197 @@ export default {
XK_yacute: 0x00fd, /* U+00FD LATIN SMALL LETTER Y WITH ACUTE */
XK_thorn: 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */
XK_ydiaeresis: 0x00ff, /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */
+
+ /*
+ * Korean
+ * Byte 3 = 0x0e
+ */
+
+ XK_Hangul: 0xff31, /* Hangul start/stop(toggle) */
+ XK_Hangul_Hanja: 0xff34, /* Start Hangul->Hanja Conversion */
+ XK_Hangul_Jeonja: 0xff38, /* Jeonja mode */
+
+ /*
+ * XFree86 vendor specific keysyms.
+ *
+ * The XFree86 keysym range is 0x10080001 - 0x1008FFFF.
+ */
+
+ XF86XK_ModeLock: 0x1008FF01,
+ XF86XK_MonBrightnessUp: 0x1008FF02,
+ XF86XK_MonBrightnessDown: 0x1008FF03,
+ XF86XK_KbdLightOnOff: 0x1008FF04,
+ XF86XK_KbdBrightnessUp: 0x1008FF05,
+ XF86XK_KbdBrightnessDown: 0x1008FF06,
+ XF86XK_Standby: 0x1008FF10,
+ XF86XK_AudioLowerVolume: 0x1008FF11,
+ XF86XK_AudioMute: 0x1008FF12,
+ XF86XK_AudioRaiseVolume: 0x1008FF13,
+ XF86XK_AudioPlay: 0x1008FF14,
+ XF86XK_AudioStop: 0x1008FF15,
+ XF86XK_AudioPrev: 0x1008FF16,
+ XF86XK_AudioNext: 0x1008FF17,
+ XF86XK_HomePage: 0x1008FF18,
+ XF86XK_Mail: 0x1008FF19,
+ XF86XK_Start: 0x1008FF1A,
+ XF86XK_Search: 0x1008FF1B,
+ XF86XK_AudioRecord: 0x1008FF1C,
+ XF86XK_Calculator: 0x1008FF1D,
+ XF86XK_Memo: 0x1008FF1E,
+ XF86XK_ToDoList: 0x1008FF1F,
+ XF86XK_Calendar: 0x1008FF20,
+ XF86XK_PowerDown: 0x1008FF21,
+ XF86XK_ContrastAdjust: 0x1008FF22,
+ XF86XK_RockerUp: 0x1008FF23,
+ XF86XK_RockerDown: 0x1008FF24,
+ XF86XK_RockerEnter: 0x1008FF25,
+ XF86XK_Back: 0x1008FF26,
+ XF86XK_Forward: 0x1008FF27,
+ XF86XK_Stop: 0x1008FF28,
+ XF86XK_Refresh: 0x1008FF29,
+ XF86XK_PowerOff: 0x1008FF2A,
+ XF86XK_WakeUp: 0x1008FF2B,
+ XF86XK_Eject: 0x1008FF2C,
+ XF86XK_ScreenSaver: 0x1008FF2D,
+ XF86XK_WWW: 0x1008FF2E,
+ XF86XK_Sleep: 0x1008FF2F,
+ XF86XK_Favorites: 0x1008FF30,
+ XF86XK_AudioPause: 0x1008FF31,
+ XF86XK_AudioMedia: 0x1008FF32,
+ XF86XK_MyComputer: 0x1008FF33,
+ XF86XK_VendorHome: 0x1008FF34,
+ XF86XK_LightBulb: 0x1008FF35,
+ XF86XK_Shop: 0x1008FF36,
+ XF86XK_History: 0x1008FF37,
+ XF86XK_OpenURL: 0x1008FF38,
+ XF86XK_AddFavorite: 0x1008FF39,
+ XF86XK_HotLinks: 0x1008FF3A,
+ XF86XK_BrightnessAdjust: 0x1008FF3B,
+ XF86XK_Finance: 0x1008FF3C,
+ XF86XK_Community: 0x1008FF3D,
+ XF86XK_AudioRewind: 0x1008FF3E,
+ XF86XK_BackForward: 0x1008FF3F,
+ XF86XK_Launch0: 0x1008FF40,
+ XF86XK_Launch1: 0x1008FF41,
+ XF86XK_Launch2: 0x1008FF42,
+ XF86XK_Launch3: 0x1008FF43,
+ XF86XK_Launch4: 0x1008FF44,
+ XF86XK_Launch5: 0x1008FF45,
+ XF86XK_Launch6: 0x1008FF46,
+ XF86XK_Launch7: 0x1008FF47,
+ XF86XK_Launch8: 0x1008FF48,
+ XF86XK_Launch9: 0x1008FF49,
+ XF86XK_LaunchA: 0x1008FF4A,
+ XF86XK_LaunchB: 0x1008FF4B,
+ XF86XK_LaunchC: 0x1008FF4C,
+ XF86XK_LaunchD: 0x1008FF4D,
+ XF86XK_LaunchE: 0x1008FF4E,
+ XF86XK_LaunchF: 0x1008FF4F,
+ XF86XK_ApplicationLeft: 0x1008FF50,
+ XF86XK_ApplicationRight: 0x1008FF51,
+ XF86XK_Book: 0x1008FF52,
+ XF86XK_CD: 0x1008FF53,
+ XF86XK_Calculater: 0x1008FF54,
+ XF86XK_Clear: 0x1008FF55,
+ XF86XK_Close: 0x1008FF56,
+ XF86XK_Copy: 0x1008FF57,
+ XF86XK_Cut: 0x1008FF58,
+ XF86XK_Display: 0x1008FF59,
+ XF86XK_DOS: 0x1008FF5A,
+ XF86XK_Documents: 0x1008FF5B,
+ XF86XK_Excel: 0x1008FF5C,
+ XF86XK_Explorer: 0x1008FF5D,
+ XF86XK_Game: 0x1008FF5E,
+ XF86XK_Go: 0x1008FF5F,
+ XF86XK_iTouch: 0x1008FF60,
+ XF86XK_LogOff: 0x1008FF61,
+ XF86XK_Market: 0x1008FF62,
+ XF86XK_Meeting: 0x1008FF63,
+ XF86XK_MenuKB: 0x1008FF65,
+ XF86XK_MenuPB: 0x1008FF66,
+ XF86XK_MySites: 0x1008FF67,
+ XF86XK_New: 0x1008FF68,
+ XF86XK_News: 0x1008FF69,
+ XF86XK_OfficeHome: 0x1008FF6A,
+ XF86XK_Open: 0x1008FF6B,
+ XF86XK_Option: 0x1008FF6C,
+ XF86XK_Paste: 0x1008FF6D,
+ XF86XK_Phone: 0x1008FF6E,
+ XF86XK_Q: 0x1008FF70,
+ XF86XK_Reply: 0x1008FF72,
+ XF86XK_Reload: 0x1008FF73,
+ XF86XK_RotateWindows: 0x1008FF74,
+ XF86XK_RotationPB: 0x1008FF75,
+ XF86XK_RotationKB: 0x1008FF76,
+ XF86XK_Save: 0x1008FF77,
+ XF86XK_ScrollUp: 0x1008FF78,
+ XF86XK_ScrollDown: 0x1008FF79,
+ XF86XK_ScrollClick: 0x1008FF7A,
+ XF86XK_Send: 0x1008FF7B,
+ XF86XK_Spell: 0x1008FF7C,
+ XF86XK_SplitScreen: 0x1008FF7D,
+ XF86XK_Support: 0x1008FF7E,
+ XF86XK_TaskPane: 0x1008FF7F,
+ XF86XK_Terminal: 0x1008FF80,
+ XF86XK_Tools: 0x1008FF81,
+ XF86XK_Travel: 0x1008FF82,
+ XF86XK_UserPB: 0x1008FF84,
+ XF86XK_User1KB: 0x1008FF85,
+ XF86XK_User2KB: 0x1008FF86,
+ XF86XK_Video: 0x1008FF87,
+ XF86XK_WheelButton: 0x1008FF88,
+ XF86XK_Word: 0x1008FF89,
+ XF86XK_Xfer: 0x1008FF8A,
+ XF86XK_ZoomIn: 0x1008FF8B,
+ XF86XK_ZoomOut: 0x1008FF8C,
+ XF86XK_Away: 0x1008FF8D,
+ XF86XK_Messenger: 0x1008FF8E,
+ XF86XK_WebCam: 0x1008FF8F,
+ XF86XK_MailForward: 0x1008FF90,
+ XF86XK_Pictures: 0x1008FF91,
+ XF86XK_Music: 0x1008FF92,
+ XF86XK_Battery: 0x1008FF93,
+ XF86XK_Bluetooth: 0x1008FF94,
+ XF86XK_WLAN: 0x1008FF95,
+ XF86XK_UWB: 0x1008FF96,
+ XF86XK_AudioForward: 0x1008FF97,
+ XF86XK_AudioRepeat: 0x1008FF98,
+ XF86XK_AudioRandomPlay: 0x1008FF99,
+ XF86XK_Subtitle: 0x1008FF9A,
+ XF86XK_AudioCycleTrack: 0x1008FF9B,
+ XF86XK_CycleAngle: 0x1008FF9C,
+ XF86XK_FrameBack: 0x1008FF9D,
+ XF86XK_FrameForward: 0x1008FF9E,
+ XF86XK_Time: 0x1008FF9F,
+ XF86XK_Select: 0x1008FFA0,
+ XF86XK_View: 0x1008FFA1,
+ XF86XK_TopMenu: 0x1008FFA2,
+ XF86XK_Red: 0x1008FFA3,
+ XF86XK_Green: 0x1008FFA4,
+ XF86XK_Yellow: 0x1008FFA5,
+ XF86XK_Blue: 0x1008FFA6,
+ XF86XK_Suspend: 0x1008FFA7,
+ XF86XK_Hibernate: 0x1008FFA8,
+ XF86XK_TouchpadToggle: 0x1008FFA9,
+ XF86XK_TouchpadOn: 0x1008FFB0,
+ XF86XK_TouchpadOff: 0x1008FFB1,
+ XF86XK_AudioMicMute: 0x1008FFB2,
+ XF86XK_Switch_VT_1: 0x1008FE01,
+ XF86XK_Switch_VT_2: 0x1008FE02,
+ XF86XK_Switch_VT_3: 0x1008FE03,
+ XF86XK_Switch_VT_4: 0x1008FE04,
+ XF86XK_Switch_VT_5: 0x1008FE05,
+ XF86XK_Switch_VT_6: 0x1008FE06,
+ XF86XK_Switch_VT_7: 0x1008FE07,
+ XF86XK_Switch_VT_8: 0x1008FE08,
+ XF86XK_Switch_VT_9: 0x1008FE09,
+ XF86XK_Switch_VT_10: 0x1008FE0A,
+ XF86XK_Switch_VT_11: 0x1008FE0B,
+ XF86XK_Switch_VT_12: 0x1008FE0C,
+ XF86XK_Ungrab: 0x1008FE20,
+ XF86XK_ClearGrab: 0x1008FE21,
+ XF86XK_Next_VMode: 0x1008FE22,
+ XF86XK_Prev_VMode: 0x1008FE23,
+ XF86XK_LogWindowTree: 0x1008FE24,
+ XF86XK_LogGrabInfo: 0x1008FE25,
};
diff --git a/core/input/keysymdef.js b/core/input/keysymdef.js
index 3d9cee3..95922b3 100644
--- a/core/input/keysymdef.js
+++ b/core/input/keysymdef.js
@@ -1,19 +1,688 @@
-// This file describes mappings from Unicode codepoints to the keysym values
-// (and optionally, key names) expected by the RFB protocol
-// How this file was generated:
-// node /Users/jalf/dev/mi/novnc/utils/parse.js /opt/X11/include/X11/keysymdef.h
+/*
+ * Mapping from Unicode codepoints to X11/RFB keysyms
+ *
+ * This file was automatically generated from keysymdef.h
+ * DO NOT EDIT!
+ */
-var keynames = null;
-var codepoints = {"32":32,"33":33,"34":34,"35":35,"36":36,"37":37,"38":38,"39":39,"40":40,"41":41,"42":42,"43":43,"44":44,"45":45,"46":46,"47":47,"48":48,"49":49,"50":50,"51":51,"52":52,"53":53,"54":54,"55":55,"56":56,"57":57,"58":58,"59":59,"60":60,"61":61,"62":62,"63":63,"64":64,"65":65,"66":66,"67":67,"68":68,"69":69,"70":70,"71":71,"72":72,"73":73,"74":74,"75":75,"76":76,"77":77,"78":78,"79":79,"80":80,"81":81,"82":82,"83":83,"84":84,"85":85,"86":86,"87":87,"88":88,"89":89,"90":90,"91":91,"92":92,"93":93,"94":94,"95":95,"96":96,"97":97,"98":98,"99":99,"100":100,"101":101,"102":102,"103":103,"104":104,"105":105,"106":106,"107":107,"108":108,"109":109,"110":110,"111":111,"112":112,"113":113,"114":114,"115":115,"116":116,"117":117,"118":118,"119":119,"120":120,"121":121,"122":122,"123":123,"124":124,"125":125,"126":126,"160":160,"161":161,"162":162,"163":163,"164":164,"165":165,"166":166,"167":167,"168":168,"169":169,"170":170,"171":171,"172":172,"173":173,"174":174,"175":175,"176":176,"177":177,"178":178,"179":179,"180":180,"181":181,"182":182,"183":183,"184":184,"185":185,"186":186,"187":187,"188":188,"189":189,"190":190,"191":191,"192":192,"193":193,"194":194,"195":195,"196":196,"197":197,"198":198,"199":199,"200":200,"201":201,"202":202,"203":203,"204":204,"205":205,"206":206,"207":207,"208":208,"209":209,"210":210,"211":211,"212":212,"213":213,"214":214,"215":215,"216":216,"217":217,"218":218,"219":219,"220":220,"221":221,"222":222,"223":223,"224":224,"225":225,"226":226,"227":227,"228":228,"229":229,"230":230,"231":231,"232":232,"233":233,"234":234,"235":235,"236":236,"237":237,"238":238,"239":239,"240":240,"241":241,"242":242,"243":243,"244":244,"245":245,"246":246,"247":247,"248":248,"249":249,"250":250,"251":251,"252":252,"253":253,"254":254,"255":255,"256":960,"257":992,"258":451,"259":483,"260":417,"261":433,"262":454,"263":486,"264":710,"265":742,"266":709,"267":741,"268":456,"269":488,"270":463,"271":495,"272":464,"273":496,"274":938,"275":954,"278":972,"279":1004,"280":458,"281":490,"282":460,"283":492,"284":728,"285":760,"286":683,"287":699,"288":725,"289":757,"290":939,"291":955,"292":678,"293":694,"294":673,"295":689,"296":933,"297":949,"298":975,"299":1007,"300":16777516,"301":16777517,"302":967,"303":999,"304":681,"305":697,"308":684,"309":700,"310":979,"311":1011,"312":930,"313":453,"314":485,"315":934,"316":950,"317":421,"318":437,"321":419,"322":435,"323":465,"324":497,"325":977,"326":1009,"327":466,"328":498,"330":957,"331":959,"332":978,"333":1010,"336":469,"337":501,"338":5052,"339":5053,"340":448,"341":480,"342":931,"343":947,"344":472,"345":504,"346":422,"347":438,"348":734,"349":766,"350":426,"351":442,"352":425,"353":441,"354":478,"355":510,"356":427,"357":443,"358":940,"359":956,"360":989,"361":1021,"362":990,"363":1022,"364":733,"365":765,"366":473,"367":505,"368":475,"369":507,"370":985,"371":1017,"372":16777588,"373":16777589,"374":16777590,"375":16777591,"376":5054,"377":428,"378":444,"379":431,"380":447,"381":430,"382":446,"399":16777615,"402":2294,"415":16777631,"416":16777632,"417":16777633,"431":16777647,"432":16777648,"437":16777653,"438":16777654,"439":16777655,"466":16777681,"486":16777702,"487":16777703,"601":16777817,"629":16777845,"658":16777874,"711":439,"728":418,"729":511,"731":434,"733":445,"901":1966,"902":1953,"904":1954,"905":1955,"906":1956,"908":1959,"910":1960,"911":1963,"912":1974,"913":1985,"914":1986,"915":1987,"916":1988,"917":1989,"918":1990,"919":1991,"920":1992,"921":1993,"922":1994,"923":1995,"924":1996,"925":1997,"926":1998,"927":1999,"928":2000,"929":2001,"931":2002,"932":2004,"933":2005,"934":2006,"935":2007,"936":2008,"937":2009,"938":1957,"939":1961,"940":1969,"941":1970,"942":1971,"943":1972,"944":1978,"945":2017,"946":2018,"947":2019,"948":2020,"949":2021,"950":2022,"951":2023,"952":2024,"953":2025,"954":2026,"955":2027,"956":2028,"957":2029,"958":2030,"959":2031,"960":2032,"961":2033,"962":2035,"963":2034,"964":2036,"965":2037,"966":2038,"967":2039,"968":2040,"969":2041,"970":1973,"971":1977,"972":1975,"973":1976,"974":1979,"1025":1715,"1026":1713,"1027":1714,"1028":1716,"1029":1717,"1030":1718,"1031":1719,"1032":1720,"1033":1721,"1034":1722,"1035":1723,"1036":1724,"1038":1726,"1039":1727,"1040":1761,"1041":1762,"1042":1783,"1043":1767,"1044":1764,"1045":1765,"1046":1782,"1047":1786,"1048":1769,"1049":1770,"1050":1771,"1051":1772,"1052":1773,"1053":1774,"1054":1775,"1055":1776,"1056":1778,"1057":1779,"1058":1780,"1059":1781,"1060":1766,"1061":1768,"1062":1763,"1063":1790,"1064":1787,"1065":1789,"1066":1791,"1067":1785,"1068":1784,"1069":1788,"1070":1760,"1071":1777,"1072":1729,"1073":1730,"1074":1751,"1075":1735,"1076":1732,"1077":1733,"1078":1750,"1079":1754,"1080":1737,"1081":1738,"1082":1739,"1083":1740,"1084":1741,"1085":1742,"1086":1743,"1087":1744,"1088":1746,"1089":1747,"1090":1748,"1091":1749,"1092":1734,"1093":1736,"1094":1731,"1095":1758,"1096":1755,"1097":1757,"1098":1759,"1099":1753,"1100":1752,"1101":1756,"1102":1728,"1103":1745,"1105":1699,"1106":1697,"1107":1698,"1108":1700,"1109":1701,"1110":1702,"1111":1703,"1112":1704,"1113":1705,"1114":1706,"1115":1707,"1116":1708,"1118":1710,"1119":1711,"1168":1725,"1169":1709,"1170":16778386,"1171":16778387,"1174":16778390,"1175":16778391,"1178":16778394,"1179":16778395,"1180":16778396,"1181":16778397,"1186":16778402,"1187":16778403,"1198":16778414,"1199":16778415,"1200":16778416,"1201":16778417,"1202":16778418,"1203":16778419,"1206":16778422,"1207":16778423,"1208":16778424,"1209":16778425,"1210":16778426,"1211":16778427,"1240":16778456,"1241":16778457,"1250":16778466,"1251":16778467,"1256":16778472,"1257":16778473,"1262":16778478,"1263":16778479,"1329":16778545,"1330":16778546,"1331":16778547,"1332":16778548,"1333":16778549,"1334":16778550,"1335":16778551,"1336":16778552,"1337":16778553,"1338":16778554,"1339":16778555,"1340":16778556,"1341":16778557,"1342":16778558,"1343":16778559,"1344":16778560,"1345":16778561,"1346":16778562,"1347":16778563,"1348":16778564,"1349":16778565,"1350":16778566,"1351":16778567,"1352":16778568,"1353":16778569,"1354":16778570,"1355":16778571,"1356":16778572,"1357":16778573,"1358":16778574,"1359":16778575,"1360":16778576,"1361":16778577,"1362":16778578,"1363":16778579,"1364":16778580,"1365":16778581,"1366":16778582,"1370":16778586,"1371":16778587,"1372":16778588,"1373":16778589,"1374":16778590,"1377":16778593,"1378":16778594,"1379":16778595,"1380":16778596,"1381":16778597,"1382":16778598,"1383":16778599,"1384":16778600,"1385":16778601,"1386":16778602,"1387":16778603,"1388":16778604,"1389":16778605,"1390":16778606,"1391":16778607,"1392":16778608,"1393":16778609,"1394":16778610,"1395":16778611,"1396":16778612,"1397":16778613,"1398":16778614,"1399":16778615,"1400":16778616,"1401":16778617,"1402":16778618,"1403":16778619,"1404":16778620,"1405":16778621,"1406":16778622,"1407":16778623,"1408":16778624,"1409":16778625,"1410":16778626,"1411":16778627,"1412":16778628,"1413":16778629,"1414":16778630,"1415":16778631,"1417":16778633,"1418":16778634,"1488":3296,"1489":3297,"1490":3298,"1491":3299,"1492":3300,"1493":3301,"1494":3302,"1495":3303,"1496":3304,"1497":3305,"1498":3306,"1499":3307,"1500":3308,"1501":3309,"1502":3310,"1503":3311,"1504":3312,"1505":3313,"1506":3314,"1507":3315,"1508":3316,"1509":3317,"1510":3318,"1511":3319,"1512":3320,"1513":3321,"1514":3322,"1548":1452,"1563":1467,"1567":1471,"1569":1473,"1570":1474,"1571":1475,"1572":1476,"1573":1477,"1574":1478,"1575":1479,"1576":1480,"1577":1481,"1578":1482,"1579":1483,"1580":1484,"1581":1485,"1582":1486,"1583":1487,"1584":1488,"1585":1489,"1586":1490,"1587":1491,"1588":1492,"1589":1493,"1590":1494,"1591":1495,"1592":1496,"1593":1497,"1594":1498,"1600":1504,"1601":1505,"1602":1506,"1603":1507,"1604":1508,"1605":1509,"1606":1510,"1607":1511,"1608":1512,"1609":1513,"1610":1514,"1611":1515,"1612":1516,"1613":1517,"1614":1518,"1615":1519,"1616":1520,"1617":1521,"1618":1522,"1619":16778835,"1620":16778836,"1621":16778837,"1632":16778848,"1633":16778849,"1634":16778850,"1635":16778851,"1636":16778852,"1637":16778853,"1638":16778854,"1639":16778855,"1640":16778856,"1641":16778857,"1642":16778858,"1648":16778864,"1657":16778873,"1662":16778878,"1670":16778886,"1672":16778888,"1681":16778897,"1688":16778904,"1700":16778916,"1705":16778921,"1711":16778927,"1722":16778938,"1726":16778942,"1729":16778945,"1740":16778956,"1746":16778962,"1748":16778964,"1776":16778992,"1777":16778993,"1778":16778994,"1779":16778995,"1780":16778996,"1781":16778997,"1782":16778998,"1783":16778999,"1784":16779000,"1785":16779001,"3458":16780674,"3459":16780675,"3461":16780677,"3462":16780678,"3463":16780679,"3464":16780680,"3465":16780681,"3466":16780682,"3467":16780683,"3468":16780684,"3469":16780685,"3470":16780686,"3471":16780687,"3472":16780688,"3473":16780689,"3474":16780690,"3475":16780691,"3476":16780692,"3477":16780693,"3478":16780694,"3482":16780698,"3483":16780699,"3484":16780700,"3485":16780701,"3486":16780702,"3487":16780703,"3488":16780704,"3489":16780705,"3490":16780706,"3491":16780707,"3492":16780708,"3493":16780709,"3494":16780710,"3495":16780711,"3496":16780712,"3497":16780713,"3498":16780714,"3499":16780715,"3500":16780716,"3501":16780717,"3502":16780718,"3503":16780719,"3504":16780720,"3505":16780721,"3507":16780723,"3508":16780724,"3509":16780725,"3510":16780726,"3511":16780727,"3512":16780728,"3513":16780729,"3514":16780730,"3515":16780731,"3517":16780733,"3520":16780736,"3521":16780737,"3522":16780738,"3523":16780739,"3524":16780740,"3525":16780741,"3526":16780742,"3530":16780746,"3535":16780751,"3536":16780752,"3537":16780753,"3538":16780754,"3539":16780755,"3540":16780756,"3542":16780758,"3544":16780760,"3545":16780761,"3546":16780762,"3547":16780763,"3548":16780764,"3549":16780765,"3550":16780766,"3551":16780767,"3570":16780786,"3571":16780787,"3572":16780788,"3585":3489,"3586":3490,"3587":3491,"3588":3492,"3589":3493,"3590":3494,"3591":3495,"3592":3496,"3593":3497,"3594":3498,"3595":3499,"3596":3500,"3597":3501,"3598":3502,"3599":3503,"3600":3504,"3601":3505,"3602":3506,"3603":3507,"3604":3508,"3605":3509,"3606":3510,"3607":3511,"3608":3512,"3609":3513,"3610":3514,"3611":3515,"3612":3516,"3613":3517,"3614":3518,"3615":3519,"3616":3520,"3617":3521,"3618":3522,"3619":3523,"3620":3524,"3621":3525,"3622":3526,"3623":3527,"3624":3528,"3625":3529,"3626":3530,"3627":3531,"3628":3532,"3629":3533,"3630":3534,"3631":3535,"3632":3536,"3633":3537,"3634":3538,"3635":3539,"3636":3540,"3637":3541,"3638":3542,"3639":3543,"3640":3544,"3641":3545,"3642":3546,"3647":3551,"3648":3552,"3649":3553,"3650":3554,"3651":3555,"3652":3556,"3653":3557,"3654":3558,"3655":3559,"3656":3560,"3657":3561,"3658":3562,"3659":3563,"3660":3564,"3661":3565,"3664":3568,"3665":3569,"3666":3570,"3667":3571,"3668":3572,"3669":3573,"3670":3574,"3671":3575,"3672":3576,"3673":3577,"4304":16781520,"4305":16781521,"4306":16781522,"4307":16781523,"4308":16781524,"4309":16781525,"4310":16781526,"4311":16781527,"4312":16781528,"4313":16781529,"4314":16781530,"4315":16781531,"4316":16781532,"4317":16781533,"4318":16781534,"4319":16781535,"4320":16781536,"4321":16781537,"4322":16781538,"4323":16781539,"4324":16781540,"4325":16781541,"4326":16781542,"4327":16781543,"4328":16781544,"4329":16781545,"4330":16781546,"4331":16781547,"4332":16781548,"4333":16781549,"4334":16781550,"4335":16781551,"4336":16781552,"4337":16781553,"4338":16781554,"4339":16781555,"4340":16781556,"4341":16781557,"4342":16781558,"7682":16784898,"7683":16784899,"7690":16784906,"7691":16784907,"7710":16784926,"7711":16784927,"7734":16784950,"7735":16784951,"7744":16784960,"7745":16784961,"7766":16784982,"7767":16784983,"7776":16784992,"7777":16784993,"7786":16785002,"7787":16785003,"7808":16785024,"7809":16785025,"7810":16785026,"7811":16785027,"7812":16785028,"7813":16785029,"7818":16785034,"7819":16785035,"7840":16785056,"7841":16785057,"7842":16785058,"7843":16785059,"7844":16785060,"7845":16785061,"7846":16785062,"7847":16785063,"7848":16785064,"7849":16785065,"7850":16785066,"7851":16785067,"7852":16785068,"7853":16785069,"7854":16785070,"7855":16785071,"7856":16785072,"7857":16785073,"7858":16785074,"7859":16785075,"7860":16785076,"7861":16785077,"7862":16785078,"7863":16785079,"7864":16785080,"7865":16785081,"7866":16785082,"7867":16785083,"7868":16785084,"7869":16785085,"7870":16785086,"7871":16785087,"7872":16785088,"7873":16785089,"7874":16785090,"7875":16785091,"7876":16785092,"7877":16785093,"7878":16785094,"7879":16785095,"7880":16785096,"7881":16785097,"7882":16785098,"7883":16785099,"7884":16785100,"7885":16785101,"7886":16785102,"7887":16785103,"7888":16785104,"7889":16785105,"7890":16785106,"7891":16785107,"7892":16785108,"7893":16785109,"7894":16785110,"7895":16785111,"7896":16785112,"7897":16785113,"7898":16785114,"7899":16785115,"7900":16785116,"7901":16785117,"7902":16785118,"7903":16785119,"7904":16785120,"7905":16785121,"7906":16785122,"7907":16785123,"7908":16785124,"7909":16785125,"7910":16785126,"7911":16785127,"7912":16785128,"7913":16785129,"7914":16785130,"7915":16785131,"7916":16785132,"7917":16785133,"7918":16785134,"7919":16785135,"7920":16785136,"7921":16785137,"7922":16785138,"7923":16785139,"7924":16785140,"7925":16785141,"7926":16785142,"7927":16785143,"7928":16785144,"7929":16785145,"8194":2722,"8195":2721,"8196":2723,"8197":2724,"8199":2725,"8200":2726,"8201":2727,"8202":2728,"8210":2747,"8211":2730,"8212":2729,"8213":1967,"8215":3295,"8216":2768,"8217":2769,"8218":2813,"8220":2770,"8221":2771,"8222":2814,"8224":2801,"8225":2802,"8226":2790,"8229":2735,"8230":2734,"8240":2773,"8242":2774,"8243":2775,"8248":2812,"8254":1150,"8304":16785520,"8308":16785524,"8309":16785525,"8310":16785526,"8311":16785527,"8312":16785528,"8313":16785529,"8320":16785536,"8321":16785537,"8322":16785538,"8323":16785539,"8324":16785540,"8325":16785541,"8326":16785542,"8327":16785543,"8328":16785544,"8329":16785545,"8352":16785568,"8353":16785569,"8354":16785570,"8355":16785571,"8356":16785572,"8357":16785573,"8358":16785574,"8359":16785575,"8360":16785576,"8361":3839,"8362":16785578,"8363":16785579,"8364":8364,"8453":2744,"8470":1712,"8471":2811,"8478":2772,"8482":2761,"8531":2736,"8532":2737,"8533":2738,"8534":2739,"8535":2740,"8536":2741,"8537":2742,"8538":2743,"8539":2755,"8540":2756,"8541":2757,"8542":2758,"8592":2299,"8593":2300,"8594":2301,"8595":2302,"8658":2254,"8660":2253,"8706":2287,"8709":16785925,"8711":2245,"8712":16785928,"8713":16785929,"8715":16785931,"8728":3018,"8730":2262,"8731":16785947,"8732":16785948,"8733":2241,"8734":2242,"8743":2270,"8744":2271,"8745":2268,"8746":2269,"8747":2239,"8748":16785964,"8749":16785965,"8756":2240,"8757":16785973,"8764":2248,"8771":2249,"8773":16785992,"8775":16785991,"8800":2237,"8801":2255,"8802":16786018,"8803":16786019,"8804":2236,"8805":2238,"8834":2266,"8835":2267,"8866":3068,"8867":3036,"8868":3010,"8869":3022,"8968":3027,"8970":3012,"8981":2810,"8992":2212,"8993":2213,"9109":3020,"9115":2219,"9117":2220,"9118":2221,"9120":2222,"9121":2215,"9123":2216,"9124":2217,"9126":2218,"9128":2223,"9132":2224,"9143":2209,"9146":2543,"9147":2544,"9148":2546,"9149":2547,"9225":2530,"9226":2533,"9227":2537,"9228":2531,"9229":2532,"9251":2732,"9252":2536,"9472":2211,"9474":2214,"9484":2210,"9488":2539,"9492":2541,"9496":2538,"9500":2548,"9508":2549,"9516":2551,"9524":2550,"9532":2542,"9618":2529,"9642":2791,"9643":2785,"9644":2779,"9645":2786,"9646":2783,"9647":2767,"9650":2792,"9651":2787,"9654":2781,"9655":2765,"9660":2793,"9661":2788,"9664":2780,"9665":2764,"9670":2528,"9675":2766,"9679":2782,"9702":2784,"9734":2789,"9742":2809,"9747":2762,"9756":2794,"9758":2795,"9792":2808,"9794":2807,"9827":2796,"9829":2798,"9830":2797,"9837":2806,"9839":2805,"10003":2803,"10007":2804,"10013":2777,"10016":2800,"10216":2748,"10217":2750,"10240":16787456,"10241":16787457,"10242":16787458,"10243":16787459,"10244":16787460,"10245":16787461,"10246":16787462,"10247":16787463,"10248":16787464,"10249":16787465,"10250":16787466,"10251":16787467,"10252":16787468,"10253":16787469,"10254":16787470,"10255":16787471,"10256":16787472,"10257":16787473,"10258":16787474,"10259":16787475,"10260":16787476,"10261":16787477,"10262":16787478,"10263":16787479,"10264":16787480,"10265":16787481,"10266":16787482,"10267":16787483,"10268":16787484,"10269":16787485,"10270":16787486,"10271":16787487,"10272":16787488,"10273":16787489,"10274":16787490,"10275":16787491,"10276":16787492,"10277":16787493,"10278":16787494,"10279":16787495,"10280":16787496,"10281":16787497,"10282":16787498,"10283":16787499,"10284":16787500,"10285":16787501,"10286":16787502,"10287":16787503,"10288":16787504,"10289":16787505,"10290":16787506,"10291":16787507,"10292":16787508,"10293":16787509,"10294":16787510,"10295":16787511,"10296":16787512,"10297":16787513,"10298":16787514,"10299":16787515,"10300":16787516,"10301":16787517,"10302":16787518,"10303":16787519,"10304":16787520,"10305":16787521,"10306":16787522,"10307":16787523,"10308":16787524,"10309":16787525,"10310":16787526,"10311":16787527,"10312":16787528,"10313":16787529,"10314":16787530,"10315":16787531,"10316":16787532,"10317":16787533,"10318":16787534,"10319":16787535,"10320":16787536,"10321":16787537,"10322":16787538,"10323":16787539,"10324":16787540,"10325":16787541,"10326":16787542,"10327":16787543,"10328":16787544,"10329":16787545,"10330":16787546,"10331":16787547,"10332":16787548,"10333":16787549,"10334":16787550,"10335":16787551,"10336":16787552,"10337":16787553,"10338":16787554,"10339":16787555,"10340":16787556,"10341":16787557,"10342":16787558,"10343":16787559,"10344":16787560,"10345":16787561,"10346":16787562,"10347":16787563,"10348":16787564,"10349":16787565,"10350":16787566,"10351":16787567,"10352":16787568,"10353":16787569,"10354":16787570,"10355":16787571,"10356":16787572,"10357":16787573,"10358":16787574,"10359":16787575,"10360":16787576,"10361":16787577,"10362":16787578,"10363":16787579,"10364":16787580,"10365":16787581,"10366":16787582,"10367":16787583,"10368":16787584,"10369":16787585,"10370":16787586,"10371":16787587,"10372":16787588,"10373":16787589,"10374":16787590,"10375":16787591,"10376":16787592,"10377":16787593,"10378":16787594,"10379":16787595,"10380":16787596,"10381":16787597,"10382":16787598,"10383":16787599,"10384":16787600,"10385":16787601,"10386":16787602,"10387":16787603,"10388":16787604,"10389":16787605,"10390":16787606,"10391":16787607,"10392":16787608,"10393":16787609,"10394":16787610,"10395":16787611,"10396":16787612,"10397":16787613,"10398":16787614,"10399":16787615,"10400":16787616,"10401":16787617,"10402":16787618,"10403":16787619,"10404":16787620,"10405":16787621,"10406":16787622,"10407":16787623,"10408":16787624,"10409":16787625,"10410":16787626,"10411":16787627,"10412":16787628,"10413":16787629,"10414":16787630,"10415":16787631,"10416":16787632,"10417":16787633,"10418":16787634,"10419":16787635,"10420":16787636,"10421":16787637,"10422":16787638,"10423":16787639,"10424":16787640,"10425":16787641,"10426":16787642,"10427":16787643,"10428":16787644,"10429":16787645,"10430":16787646,"10431":16787647,"10432":16787648,"10433":16787649,"10434":16787650,"10435":16787651,"10436":16787652,"10437":16787653,"10438":16787654,"10439":16787655,"10440":16787656,"10441":16787657,"10442":16787658,"10443":16787659,"10444":16787660,"10445":16787661,"10446":16787662,"10447":16787663,"10448":16787664,"10449":16787665,"10450":16787666,"10451":16787667,"10452":16787668,"10453":16787669,"10454":16787670,"10455":16787671,"10456":16787672,"10457":16787673,"10458":16787674,"10459":16787675,"10460":16787676,"10461":16787677,"10462":16787678,"10463":16787679,"10464":16787680,"10465":16787681,"10466":16787682,"10467":16787683,"10468":16787684,"10469":16787685,"10470":16787686,"10471":16787687,"10472":16787688,"10473":16787689,"10474":16787690,"10475":16787691,"10476":16787692,"10477":16787693,"10478":16787694,"10479":16787695,"10480":16787696,"10481":16787697,"10482":16787698,"10483":16787699,"10484":16787700,"10485":16787701,"10486":16787702,"10487":16787703,"10488":16787704,"10489":16787705,"10490":16787706,"10491":16787707,"10492":16787708,"10493":16787709,"10494":16787710,"10495":16787711,"12289":1188,"12290":1185,"12300":1186,"12301":1187,"12443":1246,"12444":1247,"12449":1191,"12450":1201,"12451":1192,"12452":1202,"12453":1193,"12454":1203,"12455":1194,"12456":1204,"12457":1195,"12458":1205,"12459":1206,"12461":1207,"12463":1208,"12465":1209,"12467":1210,"12469":1211,"12471":1212,"12473":1213,"12475":1214,"12477":1215,"12479":1216,"12481":1217,"12483":1199,"12484":1218,"12486":1219,"12488":1220,"12490":1221,"12491":1222,"12492":1223,"12493":1224,"12494":1225,"12495":1226,"12498":1227,"12501":1228,"12504":1229,"12507":1230,"12510":1231,"12511":1232,"12512":1233,"12513":1234,"12514":1235,"12515":1196,"12516":1236,"12517":1197,"12518":1237,"12519":1198,"12520":1238,"12521":1239,"12522":1240,"12523":1241,"12524":1242,"12525":1243,"12527":1244,"12530":1190,"12531":1245,"12539":1189,"12540":1200};
+/* Functions at the bottom */
+
+var codepoints = {
+ 0x0100: 0x03c0, // XK_Amacron
+ 0x0101: 0x03e0, // XK_amacron
+ 0x0102: 0x01c3, // XK_Abreve
+ 0x0103: 0x01e3, // XK_abreve
+ 0x0104: 0x01a1, // XK_Aogonek
+ 0x0105: 0x01b1, // XK_aogonek
+ 0x0106: 0x01c6, // XK_Cacute
+ 0x0107: 0x01e6, // XK_cacute
+ 0x0108: 0x02c6, // XK_Ccircumflex
+ 0x0109: 0x02e6, // XK_ccircumflex
+ 0x010a: 0x02c5, // XK_Cabovedot
+ 0x010b: 0x02e5, // XK_cabovedot
+ 0x010c: 0x01c8, // XK_Ccaron
+ 0x010d: 0x01e8, // XK_ccaron
+ 0x010e: 0x01cf, // XK_Dcaron
+ 0x010f: 0x01ef, // XK_dcaron
+ 0x0110: 0x01d0, // XK_Dstroke
+ 0x0111: 0x01f0, // XK_dstroke
+ 0x0112: 0x03aa, // XK_Emacron
+ 0x0113: 0x03ba, // XK_emacron
+ 0x0116: 0x03cc, // XK_Eabovedot
+ 0x0117: 0x03ec, // XK_eabovedot
+ 0x0118: 0x01ca, // XK_Eogonek
+ 0x0119: 0x01ea, // XK_eogonek
+ 0x011a: 0x01cc, // XK_Ecaron
+ 0x011b: 0x01ec, // XK_ecaron
+ 0x011c: 0x02d8, // XK_Gcircumflex
+ 0x011d: 0x02f8, // XK_gcircumflex
+ 0x011e: 0x02ab, // XK_Gbreve
+ 0x011f: 0x02bb, // XK_gbreve
+ 0x0120: 0x02d5, // XK_Gabovedot
+ 0x0121: 0x02f5, // XK_gabovedot
+ 0x0122: 0x03ab, // XK_Gcedilla
+ 0x0123: 0x03bb, // XK_gcedilla
+ 0x0124: 0x02a6, // XK_Hcircumflex
+ 0x0125: 0x02b6, // XK_hcircumflex
+ 0x0126: 0x02a1, // XK_Hstroke
+ 0x0127: 0x02b1, // XK_hstroke
+ 0x0128: 0x03a5, // XK_Itilde
+ 0x0129: 0x03b5, // XK_itilde
+ 0x012a: 0x03cf, // XK_Imacron
+ 0x012b: 0x03ef, // XK_imacron
+ 0x012e: 0x03c7, // XK_Iogonek
+ 0x012f: 0x03e7, // XK_iogonek
+ 0x0130: 0x02a9, // XK_Iabovedot
+ 0x0131: 0x02b9, // XK_idotless
+ 0x0134: 0x02ac, // XK_Jcircumflex
+ 0x0135: 0x02bc, // XK_jcircumflex
+ 0x0136: 0x03d3, // XK_Kcedilla
+ 0x0137: 0x03f3, // XK_kcedilla
+ 0x0138: 0x03a2, // XK_kra
+ 0x0139: 0x01c5, // XK_Lacute
+ 0x013a: 0x01e5, // XK_lacute
+ 0x013b: 0x03a6, // XK_Lcedilla
+ 0x013c: 0x03b6, // XK_lcedilla
+ 0x013d: 0x01a5, // XK_Lcaron
+ 0x013e: 0x01b5, // XK_lcaron
+ 0x0141: 0x01a3, // XK_Lstroke
+ 0x0142: 0x01b3, // XK_lstroke
+ 0x0143: 0x01d1, // XK_Nacute
+ 0x0144: 0x01f1, // XK_nacute
+ 0x0145: 0x03d1, // XK_Ncedilla
+ 0x0146: 0x03f1, // XK_ncedilla
+ 0x0147: 0x01d2, // XK_Ncaron
+ 0x0148: 0x01f2, // XK_ncaron
+ 0x014a: 0x03bd, // XK_ENG
+ 0x014b: 0x03bf, // XK_eng
+ 0x014c: 0x03d2, // XK_Omacron
+ 0x014d: 0x03f2, // XK_omacron
+ 0x0150: 0x01d5, // XK_Odoubleacute
+ 0x0151: 0x01f5, // XK_odoubleacute
+ 0x0152: 0x13bc, // XK_OE
+ 0x0153: 0x13bd, // XK_oe
+ 0x0154: 0x01c0, // XK_Racute
+ 0x0155: 0x01e0, // XK_racute
+ 0x0156: 0x03a3, // XK_Rcedilla
+ 0x0157: 0x03b3, // XK_rcedilla
+ 0x0158: 0x01d8, // XK_Rcaron
+ 0x0159: 0x01f8, // XK_rcaron
+ 0x015a: 0x01a6, // XK_Sacute
+ 0x015b: 0x01b6, // XK_sacute
+ 0x015c: 0x02de, // XK_Scircumflex
+ 0x015d: 0x02fe, // XK_scircumflex
+ 0x015e: 0x01aa, // XK_Scedilla
+ 0x015f: 0x01ba, // XK_scedilla
+ 0x0160: 0x01a9, // XK_Scaron
+ 0x0161: 0x01b9, // XK_scaron
+ 0x0162: 0x01de, // XK_Tcedilla
+ 0x0163: 0x01fe, // XK_tcedilla
+ 0x0164: 0x01ab, // XK_Tcaron
+ 0x0165: 0x01bb, // XK_tcaron
+ 0x0166: 0x03ac, // XK_Tslash
+ 0x0167: 0x03bc, // XK_tslash
+ 0x0168: 0x03dd, // XK_Utilde
+ 0x0169: 0x03fd, // XK_utilde
+ 0x016a: 0x03de, // XK_Umacron
+ 0x016b: 0x03fe, // XK_umacron
+ 0x016c: 0x02dd, // XK_Ubreve
+ 0x016d: 0x02fd, // XK_ubreve
+ 0x016e: 0x01d9, // XK_Uring
+ 0x016f: 0x01f9, // XK_uring
+ 0x0170: 0x01db, // XK_Udoubleacute
+ 0x0171: 0x01fb, // XK_udoubleacute
+ 0x0172: 0x03d9, // XK_Uogonek
+ 0x0173: 0x03f9, // XK_uogonek
+ 0x0178: 0x13be, // XK_Ydiaeresis
+ 0x0179: 0x01ac, // XK_Zacute
+ 0x017a: 0x01bc, // XK_zacute
+ 0x017b: 0x01af, // XK_Zabovedot
+ 0x017c: 0x01bf, // XK_zabovedot
+ 0x017d: 0x01ae, // XK_Zcaron
+ 0x017e: 0x01be, // XK_zcaron
+ 0x0192: 0x08f6, // XK_function
+ 0x01d2: 0x10001d1, // XK_Ocaron
+ 0x02c7: 0x01b7, // XK_caron
+ 0x02d8: 0x01a2, // XK_breve
+ 0x02d9: 0x01ff, // XK_abovedot
+ 0x02db: 0x01b2, // XK_ogonek
+ 0x02dd: 0x01bd, // XK_doubleacute
+ 0x0385: 0x07ae, // XK_Greek_accentdieresis
+ 0x0386: 0x07a1, // XK_Greek_ALPHAaccent
+ 0x0388: 0x07a2, // XK_Greek_EPSILONaccent
+ 0x0389: 0x07a3, // XK_Greek_ETAaccent
+ 0x038a: 0x07a4, // XK_Greek_IOTAaccent
+ 0x038c: 0x07a7, // XK_Greek_OMICRONaccent
+ 0x038e: 0x07a8, // XK_Greek_UPSILONaccent
+ 0x038f: 0x07ab, // XK_Greek_OMEGAaccent
+ 0x0390: 0x07b6, // XK_Greek_iotaaccentdieresis
+ 0x0391: 0x07c1, // XK_Greek_ALPHA
+ 0x0392: 0x07c2, // XK_Greek_BETA
+ 0x0393: 0x07c3, // XK_Greek_GAMMA
+ 0x0394: 0x07c4, // XK_Greek_DELTA
+ 0x0395: 0x07c5, // XK_Greek_EPSILON
+ 0x0396: 0x07c6, // XK_Greek_ZETA
+ 0x0397: 0x07c7, // XK_Greek_ETA
+ 0x0398: 0x07c8, // XK_Greek_THETA
+ 0x0399: 0x07c9, // XK_Greek_IOTA
+ 0x039a: 0x07ca, // XK_Greek_KAPPA
+ 0x039b: 0x07cb, // XK_Greek_LAMDA
+ 0x039c: 0x07cc, // XK_Greek_MU
+ 0x039d: 0x07cd, // XK_Greek_NU
+ 0x039e: 0x07ce, // XK_Greek_XI
+ 0x039f: 0x07cf, // XK_Greek_OMICRON
+ 0x03a0: 0x07d0, // XK_Greek_PI
+ 0x03a1: 0x07d1, // XK_Greek_RHO
+ 0x03a3: 0x07d2, // XK_Greek_SIGMA
+ 0x03a4: 0x07d4, // XK_Greek_TAU
+ 0x03a5: 0x07d5, // XK_Greek_UPSILON
+ 0x03a6: 0x07d6, // XK_Greek_PHI
+ 0x03a7: 0x07d7, // XK_Greek_CHI
+ 0x03a8: 0x07d8, // XK_Greek_PSI
+ 0x03a9: 0x07d9, // XK_Greek_OMEGA
+ 0x03aa: 0x07a5, // XK_Greek_IOTAdieresis
+ 0x03ab: 0x07a9, // XK_Greek_UPSILONdieresis
+ 0x03ac: 0x07b1, // XK_Greek_alphaaccent
+ 0x03ad: 0x07b2, // XK_Greek_epsilonaccent
+ 0x03ae: 0x07b3, // XK_Greek_etaaccent
+ 0x03af: 0x07b4, // XK_Greek_iotaaccent
+ 0x03b0: 0x07ba, // XK_Greek_upsilonaccentdieresis
+ 0x03b1: 0x07e1, // XK_Greek_alpha
+ 0x03b2: 0x07e2, // XK_Greek_beta
+ 0x03b3: 0x07e3, // XK_Greek_gamma
+ 0x03b4: 0x07e4, // XK_Greek_delta
+ 0x03b5: 0x07e5, // XK_Greek_epsilon
+ 0x03b6: 0x07e6, // XK_Greek_zeta
+ 0x03b7: 0x07e7, // XK_Greek_eta
+ 0x03b8: 0x07e8, // XK_Greek_theta
+ 0x03b9: 0x07e9, // XK_Greek_iota
+ 0x03ba: 0x07ea, // XK_Greek_kappa
+ 0x03bb: 0x07eb, // XK_Greek_lamda
+ 0x03bc: 0x07ec, // XK_Greek_mu
+ 0x03bd: 0x07ed, // XK_Greek_nu
+ 0x03be: 0x07ee, // XK_Greek_xi
+ 0x03bf: 0x07ef, // XK_Greek_omicron
+ 0x03c0: 0x07f0, // XK_Greek_pi
+ 0x03c1: 0x07f1, // XK_Greek_rho
+ 0x03c2: 0x07f3, // XK_Greek_finalsmallsigma
+ 0x03c3: 0x07f2, // XK_Greek_sigma
+ 0x03c4: 0x07f4, // XK_Greek_tau
+ 0x03c5: 0x07f5, // XK_Greek_upsilon
+ 0x03c6: 0x07f6, // XK_Greek_phi
+ 0x03c7: 0x07f7, // XK_Greek_chi
+ 0x03c8: 0x07f8, // XK_Greek_psi
+ 0x03c9: 0x07f9, // XK_Greek_omega
+ 0x03ca: 0x07b5, // XK_Greek_iotadieresis
+ 0x03cb: 0x07b9, // XK_Greek_upsilondieresis
+ 0x03cc: 0x07b7, // XK_Greek_omicronaccent
+ 0x03cd: 0x07b8, // XK_Greek_upsilonaccent
+ 0x03ce: 0x07bb, // XK_Greek_omegaaccent
+ 0x0401: 0x06b3, // XK_Cyrillic_IO
+ 0x0402: 0x06b1, // XK_Serbian_DJE
+ 0x0403: 0x06b2, // XK_Macedonia_GJE
+ 0x0404: 0x06b4, // XK_Ukrainian_IE
+ 0x0405: 0x06b5, // XK_Macedonia_DSE
+ 0x0406: 0x06b6, // XK_Ukrainian_I
+ 0x0407: 0x06b7, // XK_Ukrainian_YI
+ 0x0408: 0x06b8, // XK_Cyrillic_JE
+ 0x0409: 0x06b9, // XK_Cyrillic_LJE
+ 0x040a: 0x06ba, // XK_Cyrillic_NJE
+ 0x040b: 0x06bb, // XK_Serbian_TSHE
+ 0x040c: 0x06bc, // XK_Macedonia_KJE
+ 0x040e: 0x06be, // XK_Byelorussian_SHORTU
+ 0x040f: 0x06bf, // XK_Cyrillic_DZHE
+ 0x0410: 0x06e1, // XK_Cyrillic_A
+ 0x0411: 0x06e2, // XK_Cyrillic_BE
+ 0x0412: 0x06f7, // XK_Cyrillic_VE
+ 0x0413: 0x06e7, // XK_Cyrillic_GHE
+ 0x0414: 0x06e4, // XK_Cyrillic_DE
+ 0x0415: 0x06e5, // XK_Cyrillic_IE
+ 0x0416: 0x06f6, // XK_Cyrillic_ZHE
+ 0x0417: 0x06fa, // XK_Cyrillic_ZE
+ 0x0418: 0x06e9, // XK_Cyrillic_I
+ 0x0419: 0x06ea, // XK_Cyrillic_SHORTI
+ 0x041a: 0x06eb, // XK_Cyrillic_KA
+ 0x041b: 0x06ec, // XK_Cyrillic_EL
+ 0x041c: 0x06ed, // XK_Cyrillic_EM
+ 0x041d: 0x06ee, // XK_Cyrillic_EN
+ 0x041e: 0x06ef, // XK_Cyrillic_O
+ 0x041f: 0x06f0, // XK_Cyrillic_PE
+ 0x0420: 0x06f2, // XK_Cyrillic_ER
+ 0x0421: 0x06f3, // XK_Cyrillic_ES
+ 0x0422: 0x06f4, // XK_Cyrillic_TE
+ 0x0423: 0x06f5, // XK_Cyrillic_U
+ 0x0424: 0x06e6, // XK_Cyrillic_EF
+ 0x0425: 0x06e8, // XK_Cyrillic_HA
+ 0x0426: 0x06e3, // XK_Cyrillic_TSE
+ 0x0427: 0x06fe, // XK_Cyrillic_CHE
+ 0x0428: 0x06fb, // XK_Cyrillic_SHA
+ 0x0429: 0x06fd, // XK_Cyrillic_SHCHA
+ 0x042a: 0x06ff, // XK_Cyrillic_HARDSIGN
+ 0x042b: 0x06f9, // XK_Cyrillic_YERU
+ 0x042c: 0x06f8, // XK_Cyrillic_SOFTSIGN
+ 0x042d: 0x06fc, // XK_Cyrillic_E
+ 0x042e: 0x06e0, // XK_Cyrillic_YU
+ 0x042f: 0x06f1, // XK_Cyrillic_YA
+ 0x0430: 0x06c1, // XK_Cyrillic_a
+ 0x0431: 0x06c2, // XK_Cyrillic_be
+ 0x0432: 0x06d7, // XK_Cyrillic_ve
+ 0x0433: 0x06c7, // XK_Cyrillic_ghe
+ 0x0434: 0x06c4, // XK_Cyrillic_de
+ 0x0435: 0x06c5, // XK_Cyrillic_ie
+ 0x0436: 0x06d6, // XK_Cyrillic_zhe
+ 0x0437: 0x06da, // XK_Cyrillic_ze
+ 0x0438: 0x06c9, // XK_Cyrillic_i
+ 0x0439: 0x06ca, // XK_Cyrillic_shorti
+ 0x043a: 0x06cb, // XK_Cyrillic_ka
+ 0x043b: 0x06cc, // XK_Cyrillic_el
+ 0x043c: 0x06cd, // XK_Cyrillic_em
+ 0x043d: 0x06ce, // XK_Cyrillic_en
+ 0x043e: 0x06cf, // XK_Cyrillic_o
+ 0x043f: 0x06d0, // XK_Cyrillic_pe
+ 0x0440: 0x06d2, // XK_Cyrillic_er
+ 0x0441: 0x06d3, // XK_Cyrillic_es
+ 0x0442: 0x06d4, // XK_Cyrillic_te
+ 0x0443: 0x06d5, // XK_Cyrillic_u
+ 0x0444: 0x06c6, // XK_Cyrillic_ef
+ 0x0445: 0x06c8, // XK_Cyrillic_ha
+ 0x0446: 0x06c3, // XK_Cyrillic_tse
+ 0x0447: 0x06de, // XK_Cyrillic_che
+ 0x0448: 0x06db, // XK_Cyrillic_sha
+ 0x0449: 0x06dd, // XK_Cyrillic_shcha
+ 0x044a: 0x06df, // XK_Cyrillic_hardsign
+ 0x044b: 0x06d9, // XK_Cyrillic_yeru
+ 0x044c: 0x06d8, // XK_Cyrillic_softsign
+ 0x044d: 0x06dc, // XK_Cyrillic_e
+ 0x044e: 0x06c0, // XK_Cyrillic_yu
+ 0x044f: 0x06d1, // XK_Cyrillic_ya
+ 0x0451: 0x06a3, // XK_Cyrillic_io
+ 0x0452: 0x06a1, // XK_Serbian_dje
+ 0x0453: 0x06a2, // XK_Macedonia_gje
+ 0x0454: 0x06a4, // XK_Ukrainian_ie
+ 0x0455: 0x06a5, // XK_Macedonia_dse
+ 0x0456: 0x06a6, // XK_Ukrainian_i
+ 0x0457: 0x06a7, // XK_Ukrainian_yi
+ 0x0458: 0x06a8, // XK_Cyrillic_je
+ 0x0459: 0x06a9, // XK_Cyrillic_lje
+ 0x045a: 0x06aa, // XK_Cyrillic_nje
+ 0x045b: 0x06ab, // XK_Serbian_tshe
+ 0x045c: 0x06ac, // XK_Macedonia_kje
+ 0x045e: 0x06ae, // XK_Byelorussian_shortu
+ 0x045f: 0x06af, // XK_Cyrillic_dzhe
+ 0x0490: 0x06bd, // XK_Ukrainian_GHE_WITH_UPTURN
+ 0x0491: 0x06ad, // XK_Ukrainian_ghe_with_upturn
+ 0x05d0: 0x0ce0, // XK_hebrew_aleph
+ 0x05d1: 0x0ce1, // XK_hebrew_bet
+ 0x05d2: 0x0ce2, // XK_hebrew_gimel
+ 0x05d3: 0x0ce3, // XK_hebrew_dalet
+ 0x05d4: 0x0ce4, // XK_hebrew_he
+ 0x05d5: 0x0ce5, // XK_hebrew_waw
+ 0x05d6: 0x0ce6, // XK_hebrew_zain
+ 0x05d7: 0x0ce7, // XK_hebrew_chet
+ 0x05d8: 0x0ce8, // XK_hebrew_tet
+ 0x05d9: 0x0ce9, // XK_hebrew_yod
+ 0x05da: 0x0cea, // XK_hebrew_finalkaph
+ 0x05db: 0x0ceb, // XK_hebrew_kaph
+ 0x05dc: 0x0cec, // XK_hebrew_lamed
+ 0x05dd: 0x0ced, // XK_hebrew_finalmem
+ 0x05de: 0x0cee, // XK_hebrew_mem
+ 0x05df: 0x0cef, // XK_hebrew_finalnun
+ 0x05e0: 0x0cf0, // XK_hebrew_nun
+ 0x05e1: 0x0cf1, // XK_hebrew_samech
+ 0x05e2: 0x0cf2, // XK_hebrew_ayin
+ 0x05e3: 0x0cf3, // XK_hebrew_finalpe
+ 0x05e4: 0x0cf4, // XK_hebrew_pe
+ 0x05e5: 0x0cf5, // XK_hebrew_finalzade
+ 0x05e6: 0x0cf6, // XK_hebrew_zade
+ 0x05e7: 0x0cf7, // XK_hebrew_qoph
+ 0x05e8: 0x0cf8, // XK_hebrew_resh
+ 0x05e9: 0x0cf9, // XK_hebrew_shin
+ 0x05ea: 0x0cfa, // XK_hebrew_taw
+ 0x060c: 0x05ac, // XK_Arabic_comma
+ 0x061b: 0x05bb, // XK_Arabic_semicolon
+ 0x061f: 0x05bf, // XK_Arabic_question_mark
+ 0x0621: 0x05c1, // XK_Arabic_hamza
+ 0x0622: 0x05c2, // XK_Arabic_maddaonalef
+ 0x0623: 0x05c3, // XK_Arabic_hamzaonalef
+ 0x0624: 0x05c4, // XK_Arabic_hamzaonwaw
+ 0x0625: 0x05c5, // XK_Arabic_hamzaunderalef
+ 0x0626: 0x05c6, // XK_Arabic_hamzaonyeh
+ 0x0627: 0x05c7, // XK_Arabic_alef
+ 0x0628: 0x05c8, // XK_Arabic_beh
+ 0x0629: 0x05c9, // XK_Arabic_tehmarbuta
+ 0x062a: 0x05ca, // XK_Arabic_teh
+ 0x062b: 0x05cb, // XK_Arabic_theh
+ 0x062c: 0x05cc, // XK_Arabic_jeem
+ 0x062d: 0x05cd, // XK_Arabic_hah
+ 0x062e: 0x05ce, // XK_Arabic_khah
+ 0x062f: 0x05cf, // XK_Arabic_dal
+ 0x0630: 0x05d0, // XK_Arabic_thal
+ 0x0631: 0x05d1, // XK_Arabic_ra
+ 0x0632: 0x05d2, // XK_Arabic_zain
+ 0x0633: 0x05d3, // XK_Arabic_seen
+ 0x0634: 0x05d4, // XK_Arabic_sheen
+ 0x0635: 0x05d5, // XK_Arabic_sad
+ 0x0636: 0x05d6, // XK_Arabic_dad
+ 0x0637: 0x05d7, // XK_Arabic_tah
+ 0x0638: 0x05d8, // XK_Arabic_zah
+ 0x0639: 0x05d9, // XK_Arabic_ain
+ 0x063a: 0x05da, // XK_Arabic_ghain
+ 0x0640: 0x05e0, // XK_Arabic_tatweel
+ 0x0641: 0x05e1, // XK_Arabic_feh
+ 0x0642: 0x05e2, // XK_Arabic_qaf
+ 0x0643: 0x05e3, // XK_Arabic_kaf
+ 0x0644: 0x05e4, // XK_Arabic_lam
+ 0x0645: 0x05e5, // XK_Arabic_meem
+ 0x0646: 0x05e6, // XK_Arabic_noon
+ 0x0647: 0x05e7, // XK_Arabic_ha
+ 0x0648: 0x05e8, // XK_Arabic_waw
+ 0x0649: 0x05e9, // XK_Arabic_alefmaksura
+ 0x064a: 0x05ea, // XK_Arabic_yeh
+ 0x064b: 0x05eb, // XK_Arabic_fathatan
+ 0x064c: 0x05ec, // XK_Arabic_dammatan
+ 0x064d: 0x05ed, // XK_Arabic_kasratan
+ 0x064e: 0x05ee, // XK_Arabic_fatha
+ 0x064f: 0x05ef, // XK_Arabic_damma
+ 0x0650: 0x05f0, // XK_Arabic_kasra
+ 0x0651: 0x05f1, // XK_Arabic_shadda
+ 0x0652: 0x05f2, // XK_Arabic_sukun
+ 0x0e01: 0x0da1, // XK_Thai_kokai
+ 0x0e02: 0x0da2, // XK_Thai_khokhai
+ 0x0e03: 0x0da3, // XK_Thai_khokhuat
+ 0x0e04: 0x0da4, // XK_Thai_khokhwai
+ 0x0e05: 0x0da5, // XK_Thai_khokhon
+ 0x0e06: 0x0da6, // XK_Thai_khorakhang
+ 0x0e07: 0x0da7, // XK_Thai_ngongu
+ 0x0e08: 0x0da8, // XK_Thai_chochan
+ 0x0e09: 0x0da9, // XK_Thai_choching
+ 0x0e0a: 0x0daa, // XK_Thai_chochang
+ 0x0e0b: 0x0dab, // XK_Thai_soso
+ 0x0e0c: 0x0dac, // XK_Thai_chochoe
+ 0x0e0d: 0x0dad, // XK_Thai_yoying
+ 0x0e0e: 0x0dae, // XK_Thai_dochada
+ 0x0e0f: 0x0daf, // XK_Thai_topatak
+ 0x0e10: 0x0db0, // XK_Thai_thothan
+ 0x0e11: 0x0db1, // XK_Thai_thonangmontho
+ 0x0e12: 0x0db2, // XK_Thai_thophuthao
+ 0x0e13: 0x0db3, // XK_Thai_nonen
+ 0x0e14: 0x0db4, // XK_Thai_dodek
+ 0x0e15: 0x0db5, // XK_Thai_totao
+ 0x0e16: 0x0db6, // XK_Thai_thothung
+ 0x0e17: 0x0db7, // XK_Thai_thothahan
+ 0x0e18: 0x0db8, // XK_Thai_thothong
+ 0x0e19: 0x0db9, // XK_Thai_nonu
+ 0x0e1a: 0x0dba, // XK_Thai_bobaimai
+ 0x0e1b: 0x0dbb, // XK_Thai_popla
+ 0x0e1c: 0x0dbc, // XK_Thai_phophung
+ 0x0e1d: 0x0dbd, // XK_Thai_fofa
+ 0x0e1e: 0x0dbe, // XK_Thai_phophan
+ 0x0e1f: 0x0dbf, // XK_Thai_fofan
+ 0x0e20: 0x0dc0, // XK_Thai_phosamphao
+ 0x0e21: 0x0dc1, // XK_Thai_moma
+ 0x0e22: 0x0dc2, // XK_Thai_yoyak
+ 0x0e23: 0x0dc3, // XK_Thai_rorua
+ 0x0e24: 0x0dc4, // XK_Thai_ru
+ 0x0e25: 0x0dc5, // XK_Thai_loling
+ 0x0e26: 0x0dc6, // XK_Thai_lu
+ 0x0e27: 0x0dc7, // XK_Thai_wowaen
+ 0x0e28: 0x0dc8, // XK_Thai_sosala
+ 0x0e29: 0x0dc9, // XK_Thai_sorusi
+ 0x0e2a: 0x0dca, // XK_Thai_sosua
+ 0x0e2b: 0x0dcb, // XK_Thai_hohip
+ 0x0e2c: 0x0dcc, // XK_Thai_lochula
+ 0x0e2d: 0x0dcd, // XK_Thai_oang
+ 0x0e2e: 0x0dce, // XK_Thai_honokhuk
+ 0x0e2f: 0x0dcf, // XK_Thai_paiyannoi
+ 0x0e30: 0x0dd0, // XK_Thai_saraa
+ 0x0e31: 0x0dd1, // XK_Thai_maihanakat
+ 0x0e32: 0x0dd2, // XK_Thai_saraaa
+ 0x0e33: 0x0dd3, // XK_Thai_saraam
+ 0x0e34: 0x0dd4, // XK_Thai_sarai
+ 0x0e35: 0x0dd5, // XK_Thai_saraii
+ 0x0e36: 0x0dd6, // XK_Thai_saraue
+ 0x0e37: 0x0dd7, // XK_Thai_sarauee
+ 0x0e38: 0x0dd8, // XK_Thai_sarau
+ 0x0e39: 0x0dd9, // XK_Thai_sarauu
+ 0x0e3a: 0x0dda, // XK_Thai_phinthu
+ 0x0e3f: 0x0ddf, // XK_Thai_baht
+ 0x0e40: 0x0de0, // XK_Thai_sarae
+ 0x0e41: 0x0de1, // XK_Thai_saraae
+ 0x0e42: 0x0de2, // XK_Thai_sarao
+ 0x0e43: 0x0de3, // XK_Thai_saraaimaimuan
+ 0x0e44: 0x0de4, // XK_Thai_saraaimaimalai
+ 0x0e45: 0x0de5, // XK_Thai_lakkhangyao
+ 0x0e46: 0x0de6, // XK_Thai_maiyamok
+ 0x0e47: 0x0de7, // XK_Thai_maitaikhu
+ 0x0e48: 0x0de8, // XK_Thai_maiek
+ 0x0e49: 0x0de9, // XK_Thai_maitho
+ 0x0e4a: 0x0dea, // XK_Thai_maitri
+ 0x0e4b: 0x0deb, // XK_Thai_maichattawa
+ 0x0e4c: 0x0dec, // XK_Thai_thanthakhat
+ 0x0e4d: 0x0ded, // XK_Thai_nikhahit
+ 0x0e50: 0x0df0, // XK_Thai_leksun
+ 0x0e51: 0x0df1, // XK_Thai_leknung
+ 0x0e52: 0x0df2, // XK_Thai_leksong
+ 0x0e53: 0x0df3, // XK_Thai_leksam
+ 0x0e54: 0x0df4, // XK_Thai_leksi
+ 0x0e55: 0x0df5, // XK_Thai_lekha
+ 0x0e56: 0x0df6, // XK_Thai_lekhok
+ 0x0e57: 0x0df7, // XK_Thai_lekchet
+ 0x0e58: 0x0df8, // XK_Thai_lekpaet
+ 0x0e59: 0x0df9, // XK_Thai_lekkao
+ 0x2002: 0x0aa2, // XK_enspace
+ 0x2003: 0x0aa1, // XK_emspace
+ 0x2004: 0x0aa3, // XK_em3space
+ 0x2005: 0x0aa4, // XK_em4space
+ 0x2007: 0x0aa5, // XK_digitspace
+ 0x2008: 0x0aa6, // XK_punctspace
+ 0x2009: 0x0aa7, // XK_thinspace
+ 0x200a: 0x0aa8, // XK_hairspace
+ 0x2012: 0x0abb, // XK_figdash
+ 0x2013: 0x0aaa, // XK_endash
+ 0x2014: 0x0aa9, // XK_emdash
+ 0x2015: 0x07af, // XK_Greek_horizbar
+ 0x2017: 0x0cdf, // XK_hebrew_doublelowline
+ 0x2018: 0x0ad0, // XK_leftsinglequotemark
+ 0x2019: 0x0ad1, // XK_rightsinglequotemark
+ 0x201a: 0x0afd, // XK_singlelowquotemark
+ 0x201c: 0x0ad2, // XK_leftdoublequotemark
+ 0x201d: 0x0ad3, // XK_rightdoublequotemark
+ 0x201e: 0x0afe, // XK_doublelowquotemark
+ 0x2020: 0x0af1, // XK_dagger
+ 0x2021: 0x0af2, // XK_doubledagger
+ 0x2022: 0x0ae6, // XK_enfilledcircbullet
+ 0x2025: 0x0aaf, // XK_doubbaselinedot
+ 0x2026: 0x0aae, // XK_ellipsis
+ 0x2030: 0x0ad5, // XK_permille
+ 0x2032: 0x0ad6, // XK_minutes
+ 0x2033: 0x0ad7, // XK_seconds
+ 0x2038: 0x0afc, // XK_caret
+ 0x203e: 0x047e, // XK_overline
+ 0x20a9: 0x0eff, // XK_Korean_Won
+ 0x20ac: 0x20ac, // XK_EuroSign
+ 0x2105: 0x0ab8, // XK_careof
+ 0x2116: 0x06b0, // XK_numerosign
+ 0x2117: 0x0afb, // XK_phonographcopyright
+ 0x211e: 0x0ad4, // XK_prescription
+ 0x2122: 0x0ac9, // XK_trademark
+ 0x2153: 0x0ab0, // XK_onethird
+ 0x2154: 0x0ab1, // XK_twothirds
+ 0x2155: 0x0ab2, // XK_onefifth
+ 0x2156: 0x0ab3, // XK_twofifths
+ 0x2157: 0x0ab4, // XK_threefifths
+ 0x2158: 0x0ab5, // XK_fourfifths
+ 0x2159: 0x0ab6, // XK_onesixth
+ 0x215a: 0x0ab7, // XK_fivesixths
+ 0x215b: 0x0ac3, // XK_oneeighth
+ 0x215c: 0x0ac4, // XK_threeeighths
+ 0x215d: 0x0ac5, // XK_fiveeighths
+ 0x215e: 0x0ac6, // XK_seveneighths
+ 0x2190: 0x08fb, // XK_leftarrow
+ 0x2191: 0x08fc, // XK_uparrow
+ 0x2192: 0x08fd, // XK_rightarrow
+ 0x2193: 0x08fe, // XK_downarrow
+ 0x21d2: 0x08ce, // XK_implies
+ 0x21d4: 0x08cd, // XK_ifonlyif
+ 0x2202: 0x08ef, // XK_partialderivative
+ 0x2207: 0x08c5, // XK_nabla
+ 0x2218: 0x0bca, // XK_jot
+ 0x221a: 0x08d6, // XK_radical
+ 0x221d: 0x08c1, // XK_variation
+ 0x221e: 0x08c2, // XK_infinity
+ 0x2227: 0x08de, // XK_logicaland
+ 0x2228: 0x08df, // XK_logicalor
+ 0x2229: 0x08dc, // XK_intersection
+ 0x222a: 0x08dd, // XK_union
+ 0x222b: 0x08bf, // XK_integral
+ 0x2234: 0x08c0, // XK_therefore
+ 0x223c: 0x08c8, // XK_approximate
+ 0x2243: 0x08c9, // XK_similarequal
+ 0x2245: 0x1002248, // XK_approxeq
+ 0x2260: 0x08bd, // XK_notequal
+ 0x2261: 0x08cf, // XK_identical
+ 0x2264: 0x08bc, // XK_lessthanequal
+ 0x2265: 0x08be, // XK_greaterthanequal
+ 0x2282: 0x08da, // XK_includedin
+ 0x2283: 0x08db, // XK_includes
+ 0x22a2: 0x0bfc, // XK_righttack
+ 0x22a3: 0x0bdc, // XK_lefttack
+ 0x22a4: 0x0bc2, // XK_downtack
+ 0x22a5: 0x0bce, // XK_uptack
+ 0x2308: 0x0bd3, // XK_upstile
+ 0x230a: 0x0bc4, // XK_downstile
+ 0x2315: 0x0afa, // XK_telephonerecorder
+ 0x2320: 0x08a4, // XK_topintegral
+ 0x2321: 0x08a5, // XK_botintegral
+ 0x2395: 0x0bcc, // XK_quad
+ 0x239b: 0x08ab, // XK_topleftparens
+ 0x239d: 0x08ac, // XK_botleftparens
+ 0x239e: 0x08ad, // XK_toprightparens
+ 0x23a0: 0x08ae, // XK_botrightparens
+ 0x23a1: 0x08a7, // XK_topleftsqbracket
+ 0x23a3: 0x08a8, // XK_botleftsqbracket
+ 0x23a4: 0x08a9, // XK_toprightsqbracket
+ 0x23a6: 0x08aa, // XK_botrightsqbracket
+ 0x23a8: 0x08af, // XK_leftmiddlecurlybrace
+ 0x23ac: 0x08b0, // XK_rightmiddlecurlybrace
+ 0x23b7: 0x08a1, // XK_leftradical
+ 0x23ba: 0x09ef, // XK_horizlinescan1
+ 0x23bb: 0x09f0, // XK_horizlinescan3
+ 0x23bc: 0x09f2, // XK_horizlinescan7
+ 0x23bd: 0x09f3, // XK_horizlinescan9
+ 0x2409: 0x09e2, // XK_ht
+ 0x240a: 0x09e5, // XK_lf
+ 0x240b: 0x09e9, // XK_vt
+ 0x240c: 0x09e3, // XK_ff
+ 0x240d: 0x09e4, // XK_cr
+ 0x2423: 0x0aac, // XK_signifblank
+ 0x2424: 0x09e8, // XK_nl
+ 0x2500: 0x08a3, // XK_horizconnector
+ 0x2502: 0x08a6, // XK_vertconnector
+ 0x250c: 0x08a2, // XK_topleftradical
+ 0x2510: 0x09eb, // XK_uprightcorner
+ 0x2514: 0x09ed, // XK_lowleftcorner
+ 0x2518: 0x09ea, // XK_lowrightcorner
+ 0x251c: 0x09f4, // XK_leftt
+ 0x2524: 0x09f5, // XK_rightt
+ 0x252c: 0x09f7, // XK_topt
+ 0x2534: 0x09f6, // XK_bott
+ 0x253c: 0x09ee, // XK_crossinglines
+ 0x2592: 0x09e1, // XK_checkerboard
+ 0x25aa: 0x0ae7, // XK_enfilledsqbullet
+ 0x25ab: 0x0ae1, // XK_enopensquarebullet
+ 0x25ac: 0x0adb, // XK_filledrectbullet
+ 0x25ad: 0x0ae2, // XK_openrectbullet
+ 0x25ae: 0x0adf, // XK_emfilledrect
+ 0x25af: 0x0acf, // XK_emopenrectangle
+ 0x25b2: 0x0ae8, // XK_filledtribulletup
+ 0x25b3: 0x0ae3, // XK_opentribulletup
+ 0x25b6: 0x0add, // XK_filledrighttribullet
+ 0x25b7: 0x0acd, // XK_rightopentriangle
+ 0x25bc: 0x0ae9, // XK_filledtribulletdown
+ 0x25bd: 0x0ae4, // XK_opentribulletdown
+ 0x25c0: 0x0adc, // XK_filledlefttribullet
+ 0x25c1: 0x0acc, // XK_leftopentriangle
+ 0x25c6: 0x09e0, // XK_soliddiamond
+ 0x25cb: 0x0ace, // XK_emopencircle
+ 0x25cf: 0x0ade, // XK_emfilledcircle
+ 0x25e6: 0x0ae0, // XK_enopencircbullet
+ 0x2606: 0x0ae5, // XK_openstar
+ 0x260e: 0x0af9, // XK_telephone
+ 0x2613: 0x0aca, // XK_signaturemark
+ 0x261c: 0x0aea, // XK_leftpointer
+ 0x261e: 0x0aeb, // XK_rightpointer
+ 0x2640: 0x0af8, // XK_femalesymbol
+ 0x2642: 0x0af7, // XK_malesymbol
+ 0x2663: 0x0aec, // XK_club
+ 0x2665: 0x0aee, // XK_heart
+ 0x2666: 0x0aed, // XK_diamond
+ 0x266d: 0x0af6, // XK_musicalflat
+ 0x266f: 0x0af5, // XK_musicalsharp
+ 0x2713: 0x0af3, // XK_checkmark
+ 0x2717: 0x0af4, // XK_ballotcross
+ 0x271d: 0x0ad9, // XK_latincross
+ 0x2720: 0x0af0, // XK_maltesecross
+ 0x27e8: 0x0abc, // XK_leftanglebracket
+ 0x27e9: 0x0abe, // XK_rightanglebracket
+ 0x3001: 0x04a4, // XK_kana_comma
+ 0x3002: 0x04a1, // XK_kana_fullstop
+ 0x300c: 0x04a2, // XK_kana_openingbracket
+ 0x300d: 0x04a3, // XK_kana_closingbracket
+ 0x309b: 0x04de, // XK_voicedsound
+ 0x309c: 0x04df, // XK_semivoicedsound
+ 0x30a1: 0x04a7, // XK_kana_a
+ 0x30a2: 0x04b1, // XK_kana_A
+ 0x30a3: 0x04a8, // XK_kana_i
+ 0x30a4: 0x04b2, // XK_kana_I
+ 0x30a5: 0x04a9, // XK_kana_u
+ 0x30a6: 0x04b3, // XK_kana_U
+ 0x30a7: 0x04aa, // XK_kana_e
+ 0x30a8: 0x04b4, // XK_kana_E
+ 0x30a9: 0x04ab, // XK_kana_o
+ 0x30aa: 0x04b5, // XK_kana_O
+ 0x30ab: 0x04b6, // XK_kana_KA
+ 0x30ad: 0x04b7, // XK_kana_KI
+ 0x30af: 0x04b8, // XK_kana_KU
+ 0x30b1: 0x04b9, // XK_kana_KE
+ 0x30b3: 0x04ba, // XK_kana_KO
+ 0x30b5: 0x04bb, // XK_kana_SA
+ 0x30b7: 0x04bc, // XK_kana_SHI
+ 0x30b9: 0x04bd, // XK_kana_SU
+ 0x30bb: 0x04be, // XK_kana_SE
+ 0x30bd: 0x04bf, // XK_kana_SO
+ 0x30bf: 0x04c0, // XK_kana_TA
+ 0x30c1: 0x04c1, // XK_kana_CHI
+ 0x30c3: 0x04af, // XK_kana_tsu
+ 0x30c4: 0x04c2, // XK_kana_TSU
+ 0x30c6: 0x04c3, // XK_kana_TE
+ 0x30c8: 0x04c4, // XK_kana_TO
+ 0x30ca: 0x04c5, // XK_kana_NA
+ 0x30cb: 0x04c6, // XK_kana_NI
+ 0x30cc: 0x04c7, // XK_kana_NU
+ 0x30cd: 0x04c8, // XK_kana_NE
+ 0x30ce: 0x04c9, // XK_kana_NO
+ 0x30cf: 0x04ca, // XK_kana_HA
+ 0x30d2: 0x04cb, // XK_kana_HI
+ 0x30d5: 0x04cc, // XK_kana_FU
+ 0x30d8: 0x04cd, // XK_kana_HE
+ 0x30db: 0x04ce, // XK_kana_HO
+ 0x30de: 0x04cf, // XK_kana_MA
+ 0x30df: 0x04d0, // XK_kana_MI
+ 0x30e0: 0x04d1, // XK_kana_MU
+ 0x30e1: 0x04d2, // XK_kana_ME
+ 0x30e2: 0x04d3, // XK_kana_MO
+ 0x30e3: 0x04ac, // XK_kana_ya
+ 0x30e4: 0x04d4, // XK_kana_YA
+ 0x30e5: 0x04ad, // XK_kana_yu
+ 0x30e6: 0x04d5, // XK_kana_YU
+ 0x30e7: 0x04ae, // XK_kana_yo
+ 0x30e8: 0x04d6, // XK_kana_YO
+ 0x30e9: 0x04d7, // XK_kana_RA
+ 0x30ea: 0x04d8, // XK_kana_RI
+ 0x30eb: 0x04d9, // XK_kana_RU
+ 0x30ec: 0x04da, // XK_kana_RE
+ 0x30ed: 0x04db, // XK_kana_RO
+ 0x30ef: 0x04dc, // XK_kana_WA
+ 0x30f2: 0x04a6, // XK_kana_WO
+ 0x30f3: 0x04dd, // XK_kana_N
+ 0x30fb: 0x04a5, // XK_kana_conjunctive
+ 0x30fc: 0x04b0, // XK_prolongedsound
+};
-function lookup(k) { return k ? {keysym: k, keyname: keynames ? keynames[k] : k} : undefined; }
export default {
- fromUnicode : function(u) {
+ lookup : function(u) {
+ // Latin-1 is one-to-one mapping
+ if ((u >= 0x20) && (u <= 0xff)) {
+ return u;
+ }
+
+ // Lookup table (fairly random)
var keysym = codepoints[u];
- if (keysym === undefined) {
- keysym = 0x01000000 | u;
+ if (keysym !== undefined) {
+ return keysym;
}
- return lookup(keysym);
+
+ // General mapping as final fallback
+ return 0x01000000 | u;
},
- lookup : lookup
};
diff --git a/core/input/util.js b/core/input/util.js
index b1e6371..4335b0b 100644
--- a/core/input/util.js
+++ b/core/input/util.js
@@ -1,660 +1,167 @@
import KeyTable from "./keysym.js";
import keysyms from "./keysymdef.js";
-
-export function substituteCodepoint(cp) {
- // Any Unicode code points which do not have corresponding keysym entries
- // can be swapped out for another code point by adding them to this table
- var substitutions = {
- // {S,s} with comma below -> {S,s} with cedilla
- 0x218 : 0x15e,
- 0x219 : 0x15f,
- // {T,t} with comma below -> {T,t} with cedilla
- 0x21a : 0x162,
- 0x21b : 0x163
- };
-
- var sub = substitutions[cp];
- return sub ? sub : cp;
-}
+import vkeys from "./vkeys.js";
+import fixedkeys from "./fixedkeys.js";
+import DOMKeyTable from "./domkeytable.js";
function isMac() {
return navigator && !!(/mac/i).exec(navigator.platform);
}
-function isWindows() {
- return navigator && !!(/win/i).exec(navigator.platform);
+function isIE() {
+ return navigator && !!(/trident/i).exec(navigator.userAgent);
}
-function isLinux() {
- return navigator && !!(/linux/i).exec(navigator.platform);
+function isEdge() {
+ return navigator && !!(/edge/i).exec(navigator.userAgent);
}
-// Return true if a modifier which is not the specified char modifier (and is not shift) is down
-export function hasShortcutModifier(charModifier, currentModifiers) {
- var mods = {};
- for (var key in currentModifiers) {
- if (parseInt(key) !== KeyTable.XK_Shift_L) {
- mods[key] = currentModifiers[key];
+// Get 'KeyboardEvent.code', handling legacy browsers
+export function getKeycode(evt){
+ // Are we getting proper key identifiers?
+ // (unfortunately Firefox and Chrome are crappy here and gives
+ // us an empty string on some platforms, rather than leaving it
+ // undefined)
+ if (evt.code) {
+ // Mozilla isn't fully in sync with the spec yet
+ switch (evt.code) {
+ case 'OSLeft': return 'MetaLeft';
+ case 'OSRight': return 'MetaRight';
}
- }
- var sum = 0;
- for (var k in currentModifiers) {
- if (mods[k]) {
- ++sum;
- }
+ return evt.code;
}
- if (hasCharModifier(charModifier, mods)) {
- return sum > charModifier.length;
- }
- else {
- return sum > 0;
- }
-}
-// Return true if the specified char modifier is currently down
-export function hasCharModifier(charModifier, currentModifiers) {
- if (charModifier.length === 0) { return false; }
+ // The de-facto standard is to use Windows Virtual-Key codes
+ // in the 'keyCode' field for non-printable characters. However
+ // Webkit sets it to the same as charCode in 'keypress' events.
+ if ((evt.type !== 'keypress') && (evt.keyCode in vkeys)) {
+ var code = vkeys[evt.keyCode];
- for (var i = 0; i < charModifier.length; ++i) {
- if (!currentModifiers[charModifier[i]]) {
- return false;
+ // macOS has messed up this code for some reason
+ if (isMac() && (code === 'ContextMenu')) {
+ code = 'MetaRight';
}
- }
- return true;
-}
-// Helper object tracking modifier key state
-// and generates fake key events to compensate if it gets out of sync
-export function ModifierSync(charModifier) {
- if (!charModifier) {
- if (isMac()) {
- // on Mac, Option (AKA Alt) is used as a char modifier
- charModifier = [KeyTable.XK_Alt_L];
- }
- else if (isWindows()) {
- // on Windows, Ctrl+Alt is used as a char modifier
- charModifier = [KeyTable.XK_Alt_L, KeyTable.XK_Control_L];
- }
- else if (isLinux()) {
- // on Linux, ISO Level 3 Shift (AltGr) is used as a char modifier
- charModifier = [KeyTable.XK_ISO_Level3_Shift];
+ // The keyCode doesn't distinguish between left and right
+ // for the standard modifiers
+ if (evt.location === 2) {
+ switch (code) {
+ case 'ShiftLeft': return 'ShiftRight';
+ case 'ControlLeft': return 'ControlRight';
+ case 'AltLeft': return 'AltRight';
+ }
}
- else {
- charModifier = [];
+
+ // Nor a bunch of the numpad keys
+ if (evt.location === 3) {
+ switch (code) {
+ case 'Delete': return 'NumpadDecimal';
+ case 'Insert': return 'Numpad0';
+ case 'End': return 'Numpad1';
+ case 'ArrowDown': return 'Numpad2';
+ case 'PageDown': return 'Numpad3';
+ case 'ArrowLeft': return 'Numpad4';
+ case 'ArrowRight': return 'Numpad6';
+ case 'Home': return 'Numpad7';
+ case 'ArrowUp': return 'Numpad8';
+ case 'PageUp': return 'Numpad9';
+ case 'Enter': return 'NumpadEnter';
+ }
}
+
+ return code;
}
- var state = {};
- state[KeyTable.XK_Control_L] = false;
- state[KeyTable.XK_Alt_L] = false;
- state[KeyTable.XK_ISO_Level3_Shift] = false;
- state[KeyTable.XK_Shift_L] = false;
- state[KeyTable.XK_Meta_L] = false;
+ return 'Unidentified';
+}
- function sync(evt, keysym) {
- var result = [];
- function syncKey(keysym) {
- return {keysym: keysyms.lookup(keysym), type: state[keysym] ? 'keydown' : 'keyup'};
+// Get 'KeyboardEvent.key', handling legacy browsers
+export function getKey(evt) {
+ // Are we getting a proper key value?
+ if (evt.key !== undefined) {
+ // IE and Edge use some ancient version of the spec
+ // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/
+ switch (evt.key) {
+ case 'Spacebar': return ' ';
+ case 'Esc': return 'Escape';
+ case 'Scroll': return 'ScrollLock';
+ case 'Win': return 'Meta';
+ case 'Apps': return 'ContextMenu';
+ case 'Up': return 'ArrowUp';
+ case 'Left': return 'ArrowLeft';
+ case 'Right': return 'ArrowRight';
+ case 'Down': return 'ArrowDown';
+ case 'Del': return 'Delete';
+ case 'Divide': return '/';
+ case 'Multiply': return '*';
+ case 'Subtract': return '-';
+ case 'Add': return '+';
+ case 'Decimal': return evt.char;
}
- if (evt.ctrlKey !== undefined &&
- evt.ctrlKey !== state[KeyTable.XK_Control_L] && keysym !== KeyTable.XK_Control_L) {
- state[KeyTable.XK_Control_L] = evt.ctrlKey;
- result.push(syncKey(KeyTable.XK_Control_L));
+ // Mozilla isn't fully in sync with the spec yet
+ switch (evt.key) {
+ case 'OS': return 'Meta';
}
- if (evt.altKey !== undefined &&
- evt.altKey !== state[KeyTable.XK_Alt_L] && keysym !== KeyTable.XK_Alt_L) {
- state[KeyTable.XK_Alt_L] = evt.altKey;
- result.push(syncKey(KeyTable.XK_Alt_L));
- }
- if (evt.altGraphKey !== undefined &&
- evt.altGraphKey !== state[KeyTable.XK_ISO_Level3_Shift] && keysym !== KeyTable.XK_ISO_Level3_Shift) {
- state[KeyTable.XK_ISO_Level3_Shift] = evt.altGraphKey;
- result.push(syncKey(KeyTable.XK_ISO_Level3_Shift));
- }
- if (evt.shiftKey !== undefined &&
- evt.shiftKey !== state[KeyTable.XK_Shift_L] && keysym !== KeyTable.XK_Shift_L) {
- state[KeyTable.XK_Shift_L] = evt.shiftKey;
- result.push(syncKey(KeyTable.XK_Shift_L));
- }
- if (evt.metaKey !== undefined &&
- evt.metaKey !== state[KeyTable.XK_Meta_L] && keysym !== KeyTable.XK_Meta_L) {
- state[KeyTable.XK_Meta_L] = evt.metaKey;
- result.push(syncKey(KeyTable.XK_Meta_L));
- }
- return result;
- }
- function syncKeyEvent(evt, down) {
- var obj = getKeysym(evt);
- var keysym = obj ? obj.keysym : null;
- // first, apply the event itself, if relevant
- if (keysym !== null && state[keysym] !== undefined) {
- state[keysym] = down;
+ // IE and Edge have broken handling of AltGraph so we cannot
+ // trust them for printable characters
+ if ((evt.key.length !== 1) || (!isIE() && !isEdge())) {
+ return evt.key;
}
- return sync(evt, keysym);
}
- return {
- // sync on the appropriate keyboard event
- keydown: function(evt) { return syncKeyEvent(evt, true);},
- keyup: function(evt) { return syncKeyEvent(evt, false);},
- // Call this with a non-keyboard event (such as mouse events) to use its modifier state to synchronize anyway
- syncAny: function(evt) { return sync(evt);},
-
- // is a shortcut modifier down?
- hasShortcutModifier: function() { return hasShortcutModifier(charModifier, state); },
- // if a char modifier is down, return the keys it consists of, otherwise return null
- activeCharModifier: function() { return hasCharModifier(charModifier, state) ? charModifier : null; }
- };
-}
-
-// Get a key ID from a keyboard event
-// May be a string or an integer depending on the available properties
-export function getKey(evt){
- if ('keyCode' in evt && 'key' in evt) {
- return evt.key + ':' + evt.keyCode;
+ // Try to deduce it based on the physical key
+ var code = getKeycode(evt);
+ if (code in fixedkeys) {
+ return fixedkeys[code];
}
- else if ('keyCode' in evt) {
- return evt.keyCode;
- }
- else {
- return evt.key;
+
+ // If that failed, then see if we have a printable character
+ if (evt.charCode) {
+ return String.fromCharCode(evt.charCode);
}
+
+ // At this point we have nothing left to go on
+ return 'Unidentified';
}
// Get the most reliable keysym value we can get from a key event
-// if char/charCode is available, prefer those, otherwise fall back to key/keyCode/which
export function getKeysym(evt){
- var codepoint;
- if (evt.char && evt.char.length === 1) {
- codepoint = evt.char.charCodeAt();
- }
- else if (evt.charCode) {
- codepoint = evt.charCode;
- }
- else if (evt.keyCode && evt.type === 'keypress') {
- // IE10 stores the char code as keyCode, and has no other useful properties
- codepoint = evt.keyCode;
- }
- if (codepoint) {
- return keysyms.fromUnicode(substituteCodepoint(codepoint));
- }
- // we could check evt.key here.
- // Legal values are defined in http://www.w3.org/TR/DOM-Level-3-Events/#key-values-list,
- // so we "just" need to map them to keysym, but AFAIK this is only available in IE10, which also provides evt.key
- // so we don't *need* it yet
- if (evt.keyCode) {
- return keysyms.lookup(keysymFromKeyCode(evt.keyCode, evt.shiftKey));
- }
- if (evt.which) {
- return keysyms.lookup(keysymFromKeyCode(evt.which, evt.shiftKey));
- }
- return null;
-}
+ var key = getKey(evt);
-// Given a keycode, try to predict which keysym it might be.
-// If the keycode is unknown, null is returned.
-export function keysymFromKeyCode(keycode, shiftPressed) {
- if (typeof(keycode) !== 'number') {
+ if (key === 'Unidentified') {
return null;
}
- // won't be accurate for azerty
- if (keycode >= 0x30 && keycode <= 0x39) {
- return keycode; // digit
- }
- if (keycode >= 0x41 && keycode <= 0x5a) {
- // remap to lowercase unless shift is down
- return shiftPressed ? keycode : keycode + 32; // A-Z
- }
- if (keycode >= 0x60 && keycode <= 0x69) {
- return KeyTable.XK_KP_0 + (keycode - 0x60); // numpad 0-9
- }
-
- switch(keycode) {
- case 0x20: return KeyTable.XK_space;
- case 0x6a: return KeyTable.XK_KP_Multiply;
- case 0x6b: return KeyTable.XK_KP_Add;
- case 0x6c: return KeyTable.XK_KP_Separator;
- case 0x6d: return KeyTable.XK_KP_Subtract;
- case 0x6e: return KeyTable.XK_KP_Decimal;
- case 0x6f: return KeyTable.XK_KP_Divide;
- case 0xbb: return KeyTable.XK_plus;
- case 0xbc: return KeyTable.XK_comma;
- case 0xbd: return KeyTable.XK_minus;
- case 0xbe: return KeyTable.XK_period;
- }
-
- return nonCharacterKey({keyCode: keycode});
-}
-
-// if the key is a known non-character key (any key which doesn't generate character data)
-// return its keysym value. Otherwise return null
-export function nonCharacterKey(evt) {
- // evt.key not implemented yet
- if (!evt.keyCode) { return null; }
- var keycode = evt.keyCode;
-
- if (keycode >= 0x70 && keycode <= 0x87) {
- return KeyTable.XK_F1 + keycode - 0x70; // F1-F24
- }
- switch (keycode) {
-
- case 8 : return KeyTable.XK_BackSpace;
- case 13 : return KeyTable.XK_Return;
-
- case 9 : return KeyTable.XK_Tab;
-
- case 27 : return KeyTable.XK_Escape;
- case 46 : return KeyTable.XK_Delete;
-
- case 36 : return KeyTable.XK_Home;
- case 35 : return KeyTable.XK_End;
- case 33 : return KeyTable.XK_Page_Up;
- case 34 : return KeyTable.XK_Page_Down;
- case 45 : return KeyTable.XK_Insert;
-
- case 37 : return KeyTable.XK_Left;
- case 38 : return KeyTable.XK_Up;
- case 39 : return KeyTable.XK_Right;
- case 40 : return KeyTable.XK_Down;
-
- case 16 : return KeyTable.XK_Shift_L;
- case 17 : return KeyTable.XK_Control_L;
- case 18 : return KeyTable.XK_Alt_L; // also: Option-key on Mac
-
- case 224 : return KeyTable.XK_Meta_L;
- case 225 : return KeyTable.XK_ISO_Level3_Shift; // AltGr
- case 91 : return KeyTable.XK_Super_L; // also: Windows-key
- case 92 : return KeyTable.XK_Super_R; // also: Windows-key
- case 93 : return KeyTable.XK_Menu; // also: Windows-Menu, Command on Mac
- default: return null;
- }
-}
-
-export function QEMUKeyEventDecoder (modifierState, next) {
- "use strict";
-
- function sendAll(evts) {
- for (var i = 0; i < evts.length; ++i) {
- next(evts[i]);
- }
- }
-
- var numPadCodes = ["Numpad0", "Numpad1", "Numpad2",
- "Numpad3", "Numpad4", "Numpad5", "Numpad6",
- "Numpad7", "Numpad8", "Numpad9", "NumpadDecimal"];
-
- 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];
-
- function isNumPadMultiKey(evt) {
- return (numPadCodes.indexOf(evt.code) !== -1);
- }
- function getNumPadKeySym(evt) {
- if (numLockOnKeyCodes.indexOf(evt.keyCode) !== -1) {
- return numLockOnKeySyms[evt.code];
- }
- return 0;
- }
+ // First look up special keys
+ if (key in DOMKeyTable) {
+ var location = evt.location;
- function process(evt, type) {
- var result = {type: type};
- result.code = evt.code;
- result.keysym = 0;
-
- if (isNumPadMultiKey(evt)) {
- result.keysym = getNumPadKeySym(evt);
+ // Safari screws up location for the right cmd key
+ if ((key === 'Meta') && (location === 0)) {
+ location = 2;
}
- var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier();
- var isShift = evt.keyCode === 0x10 || evt.key === 'Shift';
-
- var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!nonCharacterKey(evt));
-
- next(result);
- return suppress;
- }
- return {
- keydown: function(evt) {
- sendAll(modifierState.keydown(evt));
- return process(evt, 'keydown');
- },
- keypress: function(evt) {
- return true;
- },
- keyup: function(evt) {
- sendAll(modifierState.keyup(evt));
- return process(evt, 'keyup');
- },
- syncModifiers: function(evt) {
- sendAll(modifierState.syncAny(evt));
- },
- releaseAll: function() { next({type: 'releaseall'}); }
- };
-};
-
-export function TrackQEMUKeyState (next) {
- "use strict";
- var state = [];
-
- return function (evt) {
- var last = state.length !== 0 ? state[state.length-1] : null;
-
- switch (evt.type) {
- case 'keydown':
-
- if (!last || last.code !== evt.code) {
- last = {code: evt.code};
-
- if (state.length > 0 && state[state.length-1].code == 'ControlLeft') {
- if (evt.code !== 'AltRight') {
- next({code: 'ControlLeft', type: 'keydown', keysym: 0});
- } else {
- state.pop();
- }
- }
- state.push(last);
- }
- if (evt.code !== 'ControlLeft') {
- next(evt);
- }
- break;
-
- case 'keyup':
- if (state.length === 0) {
- return;
- }
- var idx = null;
- // do we have a matching key tracked as being down?
- for (var i = 0; i !== state.length; ++i) {
- if (state[i].code === evt.code) {
- idx = i;
- break;
- }
- }
- // if we couldn't find a match (it happens), assume it was the last key pressed
- if (idx === null) {
- if (evt.code === 'ControlLeft') {
- return;
- }
- idx = state.length - 1;
- }
-
- state.splice(idx, 1);
- next(evt);
- break;
- case 'releaseall':
- /* jshint shadow: true */
- for (var i = 0; i < state.length; ++i) {
- next({code: state[i].code, keysym: 0, type: 'keyup'});
- }
- /* jshint shadow: false */
- state = [];
+ if ((location === undefined) || (location > 3)) {
+ location = 0;
}
- };
-};
-// Takes a DOM keyboard event and:
-// - determines which keysym it represents
-// - determines a keyId identifying the key that was pressed (corresponding to the key/keyCode properties on the DOM event)
-// - synthesizes events to synchronize modifier key state between which modifiers are actually down, and which we thought were down
-// - marks each event with an 'escape' property if a modifier was down which should be "escaped"
-// - generates a "stall" event in cases where it might be necessary to wait and see if a keypress event follows a keydown
-// This information is collected into an object which is passed to the next() function. (one call per event)
-export function KeyEventDecoder (modifierState, next) {
- "use strict";
- function sendAll(evts) {
- for (var i = 0; i < evts.length; ++i) {
- next(evts[i]);
- }
+ return DOMKeyTable[key][location];
}
- function process(evt, type) {
- var result = {type: type};
- var keyId = getKey(evt);
- if (keyId) {
- result.keyId = keyId;
- }
-
- var keysym = getKeysym(evt);
-
- var hasModifier = modifierState.hasShortcutModifier() || !!modifierState.activeCharModifier();
- // Is this a case where we have to decide on the keysym right away, rather than waiting for the keypress?
- // "special" keys like enter, tab or backspace don't send keypress events,
- // and some browsers don't send keypresses at all if a modifier is down
- if (keysym && (type !== 'keydown' || nonCharacterKey(evt) || hasModifier)) {
- result.keysym = keysym;
- }
- var isShift = evt.keyCode === 0x10 || evt.key === 'Shift';
+ // Now we need to look at the Unicode symbol instead
- // Should we prevent the browser from handling the event?
- // Doing so on a keydown (in most browsers) prevents keypress from being generated
- // so only do that if we have to.
- var suppress = !isShift && (type !== 'keydown' || modifierState.hasShortcutModifier() || !!nonCharacterKey(evt));
-
- // If a char modifier is down on a keydown, we need to insert a stall,
- // so VerifyCharModifier knows to wait and see if a keypress is comnig
- var stall = type === 'keydown' && modifierState.activeCharModifier() && !nonCharacterKey(evt);
-
- // if a char modifier is pressed, get the keys it consists of (on Windows, AltGr is equivalent to Ctrl+Alt)
- var active = modifierState.activeCharModifier();
-
- // If we have a char modifier down, and we're able to determine a keysym reliably
- // then (a) we know to treat the modifier as a char modifier,
- // and (b) we'll have to "escape" the modifier to undo the modifier when sending the char.
- if (active && keysym) {
- var isCharModifier = false;
- for (var i = 0; i < active.length; ++i) {
- if (active[i] === keysym.keysym) {
- isCharModifier = true;
- }
- }
- if (type === 'keypress' && !isCharModifier) {
- result.escape = modifierState.activeCharModifier();
- }
- }
-
- if (stall) {
- // insert a fake "stall" event
- next({type: 'stall'});
- }
- next(result);
+ var codepoint;
- return suppress;
+ // Special key? (FIXME: Should have been caught earlier)
+ if (key.length !== 1) {
+ return null;
}
- return {
- keydown: function(evt) {
- sendAll(modifierState.keydown(evt));
- return process(evt, 'keydown');
- },
- keypress: function(evt) {
- return process(evt, 'keypress');
- },
- keyup: function(evt) {
- sendAll(modifierState.keyup(evt));
- return process(evt, 'keyup');
- },
- syncModifiers: function(evt) {
- sendAll(modifierState.syncAny(evt));
- },
- releaseAll: function() { next({type: 'releaseall'}); }
- };
-};
-
-// Combines keydown and keypress events where necessary to handle char modifiers.
-// On some OS'es, a char modifier is sometimes used as a shortcut modifier.
-// For example, on Windows, AltGr is synonymous with Ctrl-Alt. On a Danish keyboard layout, AltGr-2 yields a @, but Ctrl-Alt-D does nothing
-// so when used with the '2' key, Ctrl-Alt counts as a char modifier (and should be escaped), but when used with 'D', it does not.
-// The only way we can distinguish these cases is to wait and see if a keypress event arrives
-// When we receive a "stall" event, wait a few ms before processing the next keydown. If a keypress has also arrived, merge the two
-export function VerifyCharModifier (next) {
- "use strict";
- var queue = [];
- var timer = null;
- function process() {
- if (timer) {
- return;
- }
-
- var delayProcess = function () {
- clearTimeout(timer);
- timer = null;
- process();
- };
-
- while (queue.length !== 0) {
- var cur = queue[0];
- queue = queue.splice(1);
- switch (cur.type) {
- case 'stall':
- // insert a delay before processing available events.
- /* jshint loopfunc: true */
- timer = setTimeout(delayProcess, 5);
- /* jshint loopfunc: false */
- return;
- case 'keydown':
- // is the next element a keypress? Then we should merge the two
- if (queue.length !== 0 && queue[0].type === 'keypress') {
- // Firefox sends keypress even when no char is generated.
- // so, if keypress keysym is the same as we'd have guessed from keydown,
- // the modifier didn't have any effect, and should not be escaped
- if (queue[0].escape && (!cur.keysym || cur.keysym.keysym !== queue[0].keysym.keysym)) {
- cur.escape = queue[0].escape;
- }
- cur.keysym = queue[0].keysym;
- queue = queue.splice(1);
- }
- break;
- }
-
- // swallow stall events, and pass all others to the next stage
- if (cur.type !== 'stall') {
- next(cur);
- }
- }
+ codepoint = key.charCodeAt();
+ if (codepoint) {
+ return keysyms.lookup(codepoint);
}
- return function(evt) {
- queue.push(evt);
- process();
- };
-};
-
-// Keeps track of which keys we (and the server) believe are down
-// When a keyup is received, match it against this list, to determine the corresponding keysym(s)
-// in some cases, a single key may produce multiple keysyms, so the corresponding keyup event must release all of these chars
-// key repeat events should be merged into a single entry.
-// Because we can't always identify which entry a keydown or keyup event corresponds to, we sometimes have to guess
-export function TrackKeyState (next) {
- "use strict";
- var state = [];
-
- return function (evt) {
- var last = state.length !== 0 ? state[state.length-1] : null;
-
- switch (evt.type) {
- case 'keydown':
- // insert a new entry if last seen key was different.
- if (!last || !evt.keyId || last.keyId !== evt.keyId) {
- last = {keyId: evt.keyId, keysyms: {}};
- state.push(last);
- }
- if (evt.keysym) {
- // make sure last event contains this keysym (a single "logical" keyevent
- // can cause multiple key events to be sent to the VNC server)
- last.keysyms[evt.keysym.keysym] = evt.keysym;
- last.ignoreKeyPress = true;
- next(evt);
- }
- break;
- case 'keypress':
- if (!last) {
- last = {keyId: evt.keyId, keysyms: {}};
- state.push(last);
- }
- if (!evt.keysym) {
- console.log('keypress with no keysym:', evt);
- }
-
- // If we didn't expect a keypress, and already sent a keydown to the VNC server
- // based on the keydown, make sure to skip this event.
- if (evt.keysym && !last.ignoreKeyPress) {
- last.keysyms[evt.keysym.keysym] = evt.keysym;
- evt.type = 'keydown';
- next(evt);
- }
- break;
- case 'keyup':
- if (state.length === 0) {
- return;
- }
- var idx = null;
- // do we have a matching key tracked as being down?
- for (var i = 0; i !== state.length; ++i) {
- if (state[i].keyId === evt.keyId) {
- idx = i;
- break;
- }
- }
- // if we couldn't find a match (it happens), assume it was the last key pressed
- if (idx === null) {
- idx = state.length - 1;
- }
-
- var item = state.splice(idx, 1)[0];
- // for each keysym tracked by this key entry, clone the current event and override the keysym
- var clone = (function(){
- function Clone(){}
- return function (obj) { Clone.prototype=obj; return new Clone(); };
- }());
- for (var key in item.keysyms) {
- var out = clone(evt);
- out.keysym = item.keysyms[key];
- next(out);
- }
- break;
- case 'releaseall':
- /* jshint shadow: true */
- for (var i = 0; i < state.length; ++i) {
- for (var key in state[i].keysyms) {
- var keysym = state[i].keysyms[key];
- next({keyId: 0, keysym: keysym, type: 'keyup'});
- }
- }
- /* jshint shadow: false */
- state = [];
- }
- };
-};
-// Handles "escaping" of modifiers: if a char modifier is used to produce a keysym (such as AltGr-2 to generate an @),
-// then the modifier must be "undone" before sending the @, and "redone" afterwards.
-export function EscapeModifiers (next) {
- "use strict";
- return function(evt) {
- if (evt.type !== 'keydown' || evt.escape === undefined) {
- next(evt);
- return;
- }
- // undo modifiers
- for (var i = 0; i < evt.escape.length; ++i) {
- next({type: 'keyup', keyId: 0, keysym: keysyms.lookup(evt.escape[i])});
- }
- // send the character event
- next(evt);
- // redo modifiers
- /* jshint shadow: true */
- for (var i = 0; i < evt.escape.length; ++i) {
- next({type: 'keydown', keyId: 0, keysym: keysyms.lookup(evt.escape[i])});
- }
- /* jshint shadow: false */
- };
-};
+ return null;
+}
diff --git a/core/input/vkeys.js b/core/input/vkeys.js
new file mode 100644
index 0000000..dc784ff
--- /dev/null
+++ b/core/input/vkeys.js
@@ -0,0 +1,116 @@
+/*
+ * noVNC: HTML5 VNC client
+ * Copyright (C) 2017 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
+ */
+
+/*
+ * Mapping between Microsoft® Windows® Virtual-Key codes and
+ * HTML key codes.
+ */
+
+export default {
+ 0x08: 'Backspace',
+ 0x09: 'Tab',
+ 0x0a: 'NumpadClear',
+ 0x0d: 'Enter',
+ 0x10: 'ShiftLeft',
+ 0x11: 'ControlLeft',
+ 0x12: 'AltLeft',
+ 0x13: 'Pause',
+ 0x14: 'CapsLock',
+ 0x15: 'Lang1',
+ 0x19: 'Lang2',
+ 0x1b: 'Escape',
+ 0x1c: 'Convert',
+ 0x1d: 'NonConvert',
+ 0x20: 'Space',
+ 0x21: 'PageUp',
+ 0x22: 'PageDown',
+ 0x23: 'End',
+ 0x24: 'Home',
+ 0x25: 'ArrowLeft',
+ 0x26: 'ArrowUp',
+ 0x27: 'ArrowRight',
+ 0x28: 'ArrowDown',
+ 0x29: 'Select',
+ 0x2c: 'PrintScreen',
+ 0x2d: 'Insert',
+ 0x2e: 'Delete',
+ 0x2f: 'Help',
+ 0x30: 'Digit0',
+ 0x31: 'Digit1',
+ 0x32: 'Digit2',
+ 0x33: 'Digit3',
+ 0x34: 'Digit4',
+ 0x35: 'Digit5',
+ 0x36: 'Digit6',
+ 0x37: 'Digit7',
+ 0x38: 'Digit8',
+ 0x39: 'Digit9',
+ 0x5b: 'MetaLeft',
+ 0x5c: 'MetaRight',
+ 0x5d: 'ContextMenu',
+ 0x5f: 'Sleep',
+ 0x60: 'Numpad0',
+ 0x61: 'Numpad1',
+ 0x62: 'Numpad2',
+ 0x63: 'Numpad3',
+ 0x64: 'Numpad4',
+ 0x65: 'Numpad5',
+ 0x66: 'Numpad6',
+ 0x67: 'Numpad7',
+ 0x68: 'Numpad8',
+ 0x69: 'Numpad9',
+ 0x6a: 'NumpadMultiply',
+ 0x6b: 'NumpadAdd',
+ 0x6c: 'NumpadDecimal',
+ 0x6d: 'NumpadSubtract',
+ 0x6e: 'NumpadDecimal', // Duplicate, because buggy on Windows
+ 0x6f: 'NumpadDivide',
+ 0x70: 'F1',
+ 0x71: 'F2',
+ 0x72: 'F3',
+ 0x73: 'F4',
+ 0x74: 'F5',
+ 0x75: 'F6',
+ 0x76: 'F7',
+ 0x77: 'F8',
+ 0x78: 'F9',
+ 0x79: 'F10',
+ 0x7a: 'F11',
+ 0x7b: 'F12',
+ 0x7c: 'F13',
+ 0x7d: 'F14',
+ 0x7e: 'F15',
+ 0x7f: 'F16',
+ 0x80: 'F17',
+ 0x81: 'F18',
+ 0x82: 'F19',
+ 0x83: 'F20',
+ 0x84: 'F21',
+ 0x85: 'F22',
+ 0x86: 'F23',
+ 0x87: 'F24',
+ 0x90: 'NumLock',
+ 0x91: 'ScrollLock',
+ 0xa6: 'BrowserBack',
+ 0xa7: 'BrowserForward',
+ 0xa8: 'BrowserRefresh',
+ 0xa9: 'BrowserStop',
+ 0xaa: 'BrowserSearch',
+ 0xab: 'BrowserFavorites',
+ 0xac: 'BrowserHome',
+ 0xad: 'AudioVolumeMute',
+ 0xae: 'AudioVolumeDown',
+ 0xaf: 'AudioVolumeUp',
+ 0xb0: 'MediaTrackNext',
+ 0xb1: 'MediaTrackPrevious',
+ 0xb2: 'MediaStop',
+ 0xb3: 'MediaPlayPause',
+ 0xb4: 'LaunchMail',
+ 0xb5: 'MediaSelect',
+ 0xb6: 'LaunchApp1',
+ 0xb7: 'LaunchApp2',
+ 0xe1: 'AltRight', // Only when it is AltGraph
+};
diff --git a/core/input/xtscancodes.js b/core/input/xtscancodes.js
index 43ea51b..e61b425 100644
--- a/core/input/xtscancodes.js
+++ b/core/input/xtscancodes.js
@@ -112,8 +112,6 @@ export default {
"Delete": 0xE053,
"MetaLeft": 0xE05B,
"MetaRight": 0xE05C,
- "OSLeft": 0xE05B, // OSLeft and OSRight are kept for compatability since
- "OSRight": 0xE05C, // Firefox haven't updated to MetaLeft and MetaRight yet
"ContextMenu": 0xE05D,
"BrowserSearch": 0xE065,
"BrowserFavorites": 0xE066,
diff --git a/core/rfb.js b/core/rfb.js
index b60a119..24fa84e 100644
--- a/core/rfb.js
+++ b/core/rfb.js
@@ -201,12 +201,11 @@ export default function RFB(defaults) {
}
this._keyboard = new Keyboard({target: this._focusContainer,
- onKeyPress: this._handleKeyPress.bind(this)});
+ onKeyEvent: this._handleKeyEvent.bind(this)});
this._mouse = new Mouse({target: this._target,
onMouseButton: this._handleMouseButton.bind(this),
- onMouseMove: this._handleMouseMove.bind(this),
- notify: this._keyboard.sync.bind(this._keyboard)});
+ onMouseMove: this._handleMouseMove.bind(this)});
this._sock = new Websock();
this._sock.on('message', this._handle_message.bind(this));
@@ -299,12 +298,13 @@ RFB.prototype = {
if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; }
Log.Info("Sending Ctrl-Alt-Del");
- RFB.messages.keyEvent(this._sock, KeyTable.XK_Control_L, 1);
- RFB.messages.keyEvent(this._sock, KeyTable.XK_Alt_L, 1);
- RFB.messages.keyEvent(this._sock, KeyTable.XK_Delete, 1);
- RFB.messages.keyEvent(this._sock, KeyTable.XK_Delete, 0);
- RFB.messages.keyEvent(this._sock, KeyTable.XK_Alt_L, 0);
- RFB.messages.keyEvent(this._sock, KeyTable.XK_Control_L, 0);
+ this.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
+ this.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
+ this.sendKey(KeyTable.XK_Delete, "Delete", true);
+ this.sendKey(KeyTable.XK_Delete, "Delete", false);
+ this.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
+ this.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
+
return true;
},
@@ -329,16 +329,33 @@ RFB.prototype = {
// Send a key press. If 'down' is not specified then send a down key
// followed by an up key.
- sendKey: function (keysym, down) {
+ sendKey: function (keysym, code, down) {
if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; }
- if (typeof down !== 'undefined') {
+
+ if (down === undefined) {
+ this.sendKey(keysym, code, true);
+ this.sendKey(keysym, code, false);
+ return true;
+ }
+
+ if (this._qemuExtKeyEventSupported) {
+ var scancode = XtScancode[code];
+
+ if (scancode === undefined) {
+ Log.Error('Unable to find a xt scancode for code: ' + code);
+ // FIXME: not in the spec, but this is what
+ // gtk-vnc does
+ scancode = 0;
+ }
+
+ Log.Info("Sending key (" + (down ? "down" : "up") + "): keysym " + keysym + ", scancode " + scancode);
+
+ RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode);
+ } else {
Log.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym);
RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
- } else {
- Log.Info("Sending keysym (down + up): " + keysym);
- RFB.messages.keyEvent(this._sock, keysym, 1);
- RFB.messages.keyEvent(this._sock, keysym, 0);
}
+
return true;
},
@@ -647,22 +664,8 @@ RFB.prototype = {
}
},
- _handleKeyPress: function (keyevent) {
- if (this._view_only) { return; } // View only, skip keyboard, events
-
- var down = (keyevent.type == 'keydown');
- if (this._qemuExtKeyEventSupported) {
- var scancode = XtScancode[keyevent.code];
- if (scancode) {
- var keysym = keyevent.keysym;
- RFB.messages.QEMUExtendedKeyEvent(this._sock, keysym, down, scancode);
- } else {
- Log.Error('Unable to find a xt scancode for code = ' + keyevent.code);
- }
- } else {
- keysym = keyevent.keysym.keysym;
- RFB.messages.keyEvent(this._sock, keysym, down);
- }
+ _handleKeyEvent: function (keysym, code, down) {
+ this.sendKey(keysym, code, down);
},
_handleMouseButton: function (x, y, down, bmask) {
@@ -2461,7 +2464,6 @@ RFB.encodingHandlers = {
var keyboardEvent = document.createEvent("keyboardEvent");
if (keyboardEvent.code !== undefined) {
this._qemuExtKeyEventSupported = true;
- this._keyboard.setQEMUVNCKeyboardHandler();
}
},
diff --git a/tests/input.html b/tests/input.html
index 0938a4a..aee9e76 100644
--- a/tests/input.html
+++ b/tests/input.html
@@ -29,6 +29,7 @@
<script src="../core/input/keysym.js"></script>
<script src="../core/input/keysymdef.js"></script>
<script src="../core/input/xtscancodes.js"></script>
+ <script src="../core/input/vkeys.js"></script>
<script src="../core/input/util.js"></script>
<script src="../core/input/devices.js"></script>
<script src="../core/display.js"></script>
@@ -61,13 +62,9 @@
//console.log(msg);
}
- function rfbKeyPress(keysym, down) {
+ function rfbKeyEvent(keysym, code, down) {
var d = down ? "down" : " up ";
- var key = keysyms.lookup(keysym);
- var msg = "RFB keypress " + d + " keysym: " + keysym;
- if (key && key.keyname) {
- msg += " key name: " + key.keyname;
- }
+ var msg = "RFB key event " + d + " keysym: " + keysym + " code: " + code;
message(msg);
}
function rawKey(e) {
@@ -106,7 +103,7 @@
window.onload = function() {
canvas = new Display({'target' : document.getElementById('canvas')});
keyboard = new Keyboard({'target': document,
- 'onKeyPress': rfbKeyPress});
+ 'onKeyEvent': rfbKeyEvent});
document.addEventListener('keypress', rawKey);
document.addEventListener('keydown', rawKey);
document.addEventListener('keyup', rawKey);
diff --git a/tests/test.helper.js b/tests/test.helper.js
index 12232d8..e0ae80a 100644
--- a/tests/test.helper.js
+++ b/tests/test.helper.js
@@ -6,261 +6,213 @@ import * as KeyboardUtil from "../core/input/util.js";
describe('Helpers', function() {
"use strict";
- describe('keysymFromKeyCode', function() {
- it('should map known keycodes to keysyms', function() {
- expect(KeyboardUtil.keysymFromKeyCode(0x41, false), 'a').to.be.equal(0x61);
- expect(KeyboardUtil.keysymFromKeyCode(0x41, true), 'A').to.be.equal(0x41);
- expect(KeyboardUtil.keysymFromKeyCode(0xd, false), 'enter').to.be.equal(0xFF0D);
- expect(KeyboardUtil.keysymFromKeyCode(0x11, false), 'ctrl').to.be.equal(0xFFE3);
- expect(KeyboardUtil.keysymFromKeyCode(0x12, false), 'alt').to.be.equal(0xFFE9);
- expect(KeyboardUtil.keysymFromKeyCode(0xe1, false), 'altgr').to.be.equal(0xFE03);
- expect(KeyboardUtil.keysymFromKeyCode(0x1b, false), 'esc').to.be.equal(0xFF1B);
- expect(KeyboardUtil.keysymFromKeyCode(0x26, false), 'up').to.be.equal(0xFF52);
- });
- it('should return null for unknown keycodes', function() {
- expect(KeyboardUtil.keysymFromKeyCode(0xc0, false), 'DK æ').to.be.null;
- expect(KeyboardUtil.keysymFromKeyCode(0xde, false), 'DK ø').to.be.null;
- });
- });
- describe('keysyms.fromUnicode', function() {
+ describe('keysyms.lookup', function() {
it('should map ASCII characters to keysyms', function() {
- expect(keysyms.fromUnicode('a'.charCodeAt())).to.have.property('keysym', 0x61);
- expect(keysyms.fromUnicode('A'.charCodeAt())).to.have.property('keysym', 0x41);
+ expect(keysyms.lookup('a'.charCodeAt())).to.be.equal(0x61);
+ expect(keysyms.lookup('A'.charCodeAt())).to.be.equal(0x41);
});
it('should map Latin-1 characters to keysyms', function() {
- expect(keysyms.fromUnicode('ø'.charCodeAt())).to.have.property('keysym', 0xf8);
+ expect(keysyms.lookup('ø'.charCodeAt())).to.be.equal(0xf8);
- expect(keysyms.fromUnicode('é'.charCodeAt())).to.have.property('keysym', 0xe9);
+ expect(keysyms.lookup('é'.charCodeAt())).to.be.equal(0xe9);
});
it('should map characters that are in Windows-1252 but not in Latin-1 to keysyms', function() {
- expect(keysyms.fromUnicode('Å '.charCodeAt())).to.have.property('keysym', 0x01a9);
+ expect(keysyms.lookup('Å '.charCodeAt())).to.be.equal(0x01a9);
});
it('should map characters which aren\'t in Latin1 *or* Windows-1252 to keysyms', function() {
- expect(keysyms.fromUnicode('ŵ'.charCodeAt())).to.have.property('keysym', 0x1000175);
+ expect(keysyms.lookup('Å©'.charCodeAt())).to.be.equal(0x03fd);
});
it('should map unknown codepoints to the Unicode range', function() {
- expect(keysyms.fromUnicode('\n'.charCodeAt())).to.have.property('keysym', 0x100000a);
- expect(keysyms.fromUnicode('\u262D'.charCodeAt())).to.have.property('keysym', 0x100262d);
+ expect(keysyms.lookup('\n'.charCodeAt())).to.be.equal(0x100000a);
+ expect(keysyms.lookup('\u262D'.charCodeAt())).to.be.equal(0x100262d);
});
// This requires very recent versions of most browsers... skipping for now
it.skip('should map UCS-4 codepoints to the Unicode range', function() {
- //expect(keysyms.fromUnicode('\u{1F686}'.codePointAt())).to.have.property('keysym', 0x101f686);
+ //expect(keysyms.lookup('\u{1F686}'.codePointAt())).to.be.equal(0x101f686);
});
});
- describe('substituteCodepoint', function() {
- it('should replace characters which don\'t have a keysym', function() {
- expect(KeyboardUtil.substituteCodepoint('Ș'.charCodeAt())).to.equal('Ş'.charCodeAt());
- expect(KeyboardUtil.substituteCodepoint('È™'.charCodeAt())).to.equal('ÅŸ'.charCodeAt());
- expect(KeyboardUtil.substituteCodepoint('Èš'.charCodeAt())).to.equal('Å¢'.charCodeAt());
- expect(KeyboardUtil.substituteCodepoint('È›'.charCodeAt())).to.equal('Å£'.charCodeAt());
+ describe('getKeycode', function() {
+ it('should pass through proper code', function() {
+ expect(KeyboardUtil.getKeycode({code: 'Semicolon'})).to.be.equal('Semicolon');
+ });
+ it('should map legacy values', function() {
+ expect(KeyboardUtil.getKeycode({code: ''})).to.be.equal('Unidentified');
+ expect(KeyboardUtil.getKeycode({code: 'OSLeft'})).to.be.equal('MetaLeft');
+ });
+ it('should map keyCode to code when possible', function() {
+ expect(KeyboardUtil.getKeycode({keyCode: 0x14})).to.be.equal('CapsLock');
+ expect(KeyboardUtil.getKeycode({keyCode: 0x5b})).to.be.equal('MetaLeft');
+ expect(KeyboardUtil.getKeycode({keyCode: 0x35})).to.be.equal('Digit5');
+ expect(KeyboardUtil.getKeycode({keyCode: 0x65})).to.be.equal('Numpad5');
+ });
+ it('should map keyCode left/right side', function() {
+ expect(KeyboardUtil.getKeycode({keyCode: 0x10, location: 1})).to.be.equal('ShiftLeft');
+ expect(KeyboardUtil.getKeycode({keyCode: 0x10, location: 2})).to.be.equal('ShiftRight');
+ expect(KeyboardUtil.getKeycode({keyCode: 0x11, location: 1})).to.be.equal('ControlLeft');
+ expect(KeyboardUtil.getKeycode({keyCode: 0x11, location: 2})).to.be.equal('ControlRight');
+ });
+ it('should map keyCode on numpad', function() {
+ expect(KeyboardUtil.getKeycode({keyCode: 0x0d, location: 0})).to.be.equal('Enter');
+ expect(KeyboardUtil.getKeycode({keyCode: 0x0d, location: 3})).to.be.equal('NumpadEnter');
+ expect(KeyboardUtil.getKeycode({keyCode: 0x23, location: 0})).to.be.equal('End');
+ expect(KeyboardUtil.getKeycode({keyCode: 0x23, location: 3})).to.be.equal('Numpad1');
+ });
+ it('should return Unidentified when it cannot map the keyCode', function() {
+ expect(KeyboardUtil.getKeycode({keycode: 0x42})).to.be.equal('Unidentified');
});
- it('should pass other characters through unchanged', function() {
- expect(KeyboardUtil.substituteCodepoint('T'.charCodeAt())).to.equal('T'.charCodeAt());
- });
- });
- describe('nonCharacterKey', function() {
- it('should recognize the right keys', function() {
- expect(KeyboardUtil.nonCharacterKey({keyCode: 0xd}), 'enter').to.be.defined;
- expect(KeyboardUtil.nonCharacterKey({keyCode: 0x08}), 'backspace').to.be.defined;
- expect(KeyboardUtil.nonCharacterKey({keyCode: 0x09}), 'tab').to.be.defined;
- expect(KeyboardUtil.nonCharacterKey({keyCode: 0x10}), 'shift').to.be.defined;
- expect(KeyboardUtil.nonCharacterKey({keyCode: 0x11}), 'ctrl').to.be.defined;
- expect(KeyboardUtil.nonCharacterKey({keyCode: 0x12}), 'alt').to.be.defined;
- expect(KeyboardUtil.nonCharacterKey({keyCode: 0xe0}), 'meta').to.be.defined;
- });
- it('should not recognize character keys', function() {
- expect(KeyboardUtil.nonCharacterKey({keyCode: 'A'}), 'A').to.be.null;
- expect(KeyboardUtil.nonCharacterKey({keyCode: '1'}), '1').to.be.null;
- expect(KeyboardUtil.nonCharacterKey({keyCode: '.'}), '.').to.be.null;
- expect(KeyboardUtil.nonCharacterKey({keyCode: ' '}), 'space').to.be.null;
+ describe('Fix Meta on macOS', function() {
+ var origNavigator;
+ beforeEach(function () {
+ // window.navigator is a protected read-only property in many
+ // environments, so we need to redefine it whilst running these
+ // tests.
+ origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
+ if (origNavigator === undefined) {
+ // Object.getOwnPropertyDescriptor() doesn't work
+ // properly in any version of IE
+ this.skip();
+ }
+
+ Object.defineProperty(window, "navigator", {value: {}});
+ if (window.navigator.platform !== undefined) {
+ // Object.defineProperty() doesn't work properly in old
+ // versions of Chrome
+ this.skip();
+ }
+
+ window.navigator.platform = "Mac x86_64";
+ });
+ afterEach(function () {
+ Object.defineProperty(window, "navigator", origNavigator);
+ });
+
+ it('should respect ContextMenu on modern browser', function() {
+ expect(KeyboardUtil.getKeycode({code: 'ContextMenu', keyCode: 0x5d})).to.be.equal('ContextMenu');
+ });
+ it('should translate legacy ContextMenu to MetaRight', function() {
+ expect(KeyboardUtil.getKeycode({keyCode: 0x5d})).to.be.equal('MetaRight');
+ });
});
});
- describe('getKeysym', function() {
- it('should prefer char', function() {
- expect(KeyboardUtil.getKeysym({char : 'a', charCode: 'Å '.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x61);
+ describe('getKey', function() {
+ it('should prefer key', function() {
+ expect(KeyboardUtil.getKey({key: 'a', charCode: 'Å '.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('a');
});
- it('should use charCode if no char', function() {
- expect(KeyboardUtil.getKeysym({char : '', charCode: 'Å '.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x01a9);
- expect(KeyboardUtil.getKeysym({charCode: 'Å '.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x01a9);
- expect(KeyboardUtil.getKeysym({char : 'hello', charCode: 'Å '.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x01a9);
+ it('should map legacy values', function() {
+ expect(KeyboardUtil.getKey({key: 'Spacebar'})).to.be.equal(' ');
+ expect(KeyboardUtil.getKey({key: 'Left'})).to.be.equal('ArrowLeft');
+ expect(KeyboardUtil.getKey({key: 'OS'})).to.be.equal('Meta');
+ expect(KeyboardUtil.getKey({key: 'Win'})).to.be.equal('Meta');
});
- it('should use keyCode if no charCode', function() {
- expect(KeyboardUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: false})).to.have.property('keysym', 0x62);
- expect(KeyboardUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: true})).to.have.property('keysym', 0x42);
+ it('should use code if no key', function() {
+ expect(KeyboardUtil.getKey({code: 'NumpadBackspace'})).to.be.equal('Backspace');
});
- it('should use which if no keyCode', function() {
- expect(KeyboardUtil.getKeysym({which: 0x43, shiftKey: false})).to.have.property('keysym', 0x63);
- expect(KeyboardUtil.getKeysym({which: 0x43, shiftKey: true})).to.have.property('keysym', 0x43);
+ it('should not use code fallback for character keys', function() {
+ expect(KeyboardUtil.getKey({code: 'KeyA'})).to.be.equal('Unidentified');
+ expect(KeyboardUtil.getKey({code: 'Digit1'})).to.be.equal('Unidentified');
+ expect(KeyboardUtil.getKey({code: 'Period'})).to.be.equal('Unidentified');
+ expect(KeyboardUtil.getKey({code: 'Numpad1'})).to.be.equal('Unidentified');
});
- it('should substitute where applicable', function() {
- expect(KeyboardUtil.getKeysym({char : 'Ș'})).to.have.property('keysym', 0x1aa);
+ it('should use charCode if no key', function() {
+ expect(KeyboardUtil.getKey({charCode: 'Å '.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('Å ');
+ });
+ it('should return Unidentified when it cannot map the key', function() {
+ expect(KeyboardUtil.getKey({keycode: 0x42})).to.be.equal('Unidentified');
});
- });
- describe('Modifier Sync', function() { // return a list of fake events necessary to fix modifier state
- describe('Toggle all modifiers', function() {
- var sync = KeyboardUtil.ModifierSync();
- it ('should do nothing if all modifiers are up as expected', function() {
- expect(sync.keydown({
- keyCode: 0x41,
- ctrlKey: false,
- altKey: false,
- altGraphKey: false,
- shiftKey: false,
- metaKey: false})
- ).to.have.lengthOf(0);
- });
- it ('should synthesize events if all keys are unexpectedly down', function() {
- var result = sync.keydown({
- keyCode: 0x41,
- ctrlKey: true,
- altKey: true,
- altGraphKey: true,
- shiftKey: true,
- metaKey: true
- });
- expect(result).to.have.lengthOf(5);
- var keysyms = {};
- for (var i = 0; i < result.length; ++i) {
- keysyms[result[i].keysym] = (result[i].type == 'keydown');
+ describe('Broken key AltGraph on IE/Edge', function() {
+ var origNavigator;
+ beforeEach(function () {
+ // window.navigator is a protected read-only property in many
+ // environments, so we need to redefine it whilst running these
+ // tests.
+ origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
+ if (origNavigator === undefined) {
+ // Object.getOwnPropertyDescriptor() doesn't work
+ // properly in any version of IE
+ this.skip();
+ }
+
+ Object.defineProperty(window, "navigator", {value: {}});
+ if (window.navigator.platform !== undefined) {
+ // Object.defineProperty() doesn't work properly in old
+ // versions of Chrome
+ this.skip();
}
- expect(keysyms[0xffe3]);
- expect(keysyms[0xffe9]);
- expect(keysyms[0xfe03]);
- expect(keysyms[0xffe1]);
- expect(keysyms[0xffe7]);
- });
- it ('should do nothing if all modifiers are down as expected', function() {
- expect(sync.keydown({
- keyCode: 0x41,
- ctrlKey: true,
- altKey: true,
- altGraphKey: true,
- shiftKey: true,
- metaKey: true
- })).to.have.lengthOf(0);
- });
- });
- describe('Toggle Ctrl', function() {
- var sync = KeyboardUtil.ModifierSync();
- it('should sync if modifier is suddenly down', function() {
- expect(sync.keydown({
- keyCode: 0x41,
- ctrlKey: true,
- })).to.be.deep.equal([{keysym: keysyms.lookup(0xffe3), type: 'keydown'}]);
- });
- it('should sync if modifier is suddenly up', function() {
- expect(sync.keydown({
- keyCode: 0x41,
- ctrlKey: false
- })).to.be.deep.equal([{keysym: keysyms.lookup(0xffe3), type: 'keyup'}]);
- });
- });
- describe('Toggle Alt', function() {
- var sync = KeyboardUtil.ModifierSync();
- it('should sync if modifier is suddenly down', function() {
- expect(sync.keydown({
- keyCode: 0x41,
- altKey: true,
- })).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keydown'}]);
- });
- it('should sync if modifier is suddenly up', function() {
- expect(sync.keydown({
- keyCode: 0x41,
- altKey: false
- })).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keyup'}]);
- });
- });
- describe('Toggle AltGr', function() {
- var sync = KeyboardUtil.ModifierSync();
- it('should sync if modifier is suddenly down', function() {
- expect(sync.keydown({
- keyCode: 0x41,
- altGraphKey: true,
- })).to.be.deep.equal([{keysym: keysyms.lookup(0xfe03), type: 'keydown'}]);
- });
- it('should sync if modifier is suddenly up', function() {
- expect(sync.keydown({
- keyCode: 0x41,
- altGraphKey: false
- })).to.be.deep.equal([{keysym: keysyms.lookup(0xfe03), type: 'keyup'}]);
});
- });
- describe('Toggle Shift', function() {
- var sync = KeyboardUtil.ModifierSync();
- it('should sync if modifier is suddenly down', function() {
- expect(sync.keydown({
- keyCode: 0x41,
- shiftKey: true,
- })).to.be.deep.equal([{keysym: keysyms.lookup(0xffe1), type: 'keydown'}]);
+ afterEach(function () {
+ Object.defineProperty(window, "navigator", origNavigator);
});
- it('should sync if modifier is suddenly up', function() {
- expect(sync.keydown({
- keyCode: 0x41,
- shiftKey: false
- })).to.be.deep.equal([{keysym: keysyms.lookup(0xffe1), type: 'keyup'}]);
+
+ it('should ignore printable character key on IE', function() {
+ window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko";
+ expect(KeyboardUtil.getKey({key: 'a'})).to.be.equal('Unidentified');
});
- });
- describe('Toggle Meta', function() {
- var sync = KeyboardUtil.ModifierSync();
- it('should sync if modifier is suddenly down', function() {
- expect(sync.keydown({
- keyCode: 0x41,
- metaKey: true,
- })).to.be.deep.equal([{keysym: keysyms.lookup(0xffe7), type: 'keydown'}]);
+ it('should ignore printable character key on Edge', function() {
+ window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393";
+ expect(KeyboardUtil.getKey({key: 'a'})).to.be.equal('Unidentified');
});
- it('should sync if modifier is suddenly up', function() {
- expect(sync.keydown({
- keyCode: 0x41,
- metaKey: false
- })).to.be.deep.equal([{keysym: keysyms.lookup(0xffe7), type: 'keyup'}]);
+ it('should allow non-printable character key on IE', function() {
+ window.navigator.userAgent = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko";
+ expect(KeyboardUtil.getKey({key: 'Shift'})).to.be.equal('Shift');
});
- });
- describe('Modifier keyevents', function() {
- it('should not sync a modifier on its own events', function() {
- expect(KeyboardUtil.ModifierSync().keydown({
- keyCode: 0x11,
- ctrlKey: false
- })).to.be.deep.equal([]);
- expect(KeyboardUtil.ModifierSync().keydown({
- keyCode: 0x11,
- ctrlKey: true
- }), 'B').to.be.deep.equal([]);
- })
- it('should update state on modifier keyevents', function() {
- var sync = KeyboardUtil.ModifierSync();
- sync.keydown({
- keyCode: 0x11,
- });
- expect(sync.keydown({
- keyCode: 0x41,
- ctrlKey: true,
- })).to.be.deep.equal([]);
+ it('should allow non-printable character key on Edge', function() {
+ window.navigator.userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393";
+ expect(KeyboardUtil.getKey({key: 'Shift'})).to.be.equal('Shift');
});
- it('should sync other modifiers on ctrl events', function() {
- expect(KeyboardUtil.ModifierSync().keydown({
- keyCode: 0x11,
- altKey: true
- })).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keydown'}]);
- })
});
- describe('sync modifiers on non-key events', function() {
- it('should generate sync events when receiving non-keyboard events', function() {
- expect(KeyboardUtil.ModifierSync().syncAny({
- altKey: true
- })).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keydown'}]);
+ });
+
+ describe('getKeysym', function() {
+ describe('Non-character keys', function() {
+ it('should recognize the right keys', function() {
+ expect(KeyboardUtil.getKeysym({key: 'Enter'})).to.be.equal(0xFF0D);
+ expect(KeyboardUtil.getKeysym({key: 'Backspace'})).to.be.equal(0xFF08);
+ expect(KeyboardUtil.getKeysym({key: 'Tab'})).to.be.equal(0xFF09);
+ expect(KeyboardUtil.getKeysym({key: 'Shift'})).to.be.equal(0xFFE1);
+ expect(KeyboardUtil.getKeysym({key: 'Control'})).to.be.equal(0xFFE3);
+ expect(KeyboardUtil.getKeysym({key: 'Alt'})).to.be.equal(0xFFE9);
+ expect(KeyboardUtil.getKeysym({key: 'Meta'})).to.be.equal(0xFFEB);
+ expect(KeyboardUtil.getKeysym({key: 'Escape'})).to.be.equal(0xFF1B);
+ expect(KeyboardUtil.getKeysym({key: 'ArrowUp'})).to.be.equal(0xFF52);
+ });
+ it('should map left/right side', function() {
+ expect(KeyboardUtil.getKeysym({key: 'Shift', location: 1})).to.be.equal(0xFFE1);
+ expect(KeyboardUtil.getKeysym({key: 'Shift', location: 2})).to.be.equal(0xFFE2);
+ expect(KeyboardUtil.getKeysym({key: 'Control', location: 1})).to.be.equal(0xFFE3);
+ expect(KeyboardUtil.getKeysym({key: 'Control', location: 2})).to.be.equal(0xFFE4);
+ });
+ it('should handle AltGraph', function() {
+ expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'Alt', location: 2})).to.be.equal(0xFFEA);
+ expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'AltGraph', location: 2})).to.be.equal(0xFE03);
+ });
+ it('should return null for unknown keys', function() {
+ expect(KeyboardUtil.getKeysym({key: 'Semicolon'})).to.be.null;
+ expect(KeyboardUtil.getKeysym({key: 'BracketRight'})).to.be.null;
+ });
+ it('should handle remappings', function() {
+ expect(KeyboardUtil.getKeysym({code: 'ControlLeft', key: 'Tab'})).to.be.equal(0xFF09);
});
});
- describe('do not treat shift as a modifier key', function() {
- it('should not treat shift as a shortcut modifier', function() {
- expect(KeyboardUtil.hasShortcutModifier([], {0xffe1 : true})).to.be.false;
- });
- it('should not treat shift as a char modifier', function() {
- expect(KeyboardUtil.hasCharModifier([], {0xffe1 : true})).to.be.false;
+
+ describe('Numpad', function() {
+ it('should handle Numpad numbers', function() {
+ expect(KeyboardUtil.getKeysym({code: 'Digit5', key: '5', location: 0})).to.be.equal(0x0035);
+ expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: '5', location: 3})).to.be.equal(0xFFB5);
+ });
+ it('should handle Numpad non-character keys', function() {
+ expect(KeyboardUtil.getKeysym({code: 'Home', key: 'Home', location: 0})).to.be.equal(0xFF50);
+ expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: 'Home', location: 3})).to.be.equal(0xFF95);
+ expect(KeyboardUtil.getKeysym({code: 'Delete', key: 'Delete', location: 0})).to.be.equal(0xFFFF);
+ expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: 'Delete', location: 3})).to.be.equal(0xFF9F);
+ });
+ it('should handle Numpad Decimal key', function() {
+ expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: '.', location: 3})).to.be.equal(0xFFAE);
+ expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: ',', location: 3})).to.be.equal(0xFFAC);
});
});
});
diff --git a/tests/test.keyboard.js b/tests/test.keyboard.js
index b68c96d..a4ac630 100644
--- a/tests/test.keyboard.js
+++ b/tests/test.keyboard.js
@@ -1,844 +1,339 @@
var assert = chai.assert;
var expect = chai.expect;
+import { Keyboard } from '../core/input/devices.js';
import keysyms from '../core/input/keysymdef.js';
import * as KeyboardUtil from '../core/input/util.js';
/* jshint newcap: false, expr: true */
-describe('Key Event Pipeline Stages', function() {
+describe('Key Event Handling', function() {
"use strict";
+
+ // The real KeyboardEvent constructor might not work everywhere we
+ // want to run these tests
+ function keyevent(typeArg, KeyboardEventInit) {
+ var e = { type: typeArg };
+ for (var key in KeyboardEventInit) {
+ e[key] = KeyboardEventInit[key];
+ }
+ e.stopPropagation = sinon.spy();
+ e.preventDefault = sinon.spy();
+ return e;
+ };
+
describe('Decode Keyboard Events', function() {
- it('should pass events to the next stage', function(done) {
- KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
- expect(evt).to.be.an.object;
- done();
- }).keydown({keyCode: 0x41});
- });
- it('should pass the right keysym through', function(done) {
- KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
- expect(evt.keysym).to.be.deep.equal(keysyms.lookup(0x61));
+ it('should decode keydown events', function(done) {
+ var kbd = new Keyboard({
+ onKeyEvent: function(keysym, code, down) {
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('KeyA');
+ expect(down).to.be.equal(true);
done();
- }).keypress({keyCode: 0x41});
- });
- it('should pass the right keyid through', function(done) {
- KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
- expect(evt).to.have.property('keyId', 0x41);
- done();
- }).keydown({keyCode: 0x41});
- });
- it('should not sync modifiers on a keypress', function() {
- // Firefox provides unreliable modifier state on keypress events
- var count = 0;
- KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
- ++count;
- }).keypress({keyCode: 0x41, ctrlKey: true});
- expect(count).to.be.equal(1);
- });
- it('should sync modifiers if necessary', function(done) {
- var count = 0;
- KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
- switch (count) {
- case 0: // fake a ctrl keydown
- expect(evt).to.be.deep.equal({keysym: keysyms.lookup(0xffe3), type: 'keydown'});
- ++count;
- break;
- case 1:
- expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown', keysym: keysyms.lookup(0x61)});
+ }});
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
+ });
+ it('should decode keyup events', function(done) {
+ var calls = 0;
+ var kbd = new Keyboard({
+ onKeyEvent: function(keysym, code, down) {
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('KeyA');
+ if (calls++ === 1) {
+ expect(down).to.be.equal(false);
done();
- break;
}
- }).keydown({keyCode: 0x41, ctrlKey: true});
- });
- it('should forward keydown events with the right type', function(done) {
- KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
- expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown'});
- done();
- }).keydown({keyCode: 0x41});
- });
- it('should forward keyup events with the right type', function(done) {
- KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
- expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keyup'});
- done();
- }).keyup({keyCode: 0x41});
- });
- it('should forward keypress events with the right type', function(done) {
- KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
- expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keypress'});
- done();
- }).keypress({keyCode: 0x41});
- });
- it('should generate stalls if a char modifier is down while a key is pressed', function(done) {
- var count = 0;
- KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {
- switch (count) {
- case 0: // fake altgr
- expect(evt).to.be.deep.equal({keysym: keysyms.lookup(0xfe03), type: 'keydown'});
- ++count;
- break;
- case 1: // stall before processing the 'a' keydown
- expect(evt).to.be.deep.equal({type: 'stall'});
- ++count;
- break;
- case 2: // 'a'
- expect(evt).to.be.deep.equal({
- type: 'keydown',
- keyId: 0x41,
- keysym: keysyms.lookup(0x61)
- });
-
+ }});
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
+ kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
+ });
+
+ describe('Legacy keypress Events', function() {
+ it('should wait for keypress when needed', function() {
+ var callback = sinon.spy();
+ var kbd = new Keyboard({onKeyEvent: callback});
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
+ expect(callback).to.not.have.been.called;
+ });
+ it('should decode keypress events', function(done) {
+ var kbd = new Keyboard({
+ onKeyEvent: function(keysym, code, down) {
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('KeyA');
+ expect(down).to.be.equal(true);
done();
- break;
- }
- }).keydown({keyCode: 0x41, altGraphKey: true});
-
- });
- describe('suppress the right events at the right time', function() {
- it('should suppress anything while a shortcut modifier is down', function() {
- var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {});
-
- obj.keydown({keyCode: 0x11}); // press ctrl
- expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.true;
- expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.true;
- expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.true;
- expect(obj.keydown({keyCode: 0x3c})).to.be.true; // < key on DK Windows
- expect(obj.keydown({keyCode: 0xde})).to.be.true; // Ø key on DK
- });
- it('should suppress non-character keys', function() {
- var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {});
-
- expect(obj.keydown({keyCode: 0x08}), 'a').to.be.true;
- expect(obj.keydown({keyCode: 0x09}), 'b').to.be.true;
- expect(obj.keydown({keyCode: 0x11}), 'd').to.be.true;
- expect(obj.keydown({keyCode: 0x12}), 'e').to.be.true;
- });
- it('should not suppress shift', function() {
- var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {});
-
- expect(obj.keydown({keyCode: 0x10}), 'd').to.be.false;
- });
- it('should generate event for shift keydown', function() {
- var called = false;
- var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
- expect(evt).to.have.property('keysym');
- called = true;
- }).keydown({keyCode: 0x10});
- expect(called).to.be.true;
- });
- it('should not suppress character keys', function() {
- var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {});
-
- expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.false;
- expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false;
- expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.false;
- expect(obj.keydown({keyCode: 0x3c})).to.be.false; // < key on DK Windows
- expect(obj.keydown({keyCode: 0xde})).to.be.false; // Ø key on DK
- });
- it('should not suppress if a char modifier is down', function() {
- var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {});
-
- obj.keydown({keyCode: 0xe1}); // press altgr
- expect(obj.keydown({keyCode: 'A'.charCodeAt()})).to.be.false;
- expect(obj.keydown({keyCode: ' '.charCodeAt()})).to.be.false;
- expect(obj.keydown({keyCode: '1'.charCodeAt()})).to.be.false;
- expect(obj.keydown({keyCode: 0x3c})).to.be.false; // < key on DK Windows
- expect(obj.keydown({keyCode: 0xde})).to.be.false; // Ø key on DK
- });
- });
- describe('Keypress and keyup events', function() {
- it('should always suppress event propagation', function() {
- var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {});
-
- expect(obj.keypress({keyCode: 'A'.charCodeAt()})).to.be.true;
- expect(obj.keypress({keyCode: 0x3c})).to.be.true; // < key on DK Windows
- expect(obj.keypress({keyCode: 0x11})).to.be.true;
-
- expect(obj.keyup({keyCode: 'A'.charCodeAt()})).to.be.true;
- expect(obj.keyup({keyCode: 0x3c})).to.be.true; // < key on DK Windows
- expect(obj.keyup({keyCode: 0x11})).to.be.true;
- });
- it('should never generate stalls', function() {
- var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
- expect(evt.type).to.not.be.equal('stall');
- });
-
- obj.keypress({keyCode: 'A'.charCodeAt()});
- obj.keypress({keyCode: 0x3c});
- obj.keypress({keyCode: 0x11});
-
- obj.keyup({keyCode: 'A'.charCodeAt()});
- obj.keyup({keyCode: 0x3c});
- obj.keyup({keyCode: 0x11});
- });
- });
- describe('mark events if a char modifier is down', function() {
- it('should not mark modifiers on a keydown event', function() {
- var times_called = 0;
- var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {
- switch (times_called++) {
- case 0: //altgr
- break;
- case 1: // 'a'
- expect(evt).to.not.have.property('escape');
- break;
- }
- });
-
- obj.keydown({keyCode: 0xe1}); // press altgr
- obj.keydown({keyCode: 'A'.charCodeAt()});
- });
-
- it('should indicate on events if a single-key char modifier is down', function(done) {
- var times_called = 0;
- var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {
- switch (times_called++) {
- case 0: //altgr
- break;
- case 1: // 'a'
- expect(evt).to.be.deep.equal({
- type: 'keypress',
- keyId: 'A'.charCodeAt(),
- keysym: keysyms.lookup('a'.charCodeAt()),
- escape: [0xfe03]
- });
- done();
- return;
- }
- });
-
- obj.keydown({keyCode: 0xe1}); // press altgr
- obj.keypress({keyCode: 'A'.charCodeAt()});
- });
- it('should indicate on events if a multi-key char modifier is down', function(done) {
- var times_called = 0;
- var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xffe9, 0xffe3]), function(evt) {
- switch (times_called++) {
- case 0: //ctrl
- break;
- case 1: //alt
- break;
- case 2: // 'a'
- expect(evt).to.be.deep.equal({
- type: 'keypress',
- keyId: 'A'.charCodeAt(),
- keysym: keysyms.lookup('a'.charCodeAt()),
- escape: [0xffe9, 0xffe3]
- });
- done();
- return;
- }
- });
-
- obj.keydown({keyCode: 0x11}); // press ctrl
- obj.keydown({keyCode: 0x12}); // press alt
- obj.keypress({keyCode: 'A'.charCodeAt()});
- });
- it('should not consider a char modifier to be down on the modifier key itself', function() {
- var obj = KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {
- expect(evt).to.not.have.property('escape');
- });
-
- obj.keydown({keyCode: 0xe1}); // press altgr
-
+ }});
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
+ kbd._handleKeyPress(keyevent('keypress', {code: 'KeyA', charCode: 0x61}));
+ });
+ it('should ignore keypress with different code', function() {
+ var callback = sinon.spy();
+ var kbd = new Keyboard({onKeyEvent: callback});
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
+ kbd._handleKeyPress(keyevent('keypress', {code: 'KeyB', charCode: 0x61}));
+ expect(callback).to.not.have.been.called;
+ });
+ it('should handle keypress with missing code', function(done) {
+ var kbd = new Keyboard({
+ onKeyEvent: function(keysym, code, down) {
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('KeyA');
+ expect(down).to.be.equal(true);
+ done();
+ }});
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', keyCode: 0x41}));
+ kbd._handleKeyPress(keyevent('keypress', {charCode: 0x61}));
});
});
- describe('add/remove keysym', function() {
- it('should remove keysym from keydown if a char key and no modifier', function() {
- KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
- expect(evt).to.be.deep.equal({keyId: 0x41, type: 'keydown'});
- }).keydown({keyCode: 0x41});
- });
- it('should not remove keysym from keydown if a shortcut modifier is down', function() {
- var times_called = 0;
- KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
- switch (times_called++) {
- case 1:
- expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keydown'});
- break;
- }
- }).keydown({keyCode: 0x41, ctrlKey: true});
- expect(times_called).to.be.equal(2);
- });
- it('should not remove keysym from keydown if a char modifier is down', function() {
- var times_called = 0;
- KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync([0xfe03]), function(evt) {
- switch (times_called++) {
- case 2:
- expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keydown'});
- break;
- }
- }).keydown({keyCode: 0x41, altGraphKey: true});
- expect(times_called).to.be.equal(3);
- });
- it('should not remove keysym from keydown if key is noncharacter', function() {
- KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
- expect(evt, 'bacobjpace').to.be.deep.equal({keyId: 0x09, keysym: keysyms.lookup(0xff09), type: 'keydown'});
- }).keydown({keyCode: 0x09});
- KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
- expect(evt, 'ctrl').to.be.deep.equal({keyId: 0x11, keysym: keysyms.lookup(0xffe3), type: 'keydown'});
- }).keydown({keyCode: 0x11});
- });
- it('should never remove keysym from keypress', function() {
- KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
- expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keypress'});
- }).keypress({keyCode: 0x41});
- });
- it('should never remove keysym from keyup', function() {
- KeyboardUtil.KeyEventDecoder(KeyboardUtil.ModifierSync(), function(evt) {
- expect(evt).to.be.deep.equal({keyId: 0x41, keysym: keysyms.lookup(0x61), type: 'keyup'});
- }).keyup({keyCode: 0x41});
+ describe('suppress the right events at the right time', function() {
+ it('should suppress anything with a valid key', function() {
+ var kbd = new Keyboard({});
+ var evt = keyevent('keydown', {code: 'KeyA', key: 'a'});
+ kbd._handleKeyDown(evt);
+ expect(evt.preventDefault).to.have.been.called;
+ evt = keyevent('keyup', {code: 'KeyA', key: 'a'});
+ kbd._handleKeyUp(evt);
+ expect(evt.preventDefault).to.have.been.called;
+ });
+ it('should not suppress keys without key', function() {
+ var kbd = new Keyboard({});
+ var evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41});
+ kbd._handleKeyDown(evt);
+ expect(evt.preventDefault).to.not.have.been.called;
+ });
+ it('should suppress the following keypress event', function() {
+ var kbd = new Keyboard({});
+ var evt = keyevent('keydown', {code: 'KeyA', keyCode: 0x41});
+ kbd._handleKeyDown(evt);
+ var evt = keyevent('keypress', {code: 'KeyA', charCode: 0x41});
+ kbd._handleKeyPress(evt);
+ expect(evt.preventDefault).to.have.been.called;
});
});
- // on keypress, keyup(?), always set keysym
- // on keydown, only do it if we don't expect a keypress: if noncharacter OR modifier is down
});
- describe('Verify that char modifiers are active', function() {
- it('should pass keydown events through if there is no stall', function(done) {
- var obj = KeyboardUtil.VerifyCharModifier(function(evt){
- expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
- done();
- })({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
+ describe('Track Key State', function() {
+ it('should send release using the same keysym as the press', function(done) {
+ var kbd = new Keyboard({
+ onKeyEvent: function(keysym, code, down) {
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('KeyA');
+ if (!down) {
+ done();
+ }
+ }});
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
+ kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'b'}));
});
- it('should pass keyup events through if there is no stall', function(done) {
- var obj = KeyboardUtil.VerifyCharModifier(function(evt){
- expect(evt).to.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x41)});
- done();
- })({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x41)});
+ it('should send the same keysym for multiple presses', function() {
+ var count = 0;
+ var kbd = new Keyboard({
+ onKeyEvent: function(keysym, code, down) {
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('KeyA');
+ expect(down).to.be.equal(true);
+ count++;
+ }});
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'b'}));
+ expect(count).to.be.equal(2);
});
- it('should pass keypress events through if there is no stall', function(done) {
- var obj = KeyboardUtil.VerifyCharModifier(function(evt){
- expect(evt).to.deep.equal({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x41)});
- done();
- })({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x41)});
+ it('should do nothing on keyup events if no keys are down', function() {
+ var callback = sinon.spy();
+ var kbd = new Keyboard({onKeyEvent: callback});
+ kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
+ expect(callback).to.not.have.been.called;
});
- it('should not pass stall events through', function(done){
- var obj = KeyboardUtil.VerifyCharModifier(function(evt){
- // should only be called once, for the keydown
- expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
- done();
- });
+ });
- obj({type: 'stall'});
- obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
- });
- it('should merge keydown and keypress events if they come after a stall', function(done) {
- var next_called = false;
- var obj = KeyboardUtil.VerifyCharModifier(function(evt){
- // should only be called once, for the keydown
- expect(next_called).to.be.false;
- next_called = true;
- expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x44)});
+ describe('Shuffle modifiers on macOS', function() {
+ var origNavigator;
+ beforeEach(function () {
+ // window.navigator is a protected read-only property in many
+ // environments, so we need to redefine it whilst running these
+ // tests.
+ origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
+ if (origNavigator === undefined) {
+ // Object.getOwnPropertyDescriptor() doesn't work
+ // properly in any version of IE
+ this.skip();
+ }
+
+ Object.defineProperty(window, "navigator", {value: {}});
+ if (window.navigator.platform !== undefined) {
+ // Object.defineProperty() doesn't work properly in old
+ // versions of Chrome
+ this.skip();
+ }
+
+ window.navigator.platform = "Mac x86_64";
+ });
+ afterEach(function () {
+ Object.defineProperty(window, "navigator", origNavigator);
+ });
+
+ it('should change Alt to AltGraph', function() {
+ var count = 0;
+ var kbd = new Keyboard({
+ onKeyEvent: function(keysym, code, down) {
+ switch (count++) {
+ case 0:
+ expect(keysym).to.be.equal(0xFF7E);
+ expect(code).to.be.equal('AltLeft');
+ break;
+ case 1:
+ expect(keysym).to.be.equal(0xFE03);
+ expect(code).to.be.equal('AltRight');
+ break;
+ }
+ }});
+ kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1}));
+ kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
+ expect(count).to.be.equal(2);
+ });
+ it('should change left Super to Alt', function(done) {
+ var kbd = new Keyboard({
+ onKeyEvent: function(keysym, code, down) {
+ expect(keysym).to.be.equal(0xFFE9);
+ expect(code).to.be.equal('MetaLeft');
done();
- });
-
- obj({type: 'stall'});
- obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
- obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)});
- expect(next_called).to.be.false;
- });
- it('should preserve modifier attribute when merging if keysyms differ', function(done) {
- var next_called = false;
- var obj = KeyboardUtil.VerifyCharModifier(function(evt){
- // should only be called once, for the keydown
- expect(next_called).to.be.false;
- next_called = true;
- expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x44), escape: [0xffe3]});
+ }});
+ kbd._handleKeyDown(keyevent('keydown', {code: 'MetaLeft', key: 'Meta', location: 1}));
+ });
+ it('should change right Super to left Super', function(done) {
+ var kbd = new Keyboard({
+ onKeyEvent: function(keysym, code, down) {
+ expect(keysym).to.be.equal(0xFFEB);
+ expect(code).to.be.equal('MetaRight');
done();
- });
-
- obj({type: 'stall'});
- obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
- obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44), escape: [0xffe3]});
- expect(next_called).to.be.false;
+ }});
+ kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2}));
});
- it('should not preserve modifier attribute when merging if keysyms are the same', function() {
- var obj = KeyboardUtil.VerifyCharModifier(function(evt){
- expect(evt).to.not.have.property('escape');
- });
+ });
- obj({type: 'stall'});
- obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
- obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x42), escape: [0xffe3]});
- });
- it('should not merge keydown and keypress events if there is no stall', function(done) {
+ describe('Escape AltGraph on Windows', function() {
+ var origNavigator;
+ beforeEach(function () {
+ // window.navigator is a protected read-only property in many
+ // environments, so we need to redefine it whilst running these
+ // tests.
+ origNavigator = Object.getOwnPropertyDescriptor(window, "navigator");
+ if (origNavigator === undefined) {
+ // Object.getOwnPropertyDescriptor() doesn't work
+ // properly in any version of IE
+ this.skip();
+ }
+
+ Object.defineProperty(window, "navigator", {value: {}});
+ if (window.navigator.platform !== undefined) {
+ // Object.defineProperty() doesn't work properly in old
+ // versions of Chrome
+ this.skip();
+ }
+
+ window.navigator.platform = "Windows x86_64";
+ });
+ afterEach(function () {
+ Object.defineProperty(window, "navigator", origNavigator);
+ });
+
+ it('should generate fake undo/redo events on press when AltGraph is down', function() {
var times_called = 0;
- var obj = KeyboardUtil.VerifyCharModifier(function(evt){
- switch(times_called) {
+ var kbd = new Keyboard({
+ onKeyEvent: function(keysym, code, down) {
+ switch(times_called++) {
case 0:
- expect(evt).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
+ expect(keysym).to.be.equal(0xFFE3);
+ expect(code).to.be.equal('ControlLeft');
+ expect(down).to.be.equal(true);
break;
case 1:
- expect(evt).to.deep.equal({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)});
- done();
+ expect(keysym).to.be.equal(0xFFEA);
+ expect(code).to.be.equal('AltRight');
+ expect(down).to.be.equal(true);
+ break;
+ case 2:
+ expect(keysym).to.be.equal(0xFFEA);
+ expect(code).to.be.equal('AltRight');
+ expect(down).to.be.equal(false);
+ break;
+ case 3:
+ expect(keysym).to.be.equal(0xFFE3);
+ expect(code).to.be.equal('ControlLeft');
+ expect(down).to.be.equal(false);
+ break;
+ case 4:
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('KeyA');
+ expect(down).to.be.equal(true);
+ break;
+ case 5:
+ expect(keysym).to.be.equal(0xFFE3);
+ expect(code).to.be.equal('ControlLeft');
+ expect(down).to.be.equal(true);
+ break;
+ case 6:
+ expect(keysym).to.be.equal(0xFFEA);
+ expect(code).to.be.equal('AltRight');
+ expect(down).to.be.equal(true);
break;
}
-
- ++times_called;
- });
-
- obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
- obj({type: 'keypress', keyId: 0x43, keysym: keysyms.lookup(0x44)});
- });
- it('should not merge keydown and keypress events if separated by another event', function(done) {
+ }});
+ // First the modifier combo
+ kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
+ kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
+ // Next a normal character
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
+ expect(times_called).to.be.equal(7);
+ });
+ it('should no do anything on key release', function() {
+ var times_called = 0;
+ var kbd = new Keyboard({
+ onKeyEvent: function(keysym, code, down) {
+ switch(times_called++) {
+ case 7:
+ expect(keysym).to.be.equal(0x61);
+ expect(code).to.be.equal('KeyA');
+ expect(down).to.be.equal(false);
+ break;
+ }
+ }});
+ // First the modifier combo
+ kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
+ kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
+ // Next a normal character
+ kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
+ kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
+ expect(times_called).to.be.equal(8);
+ });
+ it('should not consider a char modifier to be down on the modifier key itself', function() {
var times_called = 0;
- var obj = KeyboardUtil.VerifyCharModifier(function(evt){
- switch(times_called) {
+ var kbd = new Keyboard({
+ onKeyEvent: function(keysym, code, down) {
+ switch(times_called++) {
case 0:
- expect(evt,1).to.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
+ expect(keysym).to.be.equal(0xFFE3);
+ expect(code).to.be.equal('ControlLeft');
+ expect(down).to.be.equal(true);
break;
case 1:
- expect(evt,2).to.deep.equal({type: 'keyup', keyId: 0x43, keysym: keysyms.lookup(0x44)});
+ expect(keysym).to.be.equal(0xFFE9);
+ expect(code).to.be.equal('AltLeft');
+ expect(down).to.be.equal(true);
break;
case 2:
- expect(evt,3).to.deep.equal({type: 'keypress', keyId: 0x45, keysym: keysyms.lookup(0x46)});
- done();
+ expect(keysym).to.be.equal(0xFFE3);
+ expect(code).to.be.equal('ControlLeft');
+ expect(down).to.be.equal(true);
break;
}
-
- ++times_called;
- });
-
- obj({type: 'stall'});
- obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
- obj({type: 'keyup', keyId: 0x43, keysym: keysyms.lookup(0x44)});
- obj({type: 'keypress', keyId: 0x45, keysym: keysyms.lookup(0x46)});
- });
- });
-
- describe('Track Key State', function() {
- it('should do nothing on keyup events if no keys are down', function() {
- var obj = KeyboardUtil.TrackKeyState(function(evt) {
- expect(true).to.be.false;
- });
- obj({type: 'keyup', keyId: 0x41});
- });
- it('should insert into the queue on keydown if no keys are down', function() {
- var times_called = 0;
- var elem = null;
- var keysymsdown = {};
- var obj = KeyboardUtil.TrackKeyState(function(evt) {
- ++times_called;
- if (elem.type == 'keyup') {
- expect(evt).to.have.property('keysym');
- expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
- delete keysymsdown[evt.keysym.keysym];
- }
- else {
- expect(evt).to.be.deep.equal(elem);
- expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
- }
- elem = null;
- });
-
- expect(elem).to.be.null;
- elem = {type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)};
- keysymsdown[keysyms.lookup(0x42).keysym] = true;
- obj(elem);
- expect(elem).to.be.null;
- elem = {type: 'keyup', keyId: 0x41};
- obj(elem);
- expect(elem).to.be.null;
- expect(times_called).to.be.equal(2);
- });
- it('should insert into the queue on keypress if no keys are down', function() {
- var times_called = 0;
- var elem = null;
- var keysymsdown = {};
- var obj = KeyboardUtil.TrackKeyState(function(evt) {
- ++times_called;
- if (elem.type == 'keyup') {
- expect(evt).to.have.property('keysym');
- expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
- delete keysymsdown[evt.keysym.keysym];
- }
- else {
- expect(evt).to.be.deep.equal(elem);
- expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
- }
- elem = null;
- });
-
- expect(elem).to.be.null;
- elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)};
- keysymsdown[keysyms.lookup(0x42).keysym] = true;
- obj(elem);
- expect(elem).to.be.null;
- elem = {type: 'keyup', keyId: 0x41};
- obj(elem);
- expect(elem).to.be.null;
- expect(times_called).to.be.equal(2);
- });
- it('should add keysym to last key entry if keyId matches', function() {
- // this implies that a single keyup will release both keysyms
- var times_called = 0;
- var elem = null;
- var keysymsdown = {};
- var obj = KeyboardUtil.TrackKeyState(function(evt) {
- ++times_called;
- if (elem.type == 'keyup') {
- expect(evt).to.have.property('keysym');
- expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
- delete keysymsdown[evt.keysym.keysym];
- }
- else {
- expect(evt).to.be.deep.equal(elem);
- expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
- elem = null;
- }
- });
-
- expect(elem).to.be.null;
- elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)};
- keysymsdown[keysyms.lookup(0x42).keysym] = true;
- obj(elem);
- expect(elem).to.be.null;
- elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x43)};
- keysymsdown[keysyms.lookup(0x43).keysym] = true;
- obj(elem);
- expect(elem).to.be.null;
- elem = {type: 'keyup', keyId: 0x41};
- obj(elem);
- expect(times_called).to.be.equal(4);
- });
- it('should create new key entry if keyId matches and keysym does not', function() {
- // this implies that a single keyup will release both keysyms
- var times_called = 0;
- var elem = null;
- var keysymsdown = {};
- var obj = KeyboardUtil.TrackKeyState(function(evt) {
- ++times_called;
- if (elem.type == 'keyup') {
- expect(evt).to.have.property('keysym');
- expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
- delete keysymsdown[evt.keysym.keysym];
- }
- else {
- expect(evt).to.be.deep.equal(elem);
- expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
- elem = null;
- }
- });
-
- expect(elem).to.be.null;
- elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)};
- keysymsdown[keysyms.lookup(0x42).keysym] = true;
- obj(elem);
- expect(elem).to.be.null;
- elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x43)};
- keysymsdown[keysyms.lookup(0x43).keysym] = true;
- obj(elem);
- expect(times_called).to.be.equal(2);
- expect(elem).to.be.null;
- elem = {type: 'keyup', keyId: 0};
- obj(elem);
- expect(times_called).to.be.equal(3);
- elem = {type: 'keyup', keyId: 0};
- obj(elem);
- expect(times_called).to.be.equal(4);
- });
- it('should merge key entry if keyIds are zero and keysyms match', function() {
- // this implies that a single keyup will release both keysyms
- var times_called = 0;
- var elem = null;
- var keysymsdown = {};
- var obj = KeyboardUtil.TrackKeyState(function(evt) {
- ++times_called;
- if (elem.type == 'keyup') {
- expect(evt).to.have.property('keysym');
- expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
- delete keysymsdown[evt.keysym.keysym];
- }
- else {
- expect(evt).to.be.deep.equal(elem);
- expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
- elem = null;
- }
- });
-
- expect(elem).to.be.null;
- elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)};
- keysymsdown[keysyms.lookup(0x42).keysym] = true;
- obj(elem);
- expect(elem).to.be.null;
- elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)};
- keysymsdown[keysyms.lookup(0x42).keysym] = true;
- obj(elem);
- expect(times_called).to.be.equal(2);
- expect(elem).to.be.null;
- elem = {type: 'keyup', keyId: 0};
- obj(elem);
- expect(times_called).to.be.equal(3);
- });
- it('should add keysym as separate entry if keyId does not match last event', function() {
- // this implies that separate keyups are required
- var times_called = 0;
- var elem = null;
- var keysymsdown = {};
- var obj = KeyboardUtil.TrackKeyState(function(evt) {
- ++times_called;
- if (elem.type == 'keyup') {
- expect(evt).to.have.property('keysym');
- expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
- delete keysymsdown[evt.keysym.keysym];
- }
- else {
- expect(evt).to.be.deep.equal(elem);
- expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
- elem = null;
- }
- });
-
- expect(elem).to.be.null;
- elem = {type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x42)};
- keysymsdown[keysyms.lookup(0x42).keysym] = true;
- obj(elem);
- expect(elem).to.be.null;
- elem = {type: 'keypress', keyId: 0x42, keysym: keysyms.lookup(0x43)};
- keysymsdown[keysyms.lookup(0x43).keysym] = true;
- obj(elem);
- expect(elem).to.be.null;
- elem = {type: 'keyup', keyId: 0x41};
- obj(elem);
- expect(times_called).to.be.equal(4);
- elem = {type: 'keyup', keyId: 0x42};
- obj(elem);
- expect(times_called).to.be.equal(4);
- });
- it('should add keysym as separate entry if keyId does not match last event and first is zero', function() {
- // this implies that separate keyups are required
- var times_called = 0;
- var elem = null;
- var keysymsdown = {};
- var obj = KeyboardUtil.TrackKeyState(function(evt) {
- ++times_called;
- if (elem.type == 'keyup') {
- expect(evt).to.have.property('keysym');
- expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
- delete keysymsdown[evt.keysym.keysym];
- }
- else {
- expect(evt).to.be.deep.equal(elem);
- expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
- elem = null;
- }
- });
-
- expect(elem).to.be.null;
- elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x42)};
- keysymsdown[keysyms.lookup(0x42).keysym] = true;
- obj(elem);
- expect(elem).to.be.null;
- elem = {type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x43)};
- keysymsdown[keysyms.lookup(0x43).keysym] = true;
- obj(elem);
- expect(elem).to.be.null;
- expect(times_called).to.be.equal(2);
- elem = {type: 'keyup', keyId: 0};
- obj(elem);
- expect(times_called).to.be.equal(3);
- elem = {type: 'keyup', keyId: 0x42};
- obj(elem);
- expect(times_called).to.be.equal(4);
- });
- it('should add keysym as separate entry if keyId does not match last event and second is zero', function() {
- // this implies that a separate keyups are required
- var times_called = 0;
- var elem = null;
- var keysymsdown = {};
- var obj = KeyboardUtil.TrackKeyState(function(evt) {
- ++times_called;
- if (elem.type == 'keyup') {
- expect(evt).to.have.property('keysym');
- expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
- delete keysymsdown[evt.keysym.keysym];
- }
- else {
- expect(evt).to.be.deep.equal(elem);
- expect (keysymsdown[evt.keysym.keysym]).to.not.be.undefined;
- elem = null;
- }
- });
-
- expect(elem).to.be.null;
- elem = {type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)};
- keysymsdown[keysyms.lookup(0x42).keysym] = true;
- obj(elem);
- expect(elem).to.be.null;
- elem = {type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x43)};
- keysymsdown[keysyms.lookup(0x43).keysym] = true;
- obj(elem);
- expect(elem).to.be.null;
- elem = {type: 'keyup', keyId: 0x41};
- obj(elem);
+ }});
+ // First the modifier combo
+ kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
+ kbd._handleKeyDown(keyevent('keydown', {code: 'AltLeft', key: 'Alt', location: 1}));
+ // Then one of the keys again
+ kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
expect(times_called).to.be.equal(3);
- elem = {type: 'keyup', keyId: 0};
- obj(elem);
- expect(times_called).to.be.equal(4);
- });
- it('should pop matching key event on keyup', function() {
- var times_called = 0;
- var obj = KeyboardUtil.TrackKeyState(function(evt) {
- switch (times_called++) {
- case 0:
- case 1:
- case 2:
- expect(evt.type).to.be.equal('keydown');
- break;
- case 3:
- expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x42, keysym: keysyms.lookup(0x62)});
- break;
- }
- });
-
- obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x61)});
- obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x62)});
- obj({type: 'keydown', keyId: 0x43, keysym: keysyms.lookup(0x63)});
- obj({type: 'keyup', keyId: 0x42});
- expect(times_called).to.equal(4);
- });
- it('should pop the first zero keyevent on keyup with zero keyId', function() {
- var times_called = 0;
- var obj = KeyboardUtil.TrackKeyState(function(evt) {
- switch (times_called++) {
- case 0:
- case 1:
- case 2:
- expect(evt.type).to.be.equal('keydown');
- break;
- case 3:
- expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x61)});
- break;
- }
- });
-
- obj({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x61)});
- obj({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0x62)});
- obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x63)});
- obj({type: 'keyup', keyId: 0x0});
- expect(times_called).to.equal(4);
- });
- it('should pop the last keyevents keysym if no match is found for keyId', function() {
- var times_called = 0;
- var obj = KeyboardUtil.TrackKeyState(function(evt) {
- switch (times_called++) {
- case 0:
- case 1:
- case 2:
- expect(evt.type).to.be.equal('keydown');
- break;
- case 3:
- expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x44, keysym: keysyms.lookup(0x63)});
- break;
- }
- });
-
- obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x61)});
- obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x62)});
- obj({type: 'keydown', keyId: 0x43, keysym: keysyms.lookup(0x63)});
- obj({type: 'keyup', keyId: 0x44});
- expect(times_called).to.equal(4);
- });
- describe('Firefox sends keypress even when keydown is suppressed', function() {
- it('should discard the keypress', function() {
- var times_called = 0;
- var obj = KeyboardUtil.TrackKeyState(function(evt) {
- expect(times_called).to.be.equal(0);
- ++times_called;
- });
-
- obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
- expect(times_called).to.be.equal(1);
- obj({type: 'keypress', keyId: 0x41, keysym: keysyms.lookup(0x43)});
- });
- });
- describe('releaseAll', function() {
- it('should do nothing if no keys have been pressed', function() {
- var times_called = 0;
- var obj = KeyboardUtil.TrackKeyState(function(evt) {
- ++times_called;
- });
- obj({type: 'releaseall'});
- expect(times_called).to.be.equal(0);
- });
- it('should release the keys that have been pressed', function() {
- var times_called = 0;
- var obj = KeyboardUtil.TrackKeyState(function(evt) {
- switch (times_called++) {
- case 2:
- expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x41)});
- break;
- case 3:
- expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0x42)});
- break;
- }
- });
- obj({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x41)});
- obj({type: 'keydown', keyId: 0x42, keysym: keysyms.lookup(0x42)});
- expect(times_called).to.be.equal(2);
- obj({type: 'releaseall'});
- expect(times_called).to.be.equal(4);
- obj({type: 'releaseall'});
- expect(times_called).to.be.equal(4);
- });
- });
-
- });
-
- describe('Escape Modifiers', function() {
- describe('Keydown', function() {
- it('should pass through when a char modifier is not down', function() {
- var times_called = 0;
- KeyboardUtil.EscapeModifiers(function(evt) {
- expect(times_called).to.be.equal(0);
- ++times_called;
- expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
- })({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42)});
- expect(times_called).to.be.equal(1);
- });
- it('should generate fake undo/redo events when a char modifier is down', function() {
- var times_called = 0;
- KeyboardUtil.EscapeModifiers(function(evt) {
- switch(times_called++) {
- case 0:
- expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0xffe9)});
- break;
- case 1:
- expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0, keysym: keysyms.lookup(0xffe3)});
- break;
- case 2:
- expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xffe9, 0xffe3]});
- break;
- case 3:
- expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0xffe9)});
- break;
- case 4:
- expect(evt).to.be.deep.equal({type: 'keydown', keyId: 0, keysym: keysyms.lookup(0xffe3)});
- break;
- }
- })({type: 'keydown', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xffe9, 0xffe3]});
- expect(times_called).to.be.equal(5);
- });
- });
- describe('Keyup', function() {
- it('should pass through when a char modifier is down', function() {
- var times_called = 0;
- KeyboardUtil.EscapeModifiers(function(evt) {
- expect(times_called).to.be.equal(0);
- ++times_called;
- expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xfe03]});
- })({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42), escape: [0xfe03]});
- expect(times_called).to.be.equal(1);
- });
- it('should pass through when a char modifier is not down', function() {
- var times_called = 0;
- KeyboardUtil.EscapeModifiers(function(evt) {
- expect(times_called).to.be.equal(0);
- ++times_called;
- expect(evt).to.be.deep.equal({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42)});
- })({type: 'keyup', keyId: 0x41, keysym: keysyms.lookup(0x42)});
- expect(times_called).to.be.equal(1);
- });
});
});
});
diff --git a/tests/test.rfb.js b/tests/test.rfb.js
index 88878c2..c32d9e1 100644
--- a/tests/test.rfb.js
+++ b/tests/test.rfb.js
@@ -193,7 +193,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
it('should send a single key with the given code and state (down = true)', function () {
var expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}};
RFB.messages.keyEvent(expected, 123, 1);
- client.sendKey(123, true);
+ client.sendKey(123, 'Key123', true);
expect(client._sock).to.have.sent(expected._sQ);
});
@@ -201,21 +201,29 @@ describe('Remote Frame Buffer Protocol Client', function() {
var expected = {_sQ: new Uint8Array(16), _sQlen: 0, flush: function () {}};
RFB.messages.keyEvent(expected, 123, 1);
RFB.messages.keyEvent(expected, 123, 0);
- client.sendKey(123);
+ client.sendKey(123, 'Key123');
expect(client._sock).to.have.sent(expected._sQ);
});
it('should not send the key if we are not in a normal state', function () {
client._rfb_connection_state = "broken";
- client.sendKey(123);
+ client.sendKey(123, 'Key123');
expect(client._sock.flush).to.not.have.been.called;
});
it('should not send the key if we are set as view_only', function () {
client._view_only = true;
- client.sendKey(123);
+ client.sendKey(123, 'Key123');
expect(client._sock.flush).to.not.have.been.called;
});
+
+ it('should send QEMU extended events if supported', function () {
+ client._qemuExtKeyEventSupported = true;
+ var expected = {_sQ: new Uint8Array(12), _sQlen: 0, flush: function () {}};
+ RFB.messages.QEMUExtendedKeyEvent(expected, 0x20, true, 0x0039);
+ client.sendKey(0x20, 'Space', true);
+ expect(client._sock).to.have.sent(expected._sQ);
+ });
});
describe('#clipboardPasteFrom', function () {
@@ -2017,22 +2025,21 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._sock.open('ws://', 'binary');
client._sock._websocket._open();
sinon.spy(client._sock, 'flush');
+ client._rfb_connection_state = 'connected';
+ client._view_only = false;
});
it('should send a key message on a key press', function () {
var keyevent = {};
- keyevent.type = 'keydown';
- keyevent.keysym = {};
- keyevent.keysym.keysym = 1234;
- client._keyboard._onKeyPress(keyevent);
+ client._keyboard._onKeyEvent(0x41, 'KeyA', true);
var key_msg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}};
- RFB.messages.keyEvent(key_msg, 1234, 1);
+ RFB.messages.keyEvent(key_msg, 0x41, 1);
expect(client._sock).to.have.sent(key_msg._sQ);
});
it('should not send messages in view-only mode', function () {
client._view_only = true;
- client._keyboard._onKeyPress(1234, 1);
+ client._keyboard._onKeyEvent('a', 'KeyA', true);
expect(client._sock.flush).to.not.have.been.called;
});
});
diff --git a/tests/vnc_perf.html b/tests/vnc_perf.html
index ce97ca4..acae0d1 100644
--- a/tests/vnc_perf.html
+++ b/tests/vnc_perf.html
@@ -50,7 +50,8 @@
WebUtil.load_scripts({
'core': ["base64.js", "websock.js", "des.js", "input/keysym.js",
"input/keysymdef.js", "input/xtscancodes.js", "input/util.js",
- "input/devices.js", "display.js", "rfb.js", "inflator.js"],
+ "input/devices.js", "display.js", "rfb.js", "inflator.js",
+ "input/vkeys.js", "input/fixedkeys.js"],
'tests': ["playback.js"],
'recordings': [fname]});
} else {
diff --git a/utils/genkeysymdef.js b/utils/genkeysymdef.js
new file mode 100755
index 0000000..8486da3
--- /dev/null
+++ b/utils/genkeysymdef.js
@@ -0,0 +1,128 @@
+#!/usr/bin/env node
+/*
+ * genkeysymdef: X11 keysymdef.h to JavaScript converter
+ * Copyright 2013 jalf <git@jalf.dk>
+ * Copyright 2017 Pierre Ossman for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
+ */
+
+"use strict";
+
+var fs = require('fs');
+
+var show_help = process.argv.length === 2;
+var filename;
+
+for (var i = 2; i < process.argv.length; ++i) {
+ switch (process.argv[i]) {
+ case "--help":
+ case "-h":
+ show_help = true;
+ break;
+ case "--file":
+ case "-f":
+ default:
+ filename = process.argv[i];
+ }
+}
+
+if (!filename) {
+ show_help = true;
+ console.log("Error: No filename specified\n");
+}
+
+if (show_help) {
+ console.log("Parses a *nix keysymdef.h to generate Unicode code point mappings");
+ console.log("Usage: node parse.js [options] filename:");
+ console.log(" -h [ --help ] Produce this help message");
+ console.log(" filename The keysymdef.h file to parse");
+ return;
+}
+
+var buf = fs.readFileSync(filename);
+var str = buf.toString('utf8');
+
+var re = /^\#define XK_([a-zA-Z_0-9]+)\s+0x([0-9a-fA-F]+)\s*(\/\*\s*(.*)\s*\*\/)?\s*$/m;
+
+var arr = str.split('\n');
+
+var codepoints = {};
+
+for (var i = 0; i < arr.length; ++i) {
+ var result = re.exec(arr[i]);
+ if (result){
+ var keyname = result[1];
+ var keysym = parseInt(result[2], 16);
+ var remainder = result[3];
+
+ var unicodeRes = /U\+([0-9a-fA-F]+)/.exec(remainder);
+ if (unicodeRes) {
+ var unicode = parseInt(unicodeRes[1], 16);
+ // The first entry is the preferred one
+ if (!codepoints[unicode]){
+ codepoints[unicode] = { keysym: keysym, name: keyname };
+ }
+ }
+ }
+}
+
+var out =
+"/*\n" +
+" * Mapping from Unicode codepoints to X11/RFB keysyms\n" +
+" *\n" +
+" * This file was automatically generated from keysymdef.h\n" +
+" * DO NOT EDIT!\n" +
+" */\n" +
+"\n" +
+"/* Functions at the bottom */\n" +
+"\n" +
+"var codepoints = {\n";
+
+function toHex(num) {
+ var s = num.toString(16);
+ if (s.length < 4) {
+ s = ("0000" + s).slice(-4);
+ }
+ return "0x" + s;
+};
+
+for (var codepoint in codepoints) {
+ codepoint = parseInt(codepoint);
+
+ // Latin-1?
+ if ((codepoint >= 0x20) && (codepoint <= 0xff)) {
+ continue;
+ }
+
+ // Handled by the general Unicode mapping?
+ if ((codepoint | 0x01000000) === codepoints[codepoint].keysym) {
+ continue;
+ }
+
+ out += " " + toHex(codepoint) + ": " +
+ toHex(codepoints[codepoint].keysym) +
+ ", // XK_" + codepoints[codepoint].name + "\n";
+}
+
+out +=
+"};\n" +
+"\n" +
+"export default {\n" +
+" lookup : function(u) {\n" +
+" // Latin-1 is one-to-one mapping\n" +
+" if ((u >= 0x20) && (u <= 0xff)) {\n" +
+" return u;\n" +
+" }\n" +
+"\n" +
+" // Lookup table (fairly random)\n" +
+" var keysym = codepoints[u];\n" +
+" if (keysym !== undefined) {\n" +
+" return keysym;\n" +
+" }\n" +
+"\n" +
+" // General mapping as final fallback\n" +
+" return 0x01000000 | u;\n" +
+" },\n" +
+"};";
+
+console.log(out);
diff --git a/utils/parse.js b/utils/parse.js
deleted file mode 100644
index fd79b12..0000000
--- a/utils/parse.js
+++ /dev/null
@@ -1,103 +0,0 @@
-// Utility to parse keysymdef.h to produce mappings from Unicode codepoints to keysyms
-"use strict";
-
-var fs = require('fs');
-
-var show_help = process.argv.length === 2;
-var use_keynames = false;
-var filename;
-
-for (var i = 2; i < process.argv.length; ++i) {
- switch (process.argv[i]) {
- case "--help":
- case "-h":
- show_help = true;
- break;
- case "--debug-names":
- case "-d":
- use_keynames = true;
- break;
- case "--file":
- case "-f":
- default:
- filename = process.argv[i];
- }
-}
-
-if (!filename) {
- show_help = true;
- console.log("Error: No filename specified\n");
-}
-
-if (show_help) {
- console.log("Parses a *nix keysymdef.h to generate Unicode code point mappings");
- console.log("Usage: node parse.js [options] filename:");
- console.log(" -h [ --help ] Produce this help message");
- console.log(" -d [ --debug-names ] Preserve keysym names for debugging (Increases file size by ~40KB)");
- console.log(" filename The keysymdef.h file to parse");
- return;
-}
-
-// Set this to false to omit key names from the generated keysymdef.js
-// This reduces the file size by around 40kb, but may hinder debugging
-
-var buf = fs.readFileSync(filename);
-var str = buf.toString('utf8');
-
-var re = /^\#define XK_([a-zA-Z_0-9]+)\s+0x([0-9a-fA-F]+)\s*(\/\*\s*(.*)\s*\*\/)?\s*$/m;
-
-var arr = str.split('\n');
-
-var keysyms = {};
-var codepoints = {};
-
-for (var i = 0; i < arr.length; ++i) {
- var result = re.exec(arr[i]);
- if (result){
- var keyname = result[1];
- var keysym = parseInt(result[2], 16);
- var remainder = result[3];
-
- keysyms[keysym] = keyname;
-
- var unicodeRes = /U\+([0-9a-fA-F]+)/.exec(remainder);
- if (unicodeRes) {
- var unicode = parseInt(unicodeRes[1], 16);
- if (!codepoints[unicode]){
- codepoints[unicode] = keysym;
- }
- }
- else {
- console.log("no unicode codepoint found:", arr[i]);
- }
- }
- else {
- console.log("line is not a keysym:", arr[i]);
- }
-}
-
-var out = "// This file describes mappings from Unicode codepoints to the keysym values\n" +
-"// (and optionally, key names) expected by the RFB protocol\n" +
-"// How this file was generated:\n" +
-"// " + process.argv.join(" ") + "\n" +
-"var keysyms = (function(){\n" +
-" \"use strict\";\n" +
-" var keynames = {keysyms};\n" +
-" var codepoints = {codepoints};\n" +
-"\n" +
-" function lookup(k) { return k ? {keysym: k, keyname: keynames ? keynames[k] : k} : undefined; }\n" +
-" return {\n" +
-" fromUnicode : function(u) {\n" +
-" var keysym = codepoints[u];\n" +
-" if (keysym === undefined) {\n" +
-" keysym = 0x01000000 | u;\n" +
-" }\n" +
-" return lookup(keysym);\n" +
-" },\n" +
-" lookup : lookup\n" +
-" };\n" +
-"})();\n";
-out = out.replace('{keysyms}', use_keynames ? JSON.stringify(keysyms) : "null");
-out = out.replace('{codepoints}', JSON.stringify(codepoints));
-
-fs.writeFileSync("keysymdef.js", out);