3 * Guacamole - Clientless Remote Desktop
4 * Copyright (C) 2010 Michael Jumper
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 // Guacamole namespace
21 var Guacamole = Guacamole || {};
24 * Provides cross-browser and cross-keyboard keyboard for a specific element.
25 * Browser and keyboard layout variation is abstracted away, providing events
26 * which represent keys as their corresponding X11 keysym.
29 * @param {Element} element The Element to use to provide keyboard events.
31 Guacamole.Keyboard = function(element) {
34 * Reference to this Guacamole.Keyboard.
37 var guac_keyboard = this;
40 * Fired whenever the user presses a key with the element associated
41 * with this Guacamole.Keyboard in focus.
44 * @param {Number} keysym The keysym of the key being pressed.
46 this.onkeydown = null;
49 * Fired whenever the user releases a key with the element associated
50 * with this Guacamole.Keyboard in focus.
53 * @param {Number} keysym The keysym of the key being released.
58 * Map of known JavaScript keycodes which do not map to typable characters
59 * to their unshifted X11 keysym equivalents.
62 var unshiftedKeySym = {
63 8: 0xFF08, // backspace
69 19: 0xFF13, // pause/break
70 20: 0xFFE5, // caps lock
72 33: 0xFF55, // page up
73 34: 0xFF56, // page down
76 37: 0xFF51, // left arrow
77 38: 0xFF52, // up arrow
78 39: 0xFF53, // right arrow
79 40: 0xFF54, // down arrow
82 91: 0xFFEB, // left window key (super_l)
83 92: 0xFF67, // right window key (menu key?)
84 93: null, // select key
97 144: 0xFF7F, // num lock
98 145: 0xFF14 // scroll lock
102 * Map of known JavaScript keycodes which do not map to typable characters
103 * to their shifted X11 keysym equivalents. Keycodes must only be listed
104 * here if their shifted X11 keysym equivalents differ from their unshifted
108 var shiftedKeySym = {
112 // Single key state/modifier buffer
113 var modShift = false;
117 var keydownChar = new Array();
119 // ID of routine repeating keystrokes. -1 = not repeating.
120 var repeatKeyTimeoutId = -1;
121 var repeatKeyIntervalId = -1;
123 // Starts repeating keystrokes
124 function startRepeat(keySym) {
125 repeatKeyIntervalId = setInterval(function() {
126 sendKeyReleased(keySym);
127 sendKeyPressed(keySym);
131 // Stops repeating keystrokes
132 function stopRepeat() {
133 if (repeatKeyTimeoutId != -1) clearInterval(repeatKeyTimeoutId);
134 if (repeatKeyIntervalId != -1) clearInterval(repeatKeyIntervalId);
138 function getKeySymFromKeyIdentifier(shifted, keyIdentifier) {
140 var unicodePrefixLocation = keyIdentifier.indexOf("U+");
141 if (unicodePrefixLocation >= 0) {
143 var hex = keyIdentifier.substring(unicodePrefixLocation+2);
144 var codepoint = parseInt(hex, 16);
147 // Convert case if shifted
149 typedCharacter = String.fromCharCode(codepoint).toLowerCase();
151 typedCharacter = String.fromCharCode(codepoint).toUpperCase();
154 codepoint = typedCharacter.charCodeAt(0);
156 return getKeySymFromCharCode(codepoint);
164 function getKeySymFromCharCode(keyCode) {
166 if (keyCode >= 0x0000 && keyCode <= 0x00FF)
169 if (keyCode >= 0x0100 && keyCode <= 0x10FFFF)
170 return 0x01000000 | keyCode;
176 function getKeySymFromKeyCode(keyCode) {
179 if (!modShift) keysym = unshiftedKeySym[keyCode];
181 keysym = shiftedKeySym[keyCode];
182 if (keysym == null) keysym = unshiftedKeySym[keyCode];
190 // Sends a single keystroke over the network
191 function sendKeyPressed(keysym) {
192 if (keysym != null && guac_keyboard.onkeydown)
193 guac_keyboard.onkeydown(keysym);
196 // Sends a single keystroke over the network
197 function sendKeyReleased(keysym) {
198 if (keysym != null && guac_keyboard.onkeyup)
199 guac_keyboard.onkeyup(keysym);
206 var keySymSource = null;
209 var keydownCode = null;
210 element.onkeydown = function(e) {
212 // Only intercept if handler set
213 if (!guac_keyboard.onkeydown) return true;
216 if (window.event) keynum = window.event.keyCode;
217 else if (e.which) keynum = e.which;
222 else if (keynum == 17)
224 else if (keynum == 18)
227 var keysym = getKeySymFromKeyCode(keynum);
229 // Get keysyms and events from KEYDOWN
230 keySymSource = KEYDOWN;
233 // If modifier keys are held down, and we have keyIdentifier
234 else if ((modCtrl || modAlt) && e.keyIdentifier) {
236 // Get keysym from keyIdentifier
237 keysym = getKeySymFromKeyIdentifier(modShift, e.keyIdentifier);
239 // Get keysyms and events from KEYDOWN
240 keySymSource = KEYDOWN;
245 // Get keysyms and events from KEYPRESS
246 keySymSource = KEYPRESS;
248 keydownCode = keynum;
250 // Ignore key if we don't need to use KEYPRESS.
251 // Send key event here
252 if (keySymSource == KEYDOWN) {
254 if (keydownChar[keynum] != keysym) {
257 keydownChar[keynum] = keysym;
258 sendKeyPressed(keysym);
260 // Clear old key repeat, if any.
263 // Start repeating (if not a modifier key) after a short delay
264 if (keynum != 16 && keynum != 17 && keynum != 18)
265 repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);
276 element.onkeypress = function(e) {
278 // Only intercept if handler set
279 if (!guac_keyboard.onkeydown) return true;
281 if (keySymSource != KEYPRESS) return false;
284 if (window.event) keynum = window.event.keyCode;
285 else if (e.which) keynum = e.which;
287 var keysym = getKeySymFromCharCode(keynum);
288 if (keysym && keydownChar[keynum] != keysym) {
290 // If this button already pressed, release first
291 var lastKeyDownChar = keydownChar[keydownCode];
293 sendKeyReleased(lastKeyDownChar);
295 keydownChar[keydownCode] = keysym;
297 // Clear old key repeat, if any.
301 sendKeyPressed(keysym);
303 // Start repeating (if not a modifier key) after a short delay
304 repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);
311 element.onkeyup = function(e) {
313 // Only intercept if handler set
314 if (!guac_keyboard.onkeyup) return true;
317 if (window.event) keynum = window.event.keyCode;
318 else if (e.which) keynum = e.which;
323 else if (keynum == 17)
325 else if (keynum == 18)
330 // Get corresponding character
331 var lastKeyDownChar = keydownChar[keynum];
333 // Clear character record
334 keydownChar[keynum] = null;
336 // Send release event
337 sendKeyReleased(lastKeyDownChar);
342 // When focus is lost, clear modifiers.
343 element.onblur = function() {