2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is guacamole-common-js.
17 * The Initial Developer of the Original Code is
19 * Portions created by the Initial Developer are Copyright (C) 2010
20 * the Initial Developer. All Rights Reserved.
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
36 * ***** END LICENSE BLOCK ***** */
38 // Guacamole namespace
39 var Guacamole = Guacamole || {};
42 * Provides cross-browser and cross-keyboard keyboard for a specific element.
43 * Browser and keyboard layout variation is abstracted away, providing events
44 * which represent keys as their corresponding X11 keysym.
47 * @param {Element} element The Element to use to provide keyboard events.
49 Guacamole.Keyboard = function(element) {
52 * Reference to this Guacamole.Keyboard.
55 var guac_keyboard = this;
58 * Fired whenever the user presses a key with the element associated
59 * with this Guacamole.Keyboard in focus.
62 * @param {Number} keysym The keysym of the key being pressed.
63 * @returns {Boolean} true if the originating event of this keypress should
64 * be allowed through to the browser, false or undefined
67 this.onkeydown = null;
70 * Fired whenever the user releases a key with the element associated
71 * with this Guacamole.Keyboard in focus.
74 * @param {Number} keysym The keysym of the key being released.
75 * @returns {Boolean} true if the originating event of this key release
76 * should be allowed through to the browser, false or
77 * undefined otherwise.
82 * Map of known JavaScript keycodes which do not map to typable characters
83 * to their unshifted X11 keysym equivalents.
86 var unshiftedKeySym = {
87 8: 0xFF08, // backspace
93 19: 0xFF13, // pause/break
94 20: 0xFFE5, // caps lock
96 33: 0xFF55, // page up
97 34: 0xFF56, // page down
100 37: 0xFF51, // left arrow
101 38: 0xFF52, // up arrow
102 39: 0xFF53, // right arrow
103 40: 0xFF54, // down arrow
104 45: 0xFF63, // insert
105 46: 0xFFFF, // delete
106 91: 0xFFEB, // left window key (super_l)
107 92: 0xFF67, // right window key (menu key?)
108 93: null, // select key
121 144: 0xFF7F, // num lock
122 145: 0xFF14 // scroll lock
126 * Map of known JavaScript keyidentifiers which do not map to typable
127 * characters to their unshifted X11 keysym equivalents.
130 var keyidentifier_keysym = {
131 "AllCandidates": 0xFF3D,
132 "Alphanumeric": 0xFF30,
176 "HangulMode": 0xFF31,
182 "JapaneseHiragana": 0xFF25,
183 "JapaneseKatakana": 0xFF26,
184 "JapaneseRomaji": 0xFF24,
195 "PreviousCandidate": 0xFF3E,
196 "PrintScreen": 0xFD1D,
198 "RomanCharacters": null,
208 * Map of known JavaScript keycodes which do not map to typable characters
209 * to their shifted X11 keysym equivalents. Keycodes must only be listed
210 * here if their shifted X11 keysym equivalents differ from their unshifted
214 var shiftedKeySym = {
219 * All modifiers and their states.
224 * Whether shift is currently pressed.
229 * Whether ctrl is currently pressed.
234 * Whether alt is currently pressed.
241 * The state of every key, indexed by keysym. If a particular key is
242 * pressed, the value of pressed for that keysym will be true. If a key
243 * is not currently pressed, the value for that keysym may be false or
248 var keydownChar = new Array();
250 // ID of routine repeating keystrokes. -1 = not repeating.
251 var repeatKeyTimeoutId = -1;
252 var repeatKeyIntervalId = -1;
254 // Starts repeating keystrokes
255 function startRepeat(keySym) {
256 repeatKeyIntervalId = setInterval(function() {
257 sendKeyReleased(keySym);
258 sendKeyPressed(keySym);
262 // Stops repeating keystrokes
263 function stopRepeat() {
264 if (repeatKeyTimeoutId != -1) clearInterval(repeatKeyTimeoutId);
265 if (repeatKeyIntervalId != -1) clearInterval(repeatKeyIntervalId);
269 function getKeySymFromKeyIdentifier(shifted, keyIdentifier) {
271 var unicodePrefixLocation = keyIdentifier.indexOf("U+");
272 if (unicodePrefixLocation >= 0) {
274 var hex = keyIdentifier.substring(unicodePrefixLocation+2);
275 var codepoint = parseInt(hex, 16);
278 // Convert case if shifted
280 typedCharacter = String.fromCharCode(codepoint).toLowerCase();
282 typedCharacter = String.fromCharCode(codepoint).toUpperCase();
285 codepoint = typedCharacter.charCodeAt(0);
287 return getKeySymFromCharCode(codepoint);
291 return keyidentifier_keysym[keyIdentifier];
295 function getKeySymFromCharCode(keyCode) {
297 if (keyCode >= 0x0000 && keyCode <= 0x00FF)
300 if (keyCode >= 0x0100 && keyCode <= 0x10FFFF)
301 return 0x01000000 | keyCode;
307 function getKeySymFromKeyCode(keyCode) {
310 if (!guac_keyboard.modifiers.shift) keysym = unshiftedKeySym[keyCode];
312 keysym = shiftedKeySym[keyCode];
313 if (keysym == null) keysym = unshiftedKeySym[keyCode];
321 // Sends a single keystroke over the network
322 function sendKeyPressed(keysym) {
324 // Mark key as pressed
325 guac_keyboard.pressed[keysym] = true;
328 if (keysym != null && guac_keyboard.onkeydown)
329 return guac_keyboard.onkeydown(keysym) != false;
335 // Sends a single keystroke over the network
336 function sendKeyReleased(keysym) {
338 // Mark key as released
339 guac_keyboard.pressed[keysym] = false;
342 if (keysym != null && guac_keyboard.onkeyup)
343 return guac_keyboard.onkeyup(keysym) != false;
350 var keydown_code = null;
352 var deferred_keypress = null;
353 var keydown_keysym = null;
354 var keypress_keysym = null;
356 function fireKeyPress() {
358 // Prefer keysym from keypress
359 var keysym = keypress_keysym || keydown_keysym;
360 var keynum = keydown_code;
362 if (keydownChar[keynum] != keysym) {
364 // If this button is already pressed, release first
365 var lastKeyDownChar = keydownChar[keydown_code];
367 sendKeyReleased(lastKeyDownChar);
370 keydownChar[keynum] = keysym;
371 sendKeyPressed(keysym);
373 // Clear old key repeat, if any.
376 // Start repeating (if not a modifier key) after a short delay
377 if (keynum != 16 && keynum != 17 && keynum != 18)
378 repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);
382 // Done with deferred key event
383 deferred_keypress = null;
384 keypress_keysym = null;
385 keydown_keysym = null;
390 function isTypable(keyIdentifier) {
392 // Find unicode prefix
393 var unicodePrefixLocation = keyIdentifier.indexOf("U+");
394 if (unicodePrefixLocation == -1)
397 // Parse codepoint value
398 var hex = keyIdentifier.substring(unicodePrefixLocation+2);
399 var codepoint = parseInt(hex, 16);
401 // If control character, not typable
402 if (codepoint <= 0x1F) return false;
403 if (codepoint >= 0x7F && codepoint <= 0x9F) return false;
405 // Otherwise, typable
411 element.onkeydown = function(e) {
413 // Only intercept if handler set
414 if (!guac_keyboard.onkeydown) return;
417 if (window.event) keynum = window.event.keyCode;
418 else if (e.which) keynum = e.which;
421 if (keynum == 16) guac_keyboard.modifiers.shift = true;
422 else if (keynum == 17) guac_keyboard.modifiers.ctrl = true;
423 else if (keynum == 18) guac_keyboard.modifiers.alt = true;
425 // Try to get keysym from keycode
426 keydown_keysym = getKeySymFromKeyCode(keynum);
428 // Also try to get get keysym from keyIdentifier
429 if (e.keyIdentifier) {
431 keydown_keysym = keydown_keysym ||
432 getKeySymFromKeyIdentifier(guac_keyboard.modifiers.shift, e.keyIdentifier);
434 // Prevent default if non-typable character or if modifier combination
435 // likely to be eaten by browser otherwise (NOTE: We must not prevent
436 // default for Ctrl+Alt, as that combination is commonly used for
437 // AltGr. If we receive AltGr, we need to handle keypress, which
438 // means we cannot cancel keydown).
439 if (!isTypable(e.keyIdentifier)
440 || ( guac_keyboard.modifiers.ctrl && !guac_keyboard.modifiers.alt)
441 || (!guac_keyboard.modifiers.ctrl && guac_keyboard.modifiers.alt))
446 // Set keycode which will be associated with any future keypress
447 keydown_code = keynum;
449 // Defer handling of event until after any other pending
451 if (!deferred_keypress)
452 deferred_keypress = window.setTimeout(fireKeyPress, 0);
457 element.onkeypress = function(e) {
459 // Only intercept if handler set
460 if (!guac_keyboard.onkeydown) return true;
463 if (window.event) keynum = window.event.keyCode;
464 else if (e.which) keynum = e.which;
466 keypress_keysym = getKeySymFromCharCode(keynum);
468 // If event identified as a typable character (keypress involved)
469 // then release Ctrl and Alt (if pressed)
470 if (guac_keyboard.modifiers.ctrl) sendKeyReleased(0xFFE3);
471 if (guac_keyboard.modifiers.alt) sendKeyReleased(0xFFE9);
473 // Defer handling of event until after any other pending
475 if (!deferred_keypress)
476 deferred_keypress = window.setTimeout(fireKeyPress, 0);
483 element.onkeyup = function(e) {
485 // Only intercept if handler set
486 if (!guac_keyboard.onkeyup) return true;
489 if (window.event) keynum = window.event.keyCode;
490 else if (e.which) keynum = e.which;
493 if (keynum == 16) guac_keyboard.modifiers.shift = false;
494 else if (keynum == 17) guac_keyboard.modifiers.ctrl = false;
495 else if (keynum == 18) guac_keyboard.modifiers.alt = false;
499 // Get corresponding character
500 var lastKeyDownChar = keydownChar[keynum];
502 // Clear character record
503 keydownChar[keynum] = null;
505 // Send release event
506 return sendKeyReleased(lastKeyDownChar);
510 // When focus is lost, clear modifiers.
511 element.onblur = function() {
512 guac_keyboard.modifiers.alt = false;
513 guac_keyboard.modifiers.ctrl = false;
514 guac_keyboard.modifiers.shift = false;