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