/* eslint-disable */12/*3* Licensed to the Apache Software Foundation (ASF) under one4* or more contributor license agreements. See the NOTICE file5* distributed with this work for additional information6* regarding copyright ownership. The ASF licenses this file7* to you under the Apache License, Version 2.0 (the8* "License"); you may not use this file except in compliance9* with the License. You may obtain a copy of the License at10*11* http://www.apache.org/licenses/LICENSE-2.012*13* Unless required by applicable law or agreed to in writing,14* software distributed under the License is distributed on an15* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY16* KIND, either express or implied. See the License for the17* specific language governing permissions and limitations18* under the License.19*/2021var Guacamole = Guacamole || {};2223/**24* Provides cross-browser and cross-keyboard keyboard for a specific element.25* Browser and keyboard layout variation is abstracted away, providing events26* which represent keys as their corresponding X11 keysym.27*28* @constructor29* @param {Element|Document} [element]30* The Element to use to provide keyboard events. If omitted, at least one31* Element must be manually provided through the listenTo() function for32* the Guacamole.Keyboard instance to have any effect.33*/34Guacamole.Keyboard = function Keyboard(element) {3536/**37* Reference to this Guacamole.Keyboard.38*39* @private40* @type {!Guacamole.Keyboard}41*/42var guac_keyboard = this;4344/**45* An integer value which uniquely identifies this Guacamole.Keyboard46* instance with respect to other Guacamole.Keyboard instances.47*48* @private49* @type {!number}50*/51var guacKeyboardID = Guacamole.Keyboard._nextID++;5253/**54* The name of the property which is added to event objects via markEvent()55* to note that they have already been handled by this Guacamole.Keyboard.56*57* @private58* @constant59* @type {!string}60*/61var EVENT_MARKER = '_GUAC_KEYBOARD_HANDLED_BY_' + guacKeyboardID;6263/**64* Fired whenever the user presses a key with the element associated65* with this Guacamole.Keyboard in focus.66*67* @event68* @param {!number} keysym69* The keysym of the key being pressed.70*71* @return {!boolean}72* true if the key event should be allowed through to the browser,73* false otherwise.74*/75this.onkeydown = null;7677/**78* Fired whenever the user releases a key with the element associated79* with this Guacamole.Keyboard in focus.80*81* @event82* @param {!number} keysym83* The keysym of the key being released.84*/85this.onkeyup = null;8687/**88* Set of known platform-specific or browser-specific quirks which must be89* accounted for to properly interpret key events, even if the only way to90* reliably detect that quirk is to platform/browser-sniff.91*92* @private93* @type {!Object.<string, boolean>}94*/95var quirks = {9697/**98* Whether keyup events are universally unreliable.99*100* @type {!boolean}101*/102keyupUnreliable: false,103104/**105* Whether the Alt key is actually a modifier for typable keys and is106* thus never used for keyboard shortcuts.107*108* @type {!boolean}109*/110altIsTypableOnly: false,111112/**113* Whether we can rely on receiving a keyup or keydown event for the114* Caps Lock key.115*116* @type {!boolean}117*/118capsLockKeyEventUnreliable: false119120};121122// Set quirk flags depending on platform/browser, if such information is123// available124if (navigator && navigator.platform) {125126// All keyup events are unreliable on iOS (sadly)127if (navigator.platform.match(/ipad|iphone|ipod/i))128quirks.keyupUnreliable = true;129130// The Alt key on Mac is never used for keyboard shortcuts, and the131// Caps Lock key never dispatches keyup events in firefox, and it132// dispatches either keydown or keyup events in chrome, but never both133else if (navigator.platform.match(/^mac/i)) {134quirks.altIsTypableOnly = true;135quirks.capsLockKeyEventUnreliable = true;136}137138}139140/**141* A key event having a corresponding timestamp. This event is non-specific.142* Its subclasses should be used instead when recording specific key143* events.144*145* @private146* @constructor147* @param {KeyboardEvent} [orig]148* The relevant DOM keyboard event.149*/150var KeyEvent = function KeyEvent(orig) {151152/**153* Reference to this key event.154*155* @private156* @type {!KeyEvent}157*/158var key_event = this;159160/**161* The JavaScript key code of the key pressed. For most events (keydown162* and keyup), this is a scancode-like value related to the position of163* the key on the US English "Qwerty" keyboard. For keypress events,164* this is the Unicode codepoint of the character that would be typed165* by the key pressed.166*167* @type {!number}168*/169this.keyCode = orig ? (orig.which || orig.keyCode) : 0;170171/**172* The legacy DOM3 "keyIdentifier" of the key pressed, as defined at:173* http://www.w3.org/TR/2009/WD-DOM-Level-3-Events-20090908/#events-Events-KeyboardEvent174*175* @type {!string}176*/177this.keyIdentifier = orig && orig.keyIdentifier;178179/**180* The standard name of the key pressed, as defined at:181* http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent182*183* @type {!string}184*/185this.key = orig && orig.key;186187/**188* The location on the keyboard corresponding to the key pressed, as189* defined at:190* http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent191*192* @type {!number}193*/194this.location = orig ? getEventLocation(orig) : 0;195196/**197* The state of all local keyboard modifiers at the time this event was198* received.199*200* @type {!Guacamole.Keyboard.ModifierState}201*/202this.modifiers = orig ? Guacamole.Keyboard.ModifierState.fromKeyboardEvent(orig) : new Guacamole.Keyboard.ModifierState();203204/**205* An arbitrary timestamp in milliseconds, indicating this event's206* position in time relative to other events.207*208* @type {!number}209*/210this.timestamp = new Date().getTime();211212/**213* Whether the default action of this key event should be prevented.214*215* @type {!boolean}216*/217this.defaultPrevented = false;218219/**220* The keysym of the key associated with this key event, as determined221* by a best-effort guess using available event properties and keyboard222* state.223*224* @type {number}225*/226this.keysym = null;227228/**229* Whether the keysym value of this key event is known to be reliable.230* If false, the keysym may still be valid, but it's only a best guess,231* and future key events may be a better source of information.232*233* @type {!boolean}234*/235this.reliable = false;236237/**238* Returns the number of milliseconds elapsed since this event was239* received.240*241* @return {!number}242* The number of milliseconds elapsed since this event was243* received.244*/245this.getAge = function() {246return new Date().getTime() - key_event.timestamp;247};248249};250251/**252* Information related to the pressing of a key, which need not be a key253* associated with a printable character. The presence or absence of any254* information within this object is browser-dependent.255*256* @private257* @constructor258* @augments Guacamole.Keyboard.KeyEvent259* @param {!KeyboardEvent} orig260* The relevant DOM "keydown" event.261*/262var KeydownEvent = function KeydownEvent(orig) {263264// We extend KeyEvent265KeyEvent.call(this, orig);266267// If key is known from keyCode or DOM3 alone, use that268this.keysym = keysym_from_key_identifier(this.key, this.location)269|| keysym_from_keycode(this.keyCode, this.location);270271/**272* Whether the keyup following this keydown event is known to be273* reliable. If false, we cannot rely on the keyup event to occur.274*275* @type {!boolean}276*/277this.keyupReliable = !quirks.keyupUnreliable;278279// DOM3 and keyCode are reliable sources if the corresponding key is280// not a printable key281if (this.keysym && !isPrintable(this.keysym))282this.reliable = true;283284// Use legacy keyIdentifier as a last resort, if it looks sane285if (!this.keysym && key_identifier_sane(this.keyCode, this.keyIdentifier))286this.keysym = keysym_from_key_identifier(this.keyIdentifier, this.location, this.modifiers.shift);287288// If a key is pressed while meta is held down, the keyup will289// never be sent in Chrome (bug #108404)290if (this.modifiers.meta && this.keysym !== 0xFFE7 && this.keysym !== 0xFFE8)291this.keyupReliable = false;292293// We cannot rely on receiving keyup for Caps Lock on certain platforms294else if (this.keysym === 0xFFE5 && quirks.capsLockKeyEventUnreliable)295this.keyupReliable = false;296297// Determine whether default action for Alt+combinations must be prevented298var prevent_alt = !this.modifiers.ctrl && !quirks.altIsTypableOnly;299300// If alt is typeable only, and this is actually an alt key event, treat as AltGr instead301if (quirks.altIsTypableOnly && (this.keysym === 0xFFE9 || this.keysym === 0xFFEA))302this.keysym = 0xFE03;303304// Determine whether default action for Ctrl+combinations must be prevented305var prevent_ctrl = !this.modifiers.alt;306307// We must rely on the (potentially buggy) keyIdentifier if preventing308// the default action is important309if ((prevent_ctrl && this.modifiers.ctrl)310|| (prevent_alt && this.modifiers.alt)311|| this.modifiers.meta312|| this.modifiers.hyper)313this.reliable = true;314315// Record most recently known keysym by associated key code316recentKeysym[this.keyCode] = this.keysym;317318};319320KeydownEvent.prototype = new KeyEvent();321322/**323* Information related to the pressing of a key, which MUST be324* associated with a printable character. The presence or absence of any325* information within this object is browser-dependent.326*327* @private328* @constructor329* @augments Guacamole.Keyboard.KeyEvent330* @param {!KeyboardEvent} orig331* The relevant DOM "keypress" event.332*/333var KeypressEvent = function KeypressEvent(orig) {334335// We extend KeyEvent336KeyEvent.call(this, orig);337338// Pull keysym from char code339this.keysym = keysym_from_charcode(this.keyCode);340341// Keypress is always reliable342this.reliable = true;343344};345346KeypressEvent.prototype = new KeyEvent();347348/**349* Information related to the releasing of a key, which need not be a key350* associated with a printable character. The presence or absence of any351* information within this object is browser-dependent.352*353* @private354* @constructor355* @augments Guacamole.Keyboard.KeyEvent356* @param {!KeyboardEvent} orig357* The relevant DOM "keyup" event.358*/359var KeyupEvent = function KeyupEvent(orig) {360361// We extend KeyEvent362KeyEvent.call(this, orig);363364// If unreliable caps lock was pressed and event was not marked, then365// we need to pretend that this is a keydown event because we obviously366// did not receive it (issue on macos with chrome)367if (this.keyCode == 20 && quirks.capsLockKeyEventUnreliable) {368eventLog.push(new KeydownEvent(this));369return;370}371372// If key is known from keyCode or DOM3 alone, use that (keyCode is373// still more reliable for keyup when dead keys are in use)374this.keysym = keysym_from_keycode(this.keyCode, this.location)375|| keysym_from_key_identifier(this.key, this.location);376377// Fall back to the most recently pressed keysym associated with the378// keyCode if the inferred key doesn't seem to actually be pressed379if (!guac_keyboard.pressed[this.keysym])380this.keysym = recentKeysym[this.keyCode] || this.keysym;381382// Keyup is as reliable as it will ever be383this.reliable = true;384385};386387KeyupEvent.prototype = new KeyEvent();388389/**390* An array of recorded events, which can be instances of the private391* KeydownEvent, KeypressEvent, and KeyupEvent classes.392*393* @private394* @type {!KeyEvent[]}395*/396var eventLog = [];397398/**399* Map of known JavaScript keycodes which do not map to typable characters400* to their X11 keysym equivalents.401*402* @private403* @type {!Object.<number, number[]>}404*/405var keycodeKeysyms = {4068: [0xFF08], // backspace4079: [0xFF09], // tab40812: [0xFF0B, 0xFF0B, 0xFF0B, 0xFFB5], // clear / KP 540913: [0xFF0D], // enter41016: [0xFFE1, 0xFFE1, 0xFFE2], // shift41117: [0xFFE3, 0xFFE3, 0xFFE4], // ctrl41218: [0xFFE9, 0xFFE9, 0xFFEA], // alt41319: [0xFF13], // pause/break41420: [0xFFE5], // caps lock41527: [0xFF1B], // escape41632: [0x0020], // space41733: [0xFF55, 0xFF55, 0xFF55, 0xFFB9], // page up / KP 941834: [0xFF56, 0xFF56, 0xFF56, 0xFFB3], // page down / KP 341935: [0xFF57, 0xFF57, 0xFF57, 0xFFB1], // end / KP 142036: [0xFF50, 0xFF50, 0xFF50, 0xFFB7], // home / KP 742137: [0xFF51, 0xFF51, 0xFF51, 0xFFB4], // left arrow / KP 442238: [0xFF52, 0xFF52, 0xFF52, 0xFFB8], // up arrow / KP 842339: [0xFF53, 0xFF53, 0xFF53, 0xFFB6], // right arrow / KP 642440: [0xFF54, 0xFF54, 0xFF54, 0xFFB2], // down arrow / KP 242545: [0xFF63, 0xFF63, 0xFF63, 0xFFB0], // insert / KP 042646: [0xFFFF, 0xFFFF, 0xFFFF, 0xFFAE], // delete / KP decimal42791: [0xFFE7], // left windows/command key (meta_l)42892: [0xFFE8], // right window/command key (meta_r)42993: [0xFF67], // menu key43096: [0xFFB0], // KP 043197: [0xFFB1], // KP 143298: [0xFFB2], // KP 243399: [0xFFB3], // KP 3434100: [0xFFB4], // KP 4435101: [0xFFB5], // KP 5436102: [0xFFB6], // KP 6437103: [0xFFB7], // KP 7438104: [0xFFB8], // KP 8439105: [0xFFB9], // KP 9440106: [0xFFAA], // KP multiply441107: [0xFFAB], // KP add442109: [0xFFAD], // KP subtract443110: [0xFFAE], // KP decimal444111: [0xFFAF], // KP divide445112: [0xFFBE], // f1446113: [0xFFBF], // f2447114: [0xFFC0], // f3448115: [0xFFC1], // f4449116: [0xFFC2], // f5450117: [0xFFC3], // f6451118: [0xFFC4], // f7452119: [0xFFC5], // f8453120: [0xFFC6], // f9454121: [0xFFC7], // f10455122: [0xFFC8], // f11456123: [0xFFC9], // f12457144: [0xFF7F], // num lock458145: [0xFF14], // scroll lock459225: [0xFE03] // altgraph (iso_level3_shift)460};461462/**463* Map of known JavaScript keyidentifiers which do not map to typable464* characters to their unshifted X11 keysym equivalents.465*466* @private467* @type {!Object.<string, number[]>}468*/469var keyidentifier_keysym = {470"Again": [0xFF66],471"AllCandidates": [0xFF3D],472"Alphanumeric": [0xFF30],473"Alt": [0xFFE9, 0xFFE9, 0xFFEA],474"Attn": [0xFD0E],475"AltGraph": [0xFE03],476"ArrowDown": [0xFF54],477"ArrowLeft": [0xFF51],478"ArrowRight": [0xFF53],479"ArrowUp": [0xFF52],480"Backspace": [0xFF08],481"CapsLock": [0xFFE5],482"Cancel": [0xFF69],483"Clear": [0xFF0B],484"Convert": [0xFF23],485"Copy": [0xFD15],486"Crsel": [0xFD1C],487"CrSel": [0xFD1C],488"CodeInput": [0xFF37],489"Compose": [0xFF20],490"Control": [0xFFE3, 0xFFE3, 0xFFE4],491"ContextMenu": [0xFF67],492"Delete": [0xFFFF],493"Down": [0xFF54],494"End": [0xFF57],495"Enter": [0xFF0D],496"EraseEof": [0xFD06],497"Escape": [0xFF1B],498"Execute": [0xFF62],499"Exsel": [0xFD1D],500"ExSel": [0xFD1D],501"F1": [0xFFBE],502"F2": [0xFFBF],503"F3": [0xFFC0],504"F4": [0xFFC1],505"F5": [0xFFC2],506"F6": [0xFFC3],507"F7": [0xFFC4],508"F8": [0xFFC5],509"F9": [0xFFC6],510"F10": [0xFFC7],511"F11": [0xFFC8],512"F12": [0xFFC9],513"F13": [0xFFCA],514"F14": [0xFFCB],515"F15": [0xFFCC],516"F16": [0xFFCD],517"F17": [0xFFCE],518"F18": [0xFFCF],519"F19": [0xFFD0],520"F20": [0xFFD1],521"F21": [0xFFD2],522"F22": [0xFFD3],523"F23": [0xFFD4],524"F24": [0xFFD5],525"Find": [0xFF68],526"GroupFirst": [0xFE0C],527"GroupLast": [0xFE0E],528"GroupNext": [0xFE08],529"GroupPrevious": [0xFE0A],530"FullWidth": null,531"HalfWidth": null,532"HangulMode": [0xFF31],533"Hankaku": [0xFF29],534"HanjaMode": [0xFF34],535"Help": [0xFF6A],536"Hiragana": [0xFF25],537"HiraganaKatakana": [0xFF27],538"Home": [0xFF50],539"Hyper": [0xFFED, 0xFFED, 0xFFEE],540"Insert": [0xFF63],541"JapaneseHiragana": [0xFF25],542"JapaneseKatakana": [0xFF26],543"JapaneseRomaji": [0xFF24],544"JunjaMode": [0xFF38],545"KanaMode": [0xFF2D],546"KanjiMode": [0xFF21],547"Katakana": [0xFF26],548"Left": [0xFF51],549"Meta": [0xFFE7, 0xFFE7, 0xFFE8],550"ModeChange": [0xFF7E],551"NonConvert": [0xFF22],552"NumLock": [0xFF7F],553"PageDown": [0xFF56],554"PageUp": [0xFF55],555"Pause": [0xFF13],556"Play": [0xFD16],557"PreviousCandidate": [0xFF3E],558"PrintScreen": [0xFF61],559"Redo": [0xFF66],560"Right": [0xFF53],561"Romaji": [0xFF24],562"RomanCharacters": null,563"Scroll": [0xFF14],564"Select": [0xFF60],565"Separator": [0xFFAC],566"Shift": [0xFFE1, 0xFFE1, 0xFFE2],567"SingleCandidate": [0xFF3C],568"Super": [0xFFEB, 0xFFEB, 0xFFEC],569"Tab": [0xFF09],570"UIKeyInputDownArrow": [0xFF54],571"UIKeyInputEscape": [0xFF1B],572"UIKeyInputLeftArrow": [0xFF51],573"UIKeyInputRightArrow": [0xFF53],574"UIKeyInputUpArrow": [0xFF52],575"Up": [0xFF52],576"Undo": [0xFF65],577"Win": [0xFFE7, 0xFFE7, 0xFFE8],578"Zenkaku": [0xFF28],579"ZenkakuHankaku": [0xFF2A]580};581582/**583* All keysyms which should not repeat when held down.584*585* @private586* @type {!Object.<number, boolean>}587*/588var no_repeat = {5890xFE03: true, // ISO Level 3 Shift (AltGr)5900xFFE1: true, // Left shift5910xFFE2: true, // Right shift5920xFFE3: true, // Left ctrl5930xFFE4: true, // Right ctrl5940xFFE5: true, // Caps Lock5950xFFE7: true, // Left meta5960xFFE8: true, // Right meta5970xFFE9: true, // Left alt5980xFFEA: true, // Right alt5990xFFEB: true, // Left super/hyper6000xFFEC: true // Right super/hyper601};602603/**604* All modifiers and their states.605*606* @type {!Guacamole.Keyboard.ModifierState}607*/608this.modifiers = new Guacamole.Keyboard.ModifierState();609610/**611* The state of every key, indexed by keysym. If a particular key is612* pressed, the value of pressed for that keysym will be true. If a key613* is not currently pressed, it will not be defined.614*615* @type {!Object.<number, boolean>}616*/617this.pressed = {};618619/**620* The state of every key, indexed by keysym, for strictly those keys whose621* status has been indirectly determined thorugh observation of other key622* events. If a particular key is implicitly pressed, the value of623* implicitlyPressed for that keysym will be true. If a key624* is not currently implicitly pressed (the key is not pressed OR the state625* of the key is explicitly known), it will not be defined.626*627* @private628* @type {!Object.<number, boolean>}629*/630var implicitlyPressed = {};631632/**633* The last result of calling the onkeydown handler for each key, indexed634* by keysym. This is used to prevent/allow default actions for key events,635* even when the onkeydown handler cannot be called again because the key636* is (theoretically) still pressed.637*638* @private639* @type {!Object.<number, boolean>}640*/641var last_keydown_result = {};642643/**644* The keysym most recently associated with a given keycode when keydown645* fired. This object maps keycodes to keysyms.646*647* @private648* @type {!Object.<number, number>}649*/650var recentKeysym = {};651652/**653* Timeout before key repeat starts.654*655* @private656* @type {number}657*/658var key_repeat_timeout = null;659660/**661* Interval which presses and releases the last key pressed while that662* key is still being held down.663*664* @private665* @type {number}666*/667var key_repeat_interval = null;668669/**670* Given an array of keysyms indexed by location, returns the keysym671* for the given location, or the keysym for the standard location if672* undefined.673*674* @private675* @param {number[]} keysyms676* An array of keysyms, where the index of the keysym in the array is677* the location value.678*679* @param {!number} location680* The location on the keyboard corresponding to the key pressed, as681* defined at: http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent682*/683var get_keysym = function get_keysym(keysyms, location) {684685if (!keysyms)686return null;687688return keysyms[location] || keysyms[0];689};690691/**692* Returns true if the given keysym corresponds to a printable character,693* false otherwise.694*695* @param {!number} keysym696* The keysym to check.697*698* @returns {!boolean}699* true if the given keysym corresponds to a printable character,700* false otherwise.701*/702var isPrintable = function isPrintable(keysym) {703704// Keysyms with Unicode equivalents are printable705return (keysym >= 0x00 && keysym <= 0xFF)706|| (keysym & 0xFFFF0000) === 0x01000000;707708};709710function keysym_from_key_identifier(identifier, location, shifted) {711712if (!identifier)713return null;714715var typedCharacter;716717// If identifier is U+xxxx, decode Unicode character718var unicodePrefixLocation = identifier.indexOf("U+");719if (unicodePrefixLocation >= 0) {720var hex = identifier.substring(unicodePrefixLocation+2);721typedCharacter = String.fromCharCode(parseInt(hex, 16));722}723724// If single character and not keypad, use that as typed character725else if (identifier.length === 1 && location !== 3)726typedCharacter = identifier;727728// Otherwise, look up corresponding keysym729else730return get_keysym(keyidentifier_keysym[identifier], location);731732// Alter case if necessary733if (shifted === true)734typedCharacter = typedCharacter.toUpperCase();735else if (shifted === false)736typedCharacter = typedCharacter.toLowerCase();737738// Get codepoint739var codepoint = typedCharacter.charCodeAt(0);740return keysym_from_charcode(codepoint);741742}743744function isControlCharacter(codepoint) {745return codepoint <= 0x1F || (codepoint >= 0x7F && codepoint <= 0x9F);746}747748function keysym_from_charcode(codepoint) {749750// Keysyms for control characters751if (isControlCharacter(codepoint)) return 0xFF00 | codepoint;752753// Keysyms for ASCII chars754if (codepoint >= 0x0000 && codepoint <= 0x00FF)755return codepoint;756757// Keysyms for Unicode758if (codepoint >= 0x0100 && codepoint <= 0x10FFFF)759return 0x01000000 | codepoint;760761return null;762763}764765function keysym_from_keycode(keyCode, location) {766return get_keysym(keycodeKeysyms[keyCode], location);767}768769/**770* Heuristically detects if the legacy keyIdentifier property of771* a keydown/keyup event looks incorrectly derived. Chrome, and772* presumably others, will produce the keyIdentifier by assuming773* the keyCode is the Unicode codepoint for that key. This is not774* correct in all cases.775*776* @private777* @param {!number} keyCode778* The keyCode from a browser keydown/keyup event.779*780* @param {string} keyIdentifier781* The legacy keyIdentifier from a browser keydown/keyup event.782*783* @returns {!boolean}784* true if the keyIdentifier looks sane, false if the keyIdentifier785* appears incorrectly derived or is missing entirely.786*/787var key_identifier_sane = function key_identifier_sane(keyCode, keyIdentifier) {788789// Missing identifier is not sane790if (!keyIdentifier)791return false;792793// Assume non-Unicode keyIdentifier values are sane794var unicodePrefixLocation = keyIdentifier.indexOf("U+");795if (unicodePrefixLocation === -1)796return true;797798// If the Unicode codepoint isn't identical to the keyCode,799// then the identifier is likely correct800var codepoint = parseInt(keyIdentifier.substring(unicodePrefixLocation+2), 16);801if (keyCode !== codepoint)802return true;803804// The keyCodes for A-Z and 0-9 are actually identical to their805// Unicode codepoints806if ((keyCode >= 65 && keyCode <= 90) || (keyCode >= 48 && keyCode <= 57))807return true;808809// The keyIdentifier does NOT appear sane810return false;811812};813814/**815* Marks a key as pressed, firing the keydown event if registered. Key816* repeat for the pressed key will start after a delay if that key is817* not a modifier. The return value of this function depends on the818* return value of the keydown event handler, if any.819*820* @param {number} keysym821* The keysym of the key to press.822*823* @return {boolean}824* true if event should NOT be canceled, false otherwise.825*/826this.press = function(keysym) {827828// Don't bother with pressing the key if the key is unknown829if (keysym === null) return;830831// Only press if released832if (!guac_keyboard.pressed[keysym]) {833834// Mark key as pressed835guac_keyboard.pressed[keysym] = true;836837// Send key event838if (guac_keyboard.onkeydown) {839var result = guac_keyboard.onkeydown(keysym);840last_keydown_result[keysym] = result;841842// Stop any current repeat843window.clearTimeout(key_repeat_timeout);844window.clearInterval(key_repeat_interval);845846// Repeat after a delay as long as pressed847if (!no_repeat[keysym])848key_repeat_timeout = window.setTimeout(function() {849key_repeat_interval = window.setInterval(function() {850guac_keyboard.onkeyup(keysym);851guac_keyboard.onkeydown(keysym);852}, 50);853}, 500);854855return result;856}857}858859// Return the last keydown result by default, resort to false if unknown860return last_keydown_result[keysym] || false;861862};863864/**865* Marks a key as released, firing the keyup event if registered.866*867* @param {number} keysym868* The keysym of the key to release.869*/870this.release = function(keysym) {871872// Only release if pressed873if (guac_keyboard.pressed[keysym]) {874875// Mark key as released876delete guac_keyboard.pressed[keysym];877delete implicitlyPressed[keysym];878879// Stop repeat880window.clearTimeout(key_repeat_timeout);881window.clearInterval(key_repeat_interval);882883// Send key event884if (keysym !== null && guac_keyboard.onkeyup)885guac_keyboard.onkeyup(keysym);886887}888889};890891/**892* Presses and releases the keys necessary to type the given string of893* text.894*895* @param {!string} str896* The string to type.897*/898this.type = function type(str) {899900// Press/release the key corresponding to each character in the string901for (var i = 0; i < str.length; i++) {902903// Determine keysym of current character904var codepoint = str.codePointAt ? str.codePointAt(i) : str.charCodeAt(i);905var keysym = keysym_from_charcode(codepoint);906907// Press and release key for current character908guac_keyboard.press(keysym);909guac_keyboard.release(keysym);910911}912913};914915/**916* Resets the state of this keyboard, releasing all keys, and firing keyup917* events for each released key.918*/919this.reset = function() {920921// Release all pressed keys922for (var keysym in guac_keyboard.pressed)923guac_keyboard.release(parseInt(keysym));924925// Clear event log926eventLog = [];927928};929930/**931* Resynchronizes the remote state of the given modifier with its932* corresponding local modifier state, as dictated by933* {@link KeyEvent#modifiers} within the given key event, by pressing or934* releasing keysyms.935*936* @private937* @param {!string} modifier938* The name of the {@link Guacamole.Keyboard.ModifierState} property939* being updated.940*941* @param {!number[]} keysyms942* The keysyms which represent the modifier being updated.943*944* @param {!KeyEvent} keyEvent945* Guacamole's current best interpretation of the key event being946* processed.947*/948var updateModifierState = function updateModifierState(modifier,949keysyms, keyEvent) {950951var localState = keyEvent.modifiers[modifier];952var remoteState = guac_keyboard.modifiers[modifier];953954var i;955956// Do not trust changes in modifier state for events directly involving957// that modifier: (1) the flag may erroneously be cleared despite958// another version of the same key still being held and (2) the change959// in flag may be due to the current event being processed, thus960// updating things here is at best redundant and at worst incorrect961if (keysyms.indexOf(keyEvent.keysym) !== -1)962return;963964// Release all related keys if modifier is implicitly released965if (remoteState && localState === false) {966for (i = 0; i < keysyms.length; i++) {967guac_keyboard.release(keysyms[i]);968}969}970971// Press if modifier is implicitly pressed972else if (!remoteState && localState) {973974// Verify that modifier flag isn't already pressed or already set975// due to another version of the same key being held down976for (i = 0; i < keysyms.length; i++) {977if (guac_keyboard.pressed[keysyms[i]])978return;979}980981// Mark as implicitly pressed only if there is other information982// within the key event relating to a different key. Some983// platforms, such as iOS, will send essentially empty key events984// for modifier keys, using only the modifier flags to signal the985// identity of the key.986var keysym = keysyms[0];987if (keyEvent.keysym)988implicitlyPressed[keysym] = true;989990guac_keyboard.press(keysym);991992}993994};995996/**997* Given a keyboard event, updates the remote key state to match the local998* modifier state and remote based on the modifier flags within the event.999* This function pays no attention to keycodes.1000*1001* @private1002* @param {!KeyEvent} keyEvent1003* Guacamole's current best interpretation of the key event being1004* processed.1005*/1006var syncModifierStates = function syncModifierStates(keyEvent) {10071008// Resync state of alt1009updateModifierState('alt', [10100xFFE9, // Left alt10110xFFEA, // Right alt10120xFE03 // AltGr1013], keyEvent);10141015// Resync state of shift1016updateModifierState('shift', [10170xFFE1, // Left shift10180xFFE2 // Right shift1019], keyEvent);10201021// Resync state of ctrl1022updateModifierState('ctrl', [10230xFFE3, // Left ctrl10240xFFE4 // Right ctrl1025], keyEvent);10261027// Resync state of meta1028updateModifierState('meta', [10290xFFE7, // Left meta10300xFFE8 // Right meta1031], keyEvent);10321033// Resync state of hyper1034updateModifierState('hyper', [10350xFFEB, // Left super/hyper10360xFFEC // Right super/hyper1037], keyEvent);10381039// Update state1040guac_keyboard.modifiers = keyEvent.modifiers;10411042};10431044/**1045* Returns whether all currently pressed keys were implicitly pressed. A1046* key is implicitly pressed if its status was inferred indirectly from1047* inspection of other key events.1048*1049* @private1050* @returns {!boolean}1051* true if all currently pressed keys were implicitly pressed, false1052* otherwise.1053*/1054var isStateImplicit = function isStateImplicit() {10551056for (var keysym in guac_keyboard.pressed) {1057if (!implicitlyPressed[keysym])1058return false;1059}10601061return true;10621063};10641065/**1066* Reads through the event log, removing events from the head of the log1067* when the corresponding true key presses are known (or as known as they1068* can be).1069*1070* @private1071* @return {boolean}1072* Whether the default action of the latest event should be prevented.1073*/1074function interpret_events() {10751076// Do not prevent default if no event could be interpreted1077var handled_event = interpret_event();1078if (!handled_event)1079return false;10801081// Interpret as much as possible1082var last_event;1083do {1084last_event = handled_event;1085handled_event = interpret_event();1086} while (handled_event !== null);10871088// Reset keyboard state if we cannot expect to receive any further1089// keyup events1090if (isStateImplicit())1091guac_keyboard.reset();10921093return last_event.defaultPrevented;10941095}10961097/**1098* Releases Ctrl+Alt, if both are currently pressed and the given keysym1099* looks like a key that may require AltGr.1100*1101* @private1102* @param {!number} keysym1103* The key that was just pressed.1104*/1105var release_simulated_altgr = function release_simulated_altgr(keysym) {11061107// Both Ctrl+Alt must be pressed if simulated AltGr is in use1108if (!guac_keyboard.modifiers.ctrl || !guac_keyboard.modifiers.alt)1109return;11101111// Assume [A-Z] never require AltGr1112if (keysym >= 0x0041 && keysym <= 0x005A)1113return;11141115// Assume [a-z] never require AltGr1116if (keysym >= 0x0061 && keysym <= 0x007A)1117return;11181119// Release Ctrl+Alt if the keysym is printable1120if (keysym <= 0xFF || (keysym & 0xFF000000) === 0x01000000) {1121guac_keyboard.release(0xFFE3); // Left ctrl1122guac_keyboard.release(0xFFE4); // Right ctrl1123guac_keyboard.release(0xFFE9); // Left alt1124guac_keyboard.release(0xFFEA); // Right alt1125}11261127};11281129/**1130* Reads through the event log, interpreting the first event, if possible,1131* and returning that event. If no events can be interpreted, due to a1132* total lack of events or the need for more events, null is returned. Any1133* interpreted events are automatically removed from the log.1134*1135* @private1136* @return {KeyEvent}1137* The first key event in the log, if it can be interpreted, or null1138* otherwise.1139*/1140var interpret_event = function interpret_event() {11411142// Peek at first event in log1143var first = eventLog[0];1144if (!first)1145return null;11461147// Keydown event1148if (first instanceof KeydownEvent) {11491150var keysym = null;1151var accepted_events = [];11521153// Defer handling of Meta until it is known to be functioning as a1154// modifier (it may otherwise actually be an alternative method for1155// pressing a single key, such as Meta+Left for Home on ChromeOS)1156if (first.keysym === 0xFFE7 || first.keysym === 0xFFE8) {11571158// Defer handling until further events exist to provide context1159if (eventLog.length === 1)1160return null;11611162// Drop keydown if it turns out Meta does not actually apply1163if (eventLog[1].keysym !== first.keysym) {1164if (!eventLog[1].modifiers.meta)1165return eventLog.shift();1166}11671168// Drop duplicate keydown events while waiting to determine1169// whether to acknowledge Meta (browser may repeat keydown1170// while the key is held)1171else if (eventLog[1] instanceof KeydownEvent)1172return eventLog.shift();11731174}11751176// If event itself is reliable, no need to wait for other events1177if (first.reliable) {1178keysym = first.keysym;1179accepted_events = eventLog.splice(0, 1);1180}11811182// If keydown is immediately followed by a keypress, use the indicated character1183else if (eventLog[1] instanceof KeypressEvent) {1184keysym = eventLog[1].keysym;1185accepted_events = eventLog.splice(0, 2);1186}11871188// If keydown is immediately followed by anything else, then no1189// keypress can possibly occur to clarify this event, and we must1190// handle it now1191else if (eventLog[1]) {1192keysym = first.keysym;1193accepted_events = eventLog.splice(0, 1);1194}11951196// Fire a key press if valid events were found1197if (accepted_events.length > 0) {11981199syncModifierStates(first);12001201if (keysym) {12021203// Fire event1204release_simulated_altgr(keysym);1205var defaultPrevented = !guac_keyboard.press(keysym);1206recentKeysym[first.keyCode] = keysym;12071208// Release the key now if we cannot rely on the associated1209// keyup event1210if (!first.keyupReliable)1211guac_keyboard.release(keysym);12121213// Record whether default was prevented1214for (var i=0; i<accepted_events.length; i++)1215accepted_events[i].defaultPrevented = defaultPrevented;12161217}12181219return first;12201221}12221223} // end if keydown12241225// Keyup event1226else if (first instanceof KeyupEvent && !quirks.keyupUnreliable) {12271228// Release specific key if known1229var keysym = first.keysym;1230if (keysym) {1231guac_keyboard.release(keysym);1232delete recentKeysym[first.keyCode];1233first.defaultPrevented = true;1234}12351236// Otherwise, fall back to releasing all keys1237else {1238guac_keyboard.reset();1239return first;1240}12411242syncModifierStates(first);1243return eventLog.shift();12441245} // end if keyup12461247// Ignore any other type of event (keypress by itself is invalid, and1248// unreliable keyup events should simply be dumped)1249else1250return eventLog.shift();12511252// No event interpreted1253return null;12541255};12561257/**1258* Returns the keyboard location of the key associated with the given1259* keyboard event. The location differentiates key events which otherwise1260* have the same keycode, such as left shift vs. right shift.1261*1262* @private1263* @param {!KeyboardEvent} e1264* A JavaScript keyboard event, as received through the DOM via a1265* "keydown", "keyup", or "keypress" handler.1266*1267* @returns {!number}1268* The location of the key event on the keyboard, as defined at:1269* http://www.w3.org/TR/DOM-Level-3-Events/#events-KeyboardEvent1270*/1271var getEventLocation = function getEventLocation(e) {12721273// Use standard location, if possible1274if ('location' in e)1275return e.location;12761277// Failing that, attempt to use deprecated keyLocation1278if ('keyLocation' in e)1279return e.keyLocation;12801281// If no location is available, assume left side1282return 0;12831284};12851286/**1287* Attempts to mark the given Event as having been handled by this1288* Guacamole.Keyboard. If the Event has already been marked as handled,1289* false is returned.1290*1291* @param {!Event} e1292* The Event to mark.1293*1294* @returns {!boolean}1295* true if the given Event was successfully marked, false if the given1296* Event was already marked.1297*/1298var markEvent = function markEvent(e) {12991300// Fail if event is already marked1301if (e[EVENT_MARKER])1302return false;13031304// Mark event otherwise1305e[EVENT_MARKER] = true;1306return true;13071308};13091310/**1311* Attaches event listeners to the given Element, automatically translating1312* received key, input, and composition events into simple keydown/keyup1313* events signalled through this Guacamole.Keyboard's onkeydown and1314* onkeyup handlers.1315*1316* @param {!(Element|Document)} element1317* The Element to attach event listeners to for the sake of handling1318* key or input events.1319*/1320this.listenTo = function listenTo(element) {13211322// When key pressed1323element.addEventListener("keydown", function(e) {13241325// Only intercept if handler set1326if (!guac_keyboard.onkeydown) return;13271328// Ignore events which have already been handled1329if (!markEvent(e)) return;13301331var keydownEvent = new KeydownEvent(e);13321333// Ignore (but do not prevent) the event if explicitly marked as composing,1334// or when the "composition" keycode sent by some browsers when an IME is in use1335// (see: http://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html)1336if (e.isComposing || keydownEvent.keyCode === 229)1337return;13381339// Log event1340eventLog.push(keydownEvent);13411342// Interpret as many events as possible, prevent default if indicated1343if (interpret_events())1344e.preventDefault();13451346}, true);13471348// When key pressed1349element.addEventListener("keypress", function(e) {13501351// Only intercept if handler set1352if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return;13531354// Ignore events which have already been handled1355if (!markEvent(e)) return;13561357// Log event1358eventLog.push(new KeypressEvent(e));13591360// Interpret as many events as possible, prevent default if indicated1361if (interpret_events())1362e.preventDefault();13631364}, true);13651366// When key released1367element.addEventListener("keyup", function(e) {13681369// Only intercept if handler set1370if (!guac_keyboard.onkeyup) return;13711372// Ignore events which have already been handled1373if (!markEvent(e)) return;13741375e.preventDefault();13761377// Log event, call for interpretation1378eventLog.push(new KeyupEvent(e));1379interpret_events();13801381}, true);13821383/**1384* Handles the given "input" event, typing the data within the input text.1385*1386* @private1387* @param {!InputEvent} e1388* The "input" event to handle.1389*/1390var handleInput = function handleInput(e) {13911392// Only intercept if handler set1393if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return;13941395// Ignore events which have already been handled1396if (!markEvent(e)) return;13971398// Type all content written1399if (e.data && !e.isComposing)1400guac_keyboard.type(e.data);14011402};14031404/**1405* Handles the given "compositionstart" event, automatically removing1406* the "input" event handler, as "input" events should only be handled1407* if composition events are not provided by the browser.1408*1409* @private1410* @param {!CompositionEvent} e1411* The "compositionstart" event to handle.1412*/1413var handleCompositionStart = function handleCompositionStart(e) {14141415// Remove the "input" event handler now that the browser is known1416// to send composition events1417element.removeEventListener("input", handleInput, false);14181419};14201421/**1422* Handles the given "compositionend" event, typing the data within the1423* composed text.1424*1425* @private1426* @param {!CompositionEvent} e1427* The "compositionend" event to handle.1428*/1429var handleCompositionEnd = function handleCompositionEnd(e) {14301431// Only intercept if handler set1432if (!guac_keyboard.onkeydown && !guac_keyboard.onkeyup) return;14331434// Ignore events which have already been handled1435if (!markEvent(e)) return;14361437// Type all content written1438if (e.data)1439guac_keyboard.type(e.data);14401441};14421443// Automatically type text entered into the wrapped field1444element.addEventListener("input", handleInput, false);1445element.addEventListener("compositionend", handleCompositionEnd, false);1446element.addEventListener("compositionstart", handleCompositionStart, false);14471448};14491450// Listen to given element, if any1451if (element)1452guac_keyboard.listenTo(element);14531454};14551456/**1457* The unique numerical identifier to assign to the next Guacamole.Keyboard1458* instance.1459*1460* @private1461* @type {!number}1462*/1463Guacamole.Keyboard._nextID = 0;14641465/**1466* The state of all supported keyboard modifiers.1467* @constructor1468*/1469Guacamole.Keyboard.ModifierState = function() {14701471/**1472* Whether shift is currently pressed.1473*1474* @type {!boolean}1475*/1476this.shift = false;14771478/**1479* Whether ctrl is currently pressed.1480*1481* @type {!boolean}1482*/1483this.ctrl = false;14841485/**1486* Whether alt is currently pressed.1487*1488* @type {!boolean}1489*/1490this.alt = false;14911492/**1493* Whether meta (apple key) is currently pressed.1494*1495* @type {!boolean}1496*/1497this.meta = false;14981499/**1500* Whether hyper (windows key) is currently pressed.1501*1502* @type {!boolean}1503*/1504this.hyper = false;15051506};15071508/**1509* Returns the modifier state applicable to the keyboard event given.1510*1511* @param {!KeyboardEvent} e1512* The keyboard event to read.1513*1514* @returns {!Guacamole.Keyboard.ModifierState}1515* The current state of keyboard modifiers.1516*/1517Guacamole.Keyboard.ModifierState.fromKeyboardEvent = function(e) {15181519var state = new Guacamole.Keyboard.ModifierState();15201521// Assign states from old flags1522state.shift = e.shiftKey;1523state.ctrl = e.ctrlKey;1524state.alt = e.altKey;1525state.meta = e.metaKey;15261527// Use DOM3 getModifierState() for others1528if (e.getModifierState) {1529state.hyper = e.getModifierState("OS")1530|| e.getModifierState("Super")1531|| e.getModifierState("Hyper")1532|| e.getModifierState("Win");1533}15341535return state;15361537};15381539export default Guacamole.Keyboard;154015411542