-/*
- * Guacamole - Clientless Remote Desktop
- * Copyright (C) 2010 Michael Jumper
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
*
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * The Original Code is guacamole-common-js.
+ *
+ * The Initial Developer of the Original Code is
+ * Michael Jumper.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Guacamole namespace
+var Guacamole = Guacamole || {};
+
+/**
+ * Provides cross-browser and cross-keyboard keyboard for a specific element.
+ * Browser and keyboard layout variation is abstracted away, providing events
+ * which represent keys as their corresponding X11 keysym.
+ *
+ * @constructor
+ * @param {Element} element The Element to use to provide keyboard events.
*/
-
-function GuacamoleKeyboard(element) {
-
- // Keymap
-
- var unshiftedKeySym = new Array();
- unshiftedKeySym[8] = 0xFF08; // backspace
- unshiftedKeySym[9] = 0xFF09; // tab
- unshiftedKeySym[13] = 0xFF0D; // enter
- unshiftedKeySym[16] = 0xFFE1; // shift
- unshiftedKeySym[17] = 0xFFE3; // ctrl
- unshiftedKeySym[18] = 0xFFE9; // alt
- unshiftedKeySym[19] = 0xFF13; // pause/break
- unshiftedKeySym[20] = 0xFFE5; // caps lock
- unshiftedKeySym[27] = 0xFF1B; // escape
- unshiftedKeySym[33] = 0xFF55; // page up
- unshiftedKeySym[34] = 0xFF56; // page down
- unshiftedKeySym[35] = 0xFF57; // end
- unshiftedKeySym[36] = 0xFF50; // home
- unshiftedKeySym[37] = 0xFF51; // left arrow
- unshiftedKeySym[38] = 0xFF52; // up arrow
- unshiftedKeySym[39] = 0xFF53; // right arrow
- unshiftedKeySym[40] = 0xFF54; // down arrow
- unshiftedKeySym[45] = 0xFF63; // insert
- unshiftedKeySym[46] = 0xFFFF; // delete
- unshiftedKeySym[91] = 0xFFEB; // left window key (super_l)
- unshiftedKeySym[92] = 0xFF67; // right window key (menu key?)
- unshiftedKeySym[93] = null; // select key
- unshiftedKeySym[112] = 0xFFBE; // f1
- unshiftedKeySym[113] = 0xFFBF; // f2
- unshiftedKeySym[114] = 0xFFC0; // f3
- unshiftedKeySym[115] = 0xFFC1; // f4
- unshiftedKeySym[116] = 0xFFC2; // f5
- unshiftedKeySym[117] = 0xFFC3; // f6
- unshiftedKeySym[118] = 0xFFC4; // f7
- unshiftedKeySym[119] = 0xFFC5; // f8
- unshiftedKeySym[120] = 0xFFC6; // f9
- unshiftedKeySym[121] = 0xFFC7; // f10
- unshiftedKeySym[122] = 0xFFC8; // f11
- unshiftedKeySym[123] = 0xFFC9; // f12
- unshiftedKeySym[144] = 0xFF7F; // num lock
- unshiftedKeySym[145] = 0xFF14; // scroll lock
-
- // Shifted versions, IF DIFFERENT FROM UNSHIFTED!
- // If any of these are null, the unshifted one will be used.
- var shiftedKeySym = new Array();
- shiftedKeySym[18] = 0xFFE7; // alt
-
-
- /*****************************************/
- /*** Keyboard Handler ***/
- /*****************************************/
-
- // Single key state/modifier buffer
- var modShift = 0;
- var modCtrl = 0;
- var modAlt = 0;
+Guacamole.Keyboard = function(element) {
+
+ /**
+ * Reference to this Guacamole.Keyboard.
+ * @private
+ */
+ var guac_keyboard = this;
+
+ /**
+ * Fired whenever the user presses a key with the element associated
+ * with this Guacamole.Keyboard in focus.
+ *
+ * @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;
+
+ /**
+ * Fired whenever the user releases a key with the element associated
+ * with this Guacamole.Keyboard in focus.
+ *
+ * @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;
+
+ /**
+ * Map of known JavaScript keycodes which do not map to typable characters
+ * to their unshifted X11 keysym equivalents.
+ * @private
+ */
+ var unshiftedKeySym = {
+ 8: 0xFF08, // backspace
+ 9: 0xFF09, // tab
+ 13: 0xFF0D, // enter
+ 16: 0xFFE1, // shift
+ 17: 0xFFE3, // ctrl
+ 18: 0xFFE9, // alt
+ 19: 0xFF13, // pause/break
+ 20: 0xFFE5, // caps lock
+ 27: 0xFF1B, // escape
+ 33: 0xFF55, // page up
+ 34: 0xFF56, // page down
+ 35: 0xFF57, // end
+ 36: 0xFF50, // home
+ 37: 0xFF51, // left arrow
+ 38: 0xFF52, // up arrow
+ 39: 0xFF53, // right arrow
+ 40: 0xFF54, // down arrow
+ 45: 0xFF63, // insert
+ 46: 0xFFFF, // delete
+ 91: 0xFFEB, // left window key (super_l)
+ 92: 0xFF67, // right window key (menu key?)
+ 93: null, // select key
+ 112: 0xFFBE, // f1
+ 113: 0xFFBF, // f2
+ 114: 0xFFC0, // f3
+ 115: 0xFFC1, // f4
+ 116: 0xFFC2, // f5
+ 117: 0xFFC3, // f6
+ 118: 0xFFC4, // f7
+ 119: 0xFFC5, // f8
+ 120: 0xFFC6, // f9
+ 121: 0xFFC7, // f10
+ 122: 0xFFC8, // f11
+ 123: 0xFFC9, // f12
+ 144: 0xFF7F, // num lock
+ 145: 0xFF14 // scroll lock
+ };
+
+ /**
+ * 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
+ * equivalents.
+ * @private
+ */
+ var shiftedKeySym = {
+ 18: 0xFFE7 // alt
+ };
+
+ // Single key state/modifier buffer
+ var modShift = false;
+ var modCtrl = false;
+ var modAlt = false;
var keydownChar = new Array();
-
// ID of routine repeating keystrokes. -1 = not repeating.
var repeatKeyTimeoutId = -1;
var repeatKeyIntervalId = -1;
- // Starts repeating keystrokes
- function startRepeat(keySym) {
- repeatKeyIntervalId = setInterval(function() {
+ // Starts repeating keystrokes
+ function startRepeat(keySym) {
+ repeatKeyIntervalId = setInterval(function() {
sendKeyReleased(keySym);
sendKeyPressed(keySym);
}, 50);
- }
+ }
- // Stops repeating keystrokes
- function stopRepeat() {
- if (repeatKeyTimeoutId != -1) clearInterval(repeatKeyTimeoutId);
- if (repeatKeyIntervalId != -1) clearInterval(repeatKeyIntervalId);
- }
+ // Stops repeating keystrokes
+ function stopRepeat() {
+ if (repeatKeyTimeoutId != -1) clearInterval(repeatKeyTimeoutId);
+ if (repeatKeyIntervalId != -1) clearInterval(repeatKeyIntervalId);
+ }
function getKeySymFromKeyIdentifier(shifted, keyIdentifier) {
function getKeySymFromKeyCode(keyCode) {
var keysym = null;
- if (modShift == 0) keysym = unshiftedKeySym[keyCode];
- else {
+ if (!modShift) keysym = unshiftedKeySym[keyCode];
+ else {
keysym = shiftedKeySym[keyCode];
if (keysym == null) keysym = unshiftedKeySym[keyCode];
}
}
- // Sends a single keystroke over the network
- function sendKeyPressed(keysym) {
- if (keysym != null && keyPressedHandler)
- keyPressedHandler(keysym);
- }
+ // Sends a single keystroke over the network
+ function sendKeyPressed(keysym) {
+ if (keysym != null && guac_keyboard.onkeydown)
+ return guac_keyboard.onkeydown(keysym) != false;
+ return true;
+ }
- // Sends a single keystroke over the network
- function sendKeyReleased(keysym) {
- if (keysym != null)
- keyReleasedHandler(keysym);
- }
+ // Sends a single keystroke over the network
+ function sendKeyReleased(keysym) {
+ if (keysym != null && guac_keyboard.onkeyup)
+ return guac_keyboard.onkeyup(keysym) != false;
+ return true;
+ }
var KEYDOWN = 1;
var keySymSource = null;
- // When key pressed
+ // When key pressed
var keydownCode = null;
- element.onkeydown = function(e) {
+ element.onkeydown = function(e) {
// Only intercept if handler set
- if (!keyPressedHandler) return true;
+ if (!guac_keyboard.onkeydown) return true;
- var keynum;
- if (window.event) keynum = window.event.keyCode;
- else if (e.which) keynum = e.which;
+ var keynum;
+ if (window.event) keynum = window.event.keyCode;
+ else if (e.which) keynum = e.which;
- // Ctrl/Alt/Shift
- if (keynum == 16)
- modShift = 1;
- else if (keynum == 17)
- modCtrl = 1;
- else if (keynum == 18)
- modAlt = 1;
+ // Ctrl/Alt/Shift
+ if (keynum == 16)
+ modShift = true;
+ else if (keynum == 17)
+ modCtrl = true;
+ else if (keynum == 18)
+ modAlt = true;
var keysym = getKeySymFromKeyCode(keynum);
if (keysym) {
}
// If modifier keys are held down, and we have keyIdentifier
- else if ((modCtrl == 1 || modAlt == 1) && e.keyIdentifier) {
+ else if ((modCtrl || modAlt) && e.keyIdentifier) {
// Get keysym from keyIdentifier
keysym = getKeySymFromKeyIdentifier(modShift, e.keyIdentifier);
// Send event
keydownChar[keynum] = keysym;
- sendKeyPressed(keysym);
+ var returnValue = sendKeyPressed(keysym);
// Clear old key repeat, if any.
stopRepeat();
// Start repeating (if not a modifier key) after a short delay
if (keynum != 16 && keynum != 17 && keynum != 18)
repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);
+
+ // Use return code provided by handler
+ return returnValue;
+
}
+ // Default to canceling event if no keypress is being sent, but
+ // source of events is keydown.
return false;
+
}
return true;
- };
+ };
- // When key pressed
+ // When key pressed
element.onkeypress = function(e) {
// Only intercept if handler set
- if (!keyPressedHandler) return true;
+ if (!guac_keyboard.onkeydown) return true;
if (keySymSource != KEYPRESS) return false;
- var keynum;
- if (window.event) keynum = window.event.keyCode;
- else if (e.which) keynum = e.which;
+ var keynum;
+ if (window.event) keynum = window.event.keyCode;
+ else if (e.which) keynum = e.which;
var keysym = getKeySymFromCharCode(keynum);
if (keysym && keydownChar[keynum] != keysym) {
stopRepeat();
// Send key event
- sendKeyPressed(keysym);
+ var returnValue = sendKeyPressed(keysym);
// Start repeating (if not a modifier key) after a short delay
repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);
+
+ return returnValue;
}
+ // Default to canceling event if no keypress is being sent, but
+ // source of events is keypress.
return false;
- };
- // When key released
- element.onkeyup = function(e) {
+ };
+
+ // When key released
+ element.onkeyup = function(e) {
// Only intercept if handler set
- if (!keyReleasedHandler) return true;
-
- var keynum;
- if (window.event) keynum = window.event.keyCode;
- else if (e.which) keynum = e.which;
-
- // Ctrl/Alt/Shift
- if (keynum == 16)
- modShift = 0;
- else if (keynum == 17)
- modCtrl = 0;
- else if (keynum == 18)
- modAlt = 0;
+ if (!guac_keyboard.onkeyup) return true;
+
+ var keynum;
+ if (window.event) keynum = window.event.keyCode;
+ else if (e.which) keynum = e.which;
+
+ // Ctrl/Alt/Shift
+ if (keynum == 16)
+ modShift = false;
+ else if (keynum == 17)
+ modCtrl = false;
+ else if (keynum == 18)
+ modAlt = false;
else
stopRepeat();
keydownChar[keynum] = null;
// Send release event
- sendKeyReleased(lastKeyDownChar);
-
- return false;
- };
-
- // When focus is lost, clear modifiers.
- var docOnblur = element.onblur;
- element.onblur = function() {
- modAlt = 0;
- modCtrl = 0;
- modShift = 0;
- if (docOnblur != null) docOnblur();
- };
+ return sendKeyReleased(lastKeyDownChar);
- var keyPressedHandler = null;
- var keyReleasedHandler = null;
+ };
- this.setKeyPressedHandler = function(kh) { keyPressedHandler = kh; };
- this.setKeyReleasedHandler = function(kh) { keyReleasedHandler = kh; };
+ // When focus is lost, clear modifiers.
+ element.onblur = function() {
+ modAlt = false;
+ modCtrl = false;
+ modShift = false;
+ };
-}
+};