1 /*
  2  * Copyright (C) 2013 Glyptodon LLC
  3  *
  4  * Permission is hereby granted, free of charge, to any person obtaining a copy
  5  * of this software and associated documentation files (the "Software"), to deal
  6  * in the Software without restriction, including without limitation the rights
  7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8  * copies of the Software, and to permit persons to whom the Software is
  9  * furnished to do so, subject to the following conditions:
 10  *
 11  * The above copyright notice and this permission notice shall be included in
 12  * all copies or substantial portions of the Software.
 13  *
 14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 20  * THE SOFTWARE.
 21  */
 22 
 23 var Guacamole = Guacamole || {};
 24 
 25 /**
 26  * Provides cross-browser and cross-keyboard keyboard for a specific element.
 27  * Browser and keyboard layout variation is abstracted away, providing events
 28  * which represent keys as their corresponding X11 keysym.
 29  * 
 30  * @constructor
 31  * @param {Element} element The Element to use to provide keyboard events.
 32  */
 33 Guacamole.Keyboard = function(element) {
 34 
 35     /**
 36      * Reference to this Guacamole.Keyboard.
 37      * @private
 38      */
 39     var guac_keyboard = this;
 40 
 41     /**
 42      * Fired whenever the user presses a key with the element associated
 43      * with this Guacamole.Keyboard in focus.
 44      * 
 45      * @event
 46      * @param {Number} keysym The keysym of the key being pressed.
 47      * @return {Boolean} true if the key event should be allowed through to the
 48      *                   browser, false otherwise.
 49      */
 50     this.onkeydown = null;
 51 
 52     /**
 53      * Fired whenever the user releases a key with the element associated
 54      * with this Guacamole.Keyboard in focus.
 55      * 
 56      * @event
 57      * @param {Number} keysym The keysym of the key being released.
 58      */
 59     this.onkeyup = null;
 60 
 61     /**
 62      * A key event having a corresponding timestamp. This event is non-specific.
 63      * Its subclasses should be used instead when recording specific key
 64      * events.
 65      *
 66      * @private
 67      * @constructor
 68      */
 69     var KeyEvent = function() {
 70 
 71         /**
 72          * Reference to this key event.
 73          */
 74         var key_event = this;
 75 
 76         /**
 77          * An arbitrary timestamp in milliseconds, indicating this event's
 78          * position in time relative to other events.
 79          *
 80          * @type Number
 81          */
 82         this.timestamp = new Date().getTime();
 83 
 84         /**
 85          * Whether the default action of this key event should be prevented.
 86          *
 87          * @type Boolean
 88          */
 89         this.defaultPrevented = false;
 90 
 91         /**
 92          * The keysym of the key associated with this key event, as determined
 93          * by a best-effort guess using available event properties and keyboard
 94          * state.
 95          *
 96          * @type Number
 97          */
 98         this.keysym = null;
 99 
100         /**
101          * Whether the keysym value of this key event is known to be reliable.
102          * If false, the keysym may still be valid, but it's only a best guess,
103          * and future key events may be a better source of information.
104          *
105          * @type Boolean
106          */
107         this.reliable = false;
108 
109         /**
110          * Returns the number of milliseconds elapsed since this event was
111          * received.
112          *
113          * @return {Number} The number of milliseconds elapsed since this
114          *                  event was received.
115          */
116         this.getAge = function() {
117             return new Date().getTime() - key_event.timestamp;
118         };
119 
120     };
121 
122     /**
123      * Information related to the pressing of a key, which need not be a key
124      * associated with a printable character. The presence or absence of any
125      * information within this object is browser-dependent.
126      *
127      * @private
128      * @constructor
129      * @augments Guacamole.Keyboard.KeyEvent
130      * @param {Number} keyCode The JavaScript key code of the key pressed.
131      * @param {String} keyIdentifier The legacy DOM3 "keyIdentifier" of the key
132      *                               pressed, as defined at:
133      *                               http://www.w3.org/TR/2009/WD-DOM-Level-3-Events-20090908/#events-Events-KeyboardEvent
134      * @param {String} key The standard name of the key pressed, as defined at:
135      *                     http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
136      * @param {Number} location The location on the keyboard corresponding to
137      *                          the key pressed, as defined at:
138      *                          http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
139      */
140     var KeydownEvent = function(keyCode, keyIdentifier, key, location) {
141 
142         // We extend KeyEvent
143         KeyEvent.apply(this);
144 
145         /**
146          * The JavaScript key code of the key pressed.
147          *
148          * @type Number
149          */
150         this.keyCode = keyCode;
151 
152         /**
153          * The legacy DOM3 "keyIdentifier" of the key pressed, as defined at:
154          * http://www.w3.org/TR/2009/WD-DOM-Level-3-Events-20090908/#events-Events-KeyboardEvent
155          *
156          * @type String
157          */
158         this.keyIdentifier = keyIdentifier;
159 
160         /**
161          * The standard name of the key pressed, as defined at:
162          * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
163          * 
164          * @type String
165          */
166         this.key = key;
167 
168         /**
169          * The location on the keyboard corresponding to the key pressed, as
170          * defined at:
171          * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
172          * 
173          * @type Number
174          */
175         this.location = location;
176 
177         // If key is known from keyCode or DOM3 alone, use that
178         this.keysym =  keysym_from_key_identifier(key, location)
179                     || keysym_from_keycode(keyCode, location);
180 
181         // DOM3 and keyCode are reliable sources
182         if (this.keysym)
183             this.reliable = true;
184 
185         // Use legacy keyIdentifier as a last resort, if it looks sane
186         if (!this.keysym && key_identifier_sane(keyCode, keyIdentifier))
187             this.keysym = keysym_from_key_identifier(keyIdentifier, location, guac_keyboard.modifiers.shift);
188 
189         // Determine whether default action for Alt+combinations must be prevented
190         var prevent_alt =  !guac_keyboard.modifiers.ctrl
191                         && !(navigator && navigator.platform && navigator.platform.match(/^mac/i));
192 
193         // Determine whether default action for Ctrl+combinations must be prevented
194         var prevent_ctrl = !guac_keyboard.modifiers.alt;
195 
196         // We must rely on the (potentially buggy) keyIdentifier if preventing
197         // the default action is important
198         if ((prevent_ctrl && guac_keyboard.modifiers.ctrl)
199          || (prevent_alt  && guac_keyboard.modifiers.alt)
200          || guac_keyboard.modifiers.meta
201          || guac_keyboard.modifiers.hyper)
202             this.reliable = true;
203 
204         // Record most recently known keysym by associated key code
205         recentKeysym[keyCode] = this.keysym;
206 
207     };
208 
209     KeydownEvent.prototype = new KeyEvent();
210 
211     /**
212      * Information related to the pressing of a key, which MUST be
213      * associated with a printable character. The presence or absence of any
214      * information within this object is browser-dependent.
215      *
216      * @private
217      * @constructor
218      * @augments Guacamole.Keyboard.KeyEvent
219      * @param {Number} charCode The Unicode codepoint of the character that
220      *                          would be typed by the key pressed.
221      */
222     var KeypressEvent = function(charCode) {
223 
224         // We extend KeyEvent
225         KeyEvent.apply(this);
226 
227         /**
228          * The Unicode codepoint of the character that would be typed by the
229          * key pressed.
230          *
231          * @type Number
232          */
233         this.charCode = charCode;
234 
235         // Pull keysym from char code
236         this.keysym = keysym_from_charcode(charCode);
237 
238         // Keypress is always reliable
239         this.reliable = true;
240 
241     };
242 
243     KeypressEvent.prototype = new KeyEvent();
244 
245     /**
246      * Information related to the pressing of a key, which need not be a key
247      * associated with a printable character. The presence or absence of any
248      * information within this object is browser-dependent.
249      *
250      * @private
251      * @constructor
252      * @augments Guacamole.Keyboard.KeyEvent
253      * @param {Number} keyCode The JavaScript key code of the key released.
254      * @param {String} keyIdentifier The legacy DOM3 "keyIdentifier" of the key
255      *                               released, as defined at:
256      *                               http://www.w3.org/TR/2009/WD-DOM-Level-3-Events-20090908/#events-Events-KeyboardEvent
257      * @param {String} key The standard name of the key released, as defined at:
258      *                     http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
259      * @param {Number} location The location on the keyboard corresponding to
260      *                          the key released, as defined at:
261      *                          http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
262      */
263     var KeyupEvent = function(keyCode, keyIdentifier, key, location) {
264 
265         // We extend KeyEvent
266         KeyEvent.apply(this);
267 
268         /**
269          * The JavaScript key code of the key released.
270          *
271          * @type Number
272          */
273         this.keyCode = keyCode;
274 
275         /**
276          * The legacy DOM3 "keyIdentifier" of the key released, as defined at:
277          * http://www.w3.org/TR/2009/WD-DOM-Level-3-Events-20090908/#events-Events-KeyboardEvent
278          *
279          * @type String
280          */
281         this.keyIdentifier = keyIdentifier;
282 
283         /**
284          * The standard name of the key released, as defined at:
285          * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
286          * 
287          * @type String
288          */
289         this.key = key;
290 
291         /**
292          * The location on the keyboard corresponding to the key released, as
293          * defined at:
294          * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
295          * 
296          * @type Number
297          */
298         this.location = location;
299 
300         // If key is known from keyCode or DOM3 alone, use that
301         this.keysym =  keysym_from_keycode(keyCode, location)
302                     || recentKeysym[keyCode]
303                     || keysym_from_key_identifier(key, location); // keyCode is still more reliable for keyup when dead keys are in use
304 
305         // Keyup is as reliable as it will ever be
306         this.reliable = true;
307 
308     };
309 
310     KeyupEvent.prototype = new KeyEvent();
311 
312     /**
313      * An array of recorded events, which can be instances of the private
314      * KeydownEvent, KeypressEvent, and KeyupEvent classes.
315      *
316      * @type (KeydownEvent|KeypressEvent|KeyupEvent)[]
317      */
318     var eventLog = [];
319 
320     /**
321      * Map of known JavaScript keycodes which do not map to typable characters
322      * to their X11 keysym equivalents.
323      * @private
324      */
325     var keycodeKeysyms = {
326         8:   [0xFF08], // backspace
327         9:   [0xFF09], // tab
328         13:  [0xFF0D], // enter
329         16:  [0xFFE1, 0xFFE1, 0xFFE2], // shift
330         17:  [0xFFE3, 0xFFE3, 0xFFE4], // ctrl
331         18:  [0xFFE9, 0xFFE9, 0xFE03], // alt
332         19:  [0xFF13], // pause/break
333         20:  [0xFFE5], // caps lock
334         27:  [0xFF1B], // escape
335         32:  [0x0020], // space
336         33:  [0xFF55], // page up
337         34:  [0xFF56], // page down
338         35:  [0xFF57], // end
339         36:  [0xFF50], // home
340         37:  [0xFF51], // left arrow
341         38:  [0xFF52], // up arrow
342         39:  [0xFF53], // right arrow
343         40:  [0xFF54], // down arrow
344         45:  [0xFF63], // insert
345         46:  [0xFFFF], // delete
346         91:  [0xFFEB], // left window key (hyper_l)
347         92:  [0xFF67], // right window key (menu key?)
348         93:  null,     // select key
349         112: [0xFFBE], // f1
350         113: [0xFFBF], // f2
351         114: [0xFFC0], // f3
352         115: [0xFFC1], // f4
353         116: [0xFFC2], // f5
354         117: [0xFFC3], // f6
355         118: [0xFFC4], // f7
356         119: [0xFFC5], // f8
357         120: [0xFFC6], // f9
358         121: [0xFFC7], // f10
359         122: [0xFFC8], // f11
360         123: [0xFFC9], // f12
361         144: [0xFF7F], // num lock
362         145: [0xFF14], // scroll lock
363         225: [0xFE03]  // altgraph (iso_level3_shift)
364     };
365 
366     /**
367      * Map of known JavaScript keyidentifiers which do not map to typable
368      * characters to their unshifted X11 keysym equivalents.
369      * @private
370      */
371     var keyidentifier_keysym = {
372         "Again": [0xFF66],
373         "AllCandidates": [0xFF3D],
374         "Alphanumeric": [0xFF30],
375         "Alt": [0xFFE9, 0xFFE9, 0xFE03],
376         "Attn": [0xFD0E],
377         "AltGraph": [0xFE03],
378         "ArrowDown": [0xFF54],
379         "ArrowLeft": [0xFF51],
380         "ArrowRight": [0xFF53],
381         "ArrowUp": [0xFF52],
382         "Backspace": [0xFF08],
383         "CapsLock": [0xFFE5],
384         "Cancel": [0xFF69],
385         "Clear": [0xFF0B],
386         "Convert": [0xFF21],
387         "Copy": [0xFD15],
388         "Crsel": [0xFD1C],
389         "CrSel": [0xFD1C],
390         "CodeInput": [0xFF37],
391         "Compose": [0xFF20],
392         "Control": [0xFFE3, 0xFFE3, 0xFFE4],
393         "ContextMenu": [0xFF67],
394         "DeadGrave": [0xFE50],
395         "DeadAcute": [0xFE51],
396         "DeadCircumflex": [0xFE52],
397         "DeadTilde": [0xFE53],
398         "DeadMacron": [0xFE54],
399         "DeadBreve": [0xFE55],
400         "DeadAboveDot": [0xFE56],
401         "DeadUmlaut": [0xFE57],
402         "DeadAboveRing": [0xFE58],
403         "DeadDoubleacute": [0xFE59],
404         "DeadCaron": [0xFE5A],
405         "DeadCedilla": [0xFE5B],
406         "DeadOgonek": [0xFE5C],
407         "DeadIota": [0xFE5D],
408         "DeadVoicedSound": [0xFE5E],
409         "DeadSemivoicedSound": [0xFE5F],
410         "Delete": [0xFFFF],
411         "Down": [0xFF54],
412         "End": [0xFF57],
413         "Enter": [0xFF0D],
414         "EraseEof": [0xFD06],
415         "Escape": [0xFF1B],
416         "Execute": [0xFF62],
417         "Exsel": [0xFD1D],
418         "ExSel": [0xFD1D],
419         "F1": [0xFFBE],
420         "F2": [0xFFBF],
421         "F3": [0xFFC0],
422         "F4": [0xFFC1],
423         "F5": [0xFFC2],
424         "F6": [0xFFC3],
425         "F7": [0xFFC4],
426         "F8": [0xFFC5],
427         "F9": [0xFFC6],
428         "F10": [0xFFC7],
429         "F11": [0xFFC8],
430         "F12": [0xFFC9],
431         "F13": [0xFFCA],
432         "F14": [0xFFCB],
433         "F15": [0xFFCC],
434         "F16": [0xFFCD],
435         "F17": [0xFFCE],
436         "F18": [0xFFCF],
437         "F19": [0xFFD0],
438         "F20": [0xFFD1],
439         "F21": [0xFFD2],
440         "F22": [0xFFD3],
441         "F23": [0xFFD4],
442         "F24": [0xFFD5],
443         "Find": [0xFF68],
444         "GroupFirst": [0xFE0C],
445         "GroupLast": [0xFE0E],
446         "GroupNext": [0xFE08],
447         "GroupPrevious": [0xFE0A],
448         "FullWidth": null,
449         "HalfWidth": null,
450         "HangulMode": [0xFF31],
451         "Hankaku": [0xFF29],
452         "HanjaMode": [0xFF34],
453         "Help": [0xFF6A],
454         "Hiragana": [0xFF25],
455         "HiraganaKatakana": [0xFF27],
456         "Home": [0xFF50],
457         "Hyper": [0xFFED, 0xFFED, 0xFFEE],
458         "Insert": [0xFF63],
459         "JapaneseHiragana": [0xFF25],
460         "JapaneseKatakana": [0xFF26],
461         "JapaneseRomaji": [0xFF24],
462         "JunjaMode": [0xFF38],
463         "KanaMode": [0xFF2D],
464         "KanjiMode": [0xFF21],
465         "Katakana": [0xFF26],
466         "Left": [0xFF51],
467         "Meta": [0xFFE7, 0xFFE7, 0xFFE8],
468         "ModeChange": [0xFF7E],
469         "NumLock": [0xFF7F],
470         "PageDown": [0xFF55],
471         "PageUp": [0xFF56],
472         "Pause": [0xFF13],
473         "Play": [0xFD16],
474         "PreviousCandidate": [0xFF3E],
475         "PrintScreen": [0xFD1D],
476         "Redo": [0xFF66],
477         "Right": [0xFF53],
478         "RomanCharacters": null,
479         "Scroll": [0xFF14],
480         "Select": [0xFF60],
481         "Separator": [0xFFAC],
482         "Shift": [0xFFE1, 0xFFE1, 0xFFE2],
483         "SingleCandidate": [0xFF3C],
484         "Super": [0xFFEB, 0xFFEB, 0xFFEC],
485         "Tab": [0xFF09],
486         "Up": [0xFF52],
487         "Undo": [0xFF65],
488         "Win": [0xFFEB],
489         "Zenkaku": [0xFF28],
490         "ZenkakuHankaku": [0xFF2A]
491     };
492 
493     /**
494      * All keysyms which should not repeat when held down.
495      * @private
496      */
497     var no_repeat = {
498         0xFE03: true, // ISO Level 3 Shift (AltGr)
499         0xFFE1: true, // Left shift
500         0xFFE2: true, // Right shift
501         0xFFE3: true, // Left ctrl 
502         0xFFE4: true, // Right ctrl 
503         0xFFE7: true, // Left meta 
504         0xFFE8: true, // Right meta 
505         0xFFE9: true, // Left alt
506         0xFFEA: true, // Right alt
507         0xFFEB: true, // Left hyper
508         0xFFEC: true  // Right hyper
509     };
510 
511     /**
512      * All modifiers and their states.
513      */
514     this.modifiers = new Guacamole.Keyboard.ModifierState();
515         
516     /**
517      * The state of every key, indexed by keysym. If a particular key is
518      * pressed, the value of pressed for that keysym will be true. If a key
519      * is not currently pressed, it will not be defined. 
520      */
521     this.pressed = {};
522 
523     /**
524      * The last result of calling the onkeydown handler for each key, indexed
525      * by keysym. This is used to prevent/allow default actions for key events,
526      * even when the onkeydown handler cannot be called again because the key
527      * is (theoretically) still pressed.
528      */
529     var last_keydown_result = {};
530 
531     /**
532      * The keysym most recently associated with a given keycode when keydown
533      * fired. This object maps keycodes to keysyms.
534      *
535      * @private
536      * @type Object.<Number, Number>
537      */
538     var recentKeysym = {};
539 
540     /**
541      * Timeout before key repeat starts.
542      * @private
543      */
544     var key_repeat_timeout = null;
545 
546     /**
547      * Interval which presses and releases the last key pressed while that
548      * key is still being held down.
549      * @private
550      */
551     var key_repeat_interval = null;
552 
553     /**
554      * Given an array of keysyms indexed by location, returns the keysym
555      * for the given location, or the keysym for the standard location if
556      * undefined.
557      * 
558      * @param {Array} keysyms An array of keysyms, where the index of the
559      *                        keysym in the array is the location value.
560      * @param {Number} location The location on the keyboard corresponding to
561      *                          the key pressed, as defined at:
562      *                          http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
563      */
564     function get_keysym(keysyms, location) {
565 
566         if (!keysyms)
567             return null;
568 
569         return keysyms[location] || keysyms[0];
570     }
571 
572     function keysym_from_key_identifier(identifier, location, shifted) {
573 
574         if (!identifier)
575             return null;
576 
577         var typedCharacter;
578 
579         // If identifier is U+xxxx, decode Unicode character 
580         var unicodePrefixLocation = identifier.indexOf("U+");
581         if (unicodePrefixLocation >= 0) {
582             var hex = identifier.substring(unicodePrefixLocation+2);
583             typedCharacter = String.fromCharCode(parseInt(hex, 16));
584         }
585 
586         // If single character, use that as typed character
587         else if (identifier.length === 1)
588             typedCharacter = identifier;
589 
590         // Otherwise, look up corresponding keysym
591         else
592             return get_keysym(keyidentifier_keysym[identifier], location);
593 
594         // Alter case if necessary
595         if (shifted === true)
596             typedCharacter = typedCharacter.toUpperCase();
597         else if (shifted === false)
598             typedCharacter = typedCharacter.toLowerCase();
599 
600         // Get codepoint
601         var codepoint = typedCharacter.charCodeAt(0);
602         return keysym_from_charcode(codepoint);
603 
604     }
605 
606     function isControlCharacter(codepoint) {
607         return codepoint <= 0x1F || (codepoint >= 0x7F && codepoint <= 0x9F);
608     }
609 
610     function keysym_from_charcode(codepoint) {
611 
612         // Keysyms for control characters
613         if (isControlCharacter(codepoint)) return 0xFF00 | codepoint;
614 
615         // Keysyms for ASCII chars
616         if (codepoint >= 0x0000 && codepoint <= 0x00FF)
617             return codepoint;
618 
619         // Keysyms for Unicode
620         if (codepoint >= 0x0100 && codepoint <= 0x10FFFF)
621             return 0x01000000 | codepoint;
622 
623         return null;
624 
625     }
626 
627     function keysym_from_keycode(keyCode, location) {
628         return get_keysym(keycodeKeysyms[keyCode], location);
629     }
630 
631     /**
632      * Heuristically detects if the legacy keyIdentifier property of
633      * a keydown/keyup event looks incorrectly derived. Chrome, and
634      * presumably others, will produce the keyIdentifier by assuming
635      * the keyCode is the Unicode codepoint for that key. This is not
636      * correct in all cases.
637      *
638      * @param {Number} keyCode The keyCode from a browser keydown/keyup
639      *                         event.
640      * @param {String} keyIdentifier The legacy keyIdentifier from a
641      *                               browser keydown/keyup event.
642      * @returns {Boolean} true if the keyIdentifier looks sane, false if
643      *                    the keyIdentifier appears incorrectly derived.
644      */
645     function key_identifier_sane(keyCode, keyIdentifier) {
646 
647         // Assume non-Unicode keyIdentifier values are sane
648         var unicodePrefixLocation = keyIdentifier.indexOf("U+");
649         if (unicodePrefixLocation === -1)
650             return true;
651 
652         // If the Unicode codepoint isn't identical to the keyCode,
653         // then the identifier is likely correct
654         var codepoint = parseInt(keyIdentifier.substring(unicodePrefixLocation+2), 16);
655         if (keyCode !== codepoint)
656             return true;
657 
658         // The keyCodes for A-Z and 0-9 are actually identical to their
659         // Unicode codepoints
660         if ((keyCode >= 65 && keyCode <= 90) || (keyCode >= 48 && keyCode <= 57))
661             return true;
662 
663         // The keyIdentifier does NOT appear sane
664         return false;
665 
666     }
667 
668     /**
669      * Marks a key as pressed, firing the keydown event if registered. Key
670      * repeat for the pressed key will start after a delay if that key is
671      * not a modifier.
672      * 
673      * @private
674      * @param keysym The keysym of the key to press.
675      * @return {Boolean} true if event should NOT be canceled, false otherwise.
676      */
677     function press_key(keysym) {
678 
679         // Don't bother with pressing the key if the key is unknown
680         if (keysym === null) return;
681 
682         // Only press if released
683         if (!guac_keyboard.pressed[keysym]) {
684 
685             // Mark key as pressed
686             guac_keyboard.pressed[keysym] = true;
687 
688             // Send key event
689             if (guac_keyboard.onkeydown) {
690                 var result = guac_keyboard.onkeydown(keysym);
691                 last_keydown_result[keysym] = result;
692 
693                 // Stop any current repeat
694                 window.clearTimeout(key_repeat_timeout);
695                 window.clearInterval(key_repeat_interval);
696 
697                 // Repeat after a delay as long as pressed
698                 if (!no_repeat[keysym])
699                     key_repeat_timeout = window.setTimeout(function() {
700                         key_repeat_interval = window.setInterval(function() {
701                             guac_keyboard.onkeyup(keysym);
702                             guac_keyboard.onkeydown(keysym);
703                         }, 50);
704                     }, 500);
705 
706                 return result;
707             }
708         }
709 
710         // Return the last keydown result by default, resort to false if unknown
711         return last_keydown_result[keysym] || false;
712 
713     }
714 
715     /**
716      * Marks a key as released, firing the keyup event if registered.
717      * 
718      * @private
719      * @param keysym The keysym of the key to release.
720      */
721     function release_key(keysym) {
722 
723         // Only release if pressed
724         if (guac_keyboard.pressed[keysym]) {
725             
726             // Mark key as released
727             delete guac_keyboard.pressed[keysym];
728 
729             // Stop repeat
730             window.clearTimeout(key_repeat_timeout);
731             window.clearInterval(key_repeat_interval);
732 
733             // Send key event
734             if (keysym !== null && guac_keyboard.onkeyup)
735                 guac_keyboard.onkeyup(keysym);
736 
737         }
738 
739     }
740 
741     /**
742      * Given a keyboard event, updates the local modifier state and remote
743      * key state based on the modifier flags within the event. This function
744      * pays no attention to keycodes.
745      * 
746      * @param {KeyboardEvent} e The keyboard event containing the flags to update.
747      */
748     function update_modifier_state(e) {
749 
750         // Get state
751         var state = Guacamole.Keyboard.ModifierState.fromKeyboardEvent(e);
752 
753         // Release alt if implicitly released
754         if (guac_keyboard.modifiers.alt && state.alt === false) {
755             release_key(0xFFE9); // Left alt
756             release_key(0xFFEA); // Right alt
757             release_key(0xFE03); // AltGr
758         }
759 
760         // Release shift if implicitly released
761         if (guac_keyboard.modifiers.shift && state.shift === false) {
762             release_key(0xFFE1); // Left shift
763             release_key(0xFFE2); // Right shift
764         }
765 
766         // Release ctrl if implicitly released
767         if (guac_keyboard.modifiers.ctrl && state.ctrl === false) {
768             release_key(0xFFE3); // Left ctrl 
769             release_key(0xFFE4); // Right ctrl 
770         }
771 
772         // Release meta if implicitly released
773         if (guac_keyboard.modifiers.meta && state.meta === false) {
774             release_key(0xFFE7); // Left meta 
775             release_key(0xFFE8); // Right meta 
776         }
777 
778         // Release hyper if implicitly released
779         if (guac_keyboard.modifiers.hyper && state.hyper === false) {
780             release_key(0xFFEB); // Left hyper
781             release_key(0xFFEC); // Right hyper
782         }
783 
784         // Update state
785         guac_keyboard.modifiers = state;
786 
787     }
788 
789     /**
790      * Reads through the event log, removing events from the head of the log
791      * when the corresponding true key presses are known (or as known as they
792      * can be).
793      * 
794      * @return {Boolean} Whether the default action of the latest event should
795      *                   be prevented.
796      */
797     function interpret_events() {
798 
799         // Do not prevent default if no event could be interpreted
800         var handled_event = interpret_event();
801         if (!handled_event)
802             return false;
803 
804         // Interpret as much as possible
805         var last_event;
806         do {
807             last_event = handled_event;
808             handled_event = interpret_event();
809         } while (handled_event !== null);
810 
811         return last_event.defaultPrevented;
812 
813     }
814 
815     /**
816      * Searches the event log for a keyup event corresponding to the given
817      * keydown event, returning its index within the log.
818      *
819      * @param {KeydownEvent} keydown The keydown event whose corresponding keyup
820      *                               event we are to search for.
821      * @returns {Number} The index of the first keyup event in the event log
822      *                   matching the given keydown event, or -1 if no such
823      *                   event exists.
824      */
825     function indexof_keyup(keydown) {
826 
827         var i;
828 
829         // Search event log for keyup events having the given keysym
830         for (i=0; i<eventLog.length; i++) {
831 
832             // Return index of key event if found
833             var event = eventLog[i];
834             if (event instanceof KeyupEvent && event.keyCode === keydown.keyCode)
835                 return i;
836 
837         }
838 
839         // No such keyup found
840         return -1;
841 
842     }
843 
844     /**
845      * Releases Ctrl+Alt, if both are currently pressed and the given keysym
846      * looks like a key that may require AltGr.
847      * 
848      * @param {Number} keysym The key that was just pressed.
849      */
850     function release_simulated_altgr(keysym) {
851 
852         // Both Ctrl+Alt must be pressed if simulated AltGr is in use
853         if (!guac_keyboard.modifiers.ctrl || !guac_keyboard.modifiers.alt)
854             return;
855 
856         // Assume [A-Z] never require AltGr
857         if (keysym >= 0x0041 && keysym <= 0x005A)
858             return;
859 
860         // Assume [a-z] never require AltGr
861         if (keysym >= 0x0061 && keysym <= 0x007A)
862             return;
863 
864         // Release Ctrl+Alt if the keysym is printable
865         if (keysym <= 0xFF || (keysym & 0xFF000000) === 0x01000000) {
866             release_key(0xFFE3); // Left ctrl 
867             release_key(0xFFE4); // Right ctrl 
868             release_key(0xFFE9); // Left alt
869             release_key(0xFFEA); // Right alt
870         }
871 
872     }
873 
874     /**
875      * Reads through the event log, interpreting the first event, if possible,
876      * and returning that event. If no events can be interpreted, due to a
877      * total lack of events or the need for more events, null is returned. Any
878      * interpreted events are automatically removed from the log.
879      * 
880      * @return {KeyEvent} The first key event in the log, if it can be
881      *                    interpreted, or null otherwise.
882      */
883     function interpret_event() {
884 
885         // Peek at first event in log
886         var first = eventLog[0];
887         if (!first)
888             return null;
889 
890         // Keydown event
891         if (first instanceof KeydownEvent) {
892 
893             var keysym = null;
894             var accepted_events = [];
895 
896             // If event itself is reliable, no need to wait for other events
897             if (first.reliable) {
898                 keysym = first.keysym;
899                 accepted_events = eventLog.splice(0, 1);
900             }
901 
902             // If keydown is immediately followed by a keypress, use the indicated character
903             else if (eventLog[1] instanceof KeypressEvent) {
904                 keysym = eventLog[1].keysym;
905                 accepted_events = eventLog.splice(0, 2);
906             }
907 
908             // If there is a keyup already, the event must be handled now
909             else if (indexof_keyup(first) !== -1) {
910                 keysym = first.keysym;
911                 accepted_events = eventLog.splice(0, 1);
912             }
913 
914             // Fire a key press if valid events were found
915             if (accepted_events.length > 0) {
916 
917                 if (keysym) {
918 
919                     // Fire event
920                     release_simulated_altgr(keysym);
921                     var defaultPrevented = !press_key(keysym);
922                     recentKeysym[first.keyCode] = keysym;
923 
924                     // If a key is pressed while meta is held down, the keyup will
925                     // never be sent in Chrome, so send it now. (bug #108404)
926                     if (guac_keyboard.modifiers.meta && keysym !== 0xFFE7 && keysym !== 0xFFE8)
927                         release_key(keysym);
928 
929                     // Record whether default was prevented
930                     for (var i=0; i<accepted_events.length; i++)
931                         accepted_events[i].defaultPrevented = defaultPrevented;
932 
933                 }
934 
935                 return first;
936 
937             }
938 
939         } // end if keydown
940 
941         // Keyup event
942         else if (first instanceof KeyupEvent) {
943 
944             var keysym = first.keysym;
945             if (keysym) {
946                 release_key(keysym);
947                 first.defaultPrevented = true;
948             }
949 
950             return eventLog.shift();
951 
952         } // end if keyup
953 
954         // Ignore any other type of event (keypress by itself is invalid)
955         else
956             return eventLog.shift();
957 
958         // No event interpreted
959         return null;
960 
961     }
962 
963     // When key pressed
964     element.addEventListener("keydown", function(e) {
965 
966         // Only intercept if handler set
967         if (!guac_keyboard.onkeydown) return;
968 
969         var keyCode;
970         if (window.event) keyCode = window.event.keyCode;
971         else if (e.which) keyCode = e.which;
972 
973         // Fix modifier states
974         update_modifier_state(e);
975 
976         // Ignore (but do not prevent) the "composition" keycode sent by some
977         // browsers when an IME is in use (see: http://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html)
978         if (keyCode === 229)
979             return;
980 
981         // Log event
982         var keydownEvent = new KeydownEvent(keyCode, e.keyIdentifier, e.key, e.location || e.keyLocation);
983         eventLog.push(keydownEvent);
984 
985         // Interpret as many events as possible, prevent default if indicated
986         if (interpret_events())
987             e.preventDefault();
988 
989     }, true);
990 
991     // When key pressed
992     element.addEventListener("keypress", function(e) {
993 
994         // Only intercept if handler set
995         if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return;
996 
997         var charCode;
998         if (window.event) charCode = window.event.keyCode;
999         else if (e.which) charCode = e.which;
1000 
1001         // Fix modifier states
1002         update_modifier_state(e);
1003 
1004         // Log event
1005         var keypressEvent = new KeypressEvent(charCode);
1006         eventLog.push(keypressEvent);
1007 
1008         // Interpret as many events as possible, prevent default if indicated
1009         if (interpret_events())
1010             e.preventDefault();
1011 
1012     }, true);
1013 
1014     // When key released
1015     element.addEventListener("keyup", function(e) {
1016 
1017         // Only intercept if handler set
1018         if (!guac_keyboard.onkeyup) return;
1019 
1020         e.preventDefault();
1021 
1022         var keyCode;
1023         if (window.event) keyCode = window.event.keyCode;
1024         else if (e.which) keyCode = e.which;
1025         
1026         // Fix modifier states
1027         update_modifier_state(e);
1028 
1029         // Log event, call for interpretation
1030         var keyupEvent = new KeyupEvent(keyCode, e.keyIdentifier, e.key, e.location || e.keyLocation);
1031         eventLog.push(keyupEvent);
1032         interpret_events();
1033 
1034     }, true);
1035 
1036 };
1037 
1038 /**
1039  * The state of all supported keyboard modifiers.
1040  * @constructor
1041  */
1042 Guacamole.Keyboard.ModifierState = function() {
1043     
1044     /**
1045      * Whether shift is currently pressed.
1046      * @type Boolean
1047      */
1048     this.shift = false;
1049     
1050     /**
1051      * Whether ctrl is currently pressed.
1052      * @type Boolean
1053      */
1054     this.ctrl = false;
1055     
1056     /**
1057      * Whether alt is currently pressed.
1058      * @type Boolean
1059      */
1060     this.alt = false;
1061     
1062     /**
1063      * Whether meta (apple key) is currently pressed.
1064      * @type Boolean
1065      */
1066     this.meta = false;
1067 
1068     /**
1069      * Whether hyper (windows key) is currently pressed.
1070      * @type Boolean
1071      */
1072     this.hyper = false;
1073     
1074 };
1075 
1076 /**
1077  * Returns the modifier state applicable to the keyboard event given.
1078  * 
1079  * @param {KeyboardEvent} e The keyboard event to read.
1080  * @returns {Guacamole.Keyboard.ModifierState} The current state of keyboard
1081  *                                             modifiers.
1082  */
1083 Guacamole.Keyboard.ModifierState.fromKeyboardEvent = function(e) {
1084     
1085     var state = new Guacamole.Keyboard.ModifierState();
1086 
1087     // Assign states from old flags
1088     state.shift = e.shiftKey;
1089     state.ctrl  = e.ctrlKey;
1090     state.alt   = e.altKey;
1091     state.meta  = e.metaKey;
1092 
1093     // Use DOM3 getModifierState() for others
1094     if (e.getModifierState) {
1095         state.hyper = e.getModifierState("OS")
1096                    || e.getModifierState("Super")
1097                    || e.getModifierState("Hyper")
1098                    || e.getModifierState("Win");
1099     }
1100 
1101     return state;
1102     
1103 };
1104