Relicensed as Mozilla/LGPL/GPL.
[guacamole-common-js.git] / src / main / resources / keyboard.js
1
2 /* ***** BEGIN LICENSE BLOCK *****
3  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4  *
5  * The contents of this file are subject to the Mozilla Public License Version
6  * 1.1 (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  * http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the
13  * License.
14  *
15  * The Original Code is guacamole-common-js.
16  *
17  * The Initial Developer of the Original Code is
18  * Michael Jumper.
19  * Portions created by the Initial Developer are Copyright (C) 2010
20  * the Initial Developer. All Rights Reserved.
21  *
22  * Contributor(s):
23  *
24  * Alternatively, the contents of this file may be used under the terms of
25  * either the GNU General Public License Version 2 or later (the "GPL"), or
26  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27  * in which case the provisions of the GPL or the LGPL are applicable instead
28  * of those above. If you wish to allow use of your version of this file only
29  * under the terms of either the GPL or the LGPL, and not to allow others to
30  * use your version of this file under the terms of the MPL, indicate your
31  * decision by deleting the provisions above and replace them with the notice
32  * and other provisions required by the GPL or the LGPL. If you do not delete
33  * the provisions above, a recipient may use your version of this file under
34  * the terms of any one of the MPL, the GPL or the LGPL.
35  *
36  * ***** END LICENSE BLOCK ***** */
37
38 // Guacamole namespace
39 var Guacamole = Guacamole || {};
40
41 /**
42  * Provides cross-browser and cross-keyboard keyboard for a specific element.
43  * Browser and keyboard layout variation is abstracted away, providing events
44  * which represent keys as their corresponding X11 keysym.
45  * 
46  * @constructor
47  * @param {Element} element The Element to use to provide keyboard events.
48  */
49 Guacamole.Keyboard = function(element) {
50
51     /**
52      * Reference to this Guacamole.Keyboard.
53      * @private
54      */
55     var guac_keyboard = this;
56
57     /**
58      * Fired whenever the user presses a key with the element associated
59      * with this Guacamole.Keyboard in focus.
60      * 
61      * @event
62      * @param {Number} keysym The keysym of the key being pressed.
63      */
64         this.onkeydown = null;
65
66     /**
67      * Fired whenever the user releases a key with the element associated
68      * with this Guacamole.Keyboard in focus.
69      * 
70      * @event
71      * @param {Number} keysym The keysym of the key being released.
72      */
73         this.onkeyup = null;
74
75     /**
76      * Map of known JavaScript keycodes which do not map to typable characters
77      * to their unshifted X11 keysym equivalents.
78      * @private
79      */
80     var unshiftedKeySym = {
81         8:   0xFF08, // backspace
82         9:   0xFF09, // tab
83         13:  0xFF0D, // enter
84         16:  0xFFE1, // shift
85         17:  0xFFE3, // ctrl
86         18:  0xFFE9, // alt
87         19:  0xFF13, // pause/break
88         20:  0xFFE5, // caps lock
89         27:  0xFF1B, // escape
90         33:  0xFF55, // page up
91         34:  0xFF56, // page down
92         35:  0xFF57, // end
93         36:  0xFF50, // home
94         37:  0xFF51, // left arrow
95         38:  0xFF52, // up arrow
96         39:  0xFF53, // right arrow
97         40:  0xFF54, // down arrow
98         45:  0xFF63, // insert
99         46:  0xFFFF, // delete
100         91:  0xFFEB, // left window key (super_l)
101         92:  0xFF67, // right window key (menu key?)
102         93:  null,   // select key
103         112: 0xFFBE, // f1
104         113: 0xFFBF, // f2
105         114: 0xFFC0, // f3
106         115: 0xFFC1, // f4
107         116: 0xFFC2, // f5
108         117: 0xFFC3, // f6
109         118: 0xFFC4, // f7
110         119: 0xFFC5, // f8
111         120: 0xFFC6, // f9
112         121: 0xFFC7, // f10
113         122: 0xFFC8, // f11
114         123: 0xFFC9, // f12
115         144: 0xFF7F, // num lock
116         145: 0xFF14  // scroll lock
117     };
118
119     /**
120      * Map of known JavaScript keycodes which do not map to typable characters
121      * to their shifted X11 keysym equivalents. Keycodes must only be listed
122      * here if their shifted X11 keysym equivalents differ from their unshifted
123      * equivalents.
124      * @private
125      */
126     var shiftedKeySym = {
127         18:  0xFFE7  // alt
128     };
129
130         // Single key state/modifier buffer
131         var modShift = false;
132         var modCtrl = false;
133         var modAlt = false;
134
135     var keydownChar = new Array();
136
137     // ID of routine repeating keystrokes. -1 = not repeating.
138     var repeatKeyTimeoutId = -1;
139     var repeatKeyIntervalId = -1;
140
141         // Starts repeating keystrokes
142         function startRepeat(keySym) {
143                 repeatKeyIntervalId = setInterval(function() {
144             sendKeyReleased(keySym);
145             sendKeyPressed(keySym);
146         }, 50);
147         }
148
149         // Stops repeating keystrokes
150         function stopRepeat() {
151                 if (repeatKeyTimeoutId != -1) clearInterval(repeatKeyTimeoutId);
152                 if (repeatKeyIntervalId != -1) clearInterval(repeatKeyIntervalId);
153         }
154
155
156     function getKeySymFromKeyIdentifier(shifted, keyIdentifier) {
157
158         var unicodePrefixLocation = keyIdentifier.indexOf("U+");
159         if (unicodePrefixLocation >= 0) {
160
161             var hex = keyIdentifier.substring(unicodePrefixLocation+2);
162             var codepoint = parseInt(hex, 16);
163             var typedCharacter;
164
165             // Convert case if shifted
166             if (shifted == 0)
167                 typedCharacter = String.fromCharCode(codepoint).toLowerCase();
168             else
169                 typedCharacter = String.fromCharCode(codepoint).toUpperCase();
170
171             // Get codepoint
172             codepoint = typedCharacter.charCodeAt(0);
173
174             return getKeySymFromCharCode(codepoint);
175
176         }
177
178         return null;
179
180     }
181
182     function getKeySymFromCharCode(keyCode) {
183
184         if (keyCode >= 0x0000 && keyCode <= 0x00FF)
185             return keyCode;
186
187         if (keyCode >= 0x0100 && keyCode <= 0x10FFFF)
188             return 0x01000000 | keyCode;
189
190         return null;
191
192     }
193
194     function getKeySymFromKeyCode(keyCode) {
195
196         var keysym = null;
197                 if (!modShift) keysym = unshiftedKeySym[keyCode];
198                 else {
199             keysym = shiftedKeySym[keyCode];
200             if (keysym == null) keysym = unshiftedKeySym[keyCode];
201         }
202
203         return keysym;
204
205     }
206
207
208         // Sends a single keystroke over the network
209         function sendKeyPressed(keysym) {
210                 if (keysym != null && guac_keyboard.onkeydown)
211                         guac_keyboard.onkeydown(keysym);
212         }
213
214         // Sends a single keystroke over the network
215         function sendKeyReleased(keysym) {
216                 if (keysym != null && guac_keyboard.onkeyup)
217                         guac_keyboard.onkeyup(keysym);
218         }
219
220
221     var KEYDOWN = 1;
222     var KEYPRESS = 2;
223
224     var keySymSource = null;
225
226         // When key pressed
227     var keydownCode = null;
228         element.onkeydown = function(e) {
229
230         // Only intercept if handler set
231         if (!guac_keyboard.onkeydown) return true;
232
233                 var keynum;
234                 if (window.event) keynum = window.event.keyCode;
235                 else if (e.which) keynum = e.which;
236
237                 // Ctrl/Alt/Shift
238                 if (keynum == 16)
239                         modShift = true;
240                 else if (keynum == 17)
241                         modCtrl = true;
242                 else if (keynum == 18)
243                         modAlt = true;
244
245         var keysym = getKeySymFromKeyCode(keynum);
246         if (keysym) {
247             // Get keysyms and events from KEYDOWN
248             keySymSource = KEYDOWN;
249         }
250
251         // If modifier keys are held down, and we have keyIdentifier
252         else if ((modCtrl || modAlt) && e.keyIdentifier) {
253
254             // Get keysym from keyIdentifier
255             keysym = getKeySymFromKeyIdentifier(modShift, e.keyIdentifier);
256
257             // Get keysyms and events from KEYDOWN
258             keySymSource = KEYDOWN;
259
260         }
261
262         else
263             // Get keysyms and events from KEYPRESS
264             keySymSource = KEYPRESS;
265
266         keydownCode = keynum;
267
268         // Ignore key if we don't need to use KEYPRESS.
269         // Send key event here
270         if (keySymSource == KEYDOWN) {
271
272             if (keydownChar[keynum] != keysym) {
273
274                 // Send event
275                 keydownChar[keynum] = keysym;
276                 sendKeyPressed(keysym);
277
278                 // Clear old key repeat, if any.
279                 stopRepeat();
280
281                 // Start repeating (if not a modifier key) after a short delay
282                 if (keynum != 16 && keynum != 17 && keynum != 18)
283                     repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);
284             }
285
286             return false;
287         }
288
289         return true;
290
291         };
292
293         // When key pressed
294     element.onkeypress = function(e) {
295
296         // Only intercept if handler set
297         if (!guac_keyboard.onkeydown) return true;
298
299         if (keySymSource != KEYPRESS) return false;
300
301                 var keynum;
302                 if (window.event) keynum = window.event.keyCode;
303                 else if (e.which) keynum = e.which;
304
305         var keysym = getKeySymFromCharCode(keynum);
306         if (keysym && keydownChar[keynum] != keysym) {
307
308             // If this button already pressed, release first
309             var lastKeyDownChar = keydownChar[keydownCode];
310             if (lastKeyDownChar)
311                 sendKeyReleased(lastKeyDownChar);
312
313             keydownChar[keydownCode] = keysym;
314
315             // Clear old key repeat, if any.
316             stopRepeat();
317
318             // Send key event
319             sendKeyPressed(keysym);
320
321             // Start repeating (if not a modifier key) after a short delay
322             repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);
323         }
324
325         return false;
326         };
327
328         // When key released
329         element.onkeyup = function(e) {
330
331         // Only intercept if handler set
332         if (!guac_keyboard.onkeyup) return true;
333
334                 var keynum;
335                 if (window.event) keynum = window.event.keyCode;
336                 else if (e.which) keynum = e.which;
337                 
338                 // Ctrl/Alt/Shift
339                 if (keynum == 16)
340                         modShift = false;
341                 else if (keynum == 17)
342                         modCtrl = false;
343                 else if (keynum == 18)
344                         modAlt = false;
345         else
346             stopRepeat();
347
348         // Get corresponding character
349         var lastKeyDownChar = keydownChar[keynum];
350
351         // Clear character record
352         keydownChar[keynum] = null;
353
354         // Send release event
355         sendKeyReleased(lastKeyDownChar);
356
357                 return false;
358         };
359
360         // When focus is lost, clear modifiers.
361         element.onblur = function() {
362                 modAlt = false;
363                 modCtrl = false;
364                 modShift = false;
365         };
366
367 };