@illgrenoble/webx-client 1.11.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/GuacamoleKeyboard.js +1551 -0
  2. package/dist/WebXClient.d.ts +1 -1
  3. package/dist/common/WebXImageBlenderFunc.d.ts +26 -0
  4. package/dist/common/WebXSubImage.d.ts +50 -0
  5. package/dist/common/WebXWindowProperties.d.ts +44 -0
  6. package/dist/common/index.d.ts +3 -0
  7. package/dist/display/WebXCursor.d.ts +5 -17
  8. package/dist/display/WebXCursor.js +22 -33
  9. package/dist/display/WebXCursor.js.map +1 -1
  10. package/dist/display/WebXCursorFactory.d.ts +2 -2
  11. package/dist/display/WebXDisplay.d.ts +11 -9
  12. package/dist/display/WebXDisplay.js +10 -4
  13. package/dist/display/WebXDisplay.js.map +1 -1
  14. package/dist/display/WebXDisplayOverlay.d.ts +10 -0
  15. package/dist/display/WebXDisplayOverlay.js +31 -0
  16. package/dist/display/WebXDisplayOverlay.js.map +1 -0
  17. package/dist/display/WebXTexture.d.ts +6 -0
  18. package/dist/display/WebXTextureFunc.d.ts +3 -0
  19. package/dist/display/WebXWindow.d.ts +4 -4
  20. package/dist/display/WebXWindow.js +12 -9
  21. package/dist/display/WebXWindow.js.map +1 -1
  22. package/dist/display/WebXWindowImageFactory.d.ts +31 -0
  23. package/dist/display/index.d.ts +0 -6
  24. package/dist/index.d.ts +2 -0
  25. package/dist/message/WebXCursorImageMessage.d.ts +3 -3
  26. package/dist/message/WebXImageMessage.d.ts +4 -4
  27. package/dist/message/WebXMessage.d.ts +13 -1
  28. package/dist/message/WebXShapeMessage.d.ts +3 -3
  29. package/dist/message/WebXSubImagesMessage.d.ts +1 -1
  30. package/dist/message/WebXWindowsMessage.d.ts +1 -1
  31. package/dist/renderer/WebXCanvasRenderer.d.ts +1 -1
  32. package/dist/renderer/WebXImageBlender.d.ts +30 -0
  33. package/dist/renderer/WebXImageBlenderFunc.d.ts +8 -0
  34. package/dist/renderer/WebXImageBlenderWorker.d.ts +1 -0
  35. package/dist/renderer/WebXWindowCanvas.d.ts +27 -8
  36. package/dist/renderer/WebXWindowCanvas.js +10 -4
  37. package/dist/renderer/WebXWindowCanvas.js.map +1 -1
  38. package/dist/renderer/index.d.ts +1 -1
  39. package/dist/texture/WebXTexture.d.ts +18 -0
  40. package/dist/texture/WebXTextureFactory.d.ts +23 -0
  41. package/dist/texture/WebXTextureFunc.d.ts +3 -0
  42. package/dist/texture/index.d.ts +3 -0
  43. package/dist/transport/WebXBinarySerializer.d.ts +13 -8
  44. package/dist/transport/WebXMessageBuffer.d.ts +4 -0
  45. package/dist/transport/WebXMessageDecoder.d.ts +2 -5
  46. package/dist/transport/WebXMessageDecoderWorker.d.ts +1 -0
  47. package/dist/transport/WebXMessageFunc.d.ts +22 -0
  48. package/dist/transport/index.d.ts +0 -2
  49. package/dist/tunnel/WebXTunnel.d.ts +5 -2
  50. package/dist/tunnel/WebXWebSocketTunnel.d.ts +2 -4
  51. package/dist/webx-client.cjs +5391 -0
  52. package/dist/webx-client.cjs.map +1 -0
  53. package/dist/webx-client.esm.js +5322 -0
  54. package/dist/webx-client.esm.js.map +1 -0
  55. package/package.json +11 -9
  56. package/dist/renderer/WebXBlenderWorker.d.ts +0 -1
  57. package/dist/renderer/WebXBlenderWorker.js +0 -33
  58. package/dist/renderer/WebXBlenderWorker.js.map +0 -1
  59. package/dist/renderer/WebXWindowBlender.d.ts +0 -11
  60. package/dist/renderer/WebXWindowBlender.js +0 -27
  61. package/dist/renderer/WebXWindowBlender.js.map +0 -1
  62. package/dist/renderer/blender.d.ts +0 -5
  63. package/dist/renderer/blender.js +0 -201
  64. package/dist/renderer/blender.js.map +0 -1
