This commit is contained in:
Pierre Ossman 2017-05-04 12:18:55 +02:00
commit 545442afc3
18 changed files with 2445 additions and 1886 deletions

View File

@ -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));

View File

@ -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");
} }
}, },

View File

@ -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);
stopEvent(e);
} else { // We cannot handle keys we cannot track, but we also need
// Allow the event to bubble and become a keyPress event which // to deal with virtual keyboards which omit key info
// will have the character code translated 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);
return;
}
// Alt behaves more like AltGraph on macOS, so shuffle the
// keys around a bit to make things more sane for the remote
// server. This method is used by RealVNC and TigerVNC (and
// possibly others).
if (isMac()) {
switch (keysym) {
case KeyTable.XK_Super_L:
keysym = KeyTable.XK_Alt_L;
break;
case KeyTable.XK_Super_R:
keysym = KeyTable.XK_Super_L;
break;
case KeyTable.XK_Alt_L:
keysym = KeyTable.XK_Mode_switch;
break;
case KeyTable.XK_Alt_R:
keysym = KeyTable.XK_ISO_Level3_Shift;
break;
}
}
// Is this key already pressed? If so, then we must use the
// same keysym or we'll confuse the server
if (code in this._keyDownList) {
keysym = this._keyDownList[code];
}
// macOS doesn't send proper key events for modifiers, only
// state change events. That gets extra confusing for CapsLock
// which toggles on each press, but not on release. So pretend
// it was a quick press and release of the button.
if (isMac() && (code === 'CapsLock')) {
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
stopEvent(e);
return;
}
// If this is a legacy browser then we'll need to wait for
// a keypress event as well
if (!keysym) {
this._pendingKey = code;
return;
}
this._pendingKey = null;
stopEvent(e);
this._keyDownList[code] = keysym;
this._sendKeyEvent(keysym, code, true);
}, },
// Legacy event for browsers without code/key
_handleKeyPress: function (e) { _handleKeyPress: function (e) {
if (!this._focused) { return; } if (!this._focused) { return; }
if (this._handler.keypress(e)) {
// Suppress bubbling/default actions
stopEvent(e); stopEvent(e);
// Are we expecting a keypress?
if (this._pendingKey === null) {
return;
} }
var code = this._getKeyCode(e);
var keysym = KeyboardUtil.getKeysym(e);
// The key we were waiting for?
if ((code !== 'Unidentified') && (code != this._pendingKey)) {
return;
}
code = this._pendingKey;
this._pendingKey = null;
if (!keysym) {
console.log('keypress with no keysym:', e);
return;
}
this._keyDownList[code] = keysym;
this._sendKeyEvent(keysym, code, true);
}, },
_handleKeyUp: function (e) { _handleKeyUp: function (e) {
if (!this._focused) { return; } if (!this._focused) { return; }
if (this._handler.keyup(e)) {
// Suppress bubbling/default actions
stopEvent(e); stopEvent(e);
var code = this._getKeyCode(e);
// See comment in _handleKeyDown()
if (isMac() && (code === 'CapsLock')) {
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
return;
} }
// Do we really think this key is down?
if (!(code in this._keyDownList)) {
return;
}
this._sendKeyEvent(this._keyDownList[code], code, false);
delete this._keyDownList[code];
}, },
_allKeysUp: function () { _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

310
core/input/domkeytable.js Normal file
View File

@ -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;

127
core/input/fixedkeys.js Normal file
View File

@ -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',
};

View File

@ -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

View File

