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