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