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