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