Merge branch 'keyboard' of https://github.com/CendioOssman/noVNC
This commit is contained in:
commit
545442afc3
|
@ -35,8 +35,7 @@
|
||||||
msg.appendChild(div);
|
msg.appendChild(div);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err &&
|
if (err && (err.stack !== undefined)) {
|
||||||
(err.stack !== undefined)) {
|
|
||||||
div = document.createElement("div");
|
div = document.createElement("div");
|
||||||
div.className = 'noVNC_stack';
|
div.className = 'noVNC_stack';
|
||||||
div.appendChild(document.createTextNode(err.stack));
|
div.appendChild(document.createTextNode(err.stack));
|
||||||
|
|
14
app/ui.js
14
app/ui.js
|
@ -1516,10 +1516,10 @@ const UI = {
|
||||||
|
|
||||||
// Send the key events
|
// Send the key events
|
||||||
for (i = 0; i < backspaces; i++) {
|
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++) {
|
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
|
// Control the text content length in the keyboardinput element
|
||||||
|
@ -1573,7 +1573,7 @@ const UI = {
|
||||||
},
|
},
|
||||||
|
|
||||||
sendEsc: function() {
|
sendEsc: function() {
|
||||||
UI.rfb.sendKey(KeyTable.XK_Escape);
|
UI.rfb.sendKey(KeyTable.XK_Escape, "Escape");
|
||||||
},
|
},
|
||||||
|
|
||||||
sendTab: function() {
|
sendTab: function() {
|
||||||
|
@ -1583,10 +1583,10 @@ const UI = {
|
||||||
toggleCtrl: function() {
|
toggleCtrl: function() {
|
||||||
var btn = document.getElementById('noVNC_toggle_ctrl_button');
|
var btn = document.getElementById('noVNC_toggle_ctrl_button');
|
||||||
if (btn.classList.contains("noVNC_selected")) {
|
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");
|
btn.classList.remove("noVNC_selected");
|
||||||
} else {
|
} else {
|
||||||
UI.rfb.sendKey(KeyTable.XK_Control_L, true);
|
UI.rfb.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
|
||||||
btn.classList.add("noVNC_selected");
|
btn.classList.add("noVNC_selected");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1594,10 +1594,10 @@ const UI = {
|
||||||
toggleAlt: function() {
|
toggleAlt: function() {
|
||||||
var btn = document.getElementById('noVNC_toggle_alt_button');
|
var btn = document.getElementById('noVNC_toggle_alt_button');
|
||||||
if (btn.classList.contains("noVNC_selected")) {
|
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");
|
btn.classList.remove("noVNC_selected");
|
||||||
} else {
|
} else {
|
||||||
UI.rfb.sendKey(KeyTable.XK_Alt_L, true);
|
UI.rfb.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
|
||||||
btn.classList.add("noVNC_selected");
|
btn.classList.add("noVNC_selected");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,29 +13,22 @@ import { isTouchDevice } from '../util/browsers.js'
|
||||||
import { setCapture, releaseCapture, stopEvent, getPointerEvent } from '../util/events.js';
|
import { setCapture, releaseCapture, stopEvent, getPointerEvent } from '../util/events.js';
|
||||||
import { set_defaults, make_properties } from '../util/properties.js';
|
import { set_defaults, make_properties } from '../util/properties.js';
|
||||||
import * as KeyboardUtil from "./util.js";
|
import * as KeyboardUtil from "./util.js";
|
||||||
|
import KeyTable from "./keysym.js";
|
||||||
|
|
||||||
//
|
//
|
||||||
// Keyboard event handler
|
// Keyboard event handler
|
||||||
//
|
//
|
||||||
|
|
||||||
const Keyboard = function (defaults) {
|
const Keyboard = function (defaults) {
|
||||||
this._keyDownList = []; // List of depressed keys
|
this._keyDownList = {}; // List of depressed keys
|
||||||
// (even if they are happy)
|
// (even if they are happy)
|
||||||
|
this._pendingKey = null; // Key waiting for keypress
|
||||||
|
|
||||||
set_defaults(this, defaults, {
|
set_defaults(this, defaults, {
|
||||||
'target': document,
|
'target': document,
|
||||||
'focused': true
|
'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
|
// keep these here so we can refer to them later
|
||||||
this._eventHandlers = {
|
this._eventHandlers = {
|
||||||
'keyup': this._handleKeyUp.bind(this),
|
'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 = {
|
Keyboard.prototype = {
|
||||||
// private methods
|
// private methods
|
||||||
|
|
||||||
_handleRfbEvent: function (e) {
|
_sendKeyEvent: function (keysym, code, down) {
|
||||||
if (this._onKeyPress) {
|
if (!this._onKeyEvent) {
|
||||||
Log.Debug("onKeyPress " + (e.type == 'keydown' ? "down" : "up") +
|
return;
|
||||||
", keysym: " + e.keysym.keysym + "(" + e.keysym.keyname + ")");
|
}
|
||||||
this._onKeyPress(e);
|
|
||||||
|
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 () {
|
_getKeyCode: function (e) {
|
||||||
this._handler = new KeyboardUtil.QEMUKeyEventDecoder(KeyboardUtil.ModifierSync(),
|
var code = KeyboardUtil.getKeycode(e);
|
||||||
KeyboardUtil.TrackQEMUKeyState(
|
if (code === 'Unidentified') {
|
||||||
this._handleRfbEvent.bind(this)
|
// 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) {
|
_handleKeyDown: function (e) {
|
||||||
if (!this._focused) { return; }
|
if (!this._focused) { return; }
|
||||||
|
|
||||||
if (this._handler.keydown(e)) {
|
var code = this._getKeyCode(e);
|
||||||
// Suppress bubbling/default actions
|
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);
|
stopEvent(e);
|
||||||
} else {
|
return;
|
||||||
// Allow the event to bubble and become a keyPress event which
|
|
||||||
// will have the character code translated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
_handleKeyPress: function (e) {
|
||||||
if (!this._focused) { return; }
|
if (!this._focused) { return; }
|
||||||
|
|
||||||
if (this._handler.keypress(e)) {
|
stopEvent(e);
|
||||||
// Suppress bubbling/default actions
|
|
||||||
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) {
|
_handleKeyUp: function (e) {
|
||||||
if (!this._focused) { return; }
|
if (!this._focused) { return; }
|
||||||
|
|
||||||
if (this._handler.keyup(e)) {
|
stopEvent(e);
|
||||||
// Suppress bubbling/default actions
|
|
||||||
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 () {
|
_allKeysUp: function () {
|
||||||
Log.Debug(">> Keyboard.allKeysUp");
|
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");
|
Log.Debug("<< Keyboard.allKeysUp");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -130,17 +267,13 @@ Keyboard.prototype = {
|
||||||
|
|
||||||
//Log.Debug(">> Keyboard.ungrab");
|
//Log.Debug(">> Keyboard.ungrab");
|
||||||
},
|
},
|
||||||
|
|
||||||
sync: function (e) {
|
|
||||||
this._handler.syncModifiers(e);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
make_properties(Keyboard, [
|
make_properties(Keyboard, [
|
||||||
['target', 'wo', 'dom'], // DOM element that captures keyboard input
|
['target', 'wo', 'dom'], // DOM element that captures keyboard input
|
||||||
['focused', 'rw', 'bool'], // Capture and send key events
|
['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) {
|
const Mouse = function (defaults) {
|
||||||
|
@ -188,10 +321,6 @@ Mouse.prototype = {
|
||||||
_handleMouseButton: function (e, down) {
|
_handleMouseButton: function (e, down) {
|
||||||
if (!this._focused) { return; }
|
if (!this._focused) { return; }
|
||||||
|
|
||||||
if (this._notify) {
|
|
||||||
this._notify(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
var pos = this._getMousePosition(e);
|
var pos = this._getMousePosition(e);
|
||||||
|
|
||||||
var bmask;
|
var bmask;
|
||||||
|
@ -258,10 +387,6 @@ Mouse.prototype = {
|
||||||
_handleMouseWheel: function (e) {
|
_handleMouseWheel: function (e) {
|
||||||
if (!this._focused) { return; }
|
if (!this._focused) { return; }
|
||||||
|
|
||||||
if (this._notify) {
|
|
||||||
this._notify(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
var pos = this._getMousePosition(e);
|
var pos = this._getMousePosition(e);
|
||||||
|
|
||||||
if (this._onMouseButton) {
|
if (this._onMouseButton) {
|
||||||
|
@ -288,10 +413,6 @@ Mouse.prototype = {
|
||||||
_handleMouseMove: function (e) {
|
_handleMouseMove: function (e) {
|
||||||
if (! this._focused) { return; }
|
if (! this._focused) { return; }
|
||||||
|
|
||||||
if (this._notify) {
|
|
||||||
this._notify(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
var pos = this._getMousePosition(e);
|
var pos = this._getMousePosition(e);
|
||||||
if (this._onMouseMove) {
|
if (this._onMouseMove) {
|
||||||
this._onMouseMove(pos.x, pos.y);
|
this._onMouseMove(pos.x, pos.y);
|
||||||
|
@ -383,7 +504,6 @@ Mouse.prototype = {
|
||||||
|
|
||||||
make_properties(Mouse, [
|
make_properties(Mouse, [
|
||||||
['target', 'ro', 'dom'], // DOM element that captures mouse input
|
['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
|
['focused', 'rw', 'bool'], // Capture and send mouse clicks/movement
|
||||||
|
|
||||||
['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release
|
['onMouseButton', 'rw', 'func'], // Handler for mouse button click/release
|
||||||
|
|
|
@ -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;
|
|
@ -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',
|
||||||
|
};
|
|
@ -12,6 +12,37 @@ export default {
|
||||||
XK_Escape: 0xff1b,
|
XK_Escape: 0xff1b,
|
||||||
XK_Delete: 0xffff, /* Delete, rubout */
|
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 */
|
/* Cursor control & motion */
|
||||||
|
|
||||||
XK_Home: 0xff50,
|
XK_Home: 0xff50,
|
||||||
|
@ -171,7 +202,17 @@ export default {
|
||||||
XK_Hyper_L: 0xffed, /* Left hyper */
|
XK_Hyper_L: 0xffed, /* Left hyper */
|
||||||
XK_Hyper_R: 0xffee, /* Right 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_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
|
* Latin 1
|
||||||
|
@ -377,4 +418,197 @@ export default {
|
||||||
XK_yacute: 0x00fd, /* U+00FD LATIN SMALL LETTER Y WITH ACUTE */
|
XK_yacute: 0x00fd, /* U+00FD LATIN SMALL LETTER Y WITH ACUTE */
|
||||||
XK_thorn: 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */
|
XK_thorn: 0x00fe, /* U+00FE LATIN SMALL LETTER THORN */
|
||||||
XK_ydiaeresis: 0x00ff, /* U+00FF LATIN SMALL LETTER Y WITH DIAERESIS */
|
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,
|
||||||
};
|
};
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,660 +1,167 @@
|
||||||
import KeyTable from "./keysym.js";
|
import KeyTable from "./keysym.js";
|
||||||
import keysyms from "./keysymdef.js";
|
import keysyms from "./keysymdef.js";
|
||||||
|
import vkeys from "./vkeys.js";
|
||||||
export function substituteCodepoint(cp) {
|
import fixedkeys from "./fixedkeys.js";
|
||||||
// Any Unicode code points which do not have corresponding keysym entries
|
import DOMKeyTable from "./domkeytable.js";
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isMac() {
|
function isMac() {
|
||||||
return navigator && !!(/mac/i).exec(navigator.platform);
|
return navigator && !!(/mac/i).exec(navigator.platform);
|
||||||
}
|
}
|
||||||
function isWindows() {
|
function isIE() {
|
||||||
return navigator && !!(/win/i).exec(navigator.platform);
|
return navigator && !!(/trident/i).exec(navigator.userAgent);
|
||||||
}
|
}
|
||||||
function isLinux() {
|
function isEdge() {
|
||||||
return navigator && !!(/linux/i).exec(navigator.platform);
|
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
|
// Get 'KeyboardEvent.code', handling legacy browsers
|
||||||
export function hasShortcutModifier(charModifier, currentModifiers) {
|
export function getKeycode(evt){
|
||||||
var mods = {};
|
// Are we getting proper key identifiers?
|
||||||
for (var key in currentModifiers) {
|
// (unfortunately Firefox and Chrome are crappy here and gives
|
||||||
if (parseInt(key) !== KeyTable.XK_Shift_L) {
|
// us an empty string on some platforms, rather than leaving it
|
||||||
mods[key] = currentModifiers[key];
|
// 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';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return evt.code;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sum = 0;
|
// The de-facto standard is to use Windows Virtual-Key codes
|
||||||
for (var k in currentModifiers) {
|
// in the 'keyCode' field for non-printable characters. However
|
||||||
if (mods[k]) {
|
// Webkit sets it to the same as charCode in 'keypress' events.
|
||||||
++sum;
|
if ((evt.type !== 'keypress') && (evt.keyCode in vkeys)) {
|
||||||
|
var code = vkeys[evt.keyCode];
|
||||||
|
|
||||||
|
// macOS has messed up this code for some reason
|
||||||
|
if (isMac() && (code === 'ContextMenu')) {
|
||||||
|
code = 'MetaRight';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
if (hasCharModifier(charModifier, mods)) {
|
|
||||||
return sum > charModifier.length;
|
return 'Unidentified';
|
||||||
}
|
|
||||||
else {
|
|
||||||
return sum > 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return true if the specified char modifier is currently down
|
// Get 'KeyboardEvent.key', handling legacy browsers
|
||||||
export function hasCharModifier(charModifier, currentModifiers) {
|
export function getKey(evt) {
|
||||||
if (charModifier.length === 0) { return false; }
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0; i < charModifier.length; ++i) {
|
// Mozilla isn't fully in sync with the spec yet
|
||||||
if (!currentModifiers[charModifier[i]]) {
|
switch (evt.key) {
|
||||||
return false;
|
case 'OS': return 'Meta';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper object tracking modifier key state
|
// IE and Edge have broken handling of AltGraph so we cannot
|
||||||
// and generates fake key events to compensate if it gets out of sync
|
// trust them for printable characters
|
||||||
export function ModifierSync(charModifier) {
|
if ((evt.key.length !== 1) || (!isIE() && !isEdge())) {
|
||||||
if (!charModifier) {
|
return evt.key;
|
||||||
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];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
charModifier = [];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var state = {};
|
// Try to deduce it based on the physical key
|
||||||
state[KeyTable.XK_Control_L] = false;
|
var code = getKeycode(evt);
|
||||||
state[KeyTable.XK_Alt_L] = false;
|
if (code in fixedkeys) {
|
||||||
state[KeyTable.XK_ISO_Level3_Shift] = false;
|
return fixedkeys[code];
|
||||||
state[KeyTable.XK_Shift_L] = false;
|
|
||||||
state[KeyTable.XK_Meta_L] = false;
|
|
||||||
|
|
||||||
function sync(evt, keysym) {
|
|
||||||
var result = [];
|
|
||||||
function syncKey(keysym) {
|
|
||||||
return {keysym: keysyms.lookup(keysym), type: state[keysym] ? 'keydown' : 'keyup'};
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
return sync(evt, keysym);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
// If that failed, then see if we have a printable character
|
||||||
// sync on the appropriate keyboard event
|
if (evt.charCode) {
|
||||||
keydown: function(evt) { return syncKeyEvent(evt, true);},
|
return String.fromCharCode(evt.charCode);
|
||||||
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?
|
// At this point we have nothing left to go on
|
||||||
hasShortcutModifier: function() { return hasShortcutModifier(charModifier, state); },
|
return 'Unidentified';
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
else if ('keyCode' in evt) {
|
|
||||||
return evt.keyCode;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return evt.key;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the most reliable keysym value we can get from a key event
|
// 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){
|
export function getKeysym(evt){
|
||||||
var codepoint;
|
var key = getKey(evt);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Given a keycode, try to predict which keysym it might be.
|
if (key === 'Unidentified') {
|
||||||
// If the keycode is unknown, null is returned.
|
|
||||||
export function keysymFromKeyCode(keycode, shiftPressed) {
|
|
||||||
if (typeof(keycode) !== 'number') {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// won't be accurate for azerty
|
|
||||||
if (keycode >= 0x30 && keycode <= 0x39) {
|
// First look up special keys
|
||||||
return keycode; // digit
|
if (key in DOMKeyTable) {
|
||||||
}
|
var location = evt.location;
|
||||||
if (keycode >= 0x41 && keycode <= 0x5a) {
|
|
||||||
// remap to lowercase unless shift is down
|
// Safari screws up location for the right cmd key
|
||||||
return shiftPressed ? keycode : keycode + 32; // A-Z
|
if ((key === 'Meta') && (location === 0)) {
|
||||||
}
|
location = 2;
|
||||||
if (keycode >= 0x60 && keycode <= 0x69) {
|
}
|
||||||
return KeyTable.XK_KP_0 + (keycode - 0x60); // numpad 0-9
|
|
||||||
|
if ((location === undefined) || (location > 3)) {
|
||||||
|
location = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DOMKeyTable[key][location];
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(keycode) {
|
// Now we need to look at the Unicode symbol instead
|
||||||
case 0x20: return KeyTable.XK_space;
|
|
||||||
case 0x6a: return KeyTable.XK_KP_Multiply;
|
var codepoint;
|
||||||
case 0x6b: return KeyTable.XK_KP_Add;
|
|
||||||
case 0x6c: return KeyTable.XK_KP_Separator;
|
// Special key? (FIXME: Should have been caught earlier)
|
||||||
case 0x6d: return KeyTable.XK_KP_Subtract;
|
if (key.length !== 1) {
|
||||||
case 0x6e: return KeyTable.XK_KP_Decimal;
|
return null;
|
||||||
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});
|
codepoint = key.charCodeAt();
|
||||||
|
if (codepoint) {
|
||||||
|
return keysyms.lookup(codepoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function process(evt, type) {
|
|
||||||
var result = {type: type};
|
|
||||||
result.code = evt.code;
|
|
||||||
result.keysym = 0;
|
|
||||||
|
|
||||||
if (isNumPadMultiKey(evt)) {
|
|
||||||
result.keysym = getNumPadKeySym(evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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';
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
return suppress;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 */
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
|
@ -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
|
||||||
|
};
|
|
@ -112,8 +112,6 @@ export default {
|
||||||
"Delete": 0xE053,
|
"Delete": 0xE053,
|
||||||
"MetaLeft": 0xE05B,
|
"MetaLeft": 0xE05B,
|
||||||
"MetaRight": 0xE05C,
|
"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,
|
"ContextMenu": 0xE05D,
|
||||||
"BrowserSearch": 0xE065,
|
"BrowserSearch": 0xE065,
|
||||||
"BrowserFavorites": 0xE066,
|
"BrowserFavorites": 0xE066,
|
||||||
|
|
66
core/rfb.js
66
core/rfb.js
|
@ -201,12 +201,11 @@ export default function RFB(defaults) {
|
||||||
}
|
}
|
||||||
|
|
||||||
this._keyboard = new Keyboard({target: this._focusContainer,
|
this._keyboard = new Keyboard({target: this._focusContainer,
|
||||||
onKeyPress: this._handleKeyPress.bind(this)});
|
onKeyEvent: this._handleKeyEvent.bind(this)});
|
||||||
|
|
||||||
this._mouse = new Mouse({target: this._target,
|
this._mouse = new Mouse({target: this._target,
|
||||||
onMouseButton: this._handleMouseButton.bind(this),
|
onMouseButton: this._handleMouseButton.bind(this),
|
||||||
onMouseMove: this._handleMouseMove.bind(this),
|
onMouseMove: this._handleMouseMove.bind(this)});
|
||||||
notify: this._keyboard.sync.bind(this._keyboard)});
|
|
||||||
|
|
||||||
this._sock = new Websock();
|
this._sock = new Websock();
|
||||||
this._sock.on('message', this._handle_message.bind(this));
|
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; }
|
if (this._rfb_connection_state !== 'connected' || this._view_only) { return false; }
|
||||||
Log.Info("Sending Ctrl-Alt-Del");
|
Log.Info("Sending Ctrl-Alt-Del");
|
||||||
|
|
||||||
RFB.messages.keyEvent(this._sock, KeyTable.XK_Control_L, 1);
|
this.sendKey(KeyTable.XK_Control_L, "ControlLeft", true);
|
||||||
RFB.messages.keyEvent(this._sock, KeyTable.XK_Alt_L, 1);
|
this.sendKey(KeyTable.XK_Alt_L, "AltLeft", true);
|
||||||
RFB.messages.keyEvent(this._sock, KeyTable.XK_Delete, 1);
|
this.sendKey(KeyTable.XK_Delete, "Delete", true);
|
||||||
RFB.messages.keyEvent(this._sock, KeyTable.XK_Delete, 0);
|
this.sendKey(KeyTable.XK_Delete, "Delete", false);
|
||||||
RFB.messages.keyEvent(this._sock, KeyTable.XK_Alt_L, 0);
|
this.sendKey(KeyTable.XK_Alt_L, "AltLeft", false);
|
||||||
RFB.messages.keyEvent(this._sock, KeyTable.XK_Control_L, 0);
|
this.sendKey(KeyTable.XK_Control_L, "ControlLeft", false);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -329,16 +329,33 @@ RFB.prototype = {
|
||||||
|
|
||||||
// Send a key press. If 'down' is not specified then send a down key
|
// Send a key press. If 'down' is not specified then send a down key
|
||||||
// followed by an up 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 (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);
|
Log.Info("Sending keysym (" + (down ? "down" : "up") + "): " + keysym);
|
||||||
RFB.messages.keyEvent(this._sock, keysym, down ? 1 : 0);
|
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;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -647,22 +664,8 @@ RFB.prototype = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_handleKeyPress: function (keyevent) {
|
_handleKeyEvent: function (keysym, code, down) {
|
||||||
if (this._view_only) { return; } // View only, skip keyboard, events
|
this.sendKey(keysym, code, down);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_handleMouseButton: function (x, y, down, bmask) {
|
_handleMouseButton: function (x, y, down, bmask) {
|
||||||
|
@ -2461,7 +2464,6 @@ RFB.encodingHandlers = {
|
||||||
var keyboardEvent = document.createEvent("keyboardEvent");
|
var keyboardEvent = document.createEvent("keyboardEvent");
|
||||||
if (keyboardEvent.code !== undefined) {
|
if (keyboardEvent.code !== undefined) {
|
||||||
this._qemuExtKeyEventSupported = true;
|
this._qemuExtKeyEventSupported = true;
|
||||||
this._keyboard.setQEMUVNCKeyboardHandler();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
<script src="../core/input/keysym.js"></script>
|
<script src="../core/input/keysym.js"></script>
|
||||||
<script src="../core/input/keysymdef.js"></script>
|
<script src="../core/input/keysymdef.js"></script>
|
||||||
<script src="../core/input/xtscancodes.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/util.js"></script>
|
||||||
<script src="../core/input/devices.js"></script>
|
<script src="../core/input/devices.js"></script>
|
||||||
<script src="../core/display.js"></script>
|
<script src="../core/display.js"></script>
|
||||||
|
@ -61,13 +62,9 @@
|
||||||
//console.log(msg);
|
//console.log(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
function rfbKeyPress(keysym, down) {
|
function rfbKeyEvent(keysym, code, down) {
|
||||||
var d = down ? "down" : " up ";
|
var d = down ? "down" : " up ";
|
||||||
var key = keysyms.lookup(keysym);
|
var msg = "RFB key event " + d + " keysym: " + keysym + " code: " + code;
|
||||||
var msg = "RFB keypress " + d + " keysym: " + keysym;
|
|
||||||
if (key && key.keyname) {
|
|
||||||
msg += " key name: " + key.keyname;
|
|
||||||
}
|
|
||||||
message(msg);
|
message(msg);
|
||||||
}
|
}
|
||||||
function rawKey(e) {
|
function rawKey(e) {
|
||||||
|
@ -106,7 +103,7 @@
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
canvas = new Display({'target' : document.getElementById('canvas')});
|
canvas = new Display({'target' : document.getElementById('canvas')});
|
||||||
keyboard = new Keyboard({'target': document,
|
keyboard = new Keyboard({'target': document,
|
||||||
'onKeyPress': rfbKeyPress});
|
'onKeyEvent': rfbKeyEvent});
|
||||||
document.addEventListener('keypress', rawKey);
|
document.addEventListener('keypress', rawKey);
|
||||||
document.addEventListener('keydown', rawKey);
|
document.addEventListener('keydown', rawKey);
|
||||||
document.addEventListener('keyup', rawKey);
|
document.addEventListener('keyup', rawKey);
|
||||||
|
|
|
@ -6,261 +6,213 @@ import * as KeyboardUtil from "../core/input/util.js";
|
||||||
|
|
||||||
describe('Helpers', function() {
|
describe('Helpers', function() {
|
||||||
"use strict";
|
"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() {
|
it('should map ASCII characters to keysyms', function() {
|
||||||
expect(keysyms.fromUnicode('a'.charCodeAt())).to.have.property('keysym', 0x61);
|
expect(keysyms.lookup('a'.charCodeAt())).to.be.equal(0x61);
|
||||||
expect(keysyms.fromUnicode('A'.charCodeAt())).to.have.property('keysym', 0x41);
|
expect(keysyms.lookup('A'.charCodeAt())).to.be.equal(0x41);
|
||||||
});
|
});
|
||||||
it('should map Latin-1 characters to keysyms', function() {
|
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() {
|
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() {
|
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() {
|
it('should map unknown codepoints to the Unicode range', function() {
|
||||||
expect(keysyms.fromUnicode('\n'.charCodeAt())).to.have.property('keysym', 0x100000a);
|
expect(keysyms.lookup('\n'.charCodeAt())).to.be.equal(0x100000a);
|
||||||
expect(keysyms.fromUnicode('\u262D'.charCodeAt())).to.have.property('keysym', 0x100262d);
|
expect(keysyms.lookup('\u262D'.charCodeAt())).to.be.equal(0x100262d);
|
||||||
});
|
});
|
||||||
// This requires very recent versions of most browsers... skipping for now
|
// This requires very recent versions of most browsers... skipping for now
|
||||||
it.skip('should map UCS-4 codepoints to the Unicode range', function() {
|
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() {
|
describe('getKeycode', function() {
|
||||||
it('should replace characters which don\'t have a keysym', function() {
|
it('should pass through proper code', function() {
|
||||||
expect(KeyboardUtil.substituteCodepoint('Ș'.charCodeAt())).to.equal('Ş'.charCodeAt());
|
expect(KeyboardUtil.getKeycode({code: 'Semicolon'})).to.be.equal('Semicolon');
|
||||||
expect(KeyboardUtil.substituteCodepoint('ș'.charCodeAt())).to.equal('ş'.charCodeAt());
|
|
||||||
expect(KeyboardUtil.substituteCodepoint('Ț'.charCodeAt())).to.equal('Ţ'.charCodeAt());
|
|
||||||
expect(KeyboardUtil.substituteCodepoint('ț'.charCodeAt())).to.equal('ţ'.charCodeAt());
|
|
||||||
});
|
});
|
||||||
it('should pass other characters through unchanged', function() {
|
it('should map legacy values', function() {
|
||||||
expect(KeyboardUtil.substituteCodepoint('T'.charCodeAt())).to.equal('T'.charCodeAt());
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
|
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('nonCharacterKey', function() {
|
describe('getKey', function() {
|
||||||
it('should recognize the right keys', function() {
|
it('should prefer key', function() {
|
||||||
expect(KeyboardUtil.nonCharacterKey({keyCode: 0xd}), 'enter').to.be.defined;
|
expect(KeyboardUtil.getKey({key: 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.be.equal('a');
|
||||||
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() {
|
it('should map legacy values', function() {
|
||||||
expect(KeyboardUtil.nonCharacterKey({keyCode: 'A'}), 'A').to.be.null;
|
expect(KeyboardUtil.getKey({key: 'Spacebar'})).to.be.equal(' ');
|
||||||
expect(KeyboardUtil.nonCharacterKey({keyCode: '1'}), '1').to.be.null;
|
expect(KeyboardUtil.getKey({key: 'Left'})).to.be.equal('ArrowLeft');
|
||||||
expect(KeyboardUtil.nonCharacterKey({keyCode: '.'}), '.').to.be.null;
|
expect(KeyboardUtil.getKey({key: 'OS'})).to.be.equal('Meta');
|
||||||
expect(KeyboardUtil.nonCharacterKey({keyCode: ' '}), 'space').to.be.null;
|
expect(KeyboardUtil.getKey({key: 'Win'})).to.be.equal('Meta');
|
||||||
|
});
|
||||||
|
it('should use code if no key', function() {
|
||||||
|
expect(KeyboardUtil.getKey({code: 'NumpadBackspace'})).to.be.equal('Backspace');
|
||||||
|
});
|
||||||
|
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 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('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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
afterEach(function () {
|
||||||
|
Object.defineProperty(window, "navigator", origNavigator);
|
||||||
|
});
|
||||||
|
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
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 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');
|
||||||
|
});
|
||||||
|
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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getKeysym', function() {
|
describe('getKeysym', function() {
|
||||||
it('should prefer char', function() {
|
describe('Non-character keys', function() {
|
||||||
expect(KeyboardUtil.getKeysym({char : 'a', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x61);
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
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 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 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 substitute where applicable', function() {
|
|
||||||
expect(KeyboardUtil.getKeysym({char : 'Ș'})).to.have.property('keysym', 0x1aa);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Modifier Sync', function() { // return a list of fake events necessary to fix modifier state
|
describe('Numpad', function() {
|
||||||
describe('Toggle all modifiers', function() {
|
it('should handle Numpad numbers', function() {
|
||||||
var sync = KeyboardUtil.ModifierSync();
|
expect(KeyboardUtil.getKeysym({code: 'Digit5', key: '5', location: 0})).to.be.equal(0x0035);
|
||||||
it ('should do nothing if all modifiers are up as expected', function() {
|
expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: '5', location: 3})).to.be.equal(0xFFB5);
|
||||||
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() {
|
it('should handle Numpad non-character keys', function() {
|
||||||
var result = sync.keydown({
|
expect(KeyboardUtil.getKeysym({code: 'Home', key: 'Home', location: 0})).to.be.equal(0xFF50);
|
||||||
keyCode: 0x41,
|
expect(KeyboardUtil.getKeysym({code: 'Numpad5', key: 'Home', location: 3})).to.be.equal(0xFF95);
|
||||||
ctrlKey: true,
|
expect(KeyboardUtil.getKeysym({code: 'Delete', key: 'Delete', location: 0})).to.be.equal(0xFFFF);
|
||||||
altKey: true,
|
expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: 'Delete', location: 3})).to.be.equal(0xFF9F);
|
||||||
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');
|
|
||||||
}
|
|
||||||
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() {
|
it('should handle Numpad Decimal key', function() {
|
||||||
expect(sync.keydown({
|
expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: '.', location: 3})).to.be.equal(0xFFAE);
|
||||||
keyCode: 0x41,
|
expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: ',', location: 3})).to.be.equal(0xFFAC);
|
||||||
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'}]);
|
|
||||||
});
|
|
||||||
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'}]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
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 sync if modifier is suddenly up', function() {
|
|
||||||
expect(sync.keydown({
|
|
||||||
keyCode: 0x41,
|
|
||||||
metaKey: false
|
|
||||||
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe7), type: 'keyup'}]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
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 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('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;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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 () {
|
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 () {}};
|
var expected = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}};
|
||||||
RFB.messages.keyEvent(expected, 123, 1);
|
RFB.messages.keyEvent(expected, 123, 1);
|
||||||
client.sendKey(123, true);
|
client.sendKey(123, 'Key123', true);
|
||||||
expect(client._sock).to.have.sent(expected._sQ);
|
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 () {}};
|
var expected = {_sQ: new Uint8Array(16), _sQlen: 0, flush: function () {}};
|
||||||
RFB.messages.keyEvent(expected, 123, 1);
|
RFB.messages.keyEvent(expected, 123, 1);
|
||||||
RFB.messages.keyEvent(expected, 123, 0);
|
RFB.messages.keyEvent(expected, 123, 0);
|
||||||
client.sendKey(123);
|
client.sendKey(123, 'Key123');
|
||||||
expect(client._sock).to.have.sent(expected._sQ);
|
expect(client._sock).to.have.sent(expected._sQ);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not send the key if we are not in a normal state', function () {
|
it('should not send the key if we are not in a normal state', function () {
|
||||||
client._rfb_connection_state = "broken";
|
client._rfb_connection_state = "broken";
|
||||||
client.sendKey(123);
|
client.sendKey(123, 'Key123');
|
||||||
expect(client._sock.flush).to.not.have.been.called;
|
expect(client._sock.flush).to.not.have.been.called;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not send the key if we are set as view_only', function () {
|
it('should not send the key if we are set as view_only', function () {
|
||||||
client._view_only = true;
|
client._view_only = true;
|
||||||
client.sendKey(123);
|
client.sendKey(123, 'Key123');
|
||||||
expect(client._sock.flush).to.not.have.been.called;
|
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 () {
|
describe('#clipboardPasteFrom', function () {
|
||||||
|
@ -2017,22 +2025,21 @@ describe('Remote Frame Buffer Protocol Client', function() {
|
||||||
client._sock.open('ws://', 'binary');
|
client._sock.open('ws://', 'binary');
|
||||||
client._sock._websocket._open();
|
client._sock._websocket._open();
|
||||||
sinon.spy(client._sock, 'flush');
|
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 () {
|
it('should send a key message on a key press', function () {
|
||||||
var keyevent = {};
|
var keyevent = {};
|
||||||
keyevent.type = 'keydown';
|
client._keyboard._onKeyEvent(0x41, 'KeyA', true);
|
||||||
keyevent.keysym = {};
|
|
||||||
keyevent.keysym.keysym = 1234;
|
|
||||||
client._keyboard._onKeyPress(keyevent);
|
|
||||||
var key_msg = {_sQ: new Uint8Array(8), _sQlen: 0, flush: function () {}};
|
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);
|
expect(client._sock).to.have.sent(key_msg._sQ);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not send messages in view-only mode', function () {
|
it('should not send messages in view-only mode', function () {
|
||||||
client._view_only = true;
|
client._view_only = true;
|
||||||
client._keyboard._onKeyPress(1234, 1);
|
client._keyboard._onKeyEvent('a', 'KeyA', true);
|
||||||
expect(client._sock.flush).to.not.have.been.called;
|
expect(client._sock.flush).to.not.have.been.called;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -50,7 +50,8 @@
|
||||||
WebUtil.load_scripts({
|
WebUtil.load_scripts({
|
||||||
'core': ["base64.js", "websock.js", "des.js", "input/keysym.js",
|
'core': ["base64.js", "websock.js", "des.js", "input/keysym.js",
|
||||||
"input/keysymdef.js", "input/xtscancodes.js", "input/util.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"],
|
'tests': ["playback.js"],
|
||||||
'recordings': [fname]});
|
'recordings': [fname]});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -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);
|
103
utils/parse.js
103
utils/parse.js
|
@ -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);
|
|
Loading…
Reference in New Issue