]>
git.proxmox.com Git - mirror_novnc.git/blob - core/input/keyboard.js
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2019 The noVNC Authors
4 * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
7 import * as Log
from '../util/logging.js';
8 import { stopEvent
} from '../util/events.js';
9 import * as KeyboardUtil
from "./util.js";
10 import KeyTable
from "./keysym.js";
11 import * as browser
from "../util/browser.js";
14 // Keyboard event handler
17 export default class Keyboard
{
19 this._target
= target
|| null;
21 this._keyDownList
= {}; // List of depressed keys
22 // (even if they are happy)
23 this._pendingKey
= null; // Key waiting for keypress
24 this._altGrArmed
= false; // Windows AltGr detection
26 // keep these here so we can refer to them later
27 this._eventHandlers
= {
28 'keyup': this._handleKeyUp
.bind(this),
29 'keydown': this._handleKeyDown
.bind(this),
30 'keypress': this._handleKeyPress
.bind(this),
31 'blur': this._allKeysUp
.bind(this),
32 'checkalt': this._checkAlt
.bind(this),
35 // ===== EVENT HANDLERS =====
37 this.onkeyevent
= () => {}; // Handler for key press/release
40 // ===== PRIVATE METHODS =====
42 _sendKeyEvent(keysym
, code
, down
) {
44 this._keyDownList
[code
] = keysym
;
46 // Do we really think this key is down?
47 if (!(code
in this._keyDownList
)) {
50 delete this._keyDownList
[code
];
53 Log
.Debug("onkeyevent " + (down
? "down" : "up") +
54 ", keysym: " + keysym
, ", code: " + code
);
55 this.onkeyevent(keysym
, code
, down
);
59 const code
= KeyboardUtil
.getKeycode(e
);
60 if (code
!== 'Unidentified') {
64 // Unstable, but we don't have anything else to go on
65 // (don't use it for 'keypress' events thought since
66 // WebKit sets it to the same as charCode)
67 if (e
.keyCode
&& (e
.type
!== 'keypress')) {
68 // 229 is used for composition events
69 if (e
.keyCode
!== 229) {
70 return 'Platform' + e
.keyCode
;
74 // A precursor to the final DOM3 standard. Unfortunately it
75 // is not layout independent, so it is as bad as using keyCode
76 if (e
.keyIdentifier
) {
78 if (e
.keyIdentifier
.substr(0, 2) !== 'U+') {
79 return e
.keyIdentifier
;
82 const codepoint
= parseInt(e
.keyIdentifier
.substr(2), 16);
83 const char = String
.fromCharCode(codepoint
).toUpperCase();
85 return 'Platform' + char.charCodeAt();
88 return 'Unidentified';
92 const code
= this._getKeyCode(e
);
93 let keysym
= KeyboardUtil
.getKeysym(e
);
95 // Windows doesn't have a proper AltGr, but handles it using
96 // fake Ctrl+Alt. However the remote end might not be Windows,
97 // so we need to merge those in to a single AltGr event. We
98 // detect this case by seeing the two key events directly after
99 // each other with a very short time between them (<50ms).
100 if (this._altGrArmed
) {
101 this._altGrArmed
= false;
102 clearTimeout(this._altGrTimeout
);
104 if ((code
=== "AltRight") &&
105 ((e
.timeStamp
- this._altGrCtrlTime
) < 50)) {
106 // FIXME: We fail to detect this if either Ctrl key is
107 // first manually pressed as Windows then no
108 // longer sends the fake Ctrl down event. It
109 // does however happily send real Ctrl events
110 // even when AltGr is already down. Some
111 // browsers detect this for us though and set the
112 // key to "AltGraph".
113 keysym
= KeyTable
.XK_ISO_Level3_Shift
;
115 this._sendKeyEvent(KeyTable
.XK_Control_L
, "ControlLeft", true);
119 // We cannot handle keys we cannot track, but we also need
120 // to deal with virtual keyboards which omit key info
121 if (code
=== 'Unidentified') {
123 // If it's a virtual keyboard then it should be
124 // sufficient to just send press and release right
126 this._sendKeyEvent(keysym
, code
, true);
127 this._sendKeyEvent(keysym
, code
, false);
134 // Alt behaves more like AltGraph on macOS, so shuffle the
135 // keys around a bit to make things more sane for the remote
136 // server. This method is used by RealVNC and TigerVNC (and
138 if (browser
.isMac() || browser
.isIOS()) {
140 case KeyTable
.XK_Super_L
:
141 keysym
= KeyTable
.XK_Alt_L
;
143 case KeyTable
.XK_Super_R
:
144 keysym
= KeyTable
.XK_Super_L
;
146 case KeyTable
.XK_Alt_L
:
147 keysym
= KeyTable
.XK_Mode_switch
;
149 case KeyTable
.XK_Alt_R
:
150 keysym
= KeyTable
.XK_ISO_Level3_Shift
;
155 // Is this key already pressed? If so, then we must use the
156 // same keysym or we'll confuse the server
157 if (code
in this._keyDownList
) {
158 keysym
= this._keyDownList
[code
];
161 // macOS doesn't send proper key events for modifiers, only
162 // state change events. That gets extra confusing for CapsLock
163 // which toggles on each press, but not on release. So pretend
164 // it was a quick press and release of the button.
165 if (browser
.isMac() && (code
=== 'CapsLock')) {
166 this._sendKeyEvent(KeyTable
.XK_Caps_Lock
, 'CapsLock', true);
167 this._sendKeyEvent(KeyTable
.XK_Caps_Lock
, 'CapsLock', false);
172 // If this is a legacy browser then we'll need to wait for
173 // a keypress event as well
174 // (IE and Edge has a broken KeyboardEvent.key, so we can't
175 // just check for the presence of that field)
176 if (!keysym
&& (!e
.key
|| browser
.isIE() || browser
.isEdge())) {
177 this._pendingKey
= code
;
178 // However we might not get a keypress event if the key
179 // is non-printable, which needs some special fallback
181 setTimeout(this._handleKeyPressTimeout
.bind(this), 10, e
);
185 this._pendingKey
= null;
188 // Possible start of AltGr sequence? (see above)
189 if ((code
=== "ControlLeft") && browser
.isWindows() &&
190 !("ControlLeft" in this._keyDownList
)) {
191 this._altGrArmed
= true;
192 this._altGrTimeout
= setTimeout(this._handleAltGrTimeout
.bind(this), 100);
193 this._altGrCtrlTime
= e
.timeStamp
;
197 this._sendKeyEvent(keysym
, code
, true);
200 // Legacy event for browsers without code/key
204 // Are we expecting a keypress?
205 if (this._pendingKey
=== null) {
209 let code
= this._getKeyCode(e
);
210 const keysym
= KeyboardUtil
.getKeysym(e
);
212 // The key we were waiting for?
213 if ((code
!== 'Unidentified') && (code
!= this._pendingKey
)) {
217 code
= this._pendingKey
;
218 this._pendingKey
= null;
221 Log
.Info('keypress with no keysym:', e
);
225 this._sendKeyEvent(keysym
, code
, true);
228 _handleKeyPressTimeout(e
) {
229 // Did someone manage to sort out the key already?
230 if (this._pendingKey
=== null) {
236 const code
= this._pendingKey
;
237 this._pendingKey
= null;
239 // We have no way of knowing the proper keysym with the
240 // information given, but the following are true for most
242 if ((e
.keyCode
>= 0x30) && (e
.keyCode
<= 0x39)) {
245 } else if ((e
.keyCode
>= 0x41) && (e
.keyCode
<= 0x5a)) {
247 let char = String
.fromCharCode(e
.keyCode
);
248 // A feeble attempt at the correct case
250 char = char.toUpperCase();
252 char = char.toLowerCase();
254 keysym
= char.charCodeAt();
260 this._sendKeyEvent(keysym
, code
, true);
266 const code
= this._getKeyCode(e
);
268 // We can't get a release in the middle of an AltGr sequence, so
269 // abort that detection
270 if (this._altGrArmed
) {
271 this._altGrArmed
= false;
272 clearTimeout(this._altGrTimeout
);
273 this._sendKeyEvent(KeyTable
.XK_Control_L
, "ControlLeft", true);
276 // See comment in _handleKeyDown()
277 if (browser
.isMac() && (code
=== 'CapsLock')) {
278 this._sendKeyEvent(KeyTable
.XK_Caps_Lock
, 'CapsLock', true);
279 this._sendKeyEvent(KeyTable
.XK_Caps_Lock
, 'CapsLock', false);
283 this._sendKeyEvent(this._keyDownList
[code
], code
, false);
285 // Windows has a rather nasty bug where it won't send key
286 // release events for a Shift button if the other Shift is still
288 if (browser
.isWindows() && ((code
=== 'ShiftLeft') ||
289 (code
=== 'ShiftRight'))) {
290 if ('ShiftRight' in this._keyDownList
) {
291 this._sendKeyEvent(this._keyDownList
['ShiftRight'],
292 'ShiftRight', false);
294 if ('ShiftLeft' in this._keyDownList
) {
295 this._sendKeyEvent(this._keyDownList
['ShiftLeft'],
301 _handleAltGrTimeout() {
302 this._altGrArmed
= false;
303 clearTimeout(this._altGrTimeout
);
304 this._sendKeyEvent(KeyTable
.XK_Control_L
, "ControlLeft", true);
308 Log
.Debug(">> Keyboard.allKeysUp");
309 for (let code
in this._keyDownList
) {
310 this._sendKeyEvent(this._keyDownList
[code
], code
, false);
312 Log
.Debug("<< Keyboard.allKeysUp");
315 // Alt workaround for Firefox on Windows, see below
317 if (e
.skipCheckAlt
) {
324 const target
= this._target
;
325 const downList
= this._keyDownList
;
326 ['AltLeft', 'AltRight'].forEach((code
) => {
327 if (!(code
in downList
)) {
331 const event
= new KeyboardEvent('keyup',
332 { key
: downList
[code
],
334 event
.skipCheckAlt
= true;
335 target
.dispatchEvent(event
);
339 // ===== PUBLIC METHODS =====
342 //Log.Debug(">> Keyboard.grab");
344 this._target
.addEventListener('keydown', this._eventHandlers
.keydown
);
345 this._target
.addEventListener('keyup', this._eventHandlers
.keyup
);
346 this._target
.addEventListener('keypress', this._eventHandlers
.keypress
);
348 // Release (key up) if window loses focus
349 window
.addEventListener('blur', this._eventHandlers
.blur
);
351 // Firefox on Windows has broken handling of Alt, so we need to
352 // poll as best we can for releases (still doesn't prevent the
353 // menu from popping up though as we can't call
355 if (browser
.isWindows() && browser
.isFirefox()) {
356 const handler
= this._eventHandlers
.checkalt
;
357 ['mousedown', 'mouseup', 'mousemove', 'wheel',
358 'touchstart', 'touchend', 'touchmove',
359 'keydown', 'keyup'].forEach(type
=>
360 document
.addEventListener(type
, handler
,
365 //Log.Debug("<< Keyboard.grab");
369 //Log.Debug(">> Keyboard.ungrab");
371 if (browser
.isWindows() && browser
.isFirefox()) {
372 const handler
= this._eventHandlers
.checkalt
;
373 ['mousedown', 'mouseup', 'mousemove', 'wheel',
374 'touchstart', 'touchend', 'touchmove',
375 'keydown', 'keyup'].forEach(type
=> document
.removeEventListener(type
, handler
));
378 this._target
.removeEventListener('keydown', this._eventHandlers
.keydown
);
379 this._target
.removeEventListener('keyup', this._eventHandlers
.keyup
);
380 this._target
.removeEventListener('keypress', this._eventHandlers
.keypress
);
381 window
.removeEventListener('blur', this._eventHandlers
.blur
);
383 // Release (key up) all keys that are in a down state
386 //Log.Debug(">> Keyboard.ungrab");