@ -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;
}
// The de-facto standard is to use Windows Virtual-Key codes
// in the 'keyCode' field for non-printable characters. However
// Webkit sets it to the same as charCode in 'keypress' events.
if ((evt.type !== 'keypress') && (evt.keyCode in vkeys)) {
var code = vkeys[evt.keyCode];
// 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';
} }
} }
var sum = 0; // Nor a bunch of the numpad keys
for (var k in currentModifiers) { if (evt.location === 3) {
if (mods[k]) { switch (code) {
++sum; 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';
} }
} }
if (hasCharModifier(charModifier, mods)) {
return sum > charModifier.length; return code;
}
else {
return sum > 0;
} }
return 'Unidentified';
} }
// 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) {
for (var i = 0; i < charModifier.length; ++i) { // IE and Edge use some ancient version of the spec
if (!currentModifiers[charModifier[i]]) { // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8860571/
return false; switch (evt.key) {
} case 'Spacebar': return ' ';
} case 'Esc': return 'Escape';
return true; case 'Scroll': return 'ScrollLock';
} case 'Win': return 'Meta';
case 'Apps': return 'ContextMenu';
// Helper object tracking modifier key state case 'Up': return 'ArrowUp';
// and generates fake key events to compensate if it gets out of sync case 'Left': return 'ArrowLeft';
export function ModifierSync(charModifier) { case 'Right': return 'ArrowRight';
if (!charModifier) { case 'Down': return 'ArrowDown';
if (isMac()) { case 'Del': return 'Delete';
// on Mac, Option (AKA Alt) is used as a char modifier case 'Divide': return '/';
charModifier = [KeyTable.XK_Alt_L]; case 'Multiply': return '*';
} case 'Subtract': return '-';
else if (isWindows()) { case 'Add': return '+';
// on Windows, Ctrl+Alt is used as a char modifier case 'Decimal': return evt.char;
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 = {}; // Mozilla isn't fully in sync with the spec yet
state[KeyTable.XK_Control_L] = false; switch (evt.key) {
state[KeyTable.XK_Alt_L] = false; case 'OS': return 'Meta';
state[KeyTable.XK_ISO_Level3_Shift] = false;
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 && // IE and Edge have broken handling of AltGraph so we cannot
evt.ctrlKey !== state[KeyTable.XK_Control_L] && keysym !== KeyTable.XK_Control_L) { // trust them for printable characters
state[KeyTable.XK_Control_L] = evt.ctrlKey; if ((evt.key.length !== 1) || (!isIE() && !isEdge())) {
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 {
// sync on the appropriate keyboard event
keydown: function(evt) { return syncKeyEvent(evt, true);},
keyup: function(evt) { return syncKeyEvent(evt, false);},
// Call this with a non-keyboard event (such as mouse events) to use its modifier state to synchronize anyway
syncAny: function(evt) { return sync(evt);},
// is a shortcut modifier down?
hasShortcutModifier: function() { return hasShortcutModifier(charModifier, state); },
// if a char modifier is down, return the keys it consists of, otherwise return null
activeCharModifier: function() { return hasCharModifier(charModifier, state) ? charModifier : null; }
};
}
// Get a key ID from a keyboard event
// May be a string or an integer depending on the available properties
export function getKey(evt){
if ('keyCode' in evt && 'key' in evt) {
return evt.key + ':' + evt.keyCode;
}
else if ('keyCode' in evt) {
return evt.keyCode;
}
else {
return evt.key; return evt.key;
} }
}
// Try to deduce it based on the physical key
var code = getKeycode(evt);
if (code in fixedkeys) {
return fixedkeys[code];
}
// If that failed, then see if we have a printable character
if (evt.charCode) {
return String.fromCharCode(evt.charCode);
}
// At this point we have nothing left to go on
return 'Unidentified';
} }
// Get the most reliable keysym value we can get from a key event // 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 key = getKey(evt);
if (key === 'Unidentified') {
return null;
}
// First look up special keys
if (key in DOMKeyTable) {
var location = evt.location;
// Safari screws up location for the right cmd key
if ((key === 'Meta') && (location === 0)) {
location = 2;
}
if ((location === undefined) || (location > 3)) {
location = 0;
}
return DOMKeyTable[key][location];
}
// Now we need to look at the Unicode symbol instead
var codepoint; var codepoint;
if (evt.char && evt.char.length === 1) {
codepoint = evt.char.charCodeAt(); // Special key? (FIXME: Should have been caught earlier)
} if (key.length !== 1) {
else if (evt.charCode) { return null;
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;
} }
codepoint = key.charCodeAt();
if (codepoint) { if (codepoint) {
return keysyms.fromUnicode(substituteCodepoint(codepoint)); return keysyms.lookup(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; return null;
} }
// Given a keycode, try to predict which keysym it might be.
// If the keycode is unknown, null is returned.
export function keysymFromKeyCode(keycode, shiftPressed) {
if (typeof(keycode) !== 'number') {
return null;
}
// won't be accurate for azerty
if (keycode >= 0x30 && keycode <= 0x39) {
return keycode; // digit
}
if (keycode >= 0x41 && keycode <= 0x5a) {
// remap to lowercase unless shift is down
return shiftPressed ? keycode : keycode + 32; // A-Z
}
if (keycode >= 0x60 && keycode <= 0x69) {
return KeyTable.XK_KP_0 + (keycode - 0x60); // numpad 0-9
}
switch(keycode) {
case 0x20: return KeyTable.XK_space;
case 0x6a: return KeyTable.XK_KP_Multiply;
case 0x6b: return KeyTable.XK_KP_Add;
case 0x6c: return KeyTable.XK_KP_Separator;
case 0x6d: return KeyTable.XK_KP_Subtract;
case 0x6e: return KeyTable.XK_KP_Decimal;
case 0x6f: return KeyTable.XK_KP_Divide;
case 0xbb: return KeyTable.XK_plus;
case 0xbc: return KeyTable.XK_comma;
case 0xbd: return KeyTable.XK_minus;
case 0xbe: return KeyTable.XK_period;
}
return nonCharacterKey({keyCode: keycode});
}
// if the key is a known non-character key (any key which doesn't generate character data)
// return its keysym value. Otherwise return null
export function nonCharacterKey(evt) {
// evt.key not implemented yet
if (!evt.keyCode) { return null; }
var keycode = evt.keyCode;
if (keycode >= 0x70 && keycode <= 0x87) {
return KeyTable.XK_F1 + keycode - 0x70; // F1-F24
}
switch (keycode) {
case 8 : return KeyTable.XK_BackSpace;
case 13 : return KeyTable.XK_Return;
case 9 : return KeyTable.XK_Tab;
case 27 : return KeyTable.XK_Escape;
case 46 : return KeyTable.XK_Delete;
case 36 : return KeyTable.XK_Home;
case 35 : return KeyTable.XK_End;
case 33 : return KeyTable.XK_Page_Up;
case 34 : return KeyTable.XK_Page_Down;
case 45 : return KeyTable.XK_Insert;
case 37 : return KeyTable.XK_Left;
case 38 : return KeyTable.XK_Up;
case 39 : return KeyTable.XK_Right;
case 40 : return KeyTable.XK_Down;
case 16 : return KeyTable.XK_Shift_L;
case 17 : return KeyTable.XK_Control_L;
case 18 : return KeyTable.XK_Alt_L; // also: Option-key on Mac
case 224 : return KeyTable.XK_Meta_L;
case 225 : return KeyTable.XK_ISO_Level3_Shift; // AltGr
case 91 : return KeyTable.XK_Super_L; // also: Windows-key
case 92 : return KeyTable.XK_Super_R; // also: Windows-key
case 93 : return KeyTable.XK_Menu; // also: Windows-Menu, Command on Mac
default: return null;
}
}
export function QEMUKeyEventDecoder (modifierState, next) {
"use strict";
function sendAll(evts) {
for (var i = 0; i < evts.length; ++i) {
next(evts[i]);
}
}
var numPadCodes = ["Numpad0", "Numpad1", "Numpad2",
"Numpad3", "Numpad4", "Numpad5", "Numpad6",
"Numpad7", "Numpad8", "Numpad9", "NumpadDecimal"];
var numLockOnKeySyms = {
"Numpad0": 0xffb0, "Numpad1": 0xffb1, "Numpad2": 0xffb2,
"Numpad3": 0xffb3, "Numpad4": 0xffb4, "Numpad5": 0xffb5,
"Numpad6": 0xffb6, "Numpad7": 0xffb7, "Numpad8": 0xffb8,
"Numpad9": 0xffb9, "NumpadDecimal": 0xffac
};
var numLockOnKeyCodes = [96, 97, 98, 99, 100, 101, 102,
103, 104, 105, 108, 110];
function isNumPadMultiKey(evt) {
return (numPadCodes.indexOf(evt.code) !== -1);
}
function getNumPadKeySym(evt) {
if (numLockOnKeyCodes.indexOf(evt.keyCode) !== -1) {
return numLockOnKeySyms[evt.code];
}
return 0;
}
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 */
};
};

116
core/input/vkeys.js Normal file
View File

@ -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
};

