]>
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._altGrArmed
= false; // Windows AltGr detection
25 // keep these here so we can refer to them later
26 this._eventHandlers
= {
27 'keyup': this._handleKeyUp
.bind(this),
28 'keydown': this._handleKeyDown
.bind(this),
29 'blur': this._allKeysUp
.bind(this),
32 // ===== EVENT HANDLERS =====
34 this.onkeyevent
= () => {}; // Handler for key press/release
37 // ===== PRIVATE METHODS =====
39 _sendKeyEvent(keysym
, code
, down
) {
41 this._keyDownList
[code
] = keysym
;
43 // Do we really think this key is down?
44 if (!(code
in this._keyDownList
)) {
47 delete this._keyDownList
[code
];
50 Log
.Debug("onkeyevent " + (down
? "down" : "up") +
51 ", keysym: " + keysym
, ", code: " + code
);
52 this.onkeyevent(keysym
, code
, down
);
56 const code
= KeyboardUtil
.getKeycode(e
);
57 if (code
!== 'Unidentified') {
61 // Unstable, but we don't have anything else to go on
63 // 229 is used for composition events
64 if (e
.keyCode
!== 229) {
65 return 'Platform' + e
.keyCode
;
69 // A precursor to the final DOM3 standard. Unfortunately it
70 // is not layout independent, so it is as bad as using keyCode
71 if (e
.keyIdentifier
) {
73 if (e
.keyIdentifier
.substr(0, 2) !== 'U+') {
74 return e
.keyIdentifier
;
77 const codepoint
= parseInt(e
.keyIdentifier
.substr(2), 16);
78 const char = String
.fromCharCode(codepoint
).toUpperCase();
80 return 'Platform' + char.charCodeAt();
83 return 'Unidentified';
87 const code
= this._getKeyCode(e
);
88 let keysym
= KeyboardUtil
.getKeysym(e
);
90 // Windows doesn't have a proper AltGr, but handles it using
91 // fake Ctrl+Alt. However the remote end might not be Windows,
92 // so we need to merge those in to a single AltGr event. We
93 // detect this case by seeing the two key events directly after
94 // each other with a very short time between them (<50ms).
95 if (this._altGrArmed
) {
96 this._altGrArmed
= false;
97 clearTimeout(this._altGrTimeout
);
99 if ((code
=== "AltRight") &&
100 ((e
.timeStamp
- this._altGrCtrlTime
) < 50)) {
101 // FIXME: We fail to detect this if either Ctrl key is
102 // first manually pressed as Windows then no
103 // longer sends the fake Ctrl down event. It
104 // does however happily send real Ctrl events
105 // even when AltGr is already down. Some
106 // browsers detect this for us though and set the
107 // key to "AltGraph".
108 keysym
= KeyTable
.XK_ISO_Level3_Shift
;
110 this._sendKeyEvent(KeyTable
.XK_Control_L
, "ControlLeft", true);
114 // We cannot handle keys we cannot track, but we also need
115 // to deal with virtual keyboards which omit key info
116 if (code
=== 'Unidentified') {
118 // If it's a virtual keyboard then it should be
119 // sufficient to just send press and release right
121 this._sendKeyEvent(keysym
, code
, true);
122 this._sendKeyEvent(keysym
, code
, false);
129 // Alt behaves more like AltGraph on macOS, so shuffle the
130 // keys around a bit to make things more sane for the remote
131 // server. This method is used by RealVNC and TigerVNC (and
133 if (browser
.isMac() || browser
.isIOS()) {
135 case KeyTable
.XK_Super_L
:
136 keysym
= KeyTable
.XK_Alt_L
;
138 case KeyTable
.XK_Super_R
:
139 keysym
= KeyTable
.XK_Super_L
;
141 case KeyTable
.XK_Alt_L
:
142 keysym
= KeyTable
.XK_Mode_switch
;
144 case KeyTable
.XK_Alt_R
:
145 keysym
= KeyTable
.XK_ISO_Level3_Shift
;
150 // Is this key already pressed? If so, then we must use the
151 // same keysym or we'll confuse the server
152 if (code
in this._keyDownList
) {
153 keysym
= this._keyDownList
[code
];
156 // macOS doesn't send proper key events for modifiers, only
157 // state change events. That gets extra confusing for CapsLock
158 // which toggles on each press, but not on release. So pretend
159 // it was a quick press and release of the button.
160 if ((browser
.isMac() || browser
.isIOS()) && (code
=== 'CapsLock')) {
161 this._sendKeyEvent(KeyTable
.XK_Caps_Lock
, 'CapsLock', true);
162 this._sendKeyEvent(KeyTable
.XK_Caps_Lock
, 'CapsLock', false);
167 // Windows doesn't send proper key releases for a bunch of
168 // Japanese IM keys so we have to fake the release right away
169 const jpBadKeys
= [ KeyTable
.XK_Zenkaku_Hankaku
,
170 KeyTable
.XK_Eisu_toggle
,
171 KeyTable
.XK_Katakana
,
172 KeyTable
.XK_Hiragana
,
173 KeyTable
.XK_Romaji
];
174 if (browser
.isWindows() && jpBadKeys
.includes(keysym
)) {
175 this._sendKeyEvent(keysym
, code
, true);
176 this._sendKeyEvent(keysym
, code
, false);
183 // Possible start of AltGr sequence? (see above)
184 if ((code
=== "ControlLeft") && browser
.isWindows() &&
185 !("ControlLeft" in this._keyDownList
)) {
186 this._altGrArmed
= true;
187 this._altGrTimeout
= setTimeout(this._handleAltGrTimeout
.bind(this), 100);
188 this._altGrCtrlTime
= e
.timeStamp
;
192 this._sendKeyEvent(keysym
, code
, true);
198 const code
= this._getKeyCode(e
);
200 // We can't get a release in the middle of an AltGr sequence, so
201 // abort that detection
202 if (this._altGrArmed
) {
203 this._altGrArmed
= false;
204 clearTimeout(this._altGrTimeout
);
205 this._sendKeyEvent(KeyTable
.XK_Control_L
, "ControlLeft", true);
208 // See comment in _handleKeyDown()
209 if ((browser
.isMac() || browser
.isIOS()) && (code
=== 'CapsLock')) {
210 this._sendKeyEvent(KeyTable
.XK_Caps_Lock
, 'CapsLock', true);
211 this._sendKeyEvent(KeyTable
.XK_Caps_Lock
, 'CapsLock', false);
215 this._sendKeyEvent(this._keyDownList
[code
], code
, false);
217 // Windows has a rather nasty bug where it won't send key
218 // release events for a Shift button if the other Shift is still
220 if (browser
.isWindows() && ((code
=== 'ShiftLeft') ||
221 (code
=== 'ShiftRight'))) {
222 if ('ShiftRight' in this._keyDownList
) {
223 this._sendKeyEvent(this._keyDownList
['ShiftRight'],
224 'ShiftRight', false);
226 if ('ShiftLeft' in this._keyDownList
) {
227 this._sendKeyEvent(this._keyDownList
['ShiftLeft'],
233 _handleAltGrTimeout() {
234 this._altGrArmed
= false;
235 clearTimeout(this._altGrTimeout
);
236 this._sendKeyEvent(KeyTable
.XK_Control_L
, "ControlLeft", true);
240 Log
.Debug(">> Keyboard.allKeysUp");
241 for (let code
in this._keyDownList
) {
242 this._sendKeyEvent(this._keyDownList
[code
], code
, false);
244 Log
.Debug("<< Keyboard.allKeysUp");
247 // ===== PUBLIC METHODS =====
250 //Log.Debug(">> Keyboard.grab");
252 this._target
.addEventListener('keydown', this._eventHandlers
.keydown
);
253 this._target
.addEventListener('keyup', this._eventHandlers
.keyup
);
255 // Release (key up) if window loses focus
256 window
.addEventListener('blur', this._eventHandlers
.blur
);
258 //Log.Debug("<< Keyboard.grab");
262 //Log.Debug(">> Keyboard.ungrab");
264 this._target
.removeEventListener('keydown', this._eventHandlers
.keydown
);
265 this._target
.removeEventListener('keyup', this._eventHandlers
.keyup
);
266 window
.removeEventListener('blur', this._eventHandlers
.blur
);
268 // Release (key up) all keys that are in a down state
271 //Log.Debug(">> Keyboard.ungrab");