@@ -0,0 +1,1551 @@
1
+ /*
2
+ * Licensed to the Apache Software Foundation (ASF) under one
3
+ * or more contributor license agreements. See the NOTICE file
4
+ * distributed with this work for additional information
5
+ * regarding copyright ownership. The ASF licenses this file
6
+ * to you under the Apache License, Version 2.0 (the
7
+ * "License"); you may not use this file except in compliance
8
+ * with the License. You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing,
13
+ * software distributed under the License is distributed on an
14
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ * KIND, either express or implied. See the License for the
16
+ * specific language governing permissions and limitations
17
+ * under the License.
18
+ */
19
+
20
+ /* eslint-disable */
21
+
22
+ var Guacamole = Guacamole || {};
23
+
24
+ /**
25
+ * Provides cross-browser and cross-keyboard keyboard for a specific element.
26
+ * Browser and keyboard layout variation is abstracted away, providing events
27
+ * which represent keys as their corresponding X11 keysym.
28
+ *
29
+ * @constructor
30
+ * @param {Element|Document} [element]
31
+ * The Element to use to provide keyboard events. If omitted, at least one
32
+ * Element must be manually provided through the listenTo() function for
33
+ * the Guacamole.Keyboard instance to have any effect.
34
+ */
35
+ Guacamole.Keyboard = function Keyboard(element) {
36
+
37
+ /**
38
+ * Reference to this Guacamole.Keyboard.
39
+ *
40
+ * @private
41
+ * @type {!Guacamole.Keyboard}
42
+ */
43
+ var guac_keyboard = this;
44
+
45
+ /**
46
+ * An integer value which uniquely identifies this Guacamole.Keyboard
47
+ * instance with respect to other Guacamole.Keyboard instances.
48
+ *
49
+ * @private
50
+ * @type {!number}
51
+ */
52
+ var guacKeyboardID = Guacamole.Keyboard._nextID++;
53
+
54
+ /**
55
+ * The name of the property which is added to event objects via markEvent()
56
+ * to note that they have already been handled by this Guacamole.Keyboard.
57
+ *
58
+ * @private
59
+ * @constant
60
+ * @type {!string}
61
+ */
62
+ var EVENT_MARKER = '_GUAC_KEYBOARD_HANDLED_BY_' + guacKeyboardID;
63
+
64
+ /**
65
+ * Fired whenever the user presses a key with the element associated
66
+ * with this Guacamole.Keyboard in focus.
67
+ *
68
+ * @event
69
+ * @param {!number} keysym
70
+ * The keysym of the key being pressed.
71
+ *
72
+ * @return {!boolean}
73
+ * true if the key event should be allowed through to the browser,
74
+ * false otherwise.
75
+ */
76
+ this.onkeydown = null;
77
+
78
+ /**
79
+ * Fired whenever the user releases a key with the element associated
80
+ * with this Guacamole.Keyboard in focus.
81
+ *
82
+ * @event
83
+ * @param {!number} keysym
84
+ * The keysym of the key being released.
85
+ */
86
+ this.onkeyup = null;
87
+
88
+ /**
89
+ * Set of known platform-specific or browser-specific quirks which must be
90
+ * accounted for to properly interpret key events, even if the only way to
91
+ * reliably detect that quirk is to platform/browser-sniff.
92
+ *
93
+ * @private
94
+ * @type {!Object.<string, boolean>}
95
+ */
96
+ var quirks = {
97
+
98
+ /**
99
+ * Whether keyup events are universally unreliable.
100
+ *
101
+ * @type {!boolean}
102
+ */
103
+ keyupUnreliable: false,
104
+
105
+ /**
106
+ * Whether the Alt key is actually a modifier for typable keys and is
107
+ * thus never used for keyboard shortcuts.
108
+ *
109
+ * @type {!boolean}
110
+ */
111
+ altIsTypableOnly: false,
112
+
113
+ /**
114
+ * Whether we can rely on receiving a keyup event for the Caps Lock
115
+ * key.
116
+ *
117
+ * @type {!boolean}
118
+ */
119
+ capsLockKeyupUnreliable: false
120
+
121
+ };
122
+
123
+ // Set quirk flags depending on platform/browser, if such information is
124
+ // available
125
+ if (navigator && navigator.platform) {
126
+
127
+ // All keyup events are unreliable on iOS (sadly)
128
+ if (navigator.platform.match(/ipad|iphone|ipod/i))
129
+ quirks.keyupUnreliable = true;
130
+
131
+ // The Alt key on Mac is never used for keyboard shortcuts, and the
132
+ // Caps Lock key never dispatches keyup events
133
+ else if (navigator.platform.match(/^mac/i)) {
134
+ quirks.altIsTypableOnly = true;
135
+ quirks.capsLockKeyupUnreliable = true;
136
+ }
137
+
138
+ }
139
+
140
+ /**
141
+ * A key event having a corresponding timestamp. This event is non-specific.
142
+ * Its subclasses should be used instead when recording specific key
143
+ * events.
144
+ *
145
+ * @private
146
+ * @constructor
147
+ * @param {KeyboardEvent} [orig]
148
+ * The relevant DOM keyboard event.
149
+ */
150
+ var KeyEvent = function KeyEvent(orig) {
151
+
152
+ /**
153
+ * Reference to this key event.
154
+ *
155
+ * @private
156
+ * @type {!KeyEvent}
157
+ */
158
+ var key_event = this;
159
+
160
+ /**
161
+ * The JavaScript key code of the key pressed. For most events (keydown
162
+ * and keyup), this is a scancode-like value related to the position of
163
+ * the key on the US English "Qwerty" keyboard. For keypress events,
164
+ * this is the Unicode codepoint of the character that would be typed
165
+ * by the key pressed.
166
+ *
167
+ * @type {!number}
168
+ */
169
+ this.keyCode = orig ? (orig.which || orig.keyCode) : 0;
170
+
171
+ /**
172
+ * The legacy DOM3 "keyIdentifier" of the key pressed, as defined at:
173
+ * http://www.w3.org/TR/2009/WD-DOM-Level-3-Events-20090908/#events-Events-KeyboardEvent
174
+ *
175
+ * @type {!string}
176
+ */
177
+ this.keyIdentifier = orig && orig.keyIdentifier;
178
+
179
+ /**
180
+ * The standard name of the key pressed, as defined at:
181
+ * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
182
+ *
183
+ * @type {!string}
184
+ */
185
+ this.key = orig && orig.key;
186
+
187
+ /**
188
+ * The location on the keyboard corresponding to the key pressed, as
189
+ * defined at:
190
+ * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
191
+ *
192
+ * @type {!number}
193
+ */
194
+ this.location = orig ? getEventLocation(orig) : 0;
195
+
196
+ /**
197
+ * The state of all local keyboard modifiers at the time this event was
198
+ * received.
199
+ *
200
+ * @type {!Guacamole.Keyboard.ModifierState}
201
+ */
202
+ this.modifiers = orig ? Guacamole.Keyboard.ModifierState.fromKeyboardEvent(orig) : new Guacamole.Keyboard.ModifierState();
203
+
204
+ /**
205
+ * An arbitrary timestamp in milliseconds, indicating this event's
206
+ * position in time relative to other events.
207
+ *
208
+ * @type {!number}
209
+ */
210
+ this.timestamp = new Date().getTime();
211
+
212
+ /**
213
+ * Whether the default action of this key event should be prevented.
214
+ *
215
+ * @type {!boolean}
216
+ */
217
+ this.defaultPrevented = false;
218
+
219
+ /**
220
+ * The keysym of the key associated with this key event, as determined
221
+ * by a best-effort guess using available event properties and keyboard
222
+ * state.
223
+ *
224
+ * @type {number}
225
+ */
226
+ this.keysym = null;
227
+
228
+ /**
229
+ * Whether the keysym value of this key event is known to be reliable.
230
+ * If false, the keysym may still be valid, but it's only a best guess,
231
+ * and future key events may be a better source of information.
232
+ *
233
+ * @type {!boolean}
234
+ */
235
+ this.reliable = false;
236
+
237
+ /**
238
+ * Returns the number of milliseconds elapsed since this event was
239
+ * received.
240
+ *
241
+ * @return {!number}
242
+ * The number of milliseconds elapsed since this event was
243
+ * received.
244
+ */
245
+ this.getAge = function() {
246
+ return new Date().getTime() - key_event.timestamp;
247
+ };
248
+
249
+ };
250
+
251
+ /**
252
+ * Information related to the pressing of a key, which need not be a key
253
+ * associated with a printable character. The presence or absence of any
254
+ * information within this object is browser-dependent.
255
+ *
256
+ * @private
257
+ * @constructor
258
+ * @augments Guacamole.Keyboard.KeyEvent
259
+ * @param {!KeyboardEvent} orig
260
+ * The relevant DOM "keydown" event.
261
+ */
262
+ var KeydownEvent = function KeydownEvent(orig) {
263
+
264
+ // We extend KeyEvent
265
+ KeyEvent.call(this, orig);
266
+
267
+ // If key is known from keyCode or DOM3 alone, use that
268
+ this.keysym = keysym_from_key_identifier(this.key, this.location)
269
+ || keysym_from_keycode(this.keyCode, this.location);
270
+
271
+ /**
272
+ * Whether the keyup following this keydown event is known to be
273
+ * reliable. If false, we cannot rely on the keyup event to occur.
274
+ *
275
+ * @type {!boolean}
276
+ */
277
+ this.keyupReliable = !quirks.keyupUnreliable;
278
+
279
+ // DOM3 and keyCode are reliable sources if the corresponding key is
280
+ // not a printable key
281
+ if (this.keysym && !isPrintable(this.keysym))
282
+ this.reliable = true;
283
+
284
+ // Use legacy keyIdentifier as a last resort, if it looks sane
285
+ if (!this.keysym && key_identifier_sane(this.keyCode, this.keyIdentifier))
286
+ this.keysym = keysym_from_key_identifier(this.keyIdentifier, this.location, this.modifiers.shift);
287
+
288
+ // If a key is pressed while meta is held down, the keyup will
289
+ // never be sent in Chrome (bug #108404)
290
+ if (this.modifiers.meta && this.keysym !== 0xFFE7 && this.keysym !== 0xFFE8)
291
+ this.keyupReliable = false;
292
+
293
+ // We cannot rely on receiving keyup for Caps Lock on certain platforms
294
+ else if (this.keysym === 0xFFE5 && quirks.capsLockKeyupUnreliable)
295
+ this.keyupReliable = false;
296
+
297
+ // Determine whether default action for Alt+combinations must be prevented
298
+ var prevent_alt = !this.modifiers.ctrl && !quirks.altIsTypableOnly;
299
+
300
+ // If alt is typeable only, and this is actually an alt key event, treat as AltGr instead
301
+ if (quirks.altIsTypableOnly && (this.keysym === 0xFFE9 || this.keysym === 0xFFEA))
302
+ this.keysym = 0xFE03;
303
+
304
+ // Determine whether default action for Ctrl+combinations must be prevented
305
+ var prevent_ctrl = !this.modifiers.alt;
306
+
307
+ // We must rely on the (potentially buggy) keyIdentifier if preventing
308
+ // the default action is important
309
+ if ((prevent_ctrl && this.modifiers.ctrl)
310
+ || (prevent_alt && this.modifiers.alt)
311
+ || this.modifiers.meta
312
+ || this.modifiers.hyper)
313
+ this.reliable = true;
314
+
315
+ // Record most recently known keysym by associated key code
316
+ recentKeysym[this.keyCode] = this.keysym;
317
+
318
+ };
319
+
320
+ KeydownEvent.prototype = new KeyEvent();
321
+
322
+ /**
323
+ * Information related to the pressing of a key, which MUST be
324
+ * associated with a printable character. The presence or absence of any
325
+ * information within this object is browser-dependent.
326
+ *
327
+ * @private
328
+ * @constructor
329
+ * @augments Guacamole.Keyboard.KeyEvent
330
+ * @param {!KeyboardEvent} orig
331
+ * The relevant DOM "keypress" event.
332
+ */
333
+ var KeypressEvent = function KeypressEvent(orig) {
334
+
335
+ // We extend KeyEvent
336
+ KeyEvent.call(this, orig);
337
+
338
+ // Pull keysym from char code
339
+ this.keysym = keysym_from_charcode(this.keyCode);
340
+
341
+ // Keypress is always reliable
342
+ this.reliable = true;
343
+
344
+ };
345
+
346
+ KeypressEvent.prototype = new KeyEvent();
347
+
348
+ /**
349
+ * Information related to the releasing of a key, which need not be a key
350
+ * associated with a printable character. The presence or absence of any
351
+ * information within this object is browser-dependent.
352
+ *
353
+ * @private
354
+ * @constructor
355
+ * @augments Guacamole.Keyboard.KeyEvent
356
+ * @param {!KeyboardEvent} orig
357
+ * The relevant DOM "keyup" event.
358
+ */
359
+ var KeyupEvent = function KeyupEvent(orig) {
360
+
361
+ // We extend KeyEvent
362
+ KeyEvent.call(this, orig);
363
+
364
+ // If key is known from keyCode or DOM3 alone, use that (keyCode is
365
+ // still more reliable for keyup when dead keys are in use)
366
+ this.keysym = keysym_from_keycode(this.keyCode, this.location)
367
+ || keysym_from_key_identifier(this.key, this.location);
368
+
369
+ // Fall back to the most recently pressed keysym associated with the
370
+ // keyCode if the inferred key doesn't seem to actually be pressed
371
+ if (!guac_keyboard.pressed[this.keysym])
372
+ this.keysym = recentKeysym[this.keyCode] || this.keysym;
373
+
374
+ // Keyup is as reliable as it will ever be
375
+ this.reliable = true;
376
+
377
+ };
378
+
379
+ KeyupEvent.prototype = new KeyEvent();
380
+
381
+ /**
382
+ * An array of recorded events, which can be instances of the private
383
+ * KeydownEvent, KeypressEvent, and KeyupEvent classes.
384
+ *
385
+ * @private
386
+ * @type {!KeyEvent[]}
387
+ */
388
+ var eventLog = [];
389
+
390
+ /**
391
+ * Map of known JavaScript keycodes which do not map to typable characters
392
+ * to their X11 keysym equivalents.
393
+ *
394
+ * @private
395
+ * @type {!Object.<number, number[]>}
396
+ */
397
+ var keycodeKeysyms = {
398
+ 8: [0xFF08], // backspace
399
+ 9: [0xFF09], // tab
400
+ 12: [0xFF0B, 0xFF0B, 0xFF0B, 0xFFB5], // clear / KP 5
401
+ 13: [0xFF0D], // enter
402
+ 16: [0xFFE1, 0xFFE1, 0xFFE2], // shift
403
+ 17: [0xFFE3, 0xFFE3, 0xFFE4], // ctrl
404
+ 18: [0xFFE9, 0xFFE9, 0xFFEA], // alt
405
+ 19: [0xFF13], // pause/break
406
+ 20: [0xFFE5], // caps lock
407
+ 27: [0xFF1B], // escape
408
+ 32: [0x0020], // space
409
+ 33: [0xFF55, 0xFF55, 0xFF55, 0xFFB9], // page up / KP 9
410
+ 34: [0xFF56, 0xFF56, 0xFF56, 0xFFB3], // page down / KP 3
411
+ 35: [0xFF57, 0xFF57, 0xFF57, 0xFFB1], // end / KP 1
412
+ 36: [0xFF50, 0xFF50, 0xFF50, 0xFFB7], // home / KP 7
413
+ 37: [0xFF51, 0xFF51, 0xFF51, 0xFFB4], // left arrow / KP 4
414
+ 38: [0xFF52, 0xFF52, 0xFF52, 0xFFB8], // up arrow / KP 8
415
+ 39: [0xFF53, 0xFF53, 0xFF53, 0xFFB6], // right arrow / KP 6
416
+ 40: [0xFF54, 0xFF54, 0xFF54, 0xFFB2], // down arrow / KP 2
417
+ 45: [0xFF63, 0xFF63, 0xFF63, 0xFFB0], // insert / KP 0
418
+ 46: [0xFFFF, 0xFFFF, 0xFFFF, 0xFFAE], // delete / KP decimal
419
+ 91: [0xFFE7], // left windows/command key (meta_l)
420
+ 92: [0xFFE8], // right window/command key (meta_r)
421
+ 93: [0xFF67], // menu key
422
+ 96: [0xFFB0], // KP 0
423
+ 97: [0xFFB1], // KP 1
424
+ 98: [0xFFB2], // KP 2
425
+ 99: [0xFFB3], // KP 3
426
+ 100: [0xFFB4], // KP 4
427
+ 101: [0xFFB5], // KP 5
428
+ 102: [0xFFB6], // KP 6
429
+ 103: [0xFFB7], // KP 7
430
+ 104: [0xFFB8], // KP 8
431
+ 105: [0xFFB9], // KP 9
432
+ 106: [0xFFAA], // KP multiply
433
+ 107: [0xFFAB], // KP add
434
+ 109: [0xFFAD], // KP subtract
435
+ 110: [0xFFAE], // KP decimal
436
+ 111: [0xFFAF], // KP divide
437
+ 112: [0xFFBE], // f1
438
+ 113: [0xFFBF], // f2
439
+ 114: [0xFFC0], // f3
440
+ 115: [0xFFC1], // f4
441
+ 116: [0xFFC2], // f5
442
+ 117: [0xFFC3], // f6
443
+ 118: [0xFFC4], // f7
444
+ 119: [0xFFC5], // f8
445
+ 120: [0xFFC6], // f9
446
+ 121: [0xFFC7], // f10
447
+ 122: [0xFFC8], // f11
448
+ 123: [0xFFC9], // f12
449
+ 144: [0xFF7F], // num lock
450
+ 145: [0xFF14], // scroll lock
451
+ 225: [0xFE03] // altgraph (iso_level3_shift)
452
+ };
453
+
454
+ /**
455
+ * Map of known JavaScript keyidentifiers which do not map to typable
456
+ * characters to their unshifted X11 keysym equivalents.
457
+ *
458
+ * @private
459
+ * @type {!Object.<string, number[]>}
460
+ */
461
+ var keyidentifier_keysym = {
462
+ "Again": [0xFF66],
463
+ "AllCandidates": [0xFF3D],
464
+ "Alphanumeric": [0xFF30],
465
+ "Alt": [0xFFE9, 0xFFE9, 0xFFEA],
466
+ "Attn": [0xFD0E],
467
+ "AltGraph": [0xFE03],
468
+ "ArrowDown": [0xFF54],
469
+ "ArrowLeft": [0xFF51],
470
+ "ArrowRight": [0xFF53],
471
+ "ArrowUp": [0xFF52],
472
+ "Backspace": [0xFF08],
473
+ "CapsLock": [0xFFE5],
474
+ "Cancel": [0xFF69],
475
+ "Clear": [0xFF0B],
476
+ "Convert": [0xFF23],
477
+ "Copy": [0xFD15],
478
+ "Crsel": [0xFD1C],
479
+ "CrSel": [0xFD1C],
480
+ "CodeInput": [0xFF37],
481
+ "Compose": [0xFF20],
482
+ "Control": [0xFFE3, 0xFFE3, 0xFFE4],
483
+ "ContextMenu": [0xFF67],
484
+ "Delete": [0xFFFF],
485
+ "Down": [0xFF54],
486
+ "End": [0xFF57],
487
+ "Enter": [0xFF0D],
488
+ "EraseEof": [0xFD06],
489
+ "Escape": [0xFF1B],
490
+ "Execute": [0xFF62],
491
+ "Exsel": [0xFD1D],
492
+ "ExSel": [0xFD1D],
493
+ "F1": [0xFFBE],
494
+ "F2": [0xFFBF],
495
+ "F3": [0xFFC0],
496
+ "F4": [0xFFC1],
497
+ "F5": [0xFFC2],
498
+ "F6": [0xFFC3],
499
+ "F7": [0xFFC4],
500
+ "F8": [0xFFC5],
501
+ "F9": [0xFFC6],
502
+ "F10": [0xFFC7],
503
+ "F11": [0xFFC8],
504
+ "F12": [0xFFC9],
505
+ "F13": [0xFFCA],
506
+ "F14": [0xFFCB],
507
+ "F15": [0xFFCC],
508
+ "F16": [0xFFCD],
509
+ "F17": [0xFFCE],
510
+ "F18": [0xFFCF],
511
+ "F19": [0xFFD0],
512
+ "F20": [0xFFD1],
513
+ "F21": [0xFFD2],
514
+ "F22": [0xFFD3],
515
+ "F23": [0xFFD4],
516
+ "F24": [0xFFD5],
517
+ "Find": [0xFF68],
518
+ "GroupFirst": [0xFE0C],
519
+ "GroupLast": [0xFE0E],
520
+ "GroupNext": [0xFE08],
521
+ "GroupPrevious": [0xFE0A],
522
+ "FullWidth": null,
523
+ "HalfWidth": null,
524
+ "HangulMode": [0xFF31],
525
+ "Hankaku": [0xFF29],
526
+ "HanjaMode": [0xFF34],
527
+ "Help": [0xFF6A],
528
+ "Hiragana": [0xFF25],
529
+ "HiraganaKatakana": [0xFF27],
530
+ "Home": [0xFF50],
531
+ "Hyper": [0xFFED, 0xFFED, 0xFFEE],
532
+ "Insert": [0xFF63],
533
+ "JapaneseHiragana": [0xFF25],
534
+ "JapaneseKatakana": [0xFF26],
535
+ "JapaneseRomaji": [0xFF24],
536
+ "JunjaMode": [0xFF38],
537
+ "KanaMode": [0xFF2D],
538
+ "KanjiMode": [0xFF21],
539
+ "Katakana": [0xFF26],
540
+ "Left": [0xFF51],
541
+ "Meta": [0xFFE7, 0xFFE7, 0xFFE8],
542
+ "ModeChange": [0xFF7E],
543
+ "NonConvert": [0xFF22],
544
+ "NumLock": [0xFF7F],
545
+ "PageDown": [0xFF56],
546
+ "PageUp": [0xFF55],
547
+ "Pause": [0xFF13],
548
+ "Play": [0xFD16],
549
+ "PreviousCandidate": [0xFF3E],
550
+ "PrintScreen": [0xFF61],
551
+ "Redo": [0xFF66],
552
+ "Right": [0xFF53],
553
+ "Romaji": [0xFF24],
554
+ "RomanCharacters": null,
555
+ "Scroll": [0xFF14],
556
+ "Select": [0xFF60],
557
+ "Separator": [0xFFAC],
558
+ "Shift": [0xFFE1, 0xFFE1, 0xFFE2],
559
+ "SingleCandidate": [0xFF3C],
560
+ "Super": [0xFFEB, 0xFFEB, 0xFFEC],
561
+ "Tab": [0xFF09],
562
+ "UIKeyInputDownArrow": [0xFF54],
563
+ "UIKeyInputEscape": [0xFF1B],
564
+ "UIKeyInputLeftArrow": [0xFF51],
565
+ "UIKeyInputRightArrow": [0xFF53],
566
+ "UIKeyInputUpArrow": [0xFF52],
567
+ "Up": [0xFF52],
568
+ "Undo": [0xFF65],
569
+ "Win": [0xFFE7, 0xFFE7, 0xFFE8],
570
+ "Zenkaku": [0xFF28],
571
+ "ZenkakuHankaku": [0xFF2A]
572
+ };
573
+
574
+ /**
575
+ * All keysyms which should not repeat when held down.
576
+ *
577
+ * @private
578
+ * @type {!Object.<number, boolean>}
579
+ */
580
+ var no_repeat = {
581
+ 0xFE03: true, // ISO Level 3 Shift (AltGr)
582
+ 0xFFE1: true, // Left shift
583
+ 0xFFE2: true, // Right shift
584
+ 0xFFE3: true, // Left ctrl
585
+ 0xFFE4: true, // Right ctrl
586
+ 0xFFE5: true, // Caps Lock
587
+ 0xFFE7: true, // Left meta
588
+ 0xFFE8: true, // Right meta
589
+ 0xFFE9: true, // Left alt
590
+ 0xFFEA: true, // Right alt
591
+ 0xFFEB: true, // Left super/hyper
592
+ 0xFFEC: true // Right super/hyper
593
+ };
594
+
595
+ /**
596
+ * All modifiers and their states.
597
+ *
598
+ * @type {!Guacamole.Keyboard.ModifierState}
599
+ */
600
+ this.modifiers = new Guacamole.Keyboard.ModifierState();
601
+
602
+ /**
603
+ * The state of every key, indexed by keysym. If a particular key is
604
+ * pressed, the value of pressed for that keysym will be true. If a key
605
+ * is not currently pressed, it will not be defined.
606
+ *
607
+ * @type {!Object.<number, boolean>}
608
+ */
609
+ this.pressed = {};
610
+
611
+ /**
612
+ * The state of every key, indexed by keysym, for strictly those keys whose
613
+ * status has been indirectly determined thorugh observation of other key
614
+ * events. If a particular key is implicitly pressed, the value of
615
+ * implicitlyPressed for that keysym will be true. If a key
616
+ * is not currently implicitly pressed (the key is not pressed OR the state
617
+ * of the key is explicitly known), it will not be defined.
618
+ *
619
+ * @private
620
+ * @type {!Object.<number, boolean>}
621
+ */
622
+ var implicitlyPressed = {};
623
+
624
+ /**
625
+ * The last result of calling the onkeydown handler for each key, indexed
626
+ * by keysym. This is used to prevent/allow default actions for key events,
627
+ * even when the onkeydown handler cannot be called again because the key
628
+ * is (theoretically) still pressed.
629
+ *
630
+ * @private
631
+ * @type {!Object.<number, boolean>}
632
+ */
633
+ var last_keydown_result = {};
634
+
635
+ /**
636
+ * The keysym most recently associated with a given keycode when keydown
637
+ * fired. This object maps keycodes to keysyms.
638
+ *
639
+ * @private
640
+ * @type {!Object.<number, number>}
641
+ */
642
+ var recentKeysym = {};
643
+
644
+ /**
645
+ * Timeout before key repeat starts.
646
+ *
647
+ * @private
648
+ * @type {number}
649
+ */
650
+ var key_repeat_timeout = null;
651
+
652
+ /**
653
+ * Interval which presses and releases the last key pressed while that
654
+ * key is still being held down.
655
+ *
656
+ * @private
657
+ * @type {number}
658
+ */
659
+ var key_repeat_interval = null;
660
+
661
+ /**
662
+ * Given an array of keysyms indexed by location, returns the keysym
663
+ * for the given location, or the keysym for the standard location if
664
+ * undefined.
665
+ *
666
+ * @private
667
+ * @param {number[]} keysyms
668
+ * An array of keysyms, where the index of the keysym in the array is
669
+ * the location value.
670
+ *
671
+ * @param {!number} location
672
+ * The location on the keyboard corresponding to the key pressed, as
673
+ * defined at: http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
674
+ */
675
+ var get_keysym = function get_keysym(keysyms, location) {
676
+
677
+ if (!keysyms)
678
+ return null;
679
+
680
+ return keysyms[location] || keysyms[0];
681
+ };
682
+
683
+ /**
684
+ * Returns true if the given keysym corresponds to a printable character,
685
+ * false otherwise.
686
+ *
687
+ * @param {!number} keysym
688
+ * The keysym to check.
689
+ *
690
+ * @returns {!boolean}
691
+ * true if the given keysym corresponds to a printable character,
692
+ * false otherwise.
693
+ */
694
+ var isPrintable = function isPrintable(keysym) {
695
+
696
+ // Keysyms with Unicode equivalents are printable
697
+ return (keysym >= 0x00 && keysym <= 0xFF)
698
+ || (keysym & 0xFFFF0000) === 0x01000000;
699
+
700
+ };
701
+
702
+ function keysym_from_key_identifier(identifier, location, shifted) {
703
+
704
+ if (!identifier)
705
+ return null;
706
+
707
+ var typedCharacter;
708
+
709
+ // If identifier is U+xxxx, decode Unicode character
710
+ var unicodePrefixLocation = identifier.indexOf("U+");
711
+ if (unicodePrefixLocation >= 0) {
712
+ var hex = identifier.substring(unicodePrefixLocation+2);
713
+ typedCharacter = String.fromCharCode(parseInt(hex, 16));
714
+ }
715
+
716
+ // If single character and not keypad, use that as typed character
717
+ else if (identifier.length === 1 && location !== 3)
718
+ typedCharacter = identifier;
719
+
720
+ // Otherwise, look up corresponding keysym
721
+ else
722
+ return get_keysym(keyidentifier_keysym[identifier], location);
723
+
724
+ // Alter case if necessary
725
+ if (shifted === true)
726
+ typedCharacter = typedCharacter.toUpperCase();
727
+ else if (shifted === false)
728
+ typedCharacter = typedCharacter.toLowerCase();
729
+
730
+ // Get codepoint
731
+ var codepoint = typedCharacter.charCodeAt(0);
732
+ return keysym_from_charcode(codepoint);
733
+
734
+ }
735
+
736
+ function isControlCharacter(codepoint) {
737
+ return codepoint <= 0x1F || (codepoint >= 0x7F && codepoint <= 0x9F);
738
+ }
739
+
740
+ function keysym_from_charcode(codepoint) {
741
+
742
+ // Keysyms for control characters
743
+ if (isControlCharacter(codepoint)) return 0xFF00 | codepoint;
744
+
745
+ // Keysyms for ASCII chars
746
+ if (codepoint >= 0x0000 && codepoint <= 0x00FF)
747
+ return codepoint;
748
+
749
+ // Keysyms for Unicode
750
+ if (codepoint >= 0x0100 && codepoint <= 0x10FFFF)
751
+ return 0x01000000 | codepoint;
752
+
753
+ return null;
754
+
755
+ }
756
+
757
+ function keysym_from_keycode(keyCode, location) {
758
+ return get_keysym(keycodeKeysyms[keyCode], location);
759
+ }
760
+
761
+ /**
762
+ * Heuristically detects if the legacy keyIdentifier property of
763
+ * a keydown/keyup event looks incorrectly derived. Chrome, and
764
+ * presumably others, will produce the keyIdentifier by assuming
765
+ * the keyCode is the Unicode codepoint for that key. This is not
766
+ * correct in all cases.
767
+ *
768
+ * @private
769
+ * @param {!number} keyCode
770
+ * The keyCode from a browser keydown/keyup event.
771
+ *
772
+ * @param {string} keyIdentifier
773
+ * The legacy keyIdentifier from a browser keydown/keyup event.
774
+ *
775
+ * @returns {!boolean}
776
+ * true if the keyIdentifier looks sane, false if the keyIdentifier
777
+ * appears incorrectly derived or is missing entirely.
778
+ */
779
+ var key_identifier_sane = function key_identifier_sane(keyCode, keyIdentifier) {
780
+
781
+ // Missing identifier is not sane
782
+ if (!keyIdentifier)
783
+ return false;
784
+
785
+ // Assume non-Unicode keyIdentifier values are sane
786
+ var unicodePrefixLocation = keyIdentifier.indexOf("U+");
787
+ if (unicodePrefixLocation === -1)
788
+ return true;
789
+
790
+ // If the Unicode codepoint isn't identical to the keyCode,
791
+ // then the identifier is likely correct
792
+ var codepoint = parseInt(keyIdentifier.substring(unicodePrefixLocation+2), 16);
793
+ if (keyCode !== codepoint)
794
+ return true;
795
+
796
+ // The keyCodes for A-Z and 0-9 are actually identical to their
797
+ // Unicode codepoints
798
+ if ((keyCode >= 65 && keyCode <= 90) || (keyCode >= 48 && keyCode <= 57))
799
+ return true;
800
+
801
+ // The keyIdentifier does NOT appear sane
802
+ return false;
803
+
804
+ };
805
+
806
+ /**
807
+ * Marks a key as pressed, firing the keydown event if registered. Key
808
+ * repeat for the pressed key will start after a delay if that key is
809
+ * not a modifier. The return value of this function depends on the
810
+ * return value of the keydown event handler, if any.
811
+ *
812
+ * @param {number} keysym
813
+ * The keysym of the key to press.
814
+ *
815
+ * @return {boolean}
816
+ * true if event should NOT be canceled, false otherwise.
817
+ */
818
+ this.press = function(keysym) {
819
+
820
+ // Don't bother with pressing the key if the key is unknown
821
+ if (keysym === null) return;
822
+
823
+ // Only press if released
824
+ if (!guac_keyboard.pressed[keysym]) {
825
+
826
+ // Mark key as pressed
827
+ guac_keyboard.pressed[keysym] = true;
828
+
829
+ // Send key event
830
+ if (guac_keyboard.onkeydown) {
831
+ var result = guac_keyboard.onkeydown(keysym);
832
+ last_keydown_result[keysym] = result;
833
+
834
+ // Stop any current repeat
835
+ window.clearTimeout(key_repeat_timeout);
836
+ window.clearInterval(key_repeat_interval);
837
+
838
+ // Repeat after a delay as long as pressed
839
+ if (!no_repeat[keysym])
840
+ key_repeat_timeout = window.setTimeout(function() {
841
+ key_repeat_interval = window.setInterval(function() {
842
+ guac_keyboard.onkeyup(keysym);
843
+ guac_keyboard.onkeydown(keysym);
844
+ }, 50);
845
+ }, 500);
846
+
847
+ return result;
848
+ }
849
+ }
850
+
851
+ // Return the last keydown result by default, resort to false if unknown
852
+ return last_keydown_result[keysym] || false;
853
+
854
+ };
855
+
856
+ /**
857
+ * Marks a key as released, firing the keyup event if registered.
858
+ *
859
+ * @param {number} keysym
860
+ * The keysym of the key to release.
861
+ */
862
+ this.release = function(keysym) {
863
+
864
+ // Only release if pressed
865
+ if (guac_keyboard.pressed[keysym]) {
866
+
867
+ // Mark key as released
868
+ delete guac_keyboard.pressed[keysym];
869
+ delete implicitlyPressed[keysym];
870
+
871
+ // Stop repeat
872
+ window.clearTimeout(key_repeat_timeout);
873
+ window.clearInterval(key_repeat_interval);
874
+
875
+ // Send key event
876
+ if (keysym !== null && guac_keyboard.onkeyup)
877
+ guac_keyboard.onkeyup(keysym);
878
+
879
+ }
880
+
881
+ };
882
+
883
+ /**
884
+ * Presses and releases the keys necessary to type the given string of
885
+ * text.
886
+ *
887
+ * @param {!string} str
888
+ * The string to type.
889
+ */
890
+ this.type = function type(str) {
891
+
892
+ // Press/release the key corresponding to each character in the string
893
+ for (var i = 0; i < str.length; i++) {
894
+
895
+ // Determine keysym of current character
896
+ var codepoint = str.codePointAt ? str.codePointAt(i) : str.charCodeAt(i);
897
+ var keysym = keysym_from_charcode(codepoint);
898
+
899
+ // Press and release key for current character
900
+ guac_keyboard.press(keysym);
901
+ guac_keyboard.release(keysym);
902
+
903
+ }
904
+
905
+ };
906
+
907
+ /**
908
+ * Resets the state of this keyboard, releasing all keys, and firing keyup
909
+ * events for each released key.
910
+ */
911
+ this.reset = function() {
912
+
913
+ // Release all pressed keys
914
+ for (var keysym in guac_keyboard.pressed)
915
+ guac_keyboard.release(parseInt(keysym));
916
+
917
+ // Clear event log
918
+ eventLog = [];
919
+
920
+ };
921
+
922
+ /**
923
+ * Resynchronizes the remote state of the given modifier with its
924
+ * corresponding local modifier state, as dictated by
925
+ * {@link KeyEvent#modifiers} within the given key event, by pressing or
926
+ * releasing keysyms.
927
+ *
928
+ * @private
929
+ * @param {!string} modifier
930
+ * The name of the {@link Guacamole.Keyboard.ModifierState} property
931
+ * being updated.
932
+ *
933
+ * @param {!number[]} keysyms
934
+ * The keysyms which represent the modifier being updated.
935
+ *
936
+ * @param {!KeyEvent} keyEvent
937
+ * Guacamole's current best interpretation of the key event being
938
+ * processed.
939
+ */
940
+ var updateModifierState = function updateModifierState(modifier,
941
+ keysyms, keyEvent) {
942
+
943
+ var localState = keyEvent.modifiers[modifier];
944
+ var remoteState = guac_keyboard.modifiers[modifier];
945
+
946
+ var i;
947
+
948
+ // Do not trust changes in modifier state for events directly involving
949
+ // that modifier: (1) the flag may erroneously be cleared despite
950
+ // another version of the same key still being held and (2) the change
951
+ // in flag may be due to the current event being processed, thus
952
+ // updating things here is at best redundant and at worst incorrect
953
+ if (keysyms.indexOf(keyEvent.keysym) !== -1)
954
+ return;
955
+
956
+ // Release all related keys if modifier is implicitly released
957
+ if (remoteState && localState === false) {
958
+ for (i = 0; i < keysyms.length; i++) {
959
+ guac_keyboard.release(keysyms[i]);
960
+ }
961
+ }
962
+
963
+ // Press if modifier is implicitly pressed
964
+ else if (!remoteState && localState) {
965
+
966
+ // Verify that modifier flag isn't already pressed or already set
967
+ // due to another version of the same key being held down
968
+ for (i = 0; i < keysyms.length; i++) {
969
+ if (guac_keyboard.pressed[keysyms[i]])
970
+ return;
971
+ }
972
+
973
+ // Mark as implicitly pressed only if there is other information
974
+ // within the key event relating to a different key. Some
975
+ // platforms, such as iOS, will send essentially empty key events
976
+ // for modifier keys, using only the modifier flags to signal the
977
+ // identity of the key.
978
+ var keysym = keysyms[0];
979
+ if (keyEvent.keysym)
980
+ implicitlyPressed[keysym] = true;
981
+
982
+ guac_keyboard.press(keysym);
983
+
984
+ }
985
+
986
+ };
987
+
988
+ /**
989
+ * Given a keyboard event, updates the remote key state to match the local
990
+ * modifier state and remote based on the modifier flags within the event.
991
+ * This function pays no attention to keycodes.
992
+ *
993
+ * @private
994
+ * @param {!KeyEvent} keyEvent
995
+ * Guacamole's current best interpretation of the key event being
996
+ * processed.
997
+ */
998
+ var syncModifierStates = function syncModifierStates(keyEvent) {
999
+
1000
+ // Resync state of alt
1001
+ updateModifierState('alt', [
1002
+ 0xFFE9, // Left alt
1003
+ 0xFFEA, // Right alt
1004
+ 0xFE03 // AltGr
1005
+ ], keyEvent);
1006
+
1007
+ // Resync state of shift
1008
+ updateModifierState('shift', [
1009
+ 0xFFE1, // Left shift
1010
+ 0xFFE2 // Right shift
1011
+ ], keyEvent);
1012
+
1013
+ // Resync state of ctrl
1014
+ updateModifierState('ctrl', [
1015
+ 0xFFE3, // Left ctrl
1016
+ 0xFFE4 // Right ctrl
1017
+ ], keyEvent);
1018
+
1019
+ // Resync state of meta
1020
+ updateModifierState('meta', [
1021
+ 0xFFE7, // Left meta
1022
+ 0xFFE8 // Right meta
1023
+ ], keyEvent);
1024
+
1025
+ // Resync state of hyper
1026
+ updateModifierState('hyper', [
1027
+ 0xFFEB, // Left super/hyper
1028
+ 0xFFEC // Right super/hyper
1029
+ ], keyEvent);
1030
+
1031
+ // Update state
1032
+ guac_keyboard.modifiers = keyEvent.modifiers;
1033
+
1034
+ };
1035
+
1036
+ /**
1037
+ * Returns whether all currently pressed keys were implicitly pressed. A
1038
+ * key is implicitly pressed if its status was inferred indirectly from
1039
+ * inspection of other key events.
1040
+ *
1041
+ * @private
1042
+ * @returns {!boolean}
1043
+ * true if all currently pressed keys were implicitly pressed, false
1044
+ * otherwise.
1045
+ */
1046
+ var isStateImplicit = function isStateImplicit() {
1047
+
1048
+ for (var keysym in guac_keyboard.pressed) {
1049
+ if (!implicitlyPressed[keysym])
1050
+ return false;
1051
+ }
1052
+
1053
+ return true;
1054
+
1055
+ };
1056
+
1057
+ /**
1058
+ * Reads through the event log, removing events from the head of the log
1059
+ * when the corresponding true key presses are known (or as known as they
1060
+ * can be).
1061
+ *
1062
+ * @private
1063
+ * @return {boolean}
1064
+ * Whether the default action of the latest event should be prevented.
1065
+ */
1066
+ function interpret_events() {
1067
+
1068
+ // Do not prevent default if no event could be interpreted
1069
+ var handled_event = interpret_event();
1070
+ if (!handled_event)
1071
+ return false;
1072
+
1073
+ // Interpret as much as possible
1074
+ var last_event;
1075
+ do {
1076
+ last_event = handled_event;
1077
+ handled_event = interpret_event();
1078
+ } while (handled_event !== null);
1079
+
1080
+ // Reset keyboard state if we cannot expect to receive any further
1081
+ // keyup events
1082
+ if (isStateImplicit())
1083
+ guac_keyboard.reset();
1084
+
1085
+ return last_event.defaultPrevented;
1086
+
1087
+ }
1088
+
1089
+ /**
1090
+ * Releases Ctrl+Alt, if both are currently pressed and the given keysym
1091
+ * looks like a key that may require AltGr.
1092
+ *
1093
+ * @private
1094
+ * @param {!number} keysym
1095
+ * The key that was just pressed.
1096
+ */
1097
+ var release_simulated_altgr = function release_simulated_altgr(keysym) {
1098
+
1099
+ // Both Ctrl+Alt must be pressed if simulated AltGr is in use
1100
+ if (!guac_keyboard.modifiers.ctrl || !guac_keyboard.modifiers.alt)
1101
+ return;
1102
+
1103
+ // Assume [A-Z] never require AltGr
1104
+ if (keysym >= 0x0041 && keysym <= 0x005A)
1105
+ return;
1106
+
1107
+ // Assume [a-z] never require AltGr
1108
+ if (keysym >= 0x0061 && keysym <= 0x007A)
1109
+ return;
1110
+
1111
+ // Release Ctrl+Alt if the keysym is printable
1112
+ if (keysym <= 0xFF || (keysym & 0xFF000000) === 0x01000000) {
1113
+ guac_keyboard.release(0xFFE3); // Left ctrl
1114
+ guac_keyboard.release(0xFFE4); // Right ctrl
1115
+ guac_keyboard.release(0xFFE9); // Left alt
1116
+ guac_keyboard.release(0xFFEA); // Right alt
1117
+ }
1118
+
1119
+ };
1120
+
1121
+ /**
1122
+ * Reads through the event log, interpreting the first event, if possible,
1123
+ * and returning that event. If no events can be interpreted, due to a
1124
+ * total lack of events or the need for more events, null is returned. Any
1125
+ * interpreted events are automatically removed from the log.
1126
+ *
1127
+ * @private
1128
+ * @return {KeyEvent}
1129
+ * The first key event in the log, if it can be interpreted, or null
1130
+ * otherwise.
1131
+ */
1132
+ var interpret_event = function interpret_event() {
1133
+
1134
+ // Peek at first event in log
1135
+ var first = eventLog[0];
1136
+ if (!first)
1137
+ return null;
1138
+
1139
+ // Keydown event
1140
+ if (first instanceof KeydownEvent) {
1141
+
1142
+ var keysym = null;
1143
+ var accepted_events = [];
1144
+
1145
+ // Defer handling of Meta until it is known to be functioning as a
1146
+ // modifier (it may otherwise actually be an alternative method for
1147
+ // pressing a single key, such as Meta+Left for Home on ChromeOS)
1148
+ if (first.keysym === 0xFFE7 || first.keysym === 0xFFE8) {
1149
+
1150
+ // Defer handling until further events exist to provide context
1151
+ if (eventLog.length === 1)
1152
+ return null;
1153
+
1154
+ // Drop keydown if it turns out Meta does not actually apply
1155
+ if (eventLog[1].keysym !== first.keysym) {
1156
+ if (!eventLog[1].modifiers.meta)
1157
+ return eventLog.shift();
1158
+ }
1159
+
1160
+ // Drop duplicate keydown events while waiting to determine
1161
+ // whether to acknowledge Meta (browser may repeat keydown
1162
+ // while the key is held)
1163
+ else if (eventLog[1] instanceof KeydownEvent)
1164
+ return eventLog.shift();
1165
+
1166
+ }
1167
+
1168
+ // If event itself is reliable, no need to wait for other events
1169
+ if (first.reliable) {
1170
+ keysym = first.keysym;
1171
+ accepted_events = eventLog.splice(0, 1);
1172
+ }
1173
+
1174
+ // If keydown is immediately followed by a keypress, use the indicated character
1175
+ else if (eventLog[1] instanceof KeypressEvent) {
1176
+ keysym = eventLog[1].keysym;
1177
+ accepted_events = eventLog.splice(0, 2);
1178
+ }
1179
+
1180
+ // If keydown is immediately followed by anything else, then no
1181
+ // keypress can possibly occur to clarify this event, and we must
1182
+ // handle it now
1183
+ else if (eventLog[1]) {
1184
+ keysym = first.keysym;
1185
+ accepted_events = eventLog.splice(0, 1);
1186
+ }
1187
+
1188
+ // Fire a key press if valid events were found
1189
+ if (accepted_events.length > 0) {
1190
+
1191
+ syncModifierStates(first);
1192
+
1193
+ if (keysym) {
1194
+
1195
+ // Fire event
1196
+ release_simulated_altgr(keysym);
1197
+ var defaultPrevented = !guac_keyboard.press(keysym);
1198
+ recentKeysym[first.keyCode] = keysym;
1199
+
1200
+ // Release the key now if we cannot rely on the associated
1201
+ // keyup event
1202
+ if (!first.keyupReliable)
1203
+ guac_keyboard.release(keysym);
1204
+
1205
+ // Record whether default was prevented
1206
+ for (var i=0; i<accepted_events.length; i++)
1207
+ accepted_events[i].defaultPrevented = defaultPrevented;
1208
+
1209
+ }
1210
+
1211
+ return first;
1212
+
1213
+ }
1214
+
1215
+ } // end if keydown
1216
+
1217
+ // Keyup event
1218
+ else if (first instanceof KeyupEvent && !quirks.keyupUnreliable) {
1219
+
1220
+ // Release specific key if known
1221
+ var keysym = first.keysym;
1222
+ if (keysym) {
1223
+ guac_keyboard.release(keysym);
1224
+ delete recentKeysym[first.keyCode];
1225
+ first.defaultPrevented = true;
1226
+ }
1227
+
1228
+ // Otherwise, fall back to releasing all keys
1229
+ else {
1230
+ guac_keyboard.reset();
1231
+ return first;
1232
+ }
1233
+
1234
+ syncModifierStates(first);
1235
+ return eventLog.shift();
1236
+
1237
+ } // end if keyup
1238
+
1239
+ // Ignore any other type of event (keypress by itself is invalid, and
1240
+ // unreliable keyup events should simply be dumped)
1241
+ else
1242
+ return eventLog.shift();
1243
+
1244
+ // No event interpreted
1245
+ return null;
1246
+
1247
+ };
1248
+
1249
+ /**
1250
+ * Returns the keyboard location of the key associated with the given
1251
+ * keyboard event. The location differentiates key events which otherwise
1252
+ * have the same keycode, such as left shift vs. right shift.
1253
+ *
1254
+ * @private
1255
+ * @param {!KeyboardEvent} e
1256
+ * A JavaScript keyboard event, as received through the DOM via a
1257
+ * "keydown", "keyup", or "keypress" handler.
1258
+ *
1259
+ * @returns {!number}
1260
+ * The location of the key event on the keyboard, as defined at:
1261
+ * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
1262
+ */
1263
+ var getEventLocation = function getEventLocation(e) {
1264
+
1265
+ // Use standard location, if possible
1266
+ if ('location' in e)
1267
+ return e.location;
1268
+
1269
+ // Failing that, attempt to use deprecated keyLocation
1270
+ if ('keyLocation' in e)
1271
+ return e.keyLocation;
1272
+
1273
+ // If no location is available, assume left side
1274
+ return 0;
1275
+
1276
+ };
1277
+
1278
+ /**
1279
+ * Attempts to mark the given Event as having been handled by this
1280
+ * Guacamole.Keyboard. If the Event has already been marked as handled,
1281
+ * false is returned.
1282
+ *
1283
+ * @param {!Event} e
1284
+ * The Event to mark.
1285
+ *
1286
+ * @returns {!boolean}
1287
+ * true if the given Event was successfully marked, false if the given
1288
+ * Event was already marked.
1289
+ */
1290
+ var markEvent = function markEvent(e) {
1291
+
1292
+ // Fail if event is already marked
1293
+ if (e[EVENT_MARKER])
1294
+ return false;
1295
+
1296
+ // Mark event otherwise
1297
+ e[EVENT_MARKER] = true;
1298
+ return true;
1299
+
1300
+ };
1301
+
1302
+ var keyDownListener = function(e) {
1303
+
1304
+ // Only intercept if handler set
1305
+ if (!guac_keyboard.onkeydown) return;
1306
+
1307
+ // Ignore events which have already been handled
1308
+ if (!markEvent(e)) return;
1309
+
1310
+ var keydownEvent = new KeydownEvent(e);
1311
+
1312
+ // Ignore (but do not prevent) the event if explicitly marked as composing,
1313
+ // or when the "composition" keycode sent by some browsers when an IME is in use
1314
+ // (see: http://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html)
1315
+ if (e.isComposing || keydownEvent.keyCode === 229)
1316
+ return;
1317
+
1318
+ // Log event
1319
+ eventLog.push(keydownEvent);
1320
+
1321
+ // Interpret as many events as possible, prevent default if indicated
1322
+ if (interpret_events())
1323
+ e.preventDefault();
1324
+
1325
+ }
1326
+
1327
+ var keyPressListener = function(e) {
1328
+
1329
+ // Only intercept if handler set
1330
+ if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return;
1331
+
1332
+ // Ignore events which have already been handled
1333
+ if (!markEvent(e)) return;
1334
+
1335
+ // Log event
1336
+ eventLog.push(new KeypressEvent(e));
1337
+
1338
+ // Interpret as many events as possible, prevent default if indicated
1339
+ if (interpret_events())
1340
+ e.preventDefault();
1341
+ }
1342
+
1343
+ var keyUpListener = function(e) {
1344
+
1345
+ // Only intercept if handler set
1346
+ if (!guac_keyboard.onkeyup) return;
1347
+
1348
+ // Ignore events which have already been handled
1349
+ if (!markEvent(e)) return;
1350
+
1351
+ e.preventDefault();
1352
+
1353
+ // Log event, call for interpretation
1354
+ eventLog.push(new KeyupEvent(e));
1355
+ interpret_events();
1356
+ }
1357
+
1358
+ /**
1359
+ * Handles the given "input" event, typing the data within the input text.
1360
+ *
1361
+ * @private
1362
+ * @param {!InputEvent} e
1363
+ * The "input" event to handle.
1364
+ */
1365
+ var handleInput = function handleInput(e) {
1366
+
1367
+ // Only intercept if handler set
1368
+ if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return;
1369
+
1370
+ // Ignore events which have already been handled
1371
+ if (!markEvent(e)) return;
1372
+
1373
+ // Type all content written
1374
+ if (e.data && !e.isComposing)
1375
+ guac_keyboard.type(e.data);
1376
+
1377
+ };
1378
+
1379
+ /**
1380
+ * Handles the given "compositionstart" event, automatically removing
1381
+ * the "input" event handler, as "input" events should only be handled
1382
+ * if composition events are not provided by the browser.
1383
+ *
1384
+ * @private
1385
+ * @param {!CompositionEvent} e
1386
+ * The "compositionstart" event to handle.
1387
+ */
1388
+ var handleCompositionStart = function handleCompositionStart(e) {
1389
+
1390
+ // Remove the "input" event handler now that the browser is known
1391
+ // to send composition events
1392
+ element.removeEventListener("input", handleInput, false);
1393
+
1394
+ };
1395
+
1396
+ /**
1397
+ * Handles the given "compositionend" event, typing the data within the
1398
+ * composed text.
1399
+ *
1400
+ * @private
1401
+ * @param {!CompositionEvent} e
1402
+ * The "compositionend" event to handle.
1403
+ */
1404
+ var handleCompositionEnd = function handleCompositionEnd(e) {
1405
+
1406
+ // Only intercept if handler set
1407
+ if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return;
1408
+
1409
+ // Ignore events which have already been handled
1410
+ if (!markEvent(e)) return;
1411
+
1412
+ // Type all content written
1413
+ if (e.data)
1414
+ guac_keyboard.type(e.data);
1415
+
1416
+ };
1417
+
1418
+ /**
1419
+ * Attaches event listeners to the given Element, automatically translating
1420
+ * received key, input, and composition events into simple keydown/keyup
1421
+ * events signalled through this Guacamole.Keyboard's onkeydown and
1422
+ * onkeyup handlers.
1423
+ *
1424
+ * @param {!(Element|Document)} element
1425
+ * The Element to attach event listeners to for the sake of handling
1426
+ * key or input events.
1427
+ */
1428
+ this.listenTo = function listenTo(element) {
1429
+
1430
+ // When key pressed
1431
+ element.addEventListener("keydown", keyDownListener, {passive: false});
1432
+
1433
+ // When key pressed
1434
+ element.addEventListener("keypress", keyPressListener, {passive: false});
1435
+
1436
+ // When key released
1437
+ element.addEventListener("keyup", keyUpListener, {passive: false});
1438
+
1439
+
1440
+ // Automatically type text entered into the wrapped field
1441
+ element.addEventListener("input", handleInput, false);
1442
+ element.addEventListener("compositionend", handleCompositionEnd, false);
1443
+ element.addEventListener("compositionstart", handleCompositionStart, false);
1444
+
1445
+ };
1446
+
1447
+ // Listen to given element, if any
1448
+ if (element)
1449
+ guac_keyboard.listenTo(element);
1450
+
1451
+ this.dispose = function dispose() {
1452
+ // Stop listening to key events
1453
+ if (element) {
1454
+ element.removeEventListener("keydown", keyDownListener, true);
1455
+ element.removeEventListener("keypress", keyPressListener, true);
1456
+ element.removeEventListener("keyup", keyUpListener, true);
1457
+ element.removeEventListener("input", handleInput, false);
1458
+ element.removeEventListener("compositionend", handleCompositionEnd, false);
1459
+ element.removeEventListener("compositionstart", handleCompositionStart, false);
1460
+ }
1461
+ }
1462
+
1463
+ };
1464
+
1465
+
1466
+
1467
+ /**
1468
+ * The unique numerical identifier to assign to the next Guacamole.Keyboard
1469
+ * instance.
1470
+ *
1471
+ * @private
1472
+ * @type {!number}
1473
+ */
1474
+ Guacamole.Keyboard._nextID = 0;
1475
+
1476
+ /**
1477
+ * The state of all supported keyboard modifiers.
1478
+ * @constructor
1479
+ */
1480
+ Guacamole.Keyboard.ModifierState = function() {
1481
+
1482
+ /**
1483
+ * Whether shift is currently pressed.
1484
+ *
1485
+ * @type {!boolean}
1486
+ */
1487
+ this.shift = false;
1488
+
1489
+ /**
1490
+ * Whether ctrl is currently pressed.
1491
+ *
1492
+ * @type {!boolean}
1493
+ */
1494
+ this.ctrl = false;
1495
+
1496
+ /**
1497
+ * Whether alt is currently pressed.
1498
+ *
1499
+ * @type {!boolean}
1500
+ */
1501
+ this.alt = false;
1502
+
1503
+ /**
1504
+ * Whether meta (apple key) is currently pressed.
1505
+ *
1506
+ * @type {!boolean}
1507
+ */
1508
+ this.meta = false;
1509
+
1510
+ /**
1511
+ * Whether hyper (windows key) is currently pressed.
1512
+ *
1513
+ * @type {!boolean}
1514
+ */
1515
+ this.hyper = false;
1516
+
1517
+ };
1518
+
1519
+ /**
1520
+ * Returns the modifier state applicable to the keyboard event given.
1521
+ *
1522
+ * @param {!KeyboardEvent} e
1523
+ * The keyboard event to read.
1524
+ *
1525
+ * @returns {!Guacamole.Keyboard.ModifierState}
1526
+ * The current state of keyboard modifiers.
1527
+ */
1528
+ Guacamole.Keyboard.ModifierState.fromKeyboardEvent = function(e) {
1529
+
1530
+ var state = new Guacamole.Keyboard.ModifierState();
1531
+
1532
+ // Assign states from old flags
1533
+ state.shift = e.shiftKey;
1534
+ state.ctrl = e.ctrlKey;
1535
+ state.alt = e.altKey;
1536
+ state.meta = e.metaKey;
1537
+
1538
+ // Use DOM3 getModifierState() for others
1539
+ if (e.getModifierState) {
1540
+ state.hyper = e.getModifierState("OS")
1541
+ || e.getModifierState("Super")
1542
+ || e.getModifierState("Hyper")
1543
+ || e.getModifierState("Win");
1544
+ }
1545
+
1546
+ return state;
1547
+
1548
+ };
1549
+
1550
+
1551
+ module.exports = Guacamole;