Fixed regression with key repeat.
[guacamole-common-js.git] / src / main / resources / keyboard.js
index e798f59..07e4dcf 100644 (file)
 
-/*
- *  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.
+ */
 Guacamole.Keyboard = function(element) {
 
+    /**
+     * Reference to this Guacamole.Keyboard.
+     * @private
+     */
     var guac_keyboard = this;
 
-    // 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
-
-       // Single key state/modifier buffer
-       var modShift = 0;
-       var modCtrl = 0;
-       var modAlt = 0;
+    /**
+     * 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) {
@@ -137,8 +200,8 @@ Guacamole.Keyboard = function(element) {
     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];
         }
@@ -148,17 +211,19 @@ Guacamole.Keyboard = function(element) {
     }
 
 
-       // Sends a single keystroke over the network
-       function sendKeyPressed(keysym) {
-               if (keysym != null && guac_keyboard.onkeydown)
-                       guac_keyboard.onkeydown(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 && guac_keyboard.onkeyup)
-                       guac_keyboard.onkeyup(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;
@@ -166,24 +231,24 @@ Guacamole.Keyboard = function(element) {
 
     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 (!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) {
@@ -192,7 +257,7 @@ Guacamole.Keyboard = function(element) {
         }
 
         // 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);
@@ -216,7 +281,7 @@ Guacamole.Keyboard = function(element) {
 
                 // Send event
                 keydownChar[keynum] = keysym;
-                sendKeyPressed(keysym);
+                var returnValue = sendKeyPressed(keysym);
 
                 // Clear old key repeat, if any.
                 stopRepeat();
@@ -224,16 +289,23 @@ Guacamole.Keyboard = function(element) {
                 // 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
@@ -241,9 +313,9 @@ Guacamole.Keyboard = function(element) {
 
         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) {
@@ -259,32 +331,37 @@ Guacamole.Keyboard = function(element) {
             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 (!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 = 0;
-               else if (keynum == 17)
-                       modCtrl = 0;
-               else if (keynum == 18)
-                       modAlt = 0;
+        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();
 
@@ -295,21 +372,15 @@ Guacamole.Keyboard = function(element) {
         keydownChar[keynum] = null;
 
         // Send release event
-        sendKeyReleased(lastKeyDownChar);
-
-               return false;
-       };
+        return sendKeyReleased(lastKeyDownChar);
 
-       // When focus is lost, clear modifiers.
-       var docOnblur = element.onblur;
-       element.onblur = function() {
-               modAlt = 0;
-               modCtrl = 0;
-               modShift = 0;
-               if (docOnblur != null) docOnblur();
-       };
+    };
 
-       guac_keyboard.onkeydown = null;
-       guac_keyboard.onkeyup = null;
+    // When focus is lost, clear modifiers.
+    element.onblur = function() {
+        modAlt = false;
+        modCtrl = false;
+        modShift = false;
+    };
 
-}
+};