Set size in keyboard layout, migrate to new on-screen keyboard API, update keyboard...
[guacamole.git] / src / main / webapp / scripts / interface.js
1
2 // UI Definition
3 var GuacamoleUI = {
4
5     "display"     : document.getElementById("display"),
6     "menu"        : document.getElementById("menu"),
7     "menuControl" : document.getElementById("menuControl"),
8     "logo"        : document.getElementById("status-logo"),
9     "state"       : document.getElementById("state"),
10
11     "buttons": {
12
13         "showClipboard": document.getElementById("showClipboard"),
14         "showKeyboard" : document.getElementById("showKeyboard"),
15         "ctrlAltDelete": document.getElementById("ctrlAltDelete"),
16         "reconnect"    : document.getElementById("reconnect"),
17         "logout"       : document.getElementById("logout")
18
19     },
20
21     "containers": {
22         "error"    : document.getElementById("errorDialog"),
23         "clipboard": document.getElementById("clipboardDiv"),
24         "keyboard" : document.getElementById("keyboardContainer")
25     },
26     
27     "error"     : document.getElementById("errorText"),
28     "clipboard" : document.getElementById("clipboard")
29
30 };
31
32 // Constant UI initialization and behavior
33 (function() {
34
35     var menu_shaded = false;
36
37     var shade_interval = null;
38     var show_interval = null;
39
40     // Cache error image (might not be available when error occurs)
41     var guacErrorImage = new Image();
42     guacErrorImage.src = "images/noguacamole-logo-24.png";
43
44     GuacamoleUI.showError = function(error) {
45
46         GuacamoleUI.menu.className = "error";
47         GuacamoleUI.display.className += " guac-error";
48
49         GuacamoleUI.logo.src = guacErrorImage.src;
50         GuacamoleUI.error.textContent = error;
51         GuacamoleUI.containers.error.style.visibility = "visible";
52
53     };
54
55     GuacamoleUI.shadeMenu = function() {
56
57         if (!menu_shaded) {
58
59             var step = Math.floor(GuacamoleUI.menu.offsetHeight / 10) + 1;
60             var offset = 0;
61             menu_shaded = true;
62
63             window.clearInterval(show_interval);
64             shade_interval = window.setInterval(function() {
65
66                 offset -= step;
67                 GuacamoleUI.menu.style.top = offset + "px";
68
69                 if (offset <= -GuacamoleUI.menu.offsetHeight) {
70                     window.clearInterval(shade_interval);
71                     GuacamoleUI.menu.style.visiblity = "hidden";
72                 }
73
74             }, 30);
75         }
76
77     };
78
79     GuacamoleUI.showMenu = function() {
80
81         if (menu_shaded) {
82
83             var step = Math.floor(GuacamoleUI.menu.offsetHeight / 5) + 1;
84             var offset = -GuacamoleUI.menu.offsetHeight;
85             menu_shaded = false;
86             GuacamoleUI.menu.style.visiblity = "";
87
88             window.clearInterval(shade_interval);
89             show_interval = window.setInterval(function() {
90
91                 offset += step;
92
93                 if (offset >= 0) {
94                     offset = 0;
95                     window.clearInterval(show_interval);
96                 }
97
98                 GuacamoleUI.menu.style.top = offset + "px";
99
100             }, 30);
101         }
102
103     };
104
105     // Show/Hide clipboard
106     GuacamoleUI.buttons.showClipboard.onclick = function() {
107
108         var displayed = GuacamoleUI.containers.clipboard.style.display;
109         if (displayed != "block") {
110             GuacamoleUI.containers.clipboard.style.display = "block";
111             GuacamoleUI.buttons.showClipboard.innerHTML = "Hide Clipboard";
112         }
113         else {
114             GuacamoleUI.containers.clipboard.style.display = "none";
115             GuacamoleUI.buttons.showClipboard.innerHTML = "Show Clipboard";
116             GuacamoleUI.clipboard.onchange();
117         }
118
119     };
120
121     // Show/Hide keyboard
122     var keyboardResizeInterval = null;
123     GuacamoleUI.buttons.showKeyboard.onclick = function() {
124
125         var displayed = GuacamoleUI.containers.keyboard.style.display;
126         if (displayed != "block") {
127             GuacamoleUI.containers.keyboard.style.display = "block";
128             GuacamoleUI.buttons.showKeyboard.textContent = "Hide Keyboard";
129
130             // Automatically update size
131             window.onresize = updateKeyboardSize;
132             keyboardResizeInterval = window.setInterval(updateKeyboardSize, 30);
133
134             updateKeyboardSize();
135         }
136         else {
137             GuacamoleUI.containers.keyboard.style.display = "none";
138             GuacamoleUI.buttons.showKeyboard.textContent = "Show Keyboard";
139
140             window.onresize = null;
141             window.clearInterval(keyboardResizeInterval);
142         }
143
144     };
145
146     // Logout
147     GuacamoleUI.buttons.logout.onclick = function() {
148         window.location.href = "logout";
149     };
150
151     // Timeouts for detecting if users wants menu to open or close
152     var detectMenuOpenTimeout = null;
153     var detectMenuCloseTimeout = null;
154
155     // Clear detection timeouts
156     function resetMenuDetect() {
157
158         if (detectMenuOpenTimeout != null) {
159             window.clearTimeout(detectMenuOpenTimeout);
160             detectMenuOpenTimeout = null;
161         }
162
163         if (detectMenuCloseTimeout != null) {
164             window.clearTimeout(detectMenuCloseTimeout);
165             detectMenuCloseTimeout = null;
166         }
167
168     }
169
170     // Initiate detection of menu open action. If not canceled through some
171     // user event, menu will open.
172     function startMenuOpenDetect() {
173
174         // Clear detection state
175         resetMenuDetect();
176
177         // Wait and then show menu
178         detectMenuOpenTimeout = window.setTimeout(function() {
179             GuacamoleUI.showMenu();
180             detectMenuOpenTimeout = null;
181         }, 325);
182
183     }
184
185     // Initiate detection of menu close action. If not canceled through some
186     // user event, menu will close.
187     function startMenuCloseDetect() {
188
189         // Clear detection state
190         resetMenuDetect();
191
192         // Wait and then shade menu
193         detectMenuCloseTimeout = window.setTimeout(function() {
194             GuacamoleUI.shadeMenu();
195             detectMenuCloseTimeout = null;
196         }, 500);
197
198     }
199
200     // Show menu if mouseover any part of menu
201     GuacamoleUI.menu.addEventListener('mouseover', GuacamoleUI.showMenu, true);
202
203     // Stop detecting menu state change intents if mouse is over menu
204     GuacamoleUI.menu.addEventListener('mouseover', resetMenuDetect, true);
205
206     // When mouse hovers over top of screen, start detection of intent to open menu
207     GuacamoleUI.menuControl.addEventListener('mousemove', startMenuOpenDetect, true);
208
209     // When mouse enters display, start detection of intent to close menu
210     GuacamoleUI.display.addEventListener('mouseover', startMenuCloseDetect, true);
211
212     // Show menu if mouse leaves document
213     document.addEventListener('mouseout', function(e) {
214         
215         // Get parent of the element the mouse pointer is leaving
216         if (!e) e = window.event;
217         var target = e.relatedTarget || e.toElement;
218         
219         // Ensure target is not document nor child of document
220         var targetParent = target;
221         while (targetParent != null) {
222             if (targetParent == document) return;
223             targetParent = targetParent.parentNode;
224         }
225
226         // Start detection of intent to open menu
227         startMenuOpenDetect();
228  
229     }, true);
230
231     // Reconnect button
232     GuacamoleUI.buttons.reconnect.onclick = function() {
233         window.location.reload();
234     };
235
236     // On-screen keyboard
237     GuacamoleUI.keyboard = new Guacamole.OnScreenKeyboard("layouts/en-us-qwerty.xml");
238     GuacamoleUI.containers.keyboard.appendChild(GuacamoleUI.keyboard.getElement());
239
240     // Function for automatically updating keyboard size
241     var lastKeyboardWidth;
242     function updateKeyboardSize() {
243         var currentSize = GuacamoleUI.keyboard.getElement().offsetWidth;
244         if (lastKeyboardWidth != currentSize) {
245             GuacamoleUI.keyboard.resize(currentSize);
246             lastKeyboardWidth = currentSize;
247         }
248     };
249
250 })();
251
252 // Tie UI events / behavior to a specific Guacamole client
253 GuacamoleUI.attach = function(guac) {
254
255     // Mouse
256     var mouse = new Guacamole.Mouse(GuacamoleUI.display);
257     mouse.onmousedown = mouse.onmouseup = mouse.onmousemove =
258         function(mouseState) {
259             guac.sendMouseState(mouseState);
260         };
261
262     // Keyboard
263     var keyboard = new Guacamole.Keyboard(document);
264
265     function disableKeyboard() {
266         keyboard.onkeydown = null;
267         keyboard.onkeyup = null;
268     }
269
270     function enableKeyboard() {
271         keyboard.onkeydown = 
272             function (keysym) {
273                 guac.sendKeyEvent(1, keysym);
274             };
275
276         keyboard.onkeyup = 
277             function (keysym) {
278                 guac.sendKeyEvent(0, keysym);
279             };
280     }
281
282     // Enable keyboard by default
283     enableKeyboard();
284
285     // Handle client state change
286     guac.onstatechange = function(clientState) {
287         switch (clientState) {
288
289             // Idle
290             case 0:
291                 GuacamoleUI.state.textContent = "Idle."
292                 break;
293
294             // Connecting
295             case 1:
296                 GuacamoleUI.state.textContent = "Connecting...";
297                 break;
298
299             // Connected + waiting
300             case 2:
301                 GuacamoleUI.state.textContent = "Connected, waiting for first update...";
302                 break;
303
304             // Connected
305             case 3:
306                 
307                 GuacamoleUI.display.className =
308                     GuacamoleUI.display.className.replace(/guac-loading/, '');
309
310                 GuacamoleUI.menu.className = "connected";
311                 GuacamoleUI.state.textContent = "Connected.";
312                 GuacamoleUI.shadeMenu();
313                 break;
314
315             // Disconnecting
316             case 4:
317                 GuacamoleUI.state.textContent = "Disconnecting...";
318                 break;
319
320             // Disconnected
321             case 5:
322                 GuacamoleUI.state.textContent = "Disconnected.";
323                 break;
324
325             // Unknown status code
326             default:
327                 GuacamoleUI.state.textContent = "Unknown";
328
329         }
330     };
331
332     // Name instruction handler
333     guac.onname = function(name) {
334         document.title = name;
335     };
336
337     // Error handler
338     guac.onerror = function(error) {
339
340         // Disconnect, if connected
341         guac.disconnect();
342
343         // Display error message
344         GuacamoleUI.showError(error);
345
346         // Show error by desaturating display
347         var layers = guac.getLayers();
348         for (var i=0; i<layers.length; i++) {
349             layers[i].filter(desaturateFilter);
350         }
351
352         // Filter for desaturation
353         function desaturateFilter(data, width, height) {
354
355             for (var i=0; i<data.length; i+=4) {
356
357                 // Get RGB values
358                 var r = data[i];
359                 var g = data[i+1];
360                 var b = data[i+2];
361
362                 // Desaturate
363                 var v = Math.max(r, g, b) / 2;
364                 data[i]   = v;
365                 data[i+1] = v;
366                 data[i+2] = v;
367
368             }
369
370         }
371         
372     };
373
374     // Disconnect on close
375     window.onunload = function() {
376         guac.disconnect();
377     };
378
379     // Handle clipboard events
380     GuacamoleUI.clipboard.onchange = function() {
381
382         var text = GuacamoleUI.clipboard.value;
383         guac.setClipboard(text);
384
385     };
386
387     // Ignore keypresses when clipboard is focused
388     GuacamoleUI.clipboard.onfocus = function() {
389         disableKeyboard();
390     };
391
392     // Capture keypresses when clipboard is not focused
393     GuacamoleUI.clipboard.onblur = function() {
394         enableKeyboard();
395     };
396
397     // Server copy handler
398     guac.onclipboard = function(data) {
399         GuacamoleUI.clipboard.value = data;
400     };
401
402     GuacamoleUI.keyboard.onkeypress = function(keysym) {
403         guac.sendKeyEvent(1, keysym);
404     };
405
406     GuacamoleUI.keyboard.onkeyrelease = function(keysym) {
407         guac.sendKeyEvent(0, keysym);
408     };
409
410     // Send Ctrl-Alt-Delete
411     GuacamoleUI.buttons.ctrlAltDelete.onclick = function() {
412
413         var KEYSYM_CTRL   = 0xFFE3;
414         var KEYSYM_ALT    = 0xFFE9;
415         var KEYSYM_DELETE = 0xFFFF;
416
417         guac.sendKeyEvent(1, KEYSYM_CTRL);
418         guac.sendKeyEvent(1, KEYSYM_ALT);
419         guac.sendKeyEvent(1, KEYSYM_DELETE);
420         guac.sendKeyEvent(0, KEYSYM_DELETE);
421         guac.sendKeyEvent(0, KEYSYM_ALT);
422         guac.sendKeyEvent(0, KEYSYM_CTRL);
423     };
424
425 };