More migration to traditional JS 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 function GuacamoleKeyboard(element) {
21
22     var guac_keyboard = this;
23
24     // Keymap
25
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
63
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
68
69         // Single key state/modifier buffer
70         var modShift = 0;
71         var modCtrl = 0;
72         var modAlt = 0;
73
74     var keydownChar = new Array();
75
76
77     // ID of routine repeating keystrokes. -1 = not repeating.
78     var repeatKeyTimeoutId = -1;
79     var repeatKeyIntervalId = -1;
80
81         // Starts repeating keystrokes
82         function startRepeat(keySym) {
83                 repeatKeyIntervalId = setInterval(function() {
84             sendKeyReleased(keySym);
85             sendKeyPressed(keySym);
86         }, 50);
87         }
88
89         // Stops repeating keystrokes
90         function stopRepeat() {
91                 if (repeatKeyTimeoutId != -1) clearInterval(repeatKeyTimeoutId);
92                 if (repeatKeyIntervalId != -1) clearInterval(repeatKeyIntervalId);
93         }
94
95
96     function getKeySymFromKeyIdentifier(shifted, keyIdentifier) {
97
98         var unicodePrefixLocation = keyIdentifier.indexOf("U+");
99         if (unicodePrefixLocation >= 0) {
100
101             var hex = keyIdentifier.substring(unicodePrefixLocation+2);
102             var codepoint = parseInt(hex, 16);
103             var typedCharacter;
104
105             // Convert case if shifted
106             if (shifted == 0)
107                 typedCharacter = String.fromCharCode(codepoint).toLowerCase();
108             else
109                 typedCharacter = String.fromCharCode(codepoint).toUpperCase();
110
111             // Get codepoint
112             codepoint = typedCharacter.charCodeAt(0);
113
114             return getKeySymFromCharCode(codepoint);
115
116         }
117
118         return null;
119
120     }
121
122     function getKeySymFromCharCode(keyCode) {
123
124         if (keyCode >= 0x0000 && keyCode <= 0x00FF)
125             return keyCode;
126
127         if (keyCode >= 0x0100 && keyCode <= 0x10FFFF)
128             return 0x01000000 | keyCode;
129
130         return null;
131
132     }
133
134     function getKeySymFromKeyCode(keyCode) {
135
136         var keysym = null;
137                 if (modShift == 0) keysym = unshiftedKeySym[keyCode];
138                 else {
139             keysym = shiftedKeySym[keyCode];
140             if (keysym == null) keysym = unshiftedKeySym[keyCode];
141         }
142
143         return keysym;
144
145     }
146
147
148         // Sends a single keystroke over the network
149         function sendKeyPressed(keysym) {
150                 if (keysym != null && guac_keyboard.onkeydown)
151                         guac_keyboard.onkeydown(keysym);
152         }
153
154         // Sends a single keystroke over the network
155         function sendKeyReleased(keysym) {
156                 if (keysym != null && guac_keyboard.onkeyup)
157                         guac_keyboard.onkeyup(keysym);
158         }
159
160
161     var KEYDOWN = 1;
162     var KEYPRESS = 2;
163
164     var keySymSource = null;
165
166         // When key pressed
167     var keydownCode = null;
168         element.onkeydown = function(e) {
169
170         // Only intercept if handler set
171         if (!guac_keyboard.onkeydown) return true;
172
173                 var keynum;
174                 if (window.event) keynum = window.event.keyCode;
175                 else if (e.which) keynum = e.which;
176
177                 // Ctrl/Alt/Shift
178                 if (keynum == 16)
179                         modShift = 1;
180                 else if (keynum == 17)
181                         modCtrl = 1;
182                 else if (keynum == 18)
183                         modAlt = 1;
184
185         var keysym = getKeySymFromKeyCode(keynum);
186         if (keysym) {
187             // Get keysyms and events from KEYDOWN
188             keySymSource = KEYDOWN;
189         }
190
191         // If modifier keys are held down, and we have keyIdentifier
192         else if ((modCtrl == 1 || modAlt == 1) && e.keyIdentifier) {
193
194             // Get keysym from keyIdentifier
195             keysym = getKeySymFromKeyIdentifier(modShift, e.keyIdentifier);
196
197             // Get keysyms and events from KEYDOWN
198             keySymSource = KEYDOWN;
199
200         }
201
202         else
203             // Get keysyms and events from KEYPRESS
204             keySymSource = KEYPRESS;
205
206         keydownCode = keynum;
207
208         // Ignore key if we don't need to use KEYPRESS.
209         // Send key event here
210         if (keySymSource == KEYDOWN) {
211
212             if (keydownChar[keynum] != keysym) {
213
214                 // Send event
215                 keydownChar[keynum] = keysym;
216                 sendKeyPressed(keysym);
217
218                 // Clear old key repeat, if any.
219                 stopRepeat();
220
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);
224             }
225
226             return false;
227         }
228
229         return true;
230
231         };
232
233         // When key pressed
234     element.onkeypress = function(e) {
235
236         // Only intercept if handler set
237         if (!guac_keyboard.onkeydown) return true;
238
239         if (keySymSource != KEYPRESS) return false;
240
241                 var keynum;
242                 if (window.event) keynum = window.event.keyCode;
243                 else if (e.which) keynum = e.which;
244
245         var keysym = getKeySymFromCharCode(keynum);
246         if (keysym && keydownChar[keynum] != keysym) {
247
248             // If this button already pressed, release first
249             var lastKeyDownChar = keydownChar[keydownCode];
250             if (lastKeyDownChar)
251                 sendKeyReleased(lastKeyDownChar);
252
253             keydownChar[keydownCode] = keysym;
254
255             // Clear old key repeat, if any.
256             stopRepeat();
257
258             // Send key event
259             sendKeyPressed(keysym);
260
261             // Start repeating (if not a modifier key) after a short delay
262             repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);
263         }
264
265         return false;
266         };
267
268         // When key released
269         element.onkeyup = function(e) {
270
271         // Only intercept if handler set
272         if (!guac_keyboard.onkeyup) return true;
273
274                 var keynum;
275                 if (window.event) keynum = window.event.keyCode;
276                 else if (e.which) keynum = e.which;
277                 
278                 // Ctrl/Alt/Shift
279                 if (keynum == 16)
280                         modShift = 0;
281                 else if (keynum == 17)
282                         modCtrl = 0;
283                 else if (keynum == 18)
284                         modAlt = 0;
285         else
286             stopRepeat();
287
288         // Get corresponding character
289         var lastKeyDownChar = keydownChar[keynum];
290
291         // Clear character record
292         keydownChar[keynum] = null;
293
294         // Send release event
295         sendKeyReleased(lastKeyDownChar);
296
297                 return false;
298         };
299
300         // When focus is lost, clear modifiers.
301         var docOnblur = element.onblur;
302         element.onblur = function() {
303                 modAlt = 0;
304                 modCtrl = 0;
305                 modShift = 0;
306                 if (docOnblur != null) docOnblur();
307         };
308
309         guac_keyboard.onkeydown = null;
310         guac_keyboard.onkeyup = null;
311
312 }