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