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