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"),
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")
23 "state" : document.getElementById("statusDialog"),
24 "clipboard": document.getElementById("clipboardDiv"),
25 "keyboard" : document.getElementById("keyboardContainer")
28 "state" : document.getElementById("statusText"),
29 "clipboard" : document.getElementById("clipboard")
33 // Constant UI initialization and behavior
36 var menu_shaded = false;
38 var shade_interval = null;
39 var show_interval = null;
41 // Cache error image (might not be available when error occurs)
42 var guacErrorImage = new Image();
43 guacErrorImage.src = "images/noguacamole-logo-24.png";
45 // Function for adding a class to an element
48 // Function for removing a class from an element
51 // If Node.classList is supported, implement addClass/removeClass using that
54 addClass = function(element, classname) {
55 element.classList.add(classname);
58 removeClass = function(element, classname) {
59 element.classList.remove(classname);
64 // Otherwise, implement own
67 addClass = function(element, classname) {
69 // Simply add new class
70 element.className += " " + classname;
74 removeClass = function(element, classname) {
76 // Filter out classes with given name
77 element.className = element.className.replace(/([^ ]+)[ ]*/g,
78 function(match, testClassname, spaces, offset, string) {
80 // If same class, remove
81 if (testClassname == classname)
95 GuacamoleUI.hideStatus = function() {
96 removeClass(document.body, "guac-error");
97 GuacamoleUI.containers.state.style.visibility = "hidden";
98 GuacamoleUI.display.style.opacity = "1";
101 GuacamoleUI.showStatus = function(text) {
102 removeClass(document.body, "guac-error");
103 GuacamoleUI.containers.state.style.visibility = "visible";
104 GuacamoleUI.state.textContent = text;
105 GuacamoleUI.display.style.opacity = "1";
108 GuacamoleUI.showError = function(error) {
109 addClass(document.body, "guac-error");
110 GuacamoleUI.state.textContent = error;
111 GuacamoleUI.display.style.opacity = "0.1";
114 GuacamoleUI.shadeMenu = function() {
118 var step = Math.floor(GuacamoleUI.menu.offsetHeight / 10) + 1;
122 window.clearInterval(show_interval);
123 shade_interval = window.setInterval(function() {
126 GuacamoleUI.menu.style.top = offset + "px";
128 if (offset <= -GuacamoleUI.menu.offsetHeight) {
129 window.clearInterval(shade_interval);
130 GuacamoleUI.menu.style.visiblity = "hidden";
138 GuacamoleUI.showMenu = function() {
142 var step = Math.floor(GuacamoleUI.menu.offsetHeight / 5) + 1;
143 var offset = -GuacamoleUI.menu.offsetHeight;
145 GuacamoleUI.menu.style.visiblity = "";
147 window.clearInterval(shade_interval);
148 show_interval = window.setInterval(function() {
154 window.clearInterval(show_interval);
157 GuacamoleUI.menu.style.top = offset + "px";
164 // Show/Hide clipboard
165 GuacamoleUI.buttons.showClipboard.onclick = function() {
167 var displayed = GuacamoleUI.containers.clipboard.style.display;
168 if (displayed != "block") {
169 GuacamoleUI.containers.clipboard.style.display = "block";
170 GuacamoleUI.buttons.showClipboard.innerHTML = "Hide Clipboard";
173 GuacamoleUI.containers.clipboard.style.display = "none";
174 GuacamoleUI.buttons.showClipboard.innerHTML = "Show Clipboard";
175 GuacamoleUI.clipboard.onchange();
180 // Show/Hide keyboard
181 var keyboardResizeInterval = null;
182 GuacamoleUI.buttons.showKeyboard.onclick = function() {
184 var displayed = GuacamoleUI.containers.keyboard.style.display;
185 if (displayed != "block") {
186 GuacamoleUI.containers.keyboard.style.display = "block";
187 GuacamoleUI.buttons.showKeyboard.textContent = "Hide Keyboard";
189 // Automatically update size
190 window.onresize = updateKeyboardSize;
191 keyboardResizeInterval = window.setInterval(updateKeyboardSize, 30);
193 updateKeyboardSize();
196 GuacamoleUI.containers.keyboard.style.display = "none";
197 GuacamoleUI.buttons.showKeyboard.textContent = "Show Keyboard";
199 window.onresize = null;
200 window.clearInterval(keyboardResizeInterval);
206 GuacamoleUI.buttons.logout.onclick = function() {
207 window.location.href = "logout";
210 // Timeouts for detecting if users wants menu to open or close
211 var detectMenuOpenTimeout = null;
212 var detectMenuCloseTimeout = null;
214 // Clear detection timeouts
215 GuacamoleUI.resetMenuDetect = function() {
217 if (detectMenuOpenTimeout != null) {
218 window.clearTimeout(detectMenuOpenTimeout);
219 detectMenuOpenTimeout = null;
222 if (detectMenuCloseTimeout != null) {
223 window.clearTimeout(detectMenuCloseTimeout);
224 detectMenuCloseTimeout = null;
229 // Initiate detection of menu open action. If not canceled through some
230 // user event, menu will open.
231 GuacamoleUI.startMenuOpenDetect = function() {
233 if (!detectMenuOpenTimeout) {
235 // Clear detection state
236 GuacamoleUI.resetMenuDetect();
238 // Wait and then show menu
239 detectMenuOpenTimeout = window.setTimeout(function() {
240 GuacamoleUI.showMenu();
241 detectMenuOpenTimeout = null;
248 // Initiate detection of menu close action. If not canceled through some
249 // user event, menu will close.
250 GuacamoleUI.startMenuCloseDetect = function() {
252 if (!detectMenuCloseTimeout) {
254 // Clear detection state
255 GuacamoleUI.resetMenuDetect();
257 // Wait and then shade menu
258 detectMenuCloseTimeout = window.setTimeout(function() {
259 GuacamoleUI.shadeMenu();
260 detectMenuCloseTimeout = null;
267 // Show menu if mouseover any part of menu
268 GuacamoleUI.menu.addEventListener('mouseover', GuacamoleUI.showMenu, true);
270 // Stop detecting menu state change intents if mouse is over menu
271 GuacamoleUI.menu.addEventListener('mouseover', GuacamoleUI.resetMenuDetect, true);
273 // When mouse hovers over top of screen, start detection of intent to open menu
274 GuacamoleUI.menuControl.addEventListener('mousemove', GuacamoleUI.startMenuOpenDetect, true);
276 var menuShowLongPressTimeout = null;
278 GuacamoleUI.startLongPressDetect = function() {
280 if (!menuShowLongPressTimeout) {
282 menuShowLongPressTimeout = window.setTimeout(function() {
284 menuShowLongPressTimeout = null;
285 GuacamoleUI.showMenu();
292 GuacamoleUI.stopLongPressDetect = function() {
293 window.clearTimeout(menuShowLongPressTimeout);
294 menuShowLongPressTimeout = null;
297 // Detect long-press at bottom of screen
298 document.body.addEventListener('touchstart', GuacamoleUI.startLongPressDetect, true);
301 GuacamoleUI.buttons.reconnect.onclick = function() {
302 window.location.reload();
305 // On-screen keyboard
306 GuacamoleUI.keyboard = new Guacamole.OnScreenKeyboard("layouts/en-us-qwerty-mobile.xml");
307 GuacamoleUI.containers.keyboard.appendChild(GuacamoleUI.keyboard.getElement());
309 // Function for automatically updating keyboard size
310 var lastKeyboardWidth;
311 function updateKeyboardSize() {
312 var currentSize = GuacamoleUI.keyboard.getElement().offsetWidth;
313 if (lastKeyboardWidth != currentSize) {
314 GuacamoleUI.keyboard.resize(currentSize);
315 lastKeyboardWidth = currentSize;
321 // Tie UI events / behavior to a specific Guacamole client
322 GuacamoleUI.attach = function(guac) {
324 var guac_display = guac.getDisplay();
326 // When mouse enters display, start detection of intent to close menu
327 guac_display.addEventListener('mouseover', GuacamoleUI.startMenuCloseDetect, true);
329 guac_display.onclick = function(e) {
335 var mouse = new Guacamole.Mouse(guac_display);
336 mouse.onmousedown = mouse.onmouseup = mouse.onmousemove =
337 function(mouseState) {
339 // Determine mouse position within view
340 var mouse_view_x = mouseState.x + guac_display.offsetLeft - window.pageXOffset;
341 var mouse_view_y = mouseState.y + guac_display.offsetTop - window.pageYOffset;
343 // Determine viewport dimensioins
344 var view_width = GuacamoleUI.viewport.offsetWidth;
345 var view_height = GuacamoleUI.viewport.offsetHeight;
347 // Determine scroll amounts based on mouse position relative to document
350 if (mouse_view_x > view_width)
351 scroll_amount_x = mouse_view_x - view_width;
352 else if (mouse_view_x < 0)
353 scroll_amount_x = mouse_view_x;
358 if (mouse_view_y > view_height)
359 scroll_amount_y = mouse_view_y - view_height;
360 else if (mouse_view_y < 0)
361 scroll_amount_y = mouse_view_y;
365 // Scroll (if necessary) to keep mouse on screen.
366 window.scrollBy(scroll_amount_x, scroll_amount_y);
368 // Hide menu on movement
369 GuacamoleUI.startMenuCloseDetect();
371 // Stop detecting long presses if mouse is being used
372 GuacamoleUI.stopLongPressDetect();
375 guac.sendMouseState(mouseState);
380 var keyboard = new Guacamole.Keyboard(document);
382 function disableKeyboard() {
383 keyboard.onkeydown = null;
384 keyboard.onkeyup = null;
387 function enableKeyboard() {
390 guac.sendKeyEvent(1, keysym);
395 guac.sendKeyEvent(0, keysym);
399 // Enable keyboard by default
402 // Handle client state change
403 guac.onstatechange = function(clientState) {
404 switch (clientState) {
408 GuacamoleUI.showStatus("Idle.");
413 GuacamoleUI.shadeMenu();
414 GuacamoleUI.showStatus("Connecting...");
417 // Connected + waiting
419 GuacamoleUI.showStatus("Connected, waiting for first update...");
425 GuacamoleUI.hideStatus();
426 GuacamoleUI.display.className =
427 GuacamoleUI.display.className.replace(/guac-loading/, '');
429 GuacamoleUI.menu.className = "connected";
434 GuacamoleUI.showStatus("Disconnecting...");
439 GuacamoleUI.showStatus("Disconnected.");
442 // Unknown status code
444 GuacamoleUI.showStatus("[UNKNOWN STATUS]");
449 // Name instruction handler
450 guac.onname = function(name) {
451 document.title = name;
455 guac.onerror = function(error) {
457 // Disconnect, if connected
460 // Display error message
461 GuacamoleUI.showError(error);
465 // Disconnect on close
466 window.onunload = function() {
470 // Handle clipboard events
471 GuacamoleUI.clipboard.onchange = function() {
473 var text = GuacamoleUI.clipboard.value;
474 guac.setClipboard(text);
478 // Ignore keypresses when clipboard is focused
479 GuacamoleUI.clipboard.onfocus = function() {
483 // Capture keypresses when clipboard is not focused
484 GuacamoleUI.clipboard.onblur = function() {
488 // Server copy handler
489 guac.onclipboard = function(data) {
490 GuacamoleUI.clipboard.value = data;
493 GuacamoleUI.keyboard.onkeydown = function(keysym) {
494 guac.sendKeyEvent(1, keysym);
497 GuacamoleUI.keyboard.onkeyup = function(keysym) {
498 guac.sendKeyEvent(0, keysym);
501 // Send Ctrl-Alt-Delete
502 GuacamoleUI.buttons.ctrlAltDelete.onclick = function() {
504 var KEYSYM_CTRL = 0xFFE3;
505 var KEYSYM_ALT = 0xFFE9;
506 var KEYSYM_DELETE = 0xFFFF;
508 guac.sendKeyEvent(1, KEYSYM_CTRL);
509 guac.sendKeyEvent(1, KEYSYM_ALT);
510 guac.sendKeyEvent(1, KEYSYM_DELETE);
511 guac.sendKeyEvent(0, KEYSYM_DELETE);
512 guac.sendKeyEvent(0, KEYSYM_ALT);
513 guac.sendKeyEvent(0, KEYSYM_CTRL);