source: OpenRLabs-Git/web2py/applications/rlabs/static/js/guacamole-common-js/modules/Keyboard.js

main
Last change on this file was 42bd667, checked in by David Fuertes <dfuertes@…>, 4 years ago

Historial Limpio

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