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 * Map of known JavaScript keycodes which do not map to typable characters
41 * to their unshifted X11 keysym equivalents.
44 var unshiftedKeySym = {
45 8: 0xFF08, // backspace
51 19: 0xFF13, // pause/break
52 20: 0xFFE5, // caps lock
54 33: 0xFF55, // page up
55 34: 0xFF56, // page down
58 37: 0xFF51, // left arrow
59 38: 0xFF52, // up arrow
60 39: 0xFF53, // right arrow
61 40: 0xFF54, // down arrow
64 91: 0xFFEB, // left window key (super_l)
65 92: 0xFF67, // right window key (menu key?)
66 93: null, // select key
79 144: 0xFF7F, // num lock
80 145: 0xFF14 // scroll lock
84 * Map of known JavaScript keycodes which do not map to typable characters
85 * to their shifted X11 keysym equivalents. Keycodes must only be listed
86 * here if their shifted X11 keysym equivalents differ from their unshifted
94 // Single key state/modifier buffer
99 var keydownChar = new Array();
101 // ID of routine repeating keystrokes. -1 = not repeating.
102 var repeatKeyTimeoutId = -1;
103 var repeatKeyIntervalId = -1;
105 // Starts repeating keystrokes
106 function startRepeat(keySym) {
107 repeatKeyIntervalId = setInterval(function() {
108 sendKeyReleased(keySym);
109 sendKeyPressed(keySym);
113 // Stops repeating keystrokes
114 function stopRepeat() {
115 if (repeatKeyTimeoutId != -1) clearInterval(repeatKeyTimeoutId);
116 if (repeatKeyIntervalId != -1) clearInterval(repeatKeyIntervalId);
120 function getKeySymFromKeyIdentifier(shifted, keyIdentifier) {
122 var unicodePrefixLocation = keyIdentifier.indexOf("U+");
123 if (unicodePrefixLocation >= 0) {
125 var hex = keyIdentifier.substring(unicodePrefixLocation+2);
126 var codepoint = parseInt(hex, 16);
129 // Convert case if shifted
131 typedCharacter = String.fromCharCode(codepoint).toLowerCase();
133 typedCharacter = String.fromCharCode(codepoint).toUpperCase();
136 codepoint = typedCharacter.charCodeAt(0);
138 return getKeySymFromCharCode(codepoint);
146 function getKeySymFromCharCode(keyCode) {
148 if (keyCode >= 0x0000 && keyCode <= 0x00FF)
151 if (keyCode >= 0x0100 && keyCode <= 0x10FFFF)
152 return 0x01000000 | keyCode;
158 function getKeySymFromKeyCode(keyCode) {
161 if (modShift == 0) keysym = unshiftedKeySym[keyCode];
163 keysym = shiftedKeySym[keyCode];
164 if (keysym == null) keysym = unshiftedKeySym[keyCode];
172 // Sends a single keystroke over the network
173 function sendKeyPressed(keysym) {
174 if (keysym != null && guac_keyboard.onkeydown)
175 guac_keyboard.onkeydown(keysym);
178 // Sends a single keystroke over the network
179 function sendKeyReleased(keysym) {
180 if (keysym != null && guac_keyboard.onkeyup)
181 guac_keyboard.onkeyup(keysym);
188 var keySymSource = null;
191 var keydownCode = null;
192 element.onkeydown = function(e) {
194 // Only intercept if handler set
195 if (!guac_keyboard.onkeydown) return true;
198 if (window.event) keynum = window.event.keyCode;
199 else if (e.which) keynum = e.which;
204 else if (keynum == 17)
206 else if (keynum == 18)
209 var keysym = getKeySymFromKeyCode(keynum);
211 // Get keysyms and events from KEYDOWN
212 keySymSource = KEYDOWN;
215 // If modifier keys are held down, and we have keyIdentifier
216 else if ((modCtrl == 1 || modAlt == 1) && e.keyIdentifier) {
218 // Get keysym from keyIdentifier
219 keysym = getKeySymFromKeyIdentifier(modShift, e.keyIdentifier);
221 // Get keysyms and events from KEYDOWN
222 keySymSource = KEYDOWN;
227 // Get keysyms and events from KEYPRESS
228 keySymSource = KEYPRESS;
230 keydownCode = keynum;
232 // Ignore key if we don't need to use KEYPRESS.
233 // Send key event here
234 if (keySymSource == KEYDOWN) {
236 if (keydownChar[keynum] != keysym) {
239 keydownChar[keynum] = keysym;
240 sendKeyPressed(keysym);
242 // Clear old key repeat, if any.
245 // Start repeating (if not a modifier key) after a short delay
246 if (keynum != 16 && keynum != 17 && keynum != 18)
247 repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);
258 element.onkeypress = function(e) {
260 // Only intercept if handler set
261 if (!guac_keyboard.onkeydown) return true;
263 if (keySymSource != KEYPRESS) return false;
266 if (window.event) keynum = window.event.keyCode;
267 else if (e.which) keynum = e.which;
269 var keysym = getKeySymFromCharCode(keynum);
270 if (keysym && keydownChar[keynum] != keysym) {
272 // If this button already pressed, release first
273 var lastKeyDownChar = keydownChar[keydownCode];
275 sendKeyReleased(lastKeyDownChar);
277 keydownChar[keydownCode] = keysym;
279 // Clear old key repeat, if any.
283 sendKeyPressed(keysym);
285 // Start repeating (if not a modifier key) after a short delay
286 repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);
293 element.onkeyup = function(e) {
295 // Only intercept if handler set
296 if (!guac_keyboard.onkeyup) return true;
299 if (window.event) keynum = window.event.keyCode;
300 else if (e.which) keynum = e.which;
305 else if (keynum == 17)
307 else if (keynum == 18)
312 // Get corresponding character
313 var lastKeyDownChar = keydownChar[keynum];
315 // Clear character record
316 keydownChar[keynum] = null;
318 // Send release event
319 sendKeyReleased(lastKeyDownChar);
324 // When focus is lost, clear modifiers.
325 var docOnblur = element.onblur;
326 element.onblur = function() {
330 if (docOnblur != null) docOnblur();
333 guac_keyboard.onkeydown = null;
334 guac_keyboard.onkeyup = null;