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.
64 this.onkeydown = null;
67 * Fired whenever the user releases a key with the element associated
68 * with this Guacamole.Keyboard in focus.
71 * @param {Number} keysym The keysym of the key being released.
76 * Map of known JavaScript keycodes which do not map to typable characters
77 * to their unshifted X11 keysym equivalents.
80 var unshiftedKeySym = {
81 8: 0xFF08, // backspace
87 19: 0xFF13, // pause/break
88 20: 0xFFE5, // caps lock
90 33: 0xFF55, // page up
91 34: 0xFF56, // page down
94 37: 0xFF51, // left arrow
95 38: 0xFF52, // up arrow
96 39: 0xFF53, // right arrow
97 40: 0xFF54, // down arrow
100 91: 0xFFEB, // left window key (super_l)
101 92: 0xFF67, // right window key (menu key?)
102 93: null, // select key
115 144: 0xFF7F, // num lock
116 145: 0xFF14 // scroll lock
120 * Map of known JavaScript keycodes which do not map to typable characters
121 * to their shifted X11 keysym equivalents. Keycodes must only be listed
122 * here if their shifted X11 keysym equivalents differ from their unshifted
126 var shiftedKeySym = {
130 // Single key state/modifier buffer
131 var modShift = false;
135 var keydownChar = new Array();
137 // ID of routine repeating keystrokes. -1 = not repeating.
138 var repeatKeyTimeoutId = -1;
139 var repeatKeyIntervalId = -1;
141 // Starts repeating keystrokes
142 function startRepeat(keySym) {
143 repeatKeyIntervalId = setInterval(function() {
144 sendKeyReleased(keySym);
145 sendKeyPressed(keySym);
149 // Stops repeating keystrokes
150 function stopRepeat() {
151 if (repeatKeyTimeoutId != -1) clearInterval(repeatKeyTimeoutId);
152 if (repeatKeyIntervalId != -1) clearInterval(repeatKeyIntervalId);
156 function getKeySymFromKeyIdentifier(shifted, keyIdentifier) {
158 var unicodePrefixLocation = keyIdentifier.indexOf("U+");
159 if (unicodePrefixLocation >= 0) {
161 var hex = keyIdentifier.substring(unicodePrefixLocation+2);
162 var codepoint = parseInt(hex, 16);
165 // Convert case if shifted
167 typedCharacter = String.fromCharCode(codepoint).toLowerCase();
169 typedCharacter = String.fromCharCode(codepoint).toUpperCase();
172 codepoint = typedCharacter.charCodeAt(0);
174 return getKeySymFromCharCode(codepoint);
182 function getKeySymFromCharCode(keyCode) {
184 if (keyCode >= 0x0000 && keyCode <= 0x00FF)
187 if (keyCode >= 0x0100 && keyCode <= 0x10FFFF)
188 return 0x01000000 | keyCode;
194 function getKeySymFromKeyCode(keyCode) {
197 if (!modShift) keysym = unshiftedKeySym[keyCode];
199 keysym = shiftedKeySym[keyCode];
200 if (keysym == null) keysym = unshiftedKeySym[keyCode];
208 // Sends a single keystroke over the network
209 function sendKeyPressed(keysym) {
210 if (keysym != null && guac_keyboard.onkeydown)
211 guac_keyboard.onkeydown(keysym);
214 // Sends a single keystroke over the network
215 function sendKeyReleased(keysym) {
216 if (keysym != null && guac_keyboard.onkeyup)
217 guac_keyboard.onkeyup(keysym);
224 var keySymSource = null;
227 var keydownCode = null;
228 element.onkeydown = function(e) {
230 // Only intercept if handler set
231 if (!guac_keyboard.onkeydown) return true;
234 if (window.event) keynum = window.event.keyCode;
235 else if (e.which) keynum = e.which;
240 else if (keynum == 17)
242 else if (keynum == 18)
245 var keysym = getKeySymFromKeyCode(keynum);
247 // Get keysyms and events from KEYDOWN
248 keySymSource = KEYDOWN;
251 // If modifier keys are held down, and we have keyIdentifier
252 else if ((modCtrl || modAlt) && e.keyIdentifier) {
254 // Get keysym from keyIdentifier
255 keysym = getKeySymFromKeyIdentifier(modShift, e.keyIdentifier);
257 // Get keysyms and events from KEYDOWN
258 keySymSource = KEYDOWN;
263 // Get keysyms and events from KEYPRESS
264 keySymSource = KEYPRESS;
266 keydownCode = keynum;
268 // Ignore key if we don't need to use KEYPRESS.
269 // Send key event here
270 if (keySymSource == KEYDOWN) {
272 if (keydownChar[keynum] != keysym) {
275 keydownChar[keynum] = keysym;
276 sendKeyPressed(keysym);
278 // Clear old key repeat, if any.
281 // Start repeating (if not a modifier key) after a short delay
282 if (keynum != 16 && keynum != 17 && keynum != 18)
283 repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);
294 element.onkeypress = function(e) {
296 // Only intercept if handler set
297 if (!guac_keyboard.onkeydown) return true;
299 if (keySymSource != KEYPRESS) return false;
302 if (window.event) keynum = window.event.keyCode;
303 else if (e.which) keynum = e.which;
305 var keysym = getKeySymFromCharCode(keynum);
306 if (keysym && keydownChar[keynum] != keysym) {
308 // If this button already pressed, release first
309 var lastKeyDownChar = keydownChar[keydownCode];
311 sendKeyReleased(lastKeyDownChar);
313 keydownChar[keydownCode] = keysym;
315 // Clear old key repeat, if any.
319 sendKeyPressed(keysym);
321 // Start repeating (if not a modifier key) after a short delay
322 repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);
329 element.onkeyup = function(e) {
331 // Only intercept if handler set
332 if (!guac_keyboard.onkeyup) return true;
335 if (window.event) keynum = window.event.keyCode;
336 else if (e.which) keynum = e.which;
341 else if (keynum == 17)
343 else if (keynum == 18)
348 // Get corresponding character
349 var lastKeyDownChar = keydownChar[keynum];
351 // Clear character record
352 keydownChar[keynum] = null;
354 // Send release event
355 sendKeyReleased(lastKeyDownChar);
360 // When focus is lost, clear modifiers.
361 element.onblur = function() {