| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8"> |
| <title>JSDoc: Source: Keyboard.js</title> |
| |
| <script src="scripts/prettify/prettify.js"> </script> |
| <script src="scripts/prettify/lang-css.js"> </script> |
| <!--[if lt IE 9]> |
| <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> |
| <![endif]--> |
| <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> |
| <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> |
| </head> |
| |
| <body> |
| |
| <div id="main"> |
| |
| <h1 class="page-title">Source: Keyboard.js</h1> |
| |
| |
| |
| |
| |
| |
| <section> |
| <article> |
| <pre class="prettyprint source linenums"><code>/* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| var Guacamole = Guacamole || {}; |
| |
| /** |
| * Provides cross-browser and cross-keyboard keyboard for a specific element. |
| * Browser and keyboard layout variation is abstracted away, providing events |
| * which represent keys as their corresponding X11 keysym. |
| * |
| * @constructor |
| * @param {Element|Document} [element] |
| * The Element to use to provide keyboard events. If omitted, at least one |
| * Element must be manually provided through the listenTo() function for |
| * the Guacamole.Keyboard instance to have any effect. |
| */ |
| Guacamole.Keyboard = function Keyboard(element) { |
| |
| /** |
| * Reference to this Guacamole.Keyboard. |
| * @private |
| */ |
| var guac_keyboard = this; |
| |
| /** |
| * An integer value which uniquely identifies this Guacamole.Keyboard |
| * instance with respect to other Guacamole.Keyboard instances. |
| * |
| * @private |
| * @type {Number} |
| */ |
| var guacKeyboardID = Guacamole.Keyboard._nextID++; |
| |
| /** |
| * The name of the property which is added to event objects via markEvent() |
| * to note that they have already been handled by this Guacamole.Keyboard. |
| * |
| * @private |
| * @constant |
| * @type {String} |
| */ |
| var EVENT_MARKER = '_GUAC_KEYBOARD_HANDLED_BY_' + guacKeyboardID; |
| |
| /** |
| * Fired whenever the user presses a key with the element associated |
| * with this Guacamole.Keyboard in focus. |
| * |
| * @event |
| * @param {Number} keysym The keysym of the key being pressed. |
| * @return {Boolean} true if the key event should be allowed through to the |
| * browser, false otherwise. |
| */ |
| this.onkeydown = null; |
| |
| /** |
| * Fired whenever the user releases a key with the element associated |
| * with this Guacamole.Keyboard in focus. |
| * |
| * @event |
| * @param {Number} keysym The keysym of the key being released. |
| */ |
| this.onkeyup = null; |
| |
| /** |
| * Set of known platform-specific or browser-specific quirks which must be |
| * accounted for to properly interpret key events, even if the only way to |
| * reliably detect that quirk is to platform/browser-sniff. |
| * |
| * @private |
| * @type {Object.<String, Boolean>} |
| */ |
| var quirks = { |
| |
| /** |
| * Whether keyup events are universally unreliable. |
| * |
| * @type {Boolean} |
| */ |
| keyupUnreliable: false, |
| |
| /** |
| * Whether the Alt key is actually a modifier for typable keys and is |
| * thus never used for keyboard shortcuts. |
| * |
| * @type {Boolean} |
| */ |
| altIsTypableOnly: false, |
| |
| /** |
| * Whether we can rely on receiving a keyup event for the Caps Lock |
| * key. |
| * |
| * @type {Boolean} |
| */ |
| capsLockKeyupUnreliable: false |
| |
| }; |
| |
| // Set quirk flags depending on platform/browser, if such information is |
| // available |
| if (navigator && navigator.platform) { |
| |
| // All keyup events are unreliable on iOS (sadly) |
| if (navigator.platform.match(/ipad|iphone|ipod/i)) |
| quirks.keyupUnreliable = true; |
| |
| // The Alt key on Mac is never used for keyboard shortcuts, and the |
| // Caps Lock key never dispatches keyup events |
| else if (navigator.platform.match(/^mac/i)) { |
| quirks.altIsTypableOnly = true; |
| quirks.capsLockKeyupUnreliable = true; |
| } |
| |
| } |
| |
| /** |
| * A key event having a corresponding timestamp. This event is non-specific. |
| * Its subclasses should be used instead when recording specific key |
| * events. |
| * |
| * @private |
| * @constructor |
| */ |
| var KeyEvent = function() { |
| |
| /** |
| * Reference to this key event. |
| */ |
| var key_event = this; |
| |
| /** |
| * An arbitrary timestamp in milliseconds, indicating this event's |
| * position in time relative to other events. |
| * |
| * @type {Number} |
| */ |
| this.timestamp = new Date().getTime(); |
| |
| /** |
| * Whether the default action of this key event should be prevented. |
| * |
| * @type {Boolean} |
| */ |
| this.defaultPrevented = false; |
| |
| /** |
| * The keysym of the key associated with this key event, as determined |
| * by a best-effort guess using available event properties and keyboard |
| * state. |
| * |
| * @type {Number} |
| */ |
| this.keysym = null; |
| |
| /** |
| * Whether the keysym value of this key event is known to be reliable. |
| * If false, the keysym may still be valid, but it's only a best guess, |
| * and future key events may be a better source of information. |
| * |
| * @type {Boolean} |
| */ |
| this.reliable = false; |
| |
| /** |
| * Returns the number of milliseconds elapsed since this event was |
| * received. |
| * |
| * @return {Number} The number of milliseconds elapsed since this |
| * event was received. |
| */ |
| this.getAge = function() { |
| return new Date().getTime() - key_event.timestamp; |
| }; |
| |
| }; |
| |
| /** |
| * Information related to the pressing of a key, which need not be a key |
| * associated with a printable character. The presence or absence of any |
| * information within this object is browser-dependent. |
| * |
| * @private |
| * @constructor |
| * @augments Guacamole.Keyboard.KeyEvent |
| * @param {Number} keyCode The JavaScript key code of the key pressed. |
| * @param {String} keyIdentifier The legacy DOM3 "keyIdentifier" of the key |
| * pressed, as defined at: |
| * http://www.w3.org/TR/2009/WD-DOM-Level-3-Events-20090908/#events-Events-KeyboardEvent |
| * @param {String} key The standard name of the key pressed, as defined at: |
| * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent |
| * @param {Number} location The location on the keyboard corresponding to |
| * the key pressed, as defined at: |
| * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent |
| */ |
| var KeydownEvent = function(keyCode, keyIdentifier, key, location) { |
| |
| // We extend KeyEvent |
| KeyEvent.apply(this); |
| |
| /** |
| * The JavaScript key code of the key pressed. |
| * |
| * @type {Number} |
| */ |
| this.keyCode = keyCode; |
| |
| /** |
| * The legacy DOM3 "keyIdentifier" of the key pressed, as defined at: |
| * http://www.w3.org/TR/2009/WD-DOM-Level-3-Events-20090908/#events-Events-KeyboardEvent |
| * |
| * @type {String} |
| */ |
| this.keyIdentifier = keyIdentifier; |
| |
| /** |
| * The standard name of the key pressed, as defined at: |
| * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent |
| * |
| * @type {String} |
| */ |
| this.key = key; |
| |
| /** |
| * The location on the keyboard corresponding to the key pressed, as |
| * defined at: |
| * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent |
| * |
| * @type {Number} |
| */ |
| this.location = location; |
| |
| // If key is known from keyCode or DOM3 alone, use that |
| this.keysym = keysym_from_key_identifier(key, location) |
| || keysym_from_keycode(keyCode, location); |
| |
| /** |
| * Whether the keyup following this keydown event is known to be |
| * reliable. If false, we cannot rely on the keyup event to occur. |
| * |
| * @type {Boolean} |
| */ |
| this.keyupReliable = !quirks.keyupUnreliable; |
| |
| // DOM3 and keyCode are reliable sources if the corresponding key is |
| // not a printable key |
| if (this.keysym && !isPrintable(this.keysym)) |
| this.reliable = true; |
| |
| // Use legacy keyIdentifier as a last resort, if it looks sane |
| if (!this.keysym && key_identifier_sane(keyCode, keyIdentifier)) |
| this.keysym = keysym_from_key_identifier(keyIdentifier, location, guac_keyboard.modifiers.shift); |
| |
| // If a key is pressed while meta is held down, the keyup will |
| // never be sent in Chrome (bug #108404) |
| if (guac_keyboard.modifiers.meta && this.keysym !== 0xFFE7 && this.keysym !== 0xFFE8) |
| this.keyupReliable = false; |
| |
| // We cannot rely on receiving keyup for Caps Lock on certain platforms |
| else if (this.keysym === 0xFFE5 && quirks.capsLockKeyupUnreliable) |
| this.keyupReliable = false; |
| |
| // Determine whether default action for Alt+combinations must be prevented |
| var prevent_alt = !guac_keyboard.modifiers.ctrl && !quirks.altIsTypableOnly; |
| |
| // Determine whether default action for Ctrl+combinations must be prevented |
| var prevent_ctrl = !guac_keyboard.modifiers.alt; |
| |
| // We must rely on the (potentially buggy) keyIdentifier if preventing |
| // the default action is important |
| if ((prevent_ctrl && guac_keyboard.modifiers.ctrl) |
| || (prevent_alt && guac_keyboard.modifiers.alt) |
| || guac_keyboard.modifiers.meta |
| || guac_keyboard.modifiers.hyper) |
| this.reliable = true; |
| |
| // Record most recently known keysym by associated key code |
| recentKeysym[keyCode] = this.keysym; |
| |
| }; |
| |
| KeydownEvent.prototype = new KeyEvent(); |
| |
| /** |
| * Information related to the pressing of a key, which MUST be |
| * associated with a printable character. The presence or absence of any |
| * information within this object is browser-dependent. |
| * |
| * @private |
| * @constructor |
| * @augments Guacamole.Keyboard.KeyEvent |
| * @param {Number} charCode The Unicode codepoint of the character that |
| * would be typed by the key pressed. |
| */ |
| var KeypressEvent = function(charCode) { |
| |
| // We extend KeyEvent |
| KeyEvent.apply(this); |
| |
| /** |
| * The Unicode codepoint of the character that would be typed by the |
| * key pressed. |
| * |
| * @type {Number} |
| */ |
| this.charCode = charCode; |
| |
| // Pull keysym from char code |
| this.keysym = keysym_from_charcode(charCode); |
| |
| // Keypress is always reliable |
| this.reliable = true; |
| |
| }; |
| |
| KeypressEvent.prototype = new KeyEvent(); |
| |
| /** |
| * Information related to the pressing of a key, which need not be a key |
| * associated with a printable character. The presence or absence of any |
| * information within this object is browser-dependent. |
| * |
| * @private |
| * @constructor |
| * @augments Guacamole.Keyboard.KeyEvent |
| * @param {Number} keyCode The JavaScript key code of the key released. |
| * @param {String} keyIdentifier The legacy DOM3 "keyIdentifier" of the key |
| * released, as defined at: |
| * http://www.w3.org/TR/2009/WD-DOM-Level-3-Events-20090908/#events-Events-KeyboardEvent |
| * @param {String} key The standard name of the key released, as defined at: |
| * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent |
| * @param {Number} location The location on the keyboard corresponding to |
| * the key released, as defined at: |
| * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent |
| */ |
| var KeyupEvent = function(keyCode, keyIdentifier, key, location) { |
| |
| // We extend KeyEvent |
| KeyEvent.apply(this); |
| |
| /** |
| * The JavaScript key code of the key released. |
| * |
| * @type {Number} |
| */ |
| this.keyCode = keyCode; |
| |
| /** |
| * The legacy DOM3 "keyIdentifier" of the key released, as defined at: |
| * http://www.w3.org/TR/2009/WD-DOM-Level-3-Events-20090908/#events-Events-KeyboardEvent |
| * |
| * @type {String} |
| */ |
| this.keyIdentifier = keyIdentifier; |
| |
| /** |
| * The standard name of the key released, as defined at: |
| * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent |
| * |
| * @type {String} |
| */ |
| this.key = key; |
| |
| /** |
| * The location on the keyboard corresponding to the key released, as |
| * defined at: |
| * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent |
| * |
| * @type {Number} |
| */ |
| this.location = location; |
| |
| // If key is known from keyCode or DOM3 alone, use that |
| this.keysym = keysym_from_keycode(keyCode, location) |
| || keysym_from_key_identifier(key, location); // keyCode is still more reliable for keyup when dead keys are in use |
| |
| // Fall back to the most recently pressed keysym associated with the |
| // keyCode if the inferred key doesn't seem to actually be pressed |
| if (!guac_keyboard.pressed[this.keysym]) |
| this.keysym = recentKeysym[keyCode] || this.keysym; |
| |
| // Keyup is as reliable as it will ever be |
| this.reliable = true; |
| |
| }; |
| |
| KeyupEvent.prototype = new KeyEvent(); |
| |
| /** |
| * An array of recorded events, which can be instances of the private |
| * KeydownEvent, KeypressEvent, and KeyupEvent classes. |
| * |
| * @private |
| * @type {KeyEvent[]} |
| */ |
| var eventLog = []; |
| |
| /** |
| * Map of known JavaScript keycodes which do not map to typable characters |
| * to their X11 keysym equivalents. |
| * @private |
| */ |
| var keycodeKeysyms = { |
| 8: [0xFF08], // backspace |
| 9: [0xFF09], // tab |
| 12: [0xFF0B, 0xFF0B, 0xFF0B, 0xFFB5], // clear / KP 5 |
| 13: [0xFF0D], // enter |
| 16: [0xFFE1, 0xFFE1, 0xFFE2], // shift |
| 17: [0xFFE3, 0xFFE3, 0xFFE4], // ctrl |
| 18: [0xFFE9, 0xFFE9, 0xFE03], // alt |
| 19: [0xFF13], // pause/break |
| 20: [0xFFE5], // caps lock |
| 27: [0xFF1B], // escape |
| 32: [0x0020], // space |
| 33: [0xFF55, 0xFF55, 0xFF55, 0xFFB9], // page up / KP 9 |
| 34: [0xFF56, 0xFF56, 0xFF56, 0xFFB3], // page down / KP 3 |
| 35: [0xFF57, 0xFF57, 0xFF57, 0xFFB1], // end / KP 1 |
| 36: [0xFF50, 0xFF50, 0xFF50, 0xFFB7], // home / KP 7 |
| 37: [0xFF51, 0xFF51, 0xFF51, 0xFFB4], // left arrow / KP 4 |
| 38: [0xFF52, 0xFF52, 0xFF52, 0xFFB8], // up arrow / KP 8 |
| 39: [0xFF53, 0xFF53, 0xFF53, 0xFFB6], // right arrow / KP 6 |
| 40: [0xFF54, 0xFF54, 0xFF54, 0xFFB2], // down arrow / KP 2 |
| 45: [0xFF63, 0xFF63, 0xFF63, 0xFFB0], // insert / KP 0 |
| 46: [0xFFFF, 0xFFFF, 0xFFFF, 0xFFAE], // delete / KP decimal |
| 91: [0xFFEB], // left window key (hyper_l) |
| 92: [0xFF67], // right window key (menu key?) |
| 93: null, // select key |
| 96: [0xFFB0], // KP 0 |
| 97: [0xFFB1], // KP 1 |
| 98: [0xFFB2], // KP 2 |
| 99: [0xFFB3], // KP 3 |
| 100: [0xFFB4], // KP 4 |
| 101: [0xFFB5], // KP 5 |
| 102: [0xFFB6], // KP 6 |
| 103: [0xFFB7], // KP 7 |
| 104: [0xFFB8], // KP 8 |
| 105: [0xFFB9], // KP 9 |
| 106: [0xFFAA], // KP multiply |
| 107: [0xFFAB], // KP add |
| 109: [0xFFAD], // KP subtract |
| 110: [0xFFAE], // KP decimal |
| 111: [0xFFAF], // KP divide |
| 112: [0xFFBE], // f1 |
| 113: [0xFFBF], // f2 |
| 114: [0xFFC0], // f3 |
| 115: [0xFFC1], // f4 |
| 116: [0xFFC2], // f5 |
| 117: [0xFFC3], // f6 |
| 118: [0xFFC4], // f7 |
| 119: [0xFFC5], // f8 |
| 120: [0xFFC6], // f9 |
| 121: [0xFFC7], // f10 |
| 122: [0xFFC8], // f11 |
| 123: [0xFFC9], // f12 |
| 144: [0xFF7F], // num lock |
| 145: [0xFF14], // scroll lock |
| 225: [0xFE03] // altgraph (iso_level3_shift) |
| }; |
| |
| /** |
| * Map of known JavaScript keyidentifiers which do not map to typable |
| * characters to their unshifted X11 keysym equivalents. |
| * @private |
| */ |
| var keyidentifier_keysym = { |
| "Again": [0xFF66], |
| "AllCandidates": [0xFF3D], |
| "Alphanumeric": [0xFF30], |
| "Alt": [0xFFE9, 0xFFE9, 0xFE03], |
| "Attn": [0xFD0E], |
| "AltGraph": [0xFE03], |
| "ArrowDown": [0xFF54], |
| "ArrowLeft": [0xFF51], |
| "ArrowRight": [0xFF53], |
| "ArrowUp": [0xFF52], |
| "Backspace": [0xFF08], |
| "CapsLock": [0xFFE5], |
| "Cancel": [0xFF69], |
| "Clear": [0xFF0B], |
| "Convert": [0xFF21], |
| "Copy": [0xFD15], |
| "Crsel": [0xFD1C], |
| "CrSel": [0xFD1C], |
| "CodeInput": [0xFF37], |
| "Compose": [0xFF20], |
| "Control": [0xFFE3, 0xFFE3, 0xFFE4], |
| "ContextMenu": [0xFF67], |
| "Delete": [0xFFFF], |
| "Down": [0xFF54], |
| "End": [0xFF57], |
| "Enter": [0xFF0D], |
| "EraseEof": [0xFD06], |
| "Escape": [0xFF1B], |
| "Execute": [0xFF62], |
| "Exsel": [0xFD1D], |
| "ExSel": [0xFD1D], |
| "F1": [0xFFBE], |
| "F2": [0xFFBF], |
| "F3": [0xFFC0], |
| "F4": [0xFFC1], |
| "F5": [0xFFC2], |
| "F6": [0xFFC3], |
| "F7": [0xFFC4], |
| "F8": [0xFFC5], |
| "F9": [0xFFC6], |
| "F10": [0xFFC7], |
| "F11": [0xFFC8], |
| "F12": [0xFFC9], |
| "F13": [0xFFCA], |
| "F14": [0xFFCB], |
| "F15": [0xFFCC], |
| "F16": [0xFFCD], |
| "F17": [0xFFCE], |
| "F18": [0xFFCF], |
| "F19": [0xFFD0], |
| "F20": [0xFFD1], |
| "F21": [0xFFD2], |
| "F22": [0xFFD3], |
| "F23": [0xFFD4], |
| "F24": [0xFFD5], |
| "Find": [0xFF68], |
| "GroupFirst": [0xFE0C], |
| "GroupLast": [0xFE0E], |
| "GroupNext": [0xFE08], |
| "GroupPrevious": [0xFE0A], |
| "FullWidth": null, |
| "HalfWidth": null, |
| "HangulMode": [0xFF31], |
| "Hankaku": [0xFF29], |
| "HanjaMode": [0xFF34], |
| "Help": [0xFF6A], |
| "Hiragana": [0xFF25], |
| "HiraganaKatakana": [0xFF27], |
| "Home": [0xFF50], |
| "Hyper": [0xFFED, 0xFFED, 0xFFEE], |
| "Insert": [0xFF63], |
| "JapaneseHiragana": [0xFF25], |
| "JapaneseKatakana": [0xFF26], |
| "JapaneseRomaji": [0xFF24], |
| "JunjaMode": [0xFF38], |
| "KanaMode": [0xFF2D], |
| "KanjiMode": [0xFF21], |
| "Katakana": [0xFF26], |
| "Left": [0xFF51], |
| "Meta": [0xFFE7, 0xFFE7, 0xFFE8], |
| "ModeChange": [0xFF7E], |
| "NumLock": [0xFF7F], |
| "PageDown": [0xFF56], |
| "PageUp": [0xFF55], |
| "Pause": [0xFF13], |
| "Play": [0xFD16], |
| "PreviousCandidate": [0xFF3E], |
| "PrintScreen": [0xFF61], |
| "Redo": [0xFF66], |
| "Right": [0xFF53], |
| "RomanCharacters": null, |
| "Scroll": [0xFF14], |
| "Select": [0xFF60], |
| "Separator": [0xFFAC], |
| "Shift": [0xFFE1, 0xFFE1, 0xFFE2], |
| "SingleCandidate": [0xFF3C], |
| "Super": [0xFFEB, 0xFFEB, 0xFFEC], |
| "Tab": [0xFF09], |
| "UIKeyInputDownArrow": [0xFF54], |
| "UIKeyInputEscape": [0xFF1B], |
| "UIKeyInputLeftArrow": [0xFF51], |
| "UIKeyInputRightArrow": [0xFF53], |
| "UIKeyInputUpArrow": [0xFF52], |
| "Up": [0xFF52], |
| "Undo": [0xFF65], |
| "Win": [0xFFEB], |
| "Zenkaku": [0xFF28], |
| "ZenkakuHankaku": [0xFF2A] |
| }; |
| |
| /** |
| * All keysyms which should not repeat when held down. |
| * @private |
| */ |
| var no_repeat = { |
| 0xFE03: true, // ISO Level 3 Shift (AltGr) |
| 0xFFE1: true, // Left shift |
| 0xFFE2: true, // Right shift |
| 0xFFE3: true, // Left ctrl |
| 0xFFE4: true, // Right ctrl |
| 0xFFE5: true, // Caps Lock |
| 0xFFE7: true, // Left meta |
| 0xFFE8: true, // Right meta |
| 0xFFE9: true, // Left alt |
| 0xFFEA: true, // Right alt |
| 0xFFEB: true, // Left hyper |
| 0xFFEC: true // Right hyper |
| }; |
| |
| /** |
| * All modifiers and their states. |
| */ |
| this.modifiers = new Guacamole.Keyboard.ModifierState(); |
| |
| /** |
| * The state of every key, indexed by keysym. If a particular key is |
| * pressed, the value of pressed for that keysym will be true. If a key |
| * is not currently pressed, it will not be defined. |
| */ |
| this.pressed = {}; |
| |
| /** |
| * The state of every key, indexed by keysym, for strictly those keys whose |
| * status has been indirectly determined thorugh observation of other key |
| * events. If a particular key is implicitly pressed, the value of |
| * implicitlyPressed for that keysym will be true. If a key |
| * is not currently implicitly pressed (the key is not pressed OR the state |
| * of the key is explicitly known), it will not be defined. |
| * |
| * @private |
| * @tyle {Object.<Number, Boolean>} |
| */ |
| var implicitlyPressed = {}; |
| |
| /** |
| * The last result of calling the onkeydown handler for each key, indexed |
| * by keysym. This is used to prevent/allow default actions for key events, |
| * even when the onkeydown handler cannot be called again because the key |
| * is (theoretically) still pressed. |
| * |
| * @private |
| */ |
| var last_keydown_result = {}; |
| |
| /** |
| * The keysym most recently associated with a given keycode when keydown |
| * fired. This object maps keycodes to keysyms. |
| * |
| * @private |
| * @type {Object.<Number, Number>} |
| */ |
| var recentKeysym = {}; |
| |
| /** |
| * Timeout before key repeat starts. |
| * @private |
| */ |
| var key_repeat_timeout = null; |
| |
| /** |
| * Interval which presses and releases the last key pressed while that |
| * key is still being held down. |
| * @private |
| */ |
| var key_repeat_interval = null; |
| |
| /** |
| * Given an array of keysyms indexed by location, returns the keysym |
| * for the given location, or the keysym for the standard location if |
| * undefined. |
| * |
| * @private |
| * @param {Number[]} keysyms |
| * An array of keysyms, where the index of the keysym in the array is |
| * the location value. |
| * |
| * @param {Number} location |
| * The location on the keyboard corresponding to the key pressed, as |
| * defined at: http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent |
| */ |
| var get_keysym = function get_keysym(keysyms, location) { |
| |
| if (!keysyms) |
| return null; |
| |
| return keysyms[location] || keysyms[0]; |
| }; |
| |
| /** |
| * Returns true if the given keysym corresponds to a printable character, |
| * false otherwise. |
| * |
| * @param {Number} keysym |
| * The keysym to check. |
| * |
| * @returns {Boolean} |
| * true if the given keysym corresponds to a printable character, |
| * false otherwise. |
| */ |
| var isPrintable = function isPrintable(keysym) { |
| |
| // Keysyms with Unicode equivalents are printable |
| return (keysym >= 0x00 && keysym <= 0xFF) |
| || (keysym & 0xFFFF0000) === 0x01000000; |
| |
| }; |
| |
| function keysym_from_key_identifier(identifier, location, shifted) { |
| |
| if (!identifier) |
| return null; |
| |
| var typedCharacter; |
| |
| // If identifier is U+xxxx, decode Unicode character |
| var unicodePrefixLocation = identifier.indexOf("U+"); |
| if (unicodePrefixLocation >= 0) { |
| var hex = identifier.substring(unicodePrefixLocation+2); |
| typedCharacter = String.fromCharCode(parseInt(hex, 16)); |
| } |
| |
| // If single character and not keypad, use that as typed character |
| else if (identifier.length === 1 && location !== 3) |
| typedCharacter = identifier; |
| |
| // Otherwise, look up corresponding keysym |
| else |
| return get_keysym(keyidentifier_keysym[identifier], location); |
| |
| // Alter case if necessary |
| if (shifted === true) |
| typedCharacter = typedCharacter.toUpperCase(); |
| else if (shifted === false) |
| typedCharacter = typedCharacter.toLowerCase(); |
| |
| // Get codepoint |
| var codepoint = typedCharacter.charCodeAt(0); |
| return keysym_from_charcode(codepoint); |
| |
| } |
| |
| function isControlCharacter(codepoint) { |
| return codepoint <= 0x1F || (codepoint >= 0x7F && codepoint <= 0x9F); |
| } |
| |
| function keysym_from_charcode(codepoint) { |
| |
| // Keysyms for control characters |
| if (isControlCharacter(codepoint)) return 0xFF00 | codepoint; |
| |
| // Keysyms for ASCII chars |
| if (codepoint >= 0x0000 && codepoint <= 0x00FF) |
| return codepoint; |
| |
| // Keysyms for Unicode |
| if (codepoint >= 0x0100 && codepoint <= 0x10FFFF) |
| return 0x01000000 | codepoint; |
| |
| return null; |
| |
| } |
| |
| function keysym_from_keycode(keyCode, location) { |
| return get_keysym(keycodeKeysyms[keyCode], location); |
| } |
| |
| /** |
| * Heuristically detects if the legacy keyIdentifier property of |
| * a keydown/keyup event looks incorrectly derived. Chrome, and |
| * presumably others, will produce the keyIdentifier by assuming |
| * the keyCode is the Unicode codepoint for that key. This is not |
| * correct in all cases. |
| * |
| * @private |
| * @param {Number} keyCode |
| * The keyCode from a browser keydown/keyup event. |
| * |
| * @param {String} keyIdentifier |
| * The legacy keyIdentifier from a browser keydown/keyup event. |
| * |
| * @returns {Boolean} |
| * true if the keyIdentifier looks sane, false if the keyIdentifier |
| * appears incorrectly derived or is missing entirely. |
| */ |
| var key_identifier_sane = function key_identifier_sane(keyCode, keyIdentifier) { |
| |
| // Missing identifier is not sane |
| if (!keyIdentifier) |
| return false; |
| |
| // Assume non-Unicode keyIdentifier values are sane |
| var unicodePrefixLocation = keyIdentifier.indexOf("U+"); |
| if (unicodePrefixLocation === -1) |
| return true; |
| |
| // If the Unicode codepoint isn't identical to the keyCode, |
| // then the identifier is likely correct |
| var codepoint = parseInt(keyIdentifier.substring(unicodePrefixLocation+2), 16); |
| if (keyCode !== codepoint) |
| return true; |
| |
| // The keyCodes for A-Z and 0-9 are actually identical to their |
| // Unicode codepoints |
| if ((keyCode >= 65 && keyCode <= 90) || (keyCode >= 48 && keyCode <= 57)) |
| return true; |
| |
| // The keyIdentifier does NOT appear sane |
| return false; |
| |
| }; |
| |
| /** |
| * Marks a key as pressed, firing the keydown event if registered. Key |
| * repeat for the pressed key will start after a delay if that key is |
| * not a modifier. The return value of this function depends on the |
| * return value of the keydown event handler, if any. |
| * |
| * @param {Number} keysym The keysym of the key to press. |
| * @return {Boolean} true if event should NOT be canceled, false otherwise. |
| */ |
| this.press = function(keysym) { |
| |
| // Don't bother with pressing the key if the key is unknown |
| if (keysym === null) return; |
| |
| // Only press if released |
| if (!guac_keyboard.pressed[keysym]) { |
| |
| // Mark key as pressed |
| guac_keyboard.pressed[keysym] = true; |
| |
| // Send key event |
| if (guac_keyboard.onkeydown) { |
| var result = guac_keyboard.onkeydown(keysym); |
| last_keydown_result[keysym] = result; |
| |
| // Stop any current repeat |
| window.clearTimeout(key_repeat_timeout); |
| window.clearInterval(key_repeat_interval); |
| |
| // Repeat after a delay as long as pressed |
| if (!no_repeat[keysym]) |
| key_repeat_timeout = window.setTimeout(function() { |
| key_repeat_interval = window.setInterval(function() { |
| guac_keyboard.onkeyup(keysym); |
| guac_keyboard.onkeydown(keysym); |
| }, 50); |
| }, 500); |
| |
| return result; |
| } |
| } |
| |
| // Return the last keydown result by default, resort to false if unknown |
| return last_keydown_result[keysym] || false; |
| |
| }; |
| |
| /** |
| * Marks a key as released, firing the keyup event if registered. |
| * |
| * @param {Number} keysym The keysym of the key to release. |
| */ |
| this.release = function(keysym) { |
| |
| // Only release if pressed |
| if (guac_keyboard.pressed[keysym]) { |
| |
| // Mark key as released |
| delete guac_keyboard.pressed[keysym]; |
| delete implicitlyPressed[keysym]; |
| |
| // Stop repeat |
| window.clearTimeout(key_repeat_timeout); |
| window.clearInterval(key_repeat_interval); |
| |
| // Send key event |
| if (keysym !== null && guac_keyboard.onkeyup) |
| guac_keyboard.onkeyup(keysym); |
| |
| } |
| |
| }; |
| |
| /** |
| * Presses and releases the keys necessary to type the given string of |
| * text. |
| * |
| * @param {String} str |
| * The string to type. |
| */ |
| this.type = function type(str) { |
| |
| // Press/release the key corresponding to each character in the string |
| for (var i = 0; i < str.length; i++) { |
| |
| // Determine keysym of current character |
| var codepoint = str.codePointAt ? str.codePointAt(i) : str.charCodeAt(i); |
| var keysym = keysym_from_charcode(codepoint); |
| |
| // Press and release key for current character |
| guac_keyboard.press(keysym); |
| guac_keyboard.release(keysym); |
| |
| } |
| |
| }; |
| |
| /** |
| * Resets the state of this keyboard, releasing all keys, and firing keyup |
| * events for each released key. |
| */ |
| this.reset = function() { |
| |
| // Release all pressed keys |
| for (var keysym in guac_keyboard.pressed) |
| guac_keyboard.release(parseInt(keysym)); |
| |
| // Clear event log |
| eventLog = []; |
| |
| }; |
| |
| /** |
| * Given the remote and local state of a particular key, resynchronizes the |
| * remote state of that key with the local state through pressing or |
| * releasing keysyms. |
| * |
| * @private |
| * @param {Boolean} remoteState |
| * Whether the key is currently pressed remotely. |
| * |
| * @param {Boolean} localState |
| * Whether the key is currently pressed remotely locally. If the state |
| * of the key is not known, this may be undefined. |
| * |
| * @param {Number[]} keysyms |
| * The keysyms which represent the key being updated. |
| * |
| * @param {KeyEvent} keyEvent |
| * Guacamole's current best interpretation of the key event being |
| * processed. |
| */ |
| var updateModifierState = function updateModifierState(remoteState, |
| localState, keysyms, keyEvent) { |
| |
| var i; |
| |
| // Do not trust changes in modifier state for events directly involving |
| // that modifier: (1) the flag may erroneously be cleared despite |
| // another version of the same key still being held and (2) the change |
| // in flag may be due to the current event being processed, thus |
| // updating things here is at best redundant and at worst incorrect |
| if (keysyms.indexOf(keyEvent.keysym) !== -1) |
| return; |
| |
| // Release all related keys if modifier is implicitly released |
| if (remoteState && localState === false) { |
| for (i = 0; i < keysyms.length; i++) { |
| guac_keyboard.release(keysyms[i]); |
| } |
| } |
| |
| // Press if modifier is implicitly pressed |
| else if (!remoteState && localState) { |
| |
| // Verify that modifier flag isn't already pressed or already set |
| // due to another version of the same key being held down |
| for (i = 0; i < keysyms.length; i++) { |
| if (guac_keyboard.pressed[keysyms[i]]) |
| return; |
| } |
| |
| // Mark as implicitly pressed only if there is other information |
| // within the key event relating to a different key. Some |
| // platforms, such as iOS, will send essentially empty key events |
| // for modifier keys, using only the modifier flags to signal the |
| // identity of the key. |
| var keysym = keysyms[0]; |
| if (keyEvent.keysym) |
| implicitlyPressed[keysym] = true; |
| |
| guac_keyboard.press(keysym); |
| |
| } |
| |
| }; |
| |
| /** |
| * Given a keyboard event, updates the local modifier state and remote |
| * key state based on the modifier flags within the event. This function |
| * pays no attention to keycodes. |
| * |
| * @private |
| * @param {KeyboardEvent} e |
| * The keyboard event containing the flags to update. |
| * |
| * @param {KeyEvent} keyEvent |
| * Guacamole's current best interpretation of the key event being |
| * processed. |
| */ |
| var syncModifierStates = function syncModifierStates(e, keyEvent) { |
| |
| // Get state |
| var state = Guacamole.Keyboard.ModifierState.fromKeyboardEvent(e); |
| |
| // Resync state of alt |
| updateModifierState(guac_keyboard.modifiers.alt, state.alt, [ |
| 0xFFE9, // Left alt |
| 0xFFEA, // Right alt |
| 0xFE03 // AltGr |
| ], keyEvent); |
| |
| // Resync state of shift |
| updateModifierState(guac_keyboard.modifiers.shift, state.shift, [ |
| 0xFFE1, // Left shift |
| 0xFFE2 // Right shift |
| ], keyEvent); |
| |
| // Resync state of ctrl |
| updateModifierState(guac_keyboard.modifiers.ctrl, state.ctrl, [ |
| 0xFFE3, // Left ctrl |
| 0xFFE4 // Right ctrl |
| ], keyEvent); |
| |
| // Resync state of meta |
| updateModifierState(guac_keyboard.modifiers.meta, state.meta, [ |
| 0xFFE7, // Left meta |
| 0xFFE8 // Right meta |
| ], keyEvent); |
| |
| // Resync state of hyper |
| updateModifierState(guac_keyboard.modifiers.hyper, state.hyper, [ |
| 0xFFEB, // Left hyper |
| 0xFFEC // Right hyper |
| ], keyEvent); |
| |
| // Update state |
| guac_keyboard.modifiers = state; |
| |
| }; |
| |
| /** |
| * Returns whether all currently pressed keys were implicitly pressed. A |
| * key is implicitly pressed if its status was inferred indirectly from |
| * inspection of other key events. |
| * |
| * @private |
| * @returns {Boolean} |
| * true if all currently pressed keys were implicitly pressed, false |
| * otherwise. |
| */ |
| var isStateImplicit = function isStateImplicit() { |
| |
| for (var keysym in guac_keyboard.pressed) { |
| if (!implicitlyPressed[keysym]) |
| return false; |
| } |
| |
| return true; |
| |
| }; |
| |
| /** |
| * Reads through the event log, removing events from the head of the log |
| * when the corresponding true key presses are known (or as known as they |
| * can be). |
| * |
| * @private |
| * @return {Boolean} Whether the default action of the latest event should |
| * be prevented. |
| */ |
| function interpret_events() { |
| |
| // Do not prevent default if no event could be interpreted |
| var handled_event = interpret_event(); |
| if (!handled_event) |
| return false; |
| |
| // Interpret as much as possible |
| var last_event; |
| do { |
| last_event = handled_event; |
| handled_event = interpret_event(); |
| } while (handled_event !== null); |
| |
| // Reset keyboard state if we cannot expect to receive any further |
| // keyup events |
| if (isStateImplicit()) |
| guac_keyboard.reset(); |
| |
| return last_event.defaultPrevented; |
| |
| } |
| |
| /** |
| * Releases Ctrl+Alt, if both are currently pressed and the given keysym |
| * looks like a key that may require AltGr. |
| * |
| * @private |
| * @param {Number} keysym The key that was just pressed. |
| */ |
| var release_simulated_altgr = function release_simulated_altgr(keysym) { |
| |
| // Both Ctrl+Alt must be pressed if simulated AltGr is in use |
| if (!guac_keyboard.modifiers.ctrl || !guac_keyboard.modifiers.alt) |
| return; |
| |
| // Assume [A-Z] never require AltGr |
| if (keysym >= 0x0041 && keysym <= 0x005A) |
| return; |
| |
| // Assume [a-z] never require AltGr |
| if (keysym >= 0x0061 && keysym <= 0x007A) |
| return; |
| |
| // Release Ctrl+Alt if the keysym is printable |
| if (keysym <= 0xFF || (keysym & 0xFF000000) === 0x01000000) { |
| guac_keyboard.release(0xFFE3); // Left ctrl |
| guac_keyboard.release(0xFFE4); // Right ctrl |
| guac_keyboard.release(0xFFE9); // Left alt |
| guac_keyboard.release(0xFFEA); // Right alt |
| } |
| |
| }; |
| |
| /** |
| * Reads through the event log, interpreting the first event, if possible, |
| * and returning that event. If no events can be interpreted, due to a |
| * total lack of events or the need for more events, null is returned. Any |
| * interpreted events are automatically removed from the log. |
| * |
| * @private |
| * @return {KeyEvent} |
| * The first key event in the log, if it can be interpreted, or null |
| * otherwise. |
| */ |
| var interpret_event = function interpret_event() { |
| |
| // Peek at first event in log |
| var first = eventLog[0]; |
| if (!first) |
| return null; |
| |
| // Keydown event |
| if (first instanceof KeydownEvent) { |
| |
| var keysym = null; |
| var accepted_events = []; |
| |
| // If event itself is reliable, no need to wait for other events |
| if (first.reliable) { |
| keysym = first.keysym; |
| accepted_events = eventLog.splice(0, 1); |
| } |
| |
| // If keydown is immediately followed by a keypress, use the indicated character |
| else if (eventLog[1] instanceof KeypressEvent) { |
| keysym = eventLog[1].keysym; |
| accepted_events = eventLog.splice(0, 2); |
| } |
| |
| // If keydown is immediately followed by anything else, then no |
| // keypress can possibly occur to clarify this event, and we must |
| // handle it now |
| else if (eventLog[1]) { |
| keysym = first.keysym; |
| accepted_events = eventLog.splice(0, 1); |
| } |
| |
| // Fire a key press if valid events were found |
| if (accepted_events.length > 0) { |
| |
| if (keysym) { |
| |
| // Fire event |
| release_simulated_altgr(keysym); |
| var defaultPrevented = !guac_keyboard.press(keysym); |
| recentKeysym[first.keyCode] = keysym; |
| |
| // Release the key now if we cannot rely on the associated |
| // keyup event |
| if (!first.keyupReliable) |
| guac_keyboard.release(keysym); |
| |
| // Record whether default was prevented |
| for (var i=0; i<accepted_events.length; i++) |
| accepted_events[i].defaultPrevented = defaultPrevented; |
| |
| } |
| |
| return first; |
| |
| } |
| |
| } // end if keydown |
| |
| // Keyup event |
| else if (first instanceof KeyupEvent && !quirks.keyupUnreliable) { |
| |
| // Release specific key if known |
| var keysym = first.keysym; |
| if (keysym) { |
| guac_keyboard.release(keysym); |
| delete recentKeysym[first.keyCode]; |
| first.defaultPrevented = true; |
| } |
| |
| // Otherwise, fall back to releasing all keys |
| else { |
| guac_keyboard.reset(); |
| return first; |
| } |
| |
| return eventLog.shift(); |
| |
| } // end if keyup |
| |
| // Ignore any other type of event (keypress by itself is invalid, and |
| // unreliable keyup events should simply be dumped) |
| else |
| return eventLog.shift(); |
| |
| // No event interpreted |
| return null; |
| |
| }; |
| |
| /** |
| * Returns the keyboard location of the key associated with the given |
| * keyboard event. The location differentiates key events which otherwise |
| * have the same keycode, such as left shift vs. right shift. |
| * |
| * @private |
| * @param {KeyboardEvent} e |
| * A JavaScript keyboard event, as received through the DOM via a |
| * "keydown", "keyup", or "keypress" handler. |
| * |
| * @returns {Number} |
| * The location of the key event on the keyboard, as defined at: |
| * http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent |
| */ |
| var getEventLocation = function getEventLocation(e) { |
| |
| // Use standard location, if possible |
| if ('location' in e) |
| return e.location; |
| |
| // Failing that, attempt to use deprecated keyLocation |
| if ('keyLocation' in e) |
| return e.keyLocation; |
| |
| // If no location is available, assume left side |
| return 0; |
| |
| }; |
| |
| /** |
| * Attempts to mark the given Event as having been handled by this |
| * Guacamole.Keyboard. If the Event has already been marked as handled, |
| * false is returned. |
| * |
| * @param {Event} e |
| * The Event to mark. |
| * |
| * @returns {Boolean} |
| * true if the given Event was successfully marked, false if the given |
| * Event was already marked. |
| */ |
| var markEvent = function markEvent(e) { |
| |
| // Fail if event is already marked |
| if (e[EVENT_MARKER]) |
| return false; |
| |
| // Mark event otherwise |
| e[EVENT_MARKER] = true; |
| return true; |
| |
| }; |
| |
| /** |
| * Attaches event listeners to the given Element, automatically translating |
| * received key, input, and composition events into simple keydown/keyup |
| * events signalled through this Guacamole.Keyboard's onkeydown and |
| * onkeyup handlers. |
| * |
| * @param {Element|Document} element |
| * The Element to attach event listeners to for the sake of handling |
| * key or input events. |
| */ |
| this.listenTo = function listenTo(element) { |
| |
| // When key pressed |
| element.addEventListener("keydown", function(e) { |
| |
| // Only intercept if handler set |
| if (!guac_keyboard.onkeydown) return; |
| |
| // Ignore events which have already been handled |
| if (!markEvent(e)) return; |
| |
| var keyCode; |
| if (window.event) keyCode = window.event.keyCode; |
| else if (e.which) keyCode = e.which; |
| |
| // Fix modifier states |
| var keydownEvent = new KeydownEvent(keyCode, e.keyIdentifier, e.key, getEventLocation(e)); |
| syncModifierStates(e, keydownEvent); |
| |
| // Ignore (but do not prevent) the "composition" keycode sent by some |
| // browsers when an IME is in use (see: http://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html) |
| if (keyCode === 229) |
| return; |
| |
| // Log event |
| eventLog.push(keydownEvent); |
| |
| // Interpret as many events as possible, prevent default if indicated |
| if (interpret_events()) |
| e.preventDefault(); |
| |
| }, true); |
| |
| // When key pressed |
| element.addEventListener("keypress", function(e) { |
| |
| // Only intercept if handler set |
| if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return; |
| |
| // Ignore events which have already been handled |
| if (!markEvent(e)) return; |
| |
| var charCode; |
| if (window.event) charCode = window.event.keyCode; |
| else if (e.which) charCode = e.which; |
| |
| // Fix modifier states |
| var keypressEvent = new KeypressEvent(charCode); |
| syncModifierStates(e, keypressEvent); |
| |
| // Log event |
| eventLog.push(keypressEvent); |
| |
| // Interpret as many events as possible, prevent default if indicated |
| if (interpret_events()) |
| e.preventDefault(); |
| |
| }, true); |
| |
| // When key released |
| element.addEventListener("keyup", function(e) { |
| |
| // Only intercept if handler set |
| if (!guac_keyboard.onkeyup) return; |
| |
| // Ignore events which have already been handled |
| if (!markEvent(e)) return; |
| |
| e.preventDefault(); |
| |
| var keyCode; |
| if (window.event) keyCode = window.event.keyCode; |
| else if (e.which) keyCode = e.which; |
| |
| // Fix modifier states |
| var keyupEvent = new KeyupEvent(keyCode, e.keyIdentifier, e.key, getEventLocation(e)); |
| syncModifierStates(e, keyupEvent); |
| |
| // Log event, call for interpretation |
| eventLog.push(keyupEvent); |
| interpret_events(); |
| |
| }, true); |
| |
| /** |
| * Handles the given "input" event, typing the data within the input text. |
| * If the event is complete (text is provided), handling of "compositionend" |
| * events is suspended, as such events may conflict with input events. |
| * |
| * @private |
| * @param {InputEvent} e |
| * The "input" event to handle. |
| */ |
| var handleInput = function handleInput(e) { |
| |
| // Only intercept if handler set |
| if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return; |
| |
| // Ignore events which have already been handled |
| if (!markEvent(e)) return; |
| |
| // Type all content written |
| if (e.data && !e.isComposing) { |
| element.removeEventListener("compositionend", handleComposition, false); |
| guac_keyboard.type(e.data); |
| } |
| |
| }; |
| |
| /** |
| * Handles the given "compositionend" event, typing the data within the |
| * composed text. If the event is complete (composed text is provided), |
| * handling of "input" events is suspended, as such events may conflict |
| * with composition events. |
| * |
| * @private |
| * @param {CompositionEvent} e |
| * The "compositionend" event to handle. |
| */ |
| var handleComposition = function handleComposition(e) { |
| |
| // Only intercept if handler set |
| if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return; |
| |
| // Ignore events which have already been handled |
| if (!markEvent(e)) return; |
| |
| // Type all content written |
| if (e.data) { |
| element.removeEventListener("input", handleInput, false); |
| guac_keyboard.type(e.data); |
| } |
| |
| }; |
| |
| // Automatically type text entered into the wrapped field |
| element.addEventListener("input", handleInput, false); |
| element.addEventListener("compositionend", handleComposition, false); |
| |
| }; |
| |
| // Listen to given element, if any |
| if (element) |
| guac_keyboard.listenTo(element); |
| |
| }; |
| |
| /** |
| * The unique numerical identifier to assign to the next Guacamole.Keyboard |
| * instance. |
| * |
| * @private |
| * @type {Number} |
| */ |
| Guacamole.Keyboard._nextID = 0; |
| |
| /** |
| * The state of all supported keyboard modifiers. |
| * @constructor |
| */ |
| Guacamole.Keyboard.ModifierState = function() { |
| |
| /** |
| * Whether shift is currently pressed. |
| * @type {Boolean} |
| */ |
| this.shift = false; |
| |
| /** |
| * Whether ctrl is currently pressed. |
| * @type {Boolean} |
| */ |
| this.ctrl = false; |
| |
| /** |
| * Whether alt is currently pressed. |
| * @type {Boolean} |
| */ |
| this.alt = false; |
| |
| /** |
| * Whether meta (apple key) is currently pressed. |
| * @type {Boolean} |
| */ |
| this.meta = false; |
| |
| /** |
| * Whether hyper (windows key) is currently pressed. |
| * @type {Boolean} |
| */ |
| this.hyper = false; |
| |
| }; |
| |
| /** |
| * Returns the modifier state applicable to the keyboard event given. |
| * |
| * @param {KeyboardEvent} e The keyboard event to read. |
| * @returns {Guacamole.Keyboard.ModifierState} The current state of keyboard |
| * modifiers. |
| */ |
| Guacamole.Keyboard.ModifierState.fromKeyboardEvent = function(e) { |
| |
| var state = new Guacamole.Keyboard.ModifierState(); |
| |
| // Assign states from old flags |
| state.shift = e.shiftKey; |
| state.ctrl = e.ctrlKey; |
| state.alt = e.altKey; |
| state.meta = e.metaKey; |
| |
| // Use DOM3 getModifierState() for others |
| if (e.getModifierState) { |
| state.hyper = e.getModifierState("OS") |
| || e.getModifierState("Super") |
| || e.getModifierState("Hyper") |
| || e.getModifierState("Win"); |
| } |
| |
| return state; |
| |
| }; |
| </code></pre> |
| </article> |
| </section> |
| |
| |
| |
| |
| </div> |
| |
| <nav> |
| <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="Guacamole.ArrayBufferReader.html">ArrayBufferReader</a></li><li><a href="Guacamole.ArrayBufferWriter.html">ArrayBufferWriter</a></li><li><a href="Guacamole.AudioPlayer.html">AudioPlayer</a></li><li><a href="Guacamole.AudioRecorder.html">AudioRecorder</a></li><li><a href="Guacamole.BlobReader.html">BlobReader</a></li><li><a href="Guacamole.BlobWriter.html">BlobWriter</a></li><li><a href="Guacamole.ChainedTunnel.html">ChainedTunnel</a></li><li><a href="Guacamole.Client.html">Client</a></li><li><a href="Guacamole.DataURIReader.html">DataURIReader</a></li><li><a href="Guacamole.Display.html">Display</a></li><li><a href="Guacamole.Display.VisibleLayer.html">VisibleLayer</a></li><li><a href="Guacamole.HTTPTunnel.html">HTTPTunnel</a></li><li><a href="Guacamole.InputSink.html">InputSink</a></li><li><a href="Guacamole.InputStream.html">InputStream</a></li><li><a href="Guacamole.IntegerPool.html">IntegerPool</a></li><li><a href="Guacamole.JSONReader.html">JSONReader</a></li><li><a href="Guacamole.Keyboard.html">Keyboard</a></li><li><a href="Guacamole.Keyboard.ModifierState.html">ModifierState</a></li><li><a href="Guacamole.Layer.html">Layer</a></li><li><a href="Guacamole.Layer.Pixel.html">Pixel</a></li><li><a href="Guacamole.Mouse.html">Mouse</a></li><li><a href="Guacamole.Mouse.State.html">State</a></li><li><a href="Guacamole.Mouse.Touchpad.html">Touchpad</a></li><li><a href="Guacamole.Mouse.Touchscreen.html">Touchscreen</a></li><li><a href="Guacamole.Object.html">Object</a></li><li><a href="Guacamole.OnScreenKeyboard.html">OnScreenKeyboard</a></li><li><a href="Guacamole.OnScreenKeyboard.Key.html">Key</a></li><li><a href="Guacamole.OnScreenKeyboard.Layout.html">Layout</a></li><li><a href="Guacamole.OutputStream.html">OutputStream</a></li><li><a href="Guacamole.Parser.html">Parser</a></li><li><a href="Guacamole.RawAudioFormat.html">RawAudioFormat</a></li><li><a href="Guacamole.RawAudioPlayer.html">RawAudioPlayer</a></li><li><a href="Guacamole.RawAudioRecorder.html">RawAudioRecorder</a></li><li><a href="Guacamole.SessionRecording.html">SessionRecording</a></li><li><a href="Guacamole.StaticHTTPTunnel.html">StaticHTTPTunnel</a></li><li><a href="Guacamole.Status.html">Status</a></li><li><a href="Guacamole.StringReader.html">StringReader</a></li><li><a href="Guacamole.StringWriter.html">StringWriter</a></li><li><a href="Guacamole.Tunnel.html">Tunnel</a></li><li><a href="Guacamole.VideoPlayer.html">VideoPlayer</a></li><li><a href="Guacamole.WebSocketTunnel.html">WebSocketTunnel</a></li></ul><h3>Events</h3><ul><li><a href="Guacamole.ArrayBufferReader.html#event:ondata">ondata</a></li><li><a href="Guacamole.ArrayBufferReader.html#event:onend">onend</a></li><li><a href="Guacamole.ArrayBufferWriter.html#event:onack">onack</a></li><li><a href="Guacamole.AudioRecorder.html#event:onclose">onclose</a></li><li><a href="Guacamole.AudioRecorder.html#event:onerror">onerror</a></li><li><a href="Guacamole.BlobReader.html#event:onend">onend</a></li><li><a href="Guacamole.BlobReader.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.BlobWriter.html#event:onack">onack</a></li><li><a href="Guacamole.BlobWriter.html#event:oncomplete">oncomplete</a></li><li><a href="Guacamole.BlobWriter.html#event:onerror">onerror</a></li><li><a href="Guacamole.BlobWriter.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.ChainedTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.ChainedTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.ChainedTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.Client.html#event:onaudio">onaudio</a></li><li><a href="Guacamole.Client.html#event:onclipboard">onclipboard</a></li><li><a href="Guacamole.Client.html#event:onerror">onerror</a></li><li><a href="Guacamole.Client.html#event:onfile">onfile</a></li><li><a href="Guacamole.Client.html#event:onfilesystem">onfilesystem</a></li><li><a href="Guacamole.Client.html#event:onname">onname</a></li><li><a href="Guacamole.Client.html#event:onpipe">onpipe</a></li><li><a href="Guacamole.Client.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.Client.html#event:onsync">onsync</a></li><li><a href="Guacamole.Client.html#event:onvideo">onvideo</a></li><li><a href="Guacamole.DataURIReader.html#event:onend">onend</a></li><li><a href="Guacamole.Display.html#event:oncursor">oncursor</a></li><li><a href="Guacamole.Display.html#event:onresize">onresize</a></li><li><a href="Guacamole.HTTPTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.HTTPTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.HTTPTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.InputStream.html#event:onblob">onblob</a></li><li><a href="Guacamole.InputStream.html#event:onend">onend</a></li><li><a href="Guacamole.JSONReader.html#event:onend">onend</a></li><li><a href="Guacamole.JSONReader.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.Keyboard.html#event:onkeydown">onkeydown</a></li><li><a href="Guacamole.Keyboard.html#event:onkeyup">onkeyup</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:onmousedown">onmousedown</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:onmousemove">onmousemove</a></li><li><a href="Guacamole.Mouse.Touchpad.html#event:onmouseup">onmouseup</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:onmousedown">onmousedown</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:onmousemove">onmousemove</a></li><li><a href="Guacamole.Mouse.Touchscreen.html#event:onmouseup">onmouseup</a></li><li><a href="Guacamole.Mouse.html#event:onmousedown">onmousedown</a></li><li><a href="Guacamole.Mouse.html#event:onmousemove">onmousemove</a></li><li><a href="Guacamole.Mouse.html#event:onmouseout">onmouseout</a></li><li><a href="Guacamole.Mouse.html#event:onmouseup">onmouseup</a></li><li><a href="Guacamole.Object.html#event:onbody">onbody</a></li><li><a href="Guacamole.Object.html#event:onundefine">onundefine</a></li><li><a href="Guacamole.OnScreenKeyboard.html#event:onkeydown">onkeydown</a></li><li><a href="Guacamole.OnScreenKeyboard.html#event:onkeyup">onkeyup</a></li><li><a href="Guacamole.OutputStream.html#event:onack">onack</a></li><li><a href="Guacamole.Parser.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.RawAudioRecorder.html#event:onclose">onclose</a></li><li><a href="Guacamole.RawAudioRecorder.html#event:onerror">onerror</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.SessionRecording._PlaybackTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.SessionRecording.html#event:onpause">onpause</a></li><li><a href="Guacamole.SessionRecording.html#event:onplay">onplay</a></li><li><a href="Guacamole.SessionRecording.html#event:onprogress">onprogress</a></li><li><a href="Guacamole.SessionRecording.html#event:onseek">onseek</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.StaticHTTPTunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.StringReader.html#event:onend">onend</a></li><li><a href="Guacamole.StringReader.html#event:ontext">ontext</a></li><li><a href="Guacamole.StringWriter.html#event:onack">onack</a></li><li><a href="Guacamole.Tunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.Tunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.Tunnel.html#event:onstatechange">onstatechange</a></li><li><a href="Guacamole.WebSocketTunnel.html#event:onerror">onerror</a></li><li><a href="Guacamole.WebSocketTunnel.html#event:oninstruction">oninstruction</a></li><li><a href="Guacamole.WebSocketTunnel.html#event:onstatechange">onstatechange</a></li></ul><h3>Namespaces</h3><ul><li><a href="Guacamole.html">Guacamole</a></li><li><a href="Guacamole.AudioContextFactory.html">AudioContextFactory</a></li></ul> |
| </nav> |
| |
| <br class="clear"> |
| |
| <footer> |
| Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Fri Dec 21 2018 13:47:10 GMT-0800 (PST) |
| </footer> |
| |
| <script> prettyPrint(); </script> |
| <script src="scripts/linenumber.js"> </script> |
| <!-- Google Analytics --> |
| <script type="text/javascript"> |
| (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ |
| (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), |
| m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) |
| })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); |
| |
| ga('create', 'UA-75289145-1', 'auto'); |
| ga('send', 'pageview'); |
| </script> |
| </body> |
| </html> |