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 function GuacamoleKeyboard(element) {
22 var guac_keyboard = this;
26 var unshiftedKeySym = new Array();
27 unshiftedKeySym[8] = 0xFF08; // backspace
28 unshiftedKeySym[9] = 0xFF09; // tab
29 unshiftedKeySym[13] = 0xFF0D; // enter
30 unshiftedKeySym[16] = 0xFFE1; // shift
31 unshiftedKeySym[17] = 0xFFE3; // ctrl
32 unshiftedKeySym[18] = 0xFFE9; // alt
33 unshiftedKeySym[19] = 0xFF13; // pause/break
34 unshiftedKeySym[20] = 0xFFE5; // caps lock
35 unshiftedKeySym[27] = 0xFF1B; // escape
36 unshiftedKeySym[33] = 0xFF55; // page up
37 unshiftedKeySym[34] = 0xFF56; // page down
38 unshiftedKeySym[35] = 0xFF57; // end
39 unshiftedKeySym[36] = 0xFF50; // home
40 unshiftedKeySym[37] = 0xFF51; // left arrow
41 unshiftedKeySym[38] = 0xFF52; // up arrow
42 unshiftedKeySym[39] = 0xFF53; // right arrow
43 unshiftedKeySym[40] = 0xFF54; // down arrow
44 unshiftedKeySym[45] = 0xFF63; // insert
45 unshiftedKeySym[46] = 0xFFFF; // delete
46 unshiftedKeySym[91] = 0xFFEB; // left window key (super_l)
47 unshiftedKeySym[92] = 0xFF67; // right window key (menu key?)
48 unshiftedKeySym[93] = null; // select key
49 unshiftedKeySym[112] = 0xFFBE; // f1
50 unshiftedKeySym[113] = 0xFFBF; // f2
51 unshiftedKeySym[114] = 0xFFC0; // f3
52 unshiftedKeySym[115] = 0xFFC1; // f4
53 unshiftedKeySym[116] = 0xFFC2; // f5
54 unshiftedKeySym[117] = 0xFFC3; // f6
55 unshiftedKeySym[118] = 0xFFC4; // f7
56 unshiftedKeySym[119] = 0xFFC5; // f8
57 unshiftedKeySym[120] = 0xFFC6; // f9
58 unshiftedKeySym[121] = 0xFFC7; // f10
59 unshiftedKeySym[122] = 0xFFC8; // f11
60 unshiftedKeySym[123] = 0xFFC9; // f12
61 unshiftedKeySym[144] = 0xFF7F; // num lock
62 unshiftedKeySym[145] = 0xFF14; // scroll lock
64 // Shifted versions, IF DIFFERENT FROM UNSHIFTED!
65 // If any of these are null, the unshifted one will be used.
66 var shiftedKeySym = new Array();
67 shiftedKeySym[18] = 0xFFE7; // alt
69 // Single key state/modifier buffer
74 var keydownChar = new Array();
77 // ID of routine repeating keystrokes. -1 = not repeating.
78 var repeatKeyTimeoutId = -1;
79 var repeatKeyIntervalId = -1;
81 // Starts repeating keystrokes
82 function startRepeat(keySym) {
83 repeatKeyIntervalId = setInterval(function() {
84 sendKeyReleased(keySym);
85 sendKeyPressed(keySym);
89 // Stops repeating keystrokes
90 function stopRepeat() {
91 if (repeatKeyTimeoutId != -1) clearInterval(repeatKeyTimeoutId);
92 if (repeatKeyIntervalId != -1) clearInterval(repeatKeyIntervalId);
96 function getKeySymFromKeyIdentifier(shifted, keyIdentifier) {
98 var unicodePrefixLocation = keyIdentifier.indexOf("U+");
99 if (unicodePrefixLocation >= 0) {
101 var hex = keyIdentifier.substring(unicodePrefixLocation+2);
102 var codepoint = parseInt(hex, 16);
105 // Convert case if shifted
107 typedCharacter = String.fromCharCode(codepoint).toLowerCase();
109 typedCharacter = String.fromCharCode(codepoint).toUpperCase();
112 codepoint = typedCharacter.charCodeAt(0);
114 return getKeySymFromCharCode(codepoint);
122 function getKeySymFromCharCode(keyCode) {
124 if (keyCode >= 0x0000 && keyCode <= 0x00FF)
127 if (keyCode >= 0x0100 && keyCode <= 0x10FFFF)
128 return 0x01000000 | keyCode;
134 function getKeySymFromKeyCode(keyCode) {
137 if (modShift == 0) keysym = unshiftedKeySym[keyCode];
139 keysym = shiftedKeySym[keyCode];
140 if (keysym == null) keysym = unshiftedKeySym[keyCode];
148 // Sends a single keystroke over the network
149 function sendKeyPressed(keysym) {
150 if (keysym != null && guac_keyboard.onkeydown)
151 guac_keyboard.onkeydown(keysym);
154 // Sends a single keystroke over the network
155 function sendKeyReleased(keysym) {
156 if (keysym != null && guac_keyboard.onkeyup)
157 guac_keyboard.onkeyup(keysym);
164 var keySymSource = null;
167 var keydownCode = null;
168 element.onkeydown = function(e) {
170 // Only intercept if handler set
171 if (!guac_keyboard.onkeydown) return true;
174 if (window.event) keynum = window.event.keyCode;
175 else if (e.which) keynum = e.which;
180 else if (keynum == 17)
182 else if (keynum == 18)
185 var keysym = getKeySymFromKeyCode(keynum);
187 // Get keysyms and events from KEYDOWN
188 keySymSource = KEYDOWN;
191 // If modifier keys are held down, and we have keyIdentifier
192 else if ((modCtrl == 1 || modAlt == 1) && e.keyIdentifier) {
194 // Get keysym from keyIdentifier
195 keysym = getKeySymFromKeyIdentifier(modShift, e.keyIdentifier);
197 // Get keysyms and events from KEYDOWN
198 keySymSource = KEYDOWN;
203 // Get keysyms and events from KEYPRESS
204 keySymSource = KEYPRESS;
206 keydownCode = keynum;
208 // Ignore key if we don't need to use KEYPRESS.
209 // Send key event here
210 if (keySymSource == KEYDOWN) {
212 if (keydownChar[keynum] != keysym) {
215 keydownChar[keynum] = keysym;
216 sendKeyPressed(keysym);
218 // Clear old key repeat, if any.
221 // Start repeating (if not a modifier key) after a short delay
222 if (keynum != 16 && keynum != 17 && keynum != 18)
223 repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);
234 element.onkeypress = function(e) {
236 // Only intercept if handler set
237 if (!guac_keyboard.onkeydown) return true;
239 if (keySymSource != KEYPRESS) return false;
242 if (window.event) keynum = window.event.keyCode;
243 else if (e.which) keynum = e.which;
245 var keysym = getKeySymFromCharCode(keynum);
246 if (keysym && keydownChar[keynum] != keysym) {
248 // If this button already pressed, release first
249 var lastKeyDownChar = keydownChar[keydownCode];
251 sendKeyReleased(lastKeyDownChar);
253 keydownChar[keydownCode] = keysym;
255 // Clear old key repeat, if any.
259 sendKeyPressed(keysym);
261 // Start repeating (if not a modifier key) after a short delay
262 repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);
269 element.onkeyup = function(e) {
271 // Only intercept if handler set
272 if (!guac_keyboard.onkeyup) return true;
275 if (window.event) keynum = window.event.keyCode;
276 else if (e.which) keynum = e.which;
281 else if (keynum == 17)
283 else if (keynum == 18)
288 // Get corresponding character
289 var lastKeyDownChar = keydownChar[keynum];
291 // Clear character record
292 keydownChar[keynum] = null;
294 // Send release event
295 sendKeyReleased(lastKeyDownChar);
300 // When focus is lost, clear modifiers.
301 var docOnblur = element.onblur;
302 element.onblur = function() {
306 if (docOnblur != null) docOnblur();
309 guac_keyboard.onkeydown = null;
310 guac_keyboard.onkeyup = null;