]> git.proxmox.com Git - mirror_novnc.git/blob - core/input/keyboard.js
Remove console statements
[mirror_novnc.git] / core / input / keyboard.js
1 /*
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2012 Joel Martin
4 * Copyright (C) 2013 Samuel Mannehed for Cendio AB
5 * Licensed under MPL 2.0 or any later version (see LICENSE.txt)
6 */
7
8 /*jslint browser: true, white: false */
9 /*global window, Util */
10
11 import * as Log from '../util/logging.js';
12 import { stopEvent } from '../util/events.js';
13 import * as KeyboardUtil from "./util.js";
14 import KeyTable from "./keysym.js";
15
16 //
17 // Keyboard event handler
18 //
19
20 export default function Keyboard(target) {
21 this._target = target || null;
22
23 this._keyDownList = {}; // List of depressed keys
24 // (even if they are happy)
25 this._pendingKey = null; // Key waiting for keypress
26
27 // keep these here so we can refer to them later
28 this._eventHandlers = {
29 'keyup': this._handleKeyUp.bind(this),
30 'keydown': this._handleKeyDown.bind(this),
31 'keypress': this._handleKeyPress.bind(this),
32 'blur': this._allKeysUp.bind(this)
33 };
34 };
35
36 function isMac() {
37 return navigator && !!(/mac/i).exec(navigator.platform);
38 }
39 function isWindows() {
40 return navigator && !!(/win/i).exec(navigator.platform);
41 }
42 function isIOS() {
43 return navigator &&
44 (!!(/ipad/i).exec(navigator.platform) ||
45 !!(/iphone/i).exec(navigator.platform) ||
46 !!(/ipod/i).exec(navigator.platform));
47 }
48 function isIE() {
49 return navigator && !!(/trident/i).exec(navigator.userAgent);
50 }
51 function isEdge() {
52 return navigator && !!(/edge/i).exec(navigator.userAgent);
53 }
54
55 Keyboard.prototype = {
56 // ===== EVENT HANDLERS =====
57
58 onkeyevent: function () {}, // Handler for key press/release
59
60 // ===== PRIVATE METHODS =====
61
62 _sendKeyEvent: function (keysym, code, down) {
63 Log.Debug("onkeyevent " + (down ? "down" : "up") +
64 ", keysym: " + keysym, ", code: " + code);
65
66 // Windows sends CtrlLeft+AltRight when you press
67 // AltGraph, which tends to confuse the hell out of
68 // remote systems. Fake a release of these keys until
69 // there is a way to detect AltGraph properly.
70 var fakeAltGraph = false;
71 if (down && isWindows()) {
72 if ((code !== 'ControlLeft') &&
73 (code !== 'AltRight') &&
74 ('ControlLeft' in this._keyDownList) &&
75 ('AltRight' in this._keyDownList)) {
76 fakeAltGraph = true;
77 this.onkeyevent(this._keyDownList['AltRight'],
78 'AltRight', false);
79 this.onkeyevent(this._keyDownList['ControlLeft'],
80 'ControlLeft', false);
81 }
82 }
83
84 this.onkeyevent(keysym, code, down);
85
86 if (fakeAltGraph) {
87 this.onkeyevent(this._keyDownList['ControlLeft'],
88 'ControlLeft', true);
89 this.onkeyevent(this._keyDownList['AltRight'],
90 'AltRight', true);
91 }
92 },
93
94 _getKeyCode: function (e) {
95 var code = KeyboardUtil.getKeycode(e);
96 if (code !== 'Unidentified') {
97 return code;
98 }
99
100 // Unstable, but we don't have anything else to go on
101 // (don't use it for 'keypress' events thought since
102 // WebKit sets it to the same as charCode)
103 if (e.keyCode && (e.type !== 'keypress')) {
104 // 229 is used for composition events
105 if (e.keyCode !== 229) {
106 return 'Platform' + e.keyCode;
107 }
108 }
109
110 // A precursor to the final DOM3 standard. Unfortunately it
111 // is not layout independent, so it is as bad as using keyCode
112 if (e.keyIdentifier) {
113 // Non-character key?
114 if (e.keyIdentifier.substr(0, 2) !== 'U+') {
115 return e.keyIdentifier;
116 }
117
118 var codepoint = parseInt(e.keyIdentifier.substr(2), 16);
119 var char = String.fromCharCode(codepoint);
120 // Some implementations fail to uppercase the symbols
121 char = char.toUpperCase();
122
123 return 'Platform' + char.charCodeAt();
124 }
125
126 return 'Unidentified';
127 },
128
129 _handleKeyDown: function (e) {
130 var code = this._getKeyCode(e);
131 var keysym = KeyboardUtil.getKeysym(e);
132
133 // We cannot handle keys we cannot track, but we also need
134 // to deal with virtual keyboards which omit key info
135 // (iOS omits tracking info on keyup events, which forces us to
136 // special treat that platform here)
137 if ((code === 'Unidentified') || isIOS()) {
138 if (keysym) {
139 // If it's a virtual keyboard then it should be
140 // sufficient to just send press and release right
141 // after each other
142 this._sendKeyEvent(keysym, code, true);
143 this._sendKeyEvent(keysym, code, false);
144 }
145
146 stopEvent(e);
147 return;
148 }
149
150 // Alt behaves more like AltGraph on macOS, so shuffle the
151 // keys around a bit to make things more sane for the remote
152 // server. This method is used by RealVNC and TigerVNC (and
153 // possibly others).
154 if (isMac()) {
155 switch (keysym) {
156 case KeyTable.XK_Super_L:
157 keysym = KeyTable.XK_Alt_L;
158 break;
159 case KeyTable.XK_Super_R:
160 keysym = KeyTable.XK_Super_L;
161 break;
162 case KeyTable.XK_Alt_L:
163 keysym = KeyTable.XK_Mode_switch;
164 break;
165 case KeyTable.XK_Alt_R:
166 keysym = KeyTable.XK_ISO_Level3_Shift;
167 break;
168 }
169 }
170
171 // Is this key already pressed? If so, then we must use the
172 // same keysym or we'll confuse the server
173 if (code in this._keyDownList) {
174 keysym = this._keyDownList[code];
175 }
176
177 // macOS doesn't send proper key events for modifiers, only
178 // state change events. That gets extra confusing for CapsLock
179 // which toggles on each press, but not on release. So pretend
180 // it was a quick press and release of the button.
181 if (isMac() && (code === 'CapsLock')) {
182 this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
183 this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
184 stopEvent(e);
185 return;
186 }
187
188 // If this is a legacy browser then we'll need to wait for
189 // a keypress event as well
190 // (IE and Edge has a broken KeyboardEvent.key, so we can't
191 // just check for the presence of that field)
192 if (!keysym && (!e.key || isIE() || isEdge())) {
193 this._pendingKey = code;
194 // However we might not get a keypress event if the key
195 // is non-printable, which needs some special fallback
196 // handling
197 setTimeout(this._handleKeyPressTimeout.bind(this), 10, e);
198 return;
199 }
200
201 this._pendingKey = null;
202 stopEvent(e);
203
204 this._keyDownList[code] = keysym;
205
206 this._sendKeyEvent(keysym, code, true);
207 },
208
209 // Legacy event for browsers without code/key
210 _handleKeyPress: function (e) {
211 stopEvent(e);
212
213 // Are we expecting a keypress?
214 if (this._pendingKey === null) {
215 return;
216 }
217
218 var code = this._getKeyCode(e);
219 var keysym = KeyboardUtil.getKeysym(e);
220
221 // The key we were waiting for?
222 if ((code !== 'Unidentified') && (code != this._pendingKey)) {
223 return;
224 }
225
226 code = this._pendingKey;
227 this._pendingKey = null;
228
229 if (!keysym) {
230 Log.Info('keypress with no keysym:', e);
231 return;
232 }
233
234 this._keyDownList[code] = keysym;
235
236 this._sendKeyEvent(keysym, code, true);
237 },
238 _handleKeyPressTimeout: function (e) {
239 // Did someone manage to sort out the key already?
240 if (this._pendingKey === null) {
241 return;
242 }
243
244 var code, keysym;
245
246 code = this._pendingKey;
247 this._pendingKey = null;
248
249 // We have no way of knowing the proper keysym with the
250 // information given, but the following are true for most
251 // layouts
252 if ((e.keyCode >= 0x30) && (e.keyCode <= 0x39)) {
253 // Digit
254 keysym = e.keyCode;
255 } else if ((e.keyCode >= 0x41) && (e.keyCode <= 0x5a)) {
256 // Character (A-Z)
257 var char = String.fromCharCode(e.keyCode);
258 // A feeble attempt at the correct case
259 if (e.shiftKey)
260 char = char.toUpperCase();
261 else
262 char = char.toLowerCase();
263 keysym = char.charCodeAt();
264 } else {
265 // Unknown, give up
266 keysym = 0;
267 }
268
269 this._keyDownList[code] = keysym;
270
271 this._sendKeyEvent(keysym, code, true);
272 },
273
274 _handleKeyUp: function (e) {
275 stopEvent(e);
276
277 var code = this._getKeyCode(e);
278
279 // See comment in _handleKeyDown()
280 if (isMac() && (code === 'CapsLock')) {
281 this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', true);
282 this._sendKeyEvent(KeyTable.XK_Caps_Lock, 'CapsLock', false);
283 return;
284 }
285
286 // Do we really think this key is down?
287 if (!(code in this._keyDownList)) {
288 return;
289 }
290
291 this._sendKeyEvent(this._keyDownList[code], code, false);
292
293 delete this._keyDownList[code];
294 },
295
296 _allKeysUp: function () {
297 Log.Debug(">> Keyboard.allKeysUp");
298 for (var code in this._keyDownList) {
299 this._sendKeyEvent(this._keyDownList[code], code, false);
300 };
301 this._keyDownList = {};
302 Log.Debug("<< Keyboard.allKeysUp");
303 },
304
305 // ===== PUBLIC METHODS =====
306
307 grab: function () {
308 //Log.Debug(">> Keyboard.grab");
309 var c = this._target;
310
311 c.addEventListener('keydown', this._eventHandlers.keydown);
312 c.addEventListener('keyup', this._eventHandlers.keyup);
313 c.addEventListener('keypress', this._eventHandlers.keypress);
314
315 // Release (key up) if window loses focus
316 window.addEventListener('blur', this._eventHandlers.blur);
317
318 //Log.Debug("<< Keyboard.grab");
319 },
320
321 ungrab: function () {
322 //Log.Debug(">> Keyboard.ungrab");
323 var c = this._target;
324
325 c.removeEventListener('keydown', this._eventHandlers.keydown);
326 c.removeEventListener('keyup', this._eventHandlers.keyup);
327 c.removeEventListener('keypress', this._eventHandlers.keypress);
328 window.removeEventListener('blur', this._eventHandlers.blur);
329
330 // Release (key up) all keys that are in a down state
331 this._allKeysUp();
332
333 //Log.Debug(">> Keyboard.ungrab");
334 },
335 };