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