*
* ***** END LICENSE BLOCK ***** */
-// Guacamole namespace
+/**
+ * Namespace for all Guacamole JavaScript objects.
+ * @namespace
+ */
var Guacamole = Guacamole || {};
/**
*
* @event
* @param {Number} keysym The keysym of the key being pressed.
- * @returns {Boolean} true if the originating event of this keypress should
- * be allowed through to the browser, false or undefined
- * otherwise.
*/
this.onkeydown = null;
*
* @event
* @param {Number} keysym The keysym of the key being released.
- * @returns {Boolean} true if the originating event of this key release
- * should be allowed through to the browser, false or
- * undefined otherwise.
*/
this.onkeyup = null;
19: 0xFF13, // pause/break
20: 0xFFE5, // caps lock
27: 0xFF1B, // escape
+ 32: 0x0020, // space
33: 0xFF55, // page up
34: 0xFF56, // page down
35: 0xFF57, // end
};
/**
+ * Map of known JavaScript keyidentifiers which do not map to typable
+ * characters to their unshifted X11 keysym equivalents.
+ * @private
+ */
+ var keyidentifier_keysym = {
+ "AllCandidates": 0xFF3D,
+ "Alphanumeric": 0xFF30,
+ "Alt": 0xFFE9,
+ "Attn": 0xFD0E,
+ "AltGraph": 0xFFEA,
+ "CapsLock": 0xFFE5,
+ "Clear": 0xFF0B,
+ "Convert": 0xFF21,
+ "Copy": 0xFD15,
+ "Crsel": 0xFD1C,
+ "CodeInput": 0xFF37,
+ "Control": 0xFFE3,
+ "Down": 0xFF54,
+ "End": 0xFF57,
+ "Enter": 0xFF0D,
+ "EraseEof": 0xFD06,
+ "Execute": 0xFF62,
+ "Exsel": 0xFD1D,
+ "F1": 0xFFBE,
+ "F2": 0xFFBF,
+ "F3": 0xFFC0,
+ "F4": 0xFFC1,
+ "F5": 0xFFC2,
+ "F6": 0xFFC3,
+ "F7": 0xFFC4,
+ "F8": 0xFFC5,
+ "F9": 0xFFC6,
+ "F10": 0xFFC7,
+ "F11": 0xFFC8,
+ "F12": 0xFFC9,
+ "F13": 0xFFCA,
+ "F14": 0xFFCB,
+ "F15": 0xFFCC,
+ "F16": 0xFFCD,
+ "F17": 0xFFCE,
+ "F18": 0xFFCF,
+ "F19": 0xFFD0,
+ "F20": 0xFFD1,
+ "F21": 0xFFD2,
+ "F22": 0xFFD3,
+ "F23": 0xFFD4,
+ "F24": 0xFFD5,
+ "Find": 0xFF68,
+ "FullWidth": null,
+ "HalfWidth": null,
+ "HangulMode": 0xFF31,
+ "HanjaMode": 0xFF34,
+ "Help": 0xFF6A,
+ "Hiragana": 0xFF25,
+ "Home": 0xFF50,
+ "Insert": 0xFF63,
+ "JapaneseHiragana": 0xFF25,
+ "JapaneseKatakana": 0xFF26,
+ "JapaneseRomaji": 0xFF24,
+ "JunjaMode": 0xFF38,
+ "KanaMode": 0xFF2D,
+ "KanjiMode": 0xFF21,
+ "Katakana": 0xFF26,
+ "Left": 0xFF51,
+ "Meta": 0xFFE7,
+ "NumLock": 0xFF7F,
+ "PageDown": 0xFF55,
+ "PageUp": 0xFF56,
+ "Pause": 0xFF13,
+ "PreviousCandidate": 0xFF3E,
+ "PrintScreen": 0xFD1D,
+ "Right": 0xFF53,
+ "RomanCharacters": null,
+ "Scroll": 0xFF14,
+ "Select": 0xFF60,
+ "Shift": 0xFFE1,
+ "Up": 0xFF52,
+ "Undo": 0xFF65,
+ "Win": 0xFFEB
+ };
+
+ /**
* Map of known JavaScript keycodes which do not map to typable characters
* to their shifted X11 keysym equivalents. Keycodes must only be listed
* here if their shifted X11 keysym equivalents differ from their unshifted
// Stops repeating keystrokes
function stopRepeat() {
- if (repeatKeyTimeoutId != -1) clearInterval(repeatKeyTimeoutId);
+ if (repeatKeyTimeoutId != -1) clearTimeout(repeatKeyTimeoutId);
if (repeatKeyIntervalId != -1) clearInterval(repeatKeyIntervalId);
}
}
- return null;
+ return keyidentifier_keysym[keyIdentifier];
+
+ }
+ function isControlCharacter(codepoint) {
+ return codepoint <= 0x1F || (codepoint >= 0x7F && codepoint <= 0x9F);
}
- function getKeySymFromCharCode(keyCode) {
+ function getKeySymFromCharCode(codepoint) {
- if (keyCode >= 0x0000 && keyCode <= 0x00FF)
- return keyCode;
+ // Keysyms for control characters
+ if (isControlCharacter(codepoint)) return 0xFF00 | codepoint;
- if (keyCode >= 0x0100 && keyCode <= 0x10FFFF)
- return 0x01000000 | keyCode;
+ // Keysyms for ASCII chars
+ if (codepoint >= 0x0000 && codepoint <= 0x00FF)
+ return codepoint;
+
+ // Keysyms for Unicode
+ if (codepoint >= 0x0100 && codepoint <= 0x10FFFF)
+ return 0x01000000 | codepoint;
return null;
// Send key event
if (keysym != null && guac_keyboard.onkeydown)
- return guac_keyboard.onkeydown(keysym) != false;
-
- return true;
+ guac_keyboard.onkeydown(keysym);
}
// Send key event
if (keysym != null && guac_keyboard.onkeyup)
- return guac_keyboard.onkeyup(keysym) != false;
-
- return true;
+ guac_keyboard.onkeyup(keysym);
}
- var KEYDOWN = 1;
- var KEYPRESS = 2;
+ var expect_keypress = true;
+ var keydown_code = null;
- var keySymSource = null;
+ var deferred_keypress = null;
+ var keydown_keysym = null;
+ var keypress_keysym = null;
- // When key pressed
- var keydownCode = null;
- element.onkeydown = function(e) {
-
- // Only intercept if handler set
- if (!guac_keyboard.onkeydown) return true;
+ function handleKeyEvents() {
- var keynum;
- if (window.event) keynum = window.event.keyCode;
- else if (e.which) keynum = e.which;
+ // Prefer keysym from keypress
+ var keysym = keypress_keysym || keydown_keysym;
+ var keynum = keydown_code;
- // Ctrl/Alt/Shift
- if (keynum == 16) guac_keyboard.modifiers.shift = true;
- else if (keynum == 17) guac_keyboard.modifiers.ctrl = true;
- else if (keynum == 18) guac_keyboard.modifiers.alt = true;
+ if (keydownChar[keynum] != keysym) {
- // If keysym is defined for given key code, key events can come from
- // KEYDOWN.
- var keysym = getKeySymFromKeyCode(keynum);
- if (keysym)
- keySymSource = KEYDOWN;
+ // If this button is already pressed, release first
+ var lastKeyDownChar = keydownChar[keydown_code];
+ if (lastKeyDownChar)
+ sendKeyReleased(lastKeyDownChar);
- // Otherwise, if modifier keys are held down, try to get from keyIdentifier
- else if ((guac_keyboard.modifiers.ctrl || guac_keyboard.modifiers.alt) && e.keyIdentifier) {
+ // Send event
+ keydownChar[keynum] = keysym;
+ sendKeyPressed(keysym);
- // Get keysym from keyIdentifier
- keysym = getKeySymFromKeyIdentifier(guac_keyboard.modifiers.shift, e.keyIdentifier);
+ // Clear old key repeat, if any.
+ stopRepeat();
- // Get keysyms and events from KEYDOWN
- keySymSource = KEYDOWN;
+ // Start repeating (if not a modifier key) after a short delay
+ if (keynum != 16 && keynum != 17 && keynum != 18)
+ repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);
}
- // Otherwise, resort to KEYPRESS
- else
- keySymSource = KEYPRESS;
+ // Done with deferred key event
+ deferred_keypress = null;
+ keypress_keysym = null;
+ keydown_keysym = null;
+ keydown_code = null;
- keydownCode = keynum;
+ }
- // Ignore key if we don't need to use KEYPRESS.
- // Send key event here
- if (keySymSource == KEYDOWN) {
+ function isTypable(keyIdentifier) {
- if (keydownChar[keynum] != keysym) {
+ // Find unicode prefix
+ var unicodePrefixLocation = keyIdentifier.indexOf("U+");
+ if (unicodePrefixLocation == -1)
+ return false;
- // Send event
- keydownChar[keynum] = keysym;
- var returnValue = sendKeyPressed(keysym);
+ // Parse codepoint value
+ var hex = keyIdentifier.substring(unicodePrefixLocation+2);
+ var codepoint = parseInt(hex, 16);
- // Clear old key repeat, if any.
- stopRepeat();
+ // If control character, not typable
+ if (isControlCharacter(codepoint)) return false;
- // Start repeating (if not a modifier key) after a short delay
- if (keynum != 16 && keynum != 17 && keynum != 18)
- repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);
+ // Otherwise, typable
+ return true;
- // Use return code provided by handler
- return returnValue;
+ }
- }
+ // When key pressed
+ element.addEventListener("keydown", function(e) {
- // Default to canceling event if no keypress is being sent, but
- // source of events is keydown.
- return false;
+ // Only intercept if handler set
+ if (!guac_keyboard.onkeydown) return;
+
+ var keynum;
+ if (window.event) keynum = window.event.keyCode;
+ else if (e.which) keynum = e.which;
+ // Ignore any unknown key events
+ if (keynum == 0 && !e.keyIdentifier) {
+ e.preventDefault();
+ return;
}
- return true;
+ expect_keypress = true;
- };
+ // Ctrl/Alt/Shift
+ if (keynum == 16) guac_keyboard.modifiers.shift = true;
+ else if (keynum == 17) guac_keyboard.modifiers.ctrl = true;
+ else if (keynum == 18) guac_keyboard.modifiers.alt = true;
- // When key pressed
- element.onkeypress = function(e) {
+ // Try to get keysym from keycode
+ keydown_keysym = getKeySymFromKeyCode(keynum);
- // Only intercept if handler set
- if (!guac_keyboard.onkeydown) return true;
+ // If key is known from keycode, prevent default
+ if (keydown_keysym)
+ expect_keypress = false;
+
+ // Also try to get get keysym from keyIdentifier
+ if (e.keyIdentifier) {
+
+ keydown_keysym = keydown_keysym ||
+ getKeySymFromKeyIdentifier(guac_keyboard.modifiers.shift, e.keyIdentifier);
+
+ // Prevent default if non-typable character or if modifier combination
+ // likely to be eaten by browser otherwise (NOTE: We must not prevent
+ // default for Ctrl+Alt, as that combination is commonly used for
+ // AltGr. If we receive AltGr, we need to handle keypress, which
+ // means we cannot cancel keydown).
+ if (!isTypable(e.keyIdentifier)
+ || ( guac_keyboard.modifiers.ctrl && !guac_keyboard.modifiers.alt)
+ || (!guac_keyboard.modifiers.ctrl && guac_keyboard.modifiers.alt))
+ expect_keypress = false;
+
+ }
- if (keySymSource != KEYPRESS) return false;
+ // Set keycode which will be associated with any future keypress
+ keydown_code = keynum;
- var keynum;
- if (window.event) keynum = window.event.keyCode;
- else if (e.which) keynum = e.which;
+ // If we expect to handle via keypress, set failsafe timeout and
+ // wait for keypress.
+ if (expect_keypress) {
+ if (!deferred_keypress)
+ deferred_keypress = window.setTimeout(handleKeyEvents, 0);
+ }
- var keysym = getKeySymFromCharCode(keynum);
- if (keysym && keydownChar[keynum] != keysym) {
+ // Otherwise, handle now
+ else {
+ e.preventDefault();
+ handleKeyEvents();
+ }
- // If this button already pressed, release first
- var lastKeyDownChar = keydownChar[keydownCode];
- if (lastKeyDownChar)
- sendKeyReleased(lastKeyDownChar);
+ }, true);
- keydownChar[keydownCode] = keysym;
+ // When key pressed
+ element.addEventListener("keypress", function(e) {
- // Clear old key repeat, if any.
- stopRepeat();
+ // Only intercept if handler set
+ if (!guac_keyboard.onkeydown) return;
- // Send key event
- var returnValue = sendKeyPressed(keysym);
+ e.preventDefault();
- // Start repeating (if not a modifier key) after a short delay
- repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);
+ // Do not handle if we weren't expecting this event (will have already
+ // been handled by keydown)
+ if (!expect_keypress) return;
- return returnValue;
+ var keynum;
+ if (window.event) keynum = window.event.keyCode;
+ else if (e.which) keynum = e.which;
+
+ keypress_keysym = getKeySymFromCharCode(keynum);
+
+ // If event identified as a typable character, and we're holding Ctrl+Alt,
+ // assume Ctrl+Alt is actually AltGr, and release both.
+ if (!isControlCharacter(keynum) && guac_keyboard.modifiers.ctrl && guac_keyboard.modifiers.alt) {
+ sendKeyReleased(0xFFE3);
+ sendKeyReleased(0xFFE9);
}
- // Default to canceling event if no keypress is being sent, but
- // source of events is keypress.
- return false;
+ // Clear timeout, if any
+ if (deferred_keypress)
+ window.clearTimeout(deferred_keypress);
- };
+ // Handle event with all aggregated data
+ handleKeyEvents();
+
+ }, true);
// When key released
- element.onkeyup = function(e) {
+ element.addEventListener("keyup", function(e) {
// Only intercept if handler set
- if (!guac_keyboard.onkeyup) return true;
+ if (!guac_keyboard.onkeyup) return;
+
+ e.preventDefault();
var keynum;
if (window.event) keynum = window.event.keyCode;
keydownChar[keynum] = null;
// Send release event
- return sendKeyReleased(lastKeyDownChar);
+ sendKeyReleased(lastKeyDownChar);
- };
+ }, true);
// When focus is lost, clear modifiers.
- element.onblur = function() {
+ element.addEventListener("blur", function() {
guac_keyboard.modifiers.alt = false;
guac_keyboard.modifiers.ctrl = false;
guac_keyboard.modifiers.shift = false;
- };
+ }, true);
};