View File

@ -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,

View File

@ -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();
} }
}, },

View File

@ -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);

View File

@ -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()); it('should map legacy values', function() {
expect(KeyboardUtil.substituteCodepoint('ț'.charCodeAt())).to.equal('ţ'.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');
}); });
it('should pass other characters through unchanged', function() {
expect(KeyboardUtil.substituteCodepoint('T'.charCodeAt())).to.equal('T'.charCodeAt());
}); });
}); });
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; it('should map legacy values', function() {
expect(KeyboardUtil.nonCharacterKey({keyCode: 0x10}), 'shift').to.be.defined; expect(KeyboardUtil.getKey({key: 'Spacebar'})).to.be.equal(' ');
expect(KeyboardUtil.nonCharacterKey({keyCode: 0x11}), 'ctrl').to.be.defined; expect(KeyboardUtil.getKey({key: 'Left'})).to.be.equal('ArrowLeft');
expect(KeyboardUtil.nonCharacterKey({keyCode: 0x12}), 'alt').to.be.defined; expect(KeyboardUtil.getKey({key: 'OS'})).to.be.equal('Meta');
expect(KeyboardUtil.nonCharacterKey({keyCode: 0xe0}), 'meta').to.be.defined; 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');
}); });
it('should not recognize character keys', function() {
expect(KeyboardUtil.nonCharacterKey({keyCode: 'A'}), 'A').to.be.null;
expect(KeyboardUtil.nonCharacterKey({keyCode: '1'}), '1').to.be.null;
expect(KeyboardUtil.nonCharacterKey({keyCode: '.'}), '.').to.be.null;
expect(KeyboardUtil.nonCharacterKey({keyCode: ' '}), 'space').to.be.null;
}); });
}); });
describe('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 use charCode if no char', function() { it('should map left/right side', function() {
expect(KeyboardUtil.getKeysym({char : '', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x01a9); expect(KeyboardUtil.getKeysym({key: 'Shift', location: 1})).to.be.equal(0xFFE1);
expect(KeyboardUtil.getKeysym({charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x01a9); expect(KeyboardUtil.getKeysym({key: 'Shift', location: 2})).to.be.equal(0xFFE2);
expect(KeyboardUtil.getKeysym({char : 'hello', charCode: 'Š'.charCodeAt(), keyCode: 0x42, which: 0x43})).to.have.property('keysym', 0x01a9); expect(KeyboardUtil.getKeysym({key: 'Control', location: 1})).to.be.equal(0xFFE3);
expect(KeyboardUtil.getKeysym({key: 'Control', location: 2})).to.be.equal(0xFFE4);
}); });
it('should use keyCode if no charCode', function() { it('should handle AltGraph', function() {
expect(KeyboardUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: false})).to.have.property('keysym', 0x62); expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'Alt', location: 2})).to.be.equal(0xFFEA);
expect(KeyboardUtil.getKeysym({keyCode: 0x42, which: 0x43, shiftKey: true})).to.have.property('keysym', 0x42); expect(KeyboardUtil.getKeysym({code: 'AltRight', key: 'AltGraph', location: 2})).to.be.equal(0xFE03);
}); });
it('should use which if no keyCode', function() { it('should return null for unknown keys', function() {
expect(KeyboardUtil.getKeysym({which: 0x43, shiftKey: false})).to.have.property('keysym', 0x63); expect(KeyboardUtil.getKeysym({key: 'Semicolon'})).to.be.null;
expect(KeyboardUtil.getKeysym({which: 0x43, shiftKey: true})).to.have.property('keysym', 0x43); expect(KeyboardUtil.getKeysym({key: 'BracketRight'})).to.be.null;
}); });
it('should substitute where applicable', function() { it('should handle remappings', function() {
expect(KeyboardUtil.getKeysym({char : 'Ș'})).to.have.property('keysym', 0x1aa); expect(KeyboardUtil.getKeysym({code: 'ControlLeft', key: 'Tab'})).to.be.equal(0xFF09);
}); });
}); });
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); it('should handle Numpad Decimal key', function() {
var keysyms = {}; expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: '.', location: 3})).to.be.equal(0xFFAE);
for (var i = 0; i < result.length; ++i) { expect(KeyboardUtil.getKeysym({code: 'NumpadDecimal', key: ',', location: 3})).to.be.equal(0xFFAC);
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() {
expect(sync.keydown({
keyCode: 0x41,
ctrlKey: true,
altKey: true,
altGraphKey: true,
shiftKey: true,
metaKey: true
})).to.have.lengthOf(0);
});
});
describe('Toggle Ctrl', function() {
var sync = KeyboardUtil.ModifierSync();
it('should sync if modifier is suddenly down', function() {
expect(sync.keydown({
keyCode: 0x41,
ctrlKey: true,
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe3), type: 'keydown'}]);
});
it('should sync if modifier is suddenly up', function() {
expect(sync.keydown({
keyCode: 0x41,
ctrlKey: false
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe3), type: 'keyup'}]);
});
});
describe('Toggle Alt', function() {
var sync = KeyboardUtil.ModifierSync();
it('should sync if modifier is suddenly down', function() {
expect(sync.keydown({
keyCode: 0x41,
altKey: true,
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keydown'}]);
});
it('should sync if modifier is suddenly up', function() {
expect(sync.keydown({
keyCode: 0x41,
altKey: false
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe9), type: 'keyup'}]);
});
});
describe('Toggle AltGr', function() {
var sync = KeyboardUtil.ModifierSync();
it('should sync if modifier is suddenly down', function() {
expect(sync.keydown({
keyCode: 0x41,
altGraphKey: true,
})).to.be.deep.equal([{keysym: keysyms.lookup(0xfe03), type: 'keydown'}]);
});
it('should sync if modifier is suddenly up', function() {
expect(sync.keydown({
keyCode: 0x41,
altGraphKey: false
})).to.be.deep.equal([{keysym: keysyms.lookup(0xfe03), type: 'keyup'}]);
});
});
describe('Toggle Shift', function() {
var sync = KeyboardUtil.ModifierSync();
it('should sync if modifier is suddenly down', function() {
expect(sync.keydown({
keyCode: 0x41,
shiftKey: true,
})).to.be.deep.equal([{keysym: keysyms.lookup(0xffe1), type: 'keydown'}]);
});
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

View File

@ -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;
}); });
}); });

View File

@ -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 {

128
utils/genkeysymdef.js Executable file
View File

@ -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);

View File

@ -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);