Some JSDoc for keyboard, cleaned up keymap.
[guacamole-common-js.git] / src / main / resources / keyboard.js
1
2 /*
3  *  Guacamole - Clientless Remote Desktop
4  *  Copyright (C) 2010  Michael Jumper
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 // Guacamole namespace
21 var Guacamole = Guacamole || {};
22
23 /**
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.
27  * 
28  * @constructor
29  * @param {Element} element The Element to use to provide keyboard events.
30  */
31 Guacamole.Keyboard = function(element) {
32
33     /**
34      * Reference to this Guacamole.Keyboard.
35      * @private
36      */
37     var guac_keyboard = this;
38
39     /**
40      * Map of known JavaScript keycodes which do not map to typable characters
41      * to their unshifted X11 keysym equivalents.
42      * @private
43      */
44     var unshiftedKeySym = {
45         8:   0xFF08, // backspace
46         9:   0xFF09, // tab
47         13:  0xFF0D, // enter
48         16:  0xFFE1, // shift
49         17:  0xFFE3, // ctrl
50         18:  0xFFE9, // alt
51         19:  0xFF13, // pause/break
52         20:  0xFFE5, // caps lock
53         27:  0xFF1B, // escape
54         33:  0xFF55, // page up
55         34:  0xFF56, // page down
56         35:  0xFF57, // end
57         36:  0xFF50, // home
58         37:  0xFF51, // left arrow
59         38:  0xFF52, // up arrow
60         39:  0xFF53, // right arrow
61         40:  0xFF54, // down arrow
62         45:  0xFF63, // insert
63         46:  0xFFFF, // delete
64         91:  0xFFEB, // left window key (super_l)
65         92:  0xFF67, // right window key (menu key?)
66         93:  null,   // select key
67         112: 0xFFBE, // f1
68         113: 0xFFBF, // f2
69         114: 0xFFC0, // f3
70         115: 0xFFC1, // f4
71         116: 0xFFC2, // f5
72         117: 0xFFC3, // f6
73         118: 0xFFC4, // f7
74         119: 0xFFC5, // f8
75         120: 0xFFC6, // f9
76         121: 0xFFC7, // f10
77         122: 0xFFC8, // f11
78         123: 0xFFC9, // f12
79         144: 0xFF7F, // num lock
80         145: 0xFF14  // scroll lock
81     };
82
83     /**
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
87      * equivalents.
88      * @private
89      */
90     var shiftedKeySym = {
91         18:  0xFFE7  // alt
92     };
93
94         // Single key state/modifier buffer
95         var modShift = 0;
96         var modCtrl = 0;
97         var modAlt = 0;
98
99     var keydownChar = new Array();
100
101     // ID of routine repeating keystrokes. -1 = not repeating.
102     var repeatKeyTimeoutId = -1;
103     var repeatKeyIntervalId = -1;
104
105         // Starts repeating keystrokes
106         function startRepeat(keySym) {
107                 repeatKeyIntervalId = setInterval(function() {
108             sendKeyReleased(keySym);
109             sendKeyPressed(keySym);
110         }, 50);
111         }
112
113         // Stops repeating keystrokes
114         function stopRepeat() {
115                 if (repeatKeyTimeoutId != -1) clearInterval(repeatKeyTimeoutId);
116                 if (repeatKeyIntervalId != -1) clearInterval(repeatKeyIntervalId);
117         }
118
119
120     function getKeySymFromKeyIdentifier(shifted, keyIdentifier) {
121
122         var unicodePrefixLocation = keyIdentifier.indexOf("U+");
123         if (unicodePrefixLocation >= 0) {
124
125             var hex = keyIdentifier.substring(unicodePrefixLocation+2);
126             var codepoint = parseInt(hex, 16);
127             var typedCharacter;
128
129             // Convert case if shifted
130             if (shifted == 0)
131                 typedCharacter = String.fromCharCode(codepoint).toLowerCase();
132             else
133                 typedCharacter = String.fromCharCode(codepoint).toUpperCase();
134
135             // Get codepoint
136             codepoint = typedCharacter.charCodeAt(0);
137
138             return getKeySymFromCharCode(codepoint);
139
140         }
141
142         return null;
143
144     }
145
146     function getKeySymFromCharCode(keyCode) {
147
148         if (keyCode >= 0x0000 && keyCode <= 0x00FF)
149             return keyCode;
150
151         if (keyCode >= 0x0100 && keyCode <= 0x10FFFF)
152             return 0x01000000 | keyCode;
153
154         return null;
155
156     }
157
158     function getKeySymFromKeyCode(keyCode) {
159
160         var keysym = null;
161                 if (modShift == 0) keysym = unshiftedKeySym[keyCode];
162                 else {
163             keysym = shiftedKeySym[keyCode];
164             if (keysym == null) keysym = unshiftedKeySym[keyCode];
165         }
166
167         return keysym;
168
169     }
170
171
172         // Sends a single keystroke over the network
173         function sendKeyPressed(keysym) {
174                 if (keysym != null && guac_keyboard.onkeydown)
175                         guac_keyboard.onkeydown(keysym);
176         }
177
178         // Sends a single keystroke over the network
179         function sendKeyReleased(keysym) {
180                 if (keysym != null && guac_keyboard.onkeyup)
181                         guac_keyboard.onkeyup(keysym);
182         }
183
184
185     var KEYDOWN = 1;
186     var KEYPRESS = 2;
187
188     var keySymSource = null;
189
190         // When key pressed
191     var keydownCode = null;
192         element.onkeydown = function(e) {
193
194         // Only intercept if handler set
195         if (!guac_keyboard.onkeydown) return true;
196
197                 var keynum;
198                 if (window.event) keynum = window.event.keyCode;
199                 else if (e.which) keynum = e.which;
200
201                 // Ctrl/Alt/Shift
202                 if (keynum == 16)
203                         modShift = 1;
204                 else if (keynum == 17)
205                         modCtrl = 1;
206                 else if (keynum == 18)
207                         modAlt = 1;
208
209         var keysym = getKeySymFromKeyCode(keynum);
210         if (keysym) {
211             // Get keysyms and events from KEYDOWN
212             keySymSource = KEYDOWN;
213         }
214
215         // If modifier keys are held down, and we have keyIdentifier
216         else if ((modCtrl == 1 || modAlt == 1) && e.keyIdentifier) {
217
218             // Get keysym from keyIdentifier
219             keysym = getKeySymFromKeyIdentifier(modShift, e.keyIdentifier);
220
221             // Get keysyms and events from KEYDOWN
222             keySymSource = KEYDOWN;
223
224         }
225
226         else
227             // Get keysyms and events from KEYPRESS
228             keySymSource = KEYPRESS;
229
230         keydownCode = keynum;
231
232         // Ignore key if we don't need to use KEYPRESS.
233         // Send key event here
234         if (keySymSource == KEYDOWN) {
235
236             if (keydownChar[keynum] != keysym) {
237
238                 // Send event
239                 keydownChar[keynum] = keysym;
240                 sendKeyPressed(keysym);
241
242                 // Clear old key repeat, if any.
243                 stopRepeat();
244
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);
248             }
249
250             return false;
251         }
252
253         return true;
254
255         };
256
257         // When key pressed
258     element.onkeypress = function(e) {
259
260         // Only intercept if handler set
261         if (!guac_keyboard.onkeydown) return true;
262
263         if (keySymSource != KEYPRESS) return false;
264
265                 var keynum;
266                 if (window.event) keynum = window.event.keyCode;
267                 else if (e.which) keynum = e.which;
268
269         var keysym = getKeySymFromCharCode(keynum);
270         if (keysym && keydownChar[keynum] != keysym) {
271
272             // If this button already pressed, release first
273             var lastKeyDownChar = keydownChar[keydownCode];
274             if (lastKeyDownChar)
275                 sendKeyReleased(lastKeyDownChar);
276
277             keydownChar[keydownCode] = keysym;
278
279             // Clear old key repeat, if any.
280             stopRepeat();
281
282             // Send key event
283             sendKeyPressed(keysym);
284
285             // Start repeating (if not a modifier key) after a short delay
286             repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);
287         }
288
289         return false;
290         };
291
292         // When key released
293         element.onkeyup = function(e) {
294
295         // Only intercept if handler set
296         if (!guac_keyboard.onkeyup) return true;
297
298                 var keynum;
299                 if (window.event) keynum = window.event.keyCode;
300                 else if (e.which) keynum = e.which;
301                 
302                 // Ctrl/Alt/Shift
303                 if (keynum == 16)
304                         modShift = 0;
305                 else if (keynum == 17)
306                         modCtrl = 0;
307                 else if (keynum == 18)
308                         modAlt = 0;
309         else
310             stopRepeat();
311
312         // Get corresponding character
313         var lastKeyDownChar = keydownChar[keynum];
314
315         // Clear character record
316         keydownChar[keynum] = null;
317
318         // Send release event
319         sendKeyReleased(lastKeyDownChar);
320
321                 return false;
322         };
323
324         // When focus is lost, clear modifiers.
325         var docOnblur = element.onblur;
326         element.onblur = function() {
327                 modAlt = 0;
328                 modCtrl = 0;
329                 modShift = 0;
330                 if (docOnblur != null) docOnblur();
331         };
332
333         guac_keyboard.onkeydown = null;
334         guac_keyboard.onkeyup = null;
335
336 }