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 =  recentKeysym[keyCode]
302                     || keysym_from_keycode(keyCode, location)
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": [0xFF56],
471         "PageUp": [0xFF55],
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 or
644      *                    is missing entirely.
645      */
646     function key_identifier_sane(keyCode, keyIdentifier) {
647 
648         // Missing identifier is not sane
649         if (!keyIdentifier)
650             return false;
651 
652         // Assume non-Unicode keyIdentifier values are sane
653         var unicodePrefixLocation = keyIdentifier.indexOf("U+");
654         if (unicodePrefixLocation === -1)
655             return true;
656 
657         // If the Unicode codepoint isn't identical to the keyCode,
658         // then the identifier is likely correct
659         var codepoint = parseInt(keyIdentifier.substring(unicodePrefixLocation+2), 16);
660         if (keyCode !== codepoint)
661             return true;
662 
663         // The keyCodes for A-Z and 0-9 are actually identical to their
664         // Unicode codepoints
665         if ((keyCode >= 65 && keyCode <= 90) || (keyCode >= 48 && keyCode <= 57))
666             return true;
667 
668         // The keyIdentifier does NOT appear sane
669         return false;
670 
671     }
672 
673     /**
674      * Marks a key as pressed, firing the keydown event if registered. Key
675      * repeat for the pressed key will start after a delay if that key is
676      * not a modifier. The return value of this function depends on the
677      * return value of the keydown event handler, if any.
678      * 
679      * @param {Number} keysym The keysym of the key to press.
680      * @return {Boolean} true if event should NOT be canceled, false otherwise.
681      */
682     this.press = function(keysym) {
683 
684         // Don't bother with pressing the key if the key is unknown
685         if (keysym === null) return;
686 
687         // Only press if released
688         if (!guac_keyboard.pressed[keysym]) {
689 
690             // Mark key as pressed
691             guac_keyboard.pressed[keysym] = true;
692 
693             // Send key event
694             if (guac_keyboard.onkeydown) {
695                 var result = guac_keyboard.onkeydown(keysym);
696                 last_keydown_result[keysym] = result;
697 
698                 // Stop any current repeat
699                 window.clearTimeout(key_repeat_timeout);
700                 window.clearInterval(key_repeat_interval);
701 
702                 // Repeat after a delay as long as pressed
703                 if (!no_repeat[keysym])
704                     key_repeat_timeout = window.setTimeout(function() {
705                         key_repeat_interval = window.setInterval(function() {
706                             guac_keyboard.onkeyup(keysym);
707                             guac_keyboard.onkeydown(keysym);
708                         }, 50);
709                     }, 500);
710 
711                 return result;
712             }
713         }
714 
715         // Return the last keydown result by default, resort to false if unknown
716         return last_keydown_result[keysym] || false;
717 
718     };
719 
720     /**
721      * Marks a key as released, firing the keyup event if registered.
722      * 
723      * @param {Number} keysym The keysym of the key to release.
724      */
725     this.release = function(keysym) {
726 
727         // Only release if pressed
728         if (guac_keyboard.pressed[keysym]) {
729             
730             // Mark key as released
731             delete guac_keyboard.pressed[keysym];
732 
733             // Stop repeat
734             window.clearTimeout(key_repeat_timeout);
735             window.clearInterval(key_repeat_interval);
736 
737             // Send key event
738             if (keysym !== null && guac_keyboard.onkeyup)
739                 guac_keyboard.onkeyup(keysym);
740 
741         }
742 
743     };
744 
745     /**
746      * Resets the state of this keyboard, releasing all keys, and firing keyup
747      * events for each released key.
748      */
749     this.reset = function() {
750 
751         // Release all pressed keys
752         for (var keysym in guac_keyboard.pressed)
753             guac_keyboard.release(parseInt(keysym));
754 
755         // Clear event log
756         eventLog = [];
757 
758     };
759 
760     /**
761      * Given a keyboard event, updates the local modifier state and remote
762      * key state based on the modifier flags within the event. This function
763      * pays no attention to keycodes.
764      * 
765      * @param {KeyboardEvent} e The keyboard event containing the flags to update.
766      */
767     function update_modifier_state(e) {
768 
769         // Get state
770         var state = Guacamole.Keyboard.ModifierState.fromKeyboardEvent(e);
771 
772         // Release alt if implicitly released
773         if (guac_keyboard.modifiers.alt && state.alt === false) {
774             guac_keyboard.release(0xFFE9); // Left alt
775             guac_keyboard.release(0xFFEA); // Right alt
776             guac_keyboard.release(0xFE03); // AltGr
777         }
778 
779         // Release shift if implicitly released
780         if (guac_keyboard.modifiers.shift && state.shift === false) {
781             guac_keyboard.release(0xFFE1); // Left shift
782             guac_keyboard.release(0xFFE2); // Right shift
783         }
784 
785         // Release ctrl if implicitly released
786         if (guac_keyboard.modifiers.ctrl && state.ctrl === false) {
787             guac_keyboard.release(0xFFE3); // Left ctrl 
788             guac_keyboard.release(0xFFE4); // Right ctrl 
789         }
790 
791         // Release meta if implicitly released
792         if (guac_keyboard.modifiers.meta && state.meta === false) {
793             guac_keyboard.release(0xFFE7); // Left meta 
794             guac_keyboard.release(0xFFE8); // Right meta 
795         }
796 
797         // Release hyper if implicitly released
798         if (guac_keyboard.modifiers.hyper && state.hyper === false) {
799             guac_keyboard.release(0xFFEB); // Left hyper
800             guac_keyboard.release(0xFFEC); // Right hyper
801         }
802 
803         // Update state
804         guac_keyboard.modifiers = state;
805 
806     }
807 
808     /**
809      * Reads through the event log, removing events from the head of the log
810      * when the corresponding true key presses are known (or as known as they
811      * can be).
812      * 
813      * @return {Boolean} Whether the default action of the latest event should
814      *                   be prevented.
815      */
816     function interpret_events() {
817 
818         // Do not prevent default if no event could be interpreted
819         var handled_event = interpret_event();
820         if (!handled_event)
821             return false;
822 
823         // Interpret as much as possible
824         var last_event;
825         do {
826             last_event = handled_event;
827             handled_event = interpret_event();
828         } while (handled_event !== null);
829 
830         return last_event.defaultPrevented;
831 
832     }
833 
834     /**
835      * Releases Ctrl+Alt, if both are currently pressed and the given keysym
836      * looks like a key that may require AltGr.
837      * 
838      * @param {Number} keysym The key that was just pressed.
839      */
840     function release_simulated_altgr(keysym) {
841 
842         // Both Ctrl+Alt must be pressed if simulated AltGr is in use
843         if (!guac_keyboard.modifiers.ctrl || !guac_keyboard.modifiers.alt)
844             return;
845 
846         // Assume [A-Z] never require AltGr
847         if (keysym >= 0x0041 && keysym <= 0x005A)
848             return;
849 
850         // Assume [a-z] never require AltGr
851         if (keysym >= 0x0061 && keysym <= 0x007A)
852             return;
853 
854         // Release Ctrl+Alt if the keysym is printable
855         if (keysym <= 0xFF || (keysym & 0xFF000000) === 0x01000000) {
856             guac_keyboard.release(0xFFE3); // Left ctrl 
857             guac_keyboard.release(0xFFE4); // Right ctrl 
858             guac_keyboard.release(0xFFE9); // Left alt
859             guac_keyboard.release(0xFFEA); // Right alt
860         }
861 
862     }
863 
864     /**
865      * Reads through the event log, interpreting the first event, if possible,
866      * and returning that event. If no events can be interpreted, due to a
867      * total lack of events or the need for more events, null is returned. Any
868      * interpreted events are automatically removed from the log.
869      * 
870      * @return {KeyEvent} The first key event in the log, if it can be
871      *                    interpreted, or null otherwise.
872      */
873     function interpret_event() {
874 
875         // Peek at first event in log
876         var first = eventLog[0];
877         if (!first)
878             return null;
879 
880         // Keydown event
881         if (first instanceof KeydownEvent) {
882 
883             var keysym = null;
884             var accepted_events = [];
885 
886             // If event itself is reliable, no need to wait for other events
887             if (first.reliable) {
888                 keysym = first.keysym;
889                 accepted_events = eventLog.splice(0, 1);
890             }
891 
892             // If keydown is immediately followed by a keypress, use the indicated character
893             else if (eventLog[1] instanceof KeypressEvent) {
894                 keysym = eventLog[1].keysym;
895                 accepted_events = eventLog.splice(0, 2);
896             }
897 
898             // If keydown is immediately followed by anything else, then no
899             // keypress can possibly occur to clarify this event, and we must
900             // handle it now
901             else if (eventLog[1]) {
902                 keysym = first.keysym;
903                 accepted_events = eventLog.splice(0, 1);
904             }
905 
906             // Fire a key press if valid events were found
907             if (accepted_events.length > 0) {
908 
909                 if (keysym) {
910 
911                     // Fire event
912                     release_simulated_altgr(keysym);
913                     var defaultPrevented = !guac_keyboard.press(keysym);
914                     recentKeysym[first.keyCode] = keysym;
915 
916                     // If a key is pressed while meta is held down, the keyup will
917                     // never be sent in Chrome, so send it now. (bug #108404)
918                     if (guac_keyboard.modifiers.meta && keysym !== 0xFFE7 && keysym !== 0xFFE8)
919                         guac_keyboard.release(keysym);
920 
921                     // Record whether default was prevented
922                     for (var i=0; i<accepted_events.length; i++)
923                         accepted_events[i].defaultPrevented = defaultPrevented;
924 
925                 }
926 
927                 return first;
928 
929             }
930 
931         } // end if keydown
932 
933         // Keyup event
934         else if (first instanceof KeyupEvent) {
935 
936             var keysym = first.keysym;
937             if (keysym) {
938                 guac_keyboard.release(keysym);
939                 first.defaultPrevented = true;
940             }
941             else
942                 guac_keyboard.reset();
943 
944             return eventLog.shift();
945 
946         } // end if keyup
947 
948         // Ignore any other type of event (keypress by itself is invalid)
949         else
950             return eventLog.shift();
951 
952         // No event interpreted
953         return null;
954 
955     }
956 
957     /**
958      * Returns the keyboard location of the key associated with the given
959      * keyboard event. The location differentiates key events which otherwise
960      * have the same keycode, such as left shift vs. right shift.
961      *
962      * @param {KeyboardEvent} e
963      *     A JavaScript keyboard event, as received through the DOM via a
964      *     "keydown", "keyup", or "keypress" handler.
965      *
966      * @returns {Number}
967      *     The location of the key event on the keyboard, as defined at:
968      *     http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent
969      */
970     var getEventLocation = function getEventLocation(e) {
971 
972         // Use standard location, if possible
973         if ('location' in e)
974             return e.location;
975 
976         // Failing that, attempt to use deprecated keyLocation
977         if ('keyLocation' in e)
978             return e.keyLocation;
979 
980         // If no location is available, assume left side
981         return 0;
982 
983     };
984 
985     // When key pressed
986     element.addEventListener("keydown", function(e) {
987 
988         // Only intercept if handler set
989         if (!guac_keyboard.onkeydown) return;
990 
991         var keyCode;
992         if (window.event) keyCode = window.event.keyCode;
993         else if (e.which) keyCode = e.which;
994 
995         // Fix modifier states
996         update_modifier_state(e);
997 
998         // Ignore (but do not prevent) the "composition" keycode sent by some
999         // browsers when an IME is in use (see: http://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html)
1000         if (keyCode === 229)
1001             return;
1002 
1003         // Log event
1004         var keydownEvent = new KeydownEvent(keyCode, e.keyIdentifier, e.key, getEventLocation(e));
1005         eventLog.push(keydownEvent);
1006 
1007         // Interpret as many events as possible, prevent default if indicated
1008         if (interpret_events())
1009             e.preventDefault();
1010 
1011     }, true);
1012 
1013     // When key pressed
1014     element.addEventListener("keypress", function(e) {
1015 
1016         // Only intercept if handler set
1017         if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return;
1018 
1019         var charCode;
1020         if (window.event) charCode = window.event.keyCode;
1021         else if (e.which) charCode = e.which;
1022 
1023         // Fix modifier states
1024         update_modifier_state(e);
1025 
1026         // Log event
1027         var keypressEvent = new KeypressEvent(charCode);
1028         eventLog.push(keypressEvent);
1029 
1030         // Interpret as many events as possible, prevent default if indicated
1031         if (interpret_events())
1032             e.preventDefault();
1033 
1034     }, true);
1035 
1036     // When key released
1037     element.addEventListener("keyup", function(e) {
1038 
1039         // Only intercept if handler set
1040         if (!guac_keyboard.onkeyup) return;
1041 
1042         e.preventDefault();
1043 
1044         var keyCode;
1045         if (window.event) keyCode = window.event.keyCode;
1046         else if (e.which) keyCode = e.which;
1047         
1048         // Fix modifier states
1049         update_modifier_state(e);
1050 
1051         // Log event, call for interpretation
1052         var keyupEvent = new KeyupEvent(keyCode, e.keyIdentifier, e.key, getEventLocation(e));
1053         eventLog.push(keyupEvent);
1054         interpret_events();
1055 
1056     }, true);
1057 
1058 };
1059 
1060 /**
1061  * The state of all supported keyboard modifiers.
1062  * @constructor
1063  */
1064 Guacamole.Keyboard.ModifierState = function() {
1065     
1066     /**
1067      * Whether shift is currently pressed.
1068      * @type Boolean
1069      */
1070     this.shift = false;
1071     
1072     /**
1073      * Whether ctrl is currently pressed.
1074      * @type Boolean
1075      */
1076     this.ctrl = false;
1077     
1078     /**
1079      * Whether alt is currently pressed.
1080      * @type Boolean
1081      */
1082     this.alt = false;
1083     
1084     /**
1085      * Whether meta (apple key) is currently pressed.
1086      * @type Boolean
1087      */
1088     this.meta = false;
1089 
1090     /**
1091      * Whether hyper (windows key) is currently pressed.
1092      * @type Boolean
1093      */
1094     this.hyper = false;
1095     
1096 };
1097 
1098 /**
1099  * Returns the modifier state applicable to the keyboard event given.
1100  * 
1101  * @param {KeyboardEvent} e The keyboard event to read.
1102  * @returns {Guacamole.Keyboard.ModifierState} The current state of keyboard
1103  *                                             modifiers.
1104  */
1105 Guacamole.Keyboard.ModifierState.fromKeyboardEvent = function(e) {
1106     
1107     var state = new Guacamole.Keyboard.ModifierState();
1108 
1109     // Assign states from old flags
1110     state.shift = e.shiftKey;
1111     state.ctrl  = e.ctrlKey;
1112     state.alt   = e.altKey;
1113     state.meta  = e.metaKey;
1114 
1115     // Use DOM3 getModifierState() for others
1116     if (e.getModifierState) {
1117         state.hyper = e.getModifierState("OS")
1118                    || e.getModifierState("Super")
1119                    || e.getModifierState("Hyper")
1120                    || e.getModifierState("Win");
1121     }
1122 
1123     return state;
1124     
1125 };
1126