JavaScript 65.5%
Python 17.8%
CSS 13%
HTML 3.7%
1/*2 * noVNC: HTML5 VNC client3 * Copyright (C) 2019 The noVNC authors4 * Licensed under MPL 2.0 or any later version (see LICENSE.txt)5 */67import * as Log from '../util/logging.js';8import { stopEvent } from '../util/events.js';9import * as KeyboardUtil from "./util.js";10import KeyTable from "./keysym.js";11import * as browser from "../util/browser.js";1213//14// Keyboard event handler15//1617export default class Keyboard {18 constructor(target) {19 this._target = target || null;2021 this._keyDownList = {}; // List of depressed keys22 // (even if they are happy)23 this._altGrArmed = false; // Windows AltGr detection2425 // keep these here so we can refer to them later26 this._eventHandlers = {27 'keyup': this._handleKeyUp.bind(this),28 'keydown': this._handleKeyDown.bind(this),29 'blur': this._allKeysUp.bind(this),30 };3132 // ===== EVENT HANDLERS =====3334 this.onkeyevent = () => {}; // Handler for key press/release35 }3637 // ===== PRIVATE METHODS =====3839 _sendKeyEvent(keysym, code, down, numlock = null, capslock = null) {40 if (down) {41 this._keyDownList[code] = keysym;42 } else {43 // Do we really think this key is down?44 if (!(code in this._keyDownList)) {45 return;46 }47 delete this._keyDownList[code];48 }4950 Log.Debug("onkeyevent " + (down ? "down" : "up") +51 ", keysym: " + keysym, ", code: " + code +52 ", numlock: " + numlock + ", capslock: " + capslock);53 this.onkeyevent(keysym, code, down, numlock, capslock);54 }5556 _getKeyCode(e) {57 const code = KeyboardUtil.getKeycode(e);58 if (code !== 'Unidentified') {59 return code;60 }6162 // Unstable, but we don't have anything else to go on63 if (e.keyCode) {64 // 229 is used for composition events65 if (e.keyCode !== 229) {66 return 'Platform' + e.keyCode;67 }68 }6970 // A precursor to the final DOM3 standard. Unfortunately it71 // is not layout independent, so it is as bad as using keyCode72 if (e.keyIdentifier) {73 // Non-character key?74 if (e.keyIdentifier.substr(0, 2) !== 'U+') {75 return e.keyIdentifier;76 }7778 const codepoint = parseInt(e.keyIdentifier.substr(2), 16);79 const char = String.fromCharCode(codepoint).toUpperCase();8081 return 'Platform' + char.charCodeAt();82 }8384 return 'Unidentified';85 }8687 _handleKeyDown(e) {88 const code = this._getKeyCode(e);89 let keysym = KeyboardUtil.getKeysym(e);90 let numlock = e.getModifierState('NumLock');91 let capslock = e.getModifierState('CapsLock');9293 // getModifierState for NumLock is not supported on mac and ios and always returns false.94 // Set to null to indicate unknown/unsupported instead.95 if (browser.isMac() || browser.isIOS()) {96 numlock = null;97 }9899 // Windows doesn't have a proper AltGr, but handles it using100 // fake Ctrl+Alt. However the remote end might not be Windows,101 // so we need to merge those in to a single AltGr event. We102 // detect this case by seeing the two key events directly after103 // each other with a very short time between them (<50ms).104 if (this._altGrArmed) {105 this._altGrArmed = false;106 clearTimeout(this._altGrTimeout);107108 if ((code === "AltRight") &&109 ((e.timeStamp - this._altGrCtrlTime) < 50)) {110 // FIXME: We fail to detect this if either Ctrl key is111 // first manually pressed as Windows then no112 // longer sends the fake Ctrl down event. It113 // does however happily send real Ctrl events114 // even when AltGr is already down. Some115 // browsers detect this for us though and set the116 // key to "AltGraph".117 keysym = KeyTable.XK_ISO_Level3_Shift;118 } else {119 this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true, numlock, capslock);120 }121 }122123 // We cannot handle keys we cannot track, but we also need124 // to deal with virtual keyboards which omit key info125 if (code === 'Unidentified') {126 if (keysym) {127 // If it's a virtual keyboard then it should be128 // sufficient to just send press and release right129 // after each other130 this._sendKeyEvent(keysym, code, true, numlock, capslock);131 this._sendKeyEvent(keysym, code, false, numlock, capslock);132 }133134 stopEvent(e);135 return;136 }137138 // Alt behaves more like AltGraph on macOS, so shuffle the139 // keys around a bit to make things more sane for the remote140 // server. This method is used by RealVNC and TigerVNC (and141 // possibly others).142 if (browser.isMac() || browser.isIOS()) {143 switch (keysym) {144 case KeyTable.XK_Super_L:145 keysym = KeyTable.XK_Alt_L;146 break;147 case KeyTable.XK_Super_R:148 keysym = KeyTable.XK_Super_L;149 break;150 case KeyTable.XK_Alt_L:151 keysym = KeyTable.XK_Mode_switch;152 break;153 case KeyTable.XK_Alt_R:154 keysym = KeyTable.XK_ISO_Level3_Shift;155 break;156 }157 }158159 // Is this key already pressed? If so, then we must use the160 // same keysym or we'll confuse the server161 if (code in this._keyDownList) {162 keysym = this._keyDownList[code];163 }164165 // macOS doesn't send proper key releases if a key is pressed166 // while meta is held down167 if ((browser.isMac() || browser.isIOS()) &&168 (e.metaKey && code !== 'MetaLeft' && code !== 'MetaRight')) {169 this._sendKeyEvent(keysym, code, true, numlock, capslock);170 this._sendKeyEvent(keysym, code, false, numlock, capslock);171 stopEvent(e);172 return;173 }174175 // macOS doesn't send proper key events for modifiers, only176 // state change events. That gets extra confusing for CapsLock177 // which toggles on each press, but not on release. So pretend178 // it was a quick press and release of the button.179 if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {180 this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true, numlock, capslock);181 this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false, numlock, capslock);182 stopEvent(e);183 return;184 }185186 // Windows doesn't send proper key releases for a bunch of187 // Japanese IM keys so we have to fake the release right away188 const jpBadKeys = [ KeyTable.XK_Zenkaku_Hankaku,189 KeyTable.XK_Eisu_toggle,190 KeyTable.XK_Katakana,191 KeyTable.XK_Hiragana,192 KeyTable.XK_Romaji ];193 if (browser.isWindows() && jpBadKeys.includes(keysym)) {194 this._sendKeyEvent(keysym, code, true, numlock, capslock);195 this._sendKeyEvent(keysym, code, false, numlock, capslock);196 stopEvent(e);197 return;198 }199200 stopEvent(e);201202 // Possible start of AltGr sequence? (see above)203 if ((code === "ControlLeft") && browser.isWindows() &&204 !("ControlLeft" in this._keyDownList)) {205 this._altGrArmed = true;206 this._altGrTimeout = setTimeout(this._interruptAltGrSequence.bind(this), 100);207 this._altGrCtrlTime = e.timeStamp;208 return;209 }210211 this._sendKeyEvent(keysym, code, true, numlock, capslock);212 }213214 _handleKeyUp(e) {215 stopEvent(e);216217 const code = this._getKeyCode(e);218219 // We can't get a release in the middle of an AltGr sequence, so220 // abort that detection221 this._interruptAltGrSequence();222223 // See comment in _handleKeyDown()224 if ((browser.isMac() || browser.isIOS()) && (code === 'CapsLock')) {225 this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);226 this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);227 return;228 }229230 this._sendKeyEvent(this._keyDownList[code], code, false);231232 // Windows has a rather nasty bug where it won't send key233 // release events for a Shift button if the other Shift is still234 // pressed235 if (browser.isWindows() && ((code === 'ShiftLeft') ||236 (code === 'ShiftRight'))) {237 if ('ShiftRight' in this._keyDownList) {238 this._sendKeyEvent(this._keyDownList['ShiftRight'],239 'ShiftRight', false);240 }241 if ('ShiftLeft' in this._keyDownList) {242 this._sendKeyEvent(this._keyDownList['ShiftLeft'],243 'ShiftLeft', false);244 }245 }246 }247248 _interruptAltGrSequence() {249 if (this._altGrArmed) {250 this._altGrArmed = false;251 clearTimeout(this._altGrTimeout);252 this._sendKeyEvent(KeyTable.XK_Control_L, "ControlLeft", true);253 }254 }255256 _allKeysUp() {257 Log.Debug(">> Keyboard.allKeysUp");258259 // Prevent control key being processed after losing focus.260 this._interruptAltGrSequence();261262 for (let code in this._keyDownList) {263 this._sendKeyEvent(this._keyDownList[code], code, false);264 }265 Log.Debug("<< Keyboard.allKeysUp");266 }267268 // ===== PUBLIC METHODS =====269270 grab() {271 //Log.Debug(">> Keyboard.grab");272273 this._target.addEventListener('keydown', this._eventHandlers.keydown);274 this._target.addEventListener('keyup', this._eventHandlers.keyup);275276 // Release (key up) if window loses focus277 window.addEventListener('blur', this._eventHandlers.blur);278279 //Log.Debug("<< Keyboard.grab");280 }281282 ungrab() {283 //Log.Debug(">> Keyboard.ungrab");284285 this._target.removeEventListener('keydown', this._eventHandlers.keydown);286 this._target.removeEventListener('keyup', this._eventHandlers.keyup);287 window.removeEventListener('blur', this._eventHandlers.blur);288289 // Release (key up) all keys that are in a down state290 this._allKeysUp();291292 //Log.Debug(">> Keyboard.ungrab");293 }294}295