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 "eventTarget" : document.getElementById("eventTarget"),
15 "showClipboard": document.getElementById("showClipboard"),
16 "showKeyboard" : document.getElementById("showKeyboard"),
17 "ctrlAltDelete": document.getElementById("ctrlAltDelete"),
18 "reconnect" : document.getElementById("reconnect"),
19 "logout" : document.getElementById("logout")
24 "state" : document.getElementById("statusDialog"),
25 "clipboard": document.getElementById("clipboardDiv"),
26 "keyboard" : document.getElementById("keyboardContainer")
29 "state" : document.getElementById("statusText"),
30 "clipboard" : document.getElementById("clipboard")
34 // Constant UI initialization and behavior
37 var menu_shaded = false;
39 var shade_interval = null;
40 var show_interval = null;
42 // Cache error image (might not be available when error occurs)
43 var guacErrorImage = new Image();
44 guacErrorImage.src = "images/noguacamole-logo-24.png";
46 // Function for adding a class to an element
49 // Function for removing a class from an element
52 // If Node.classList is supported, implement addClass/removeClass using that
55 addClass = function(element, classname) {
56 element.classList.add(classname);
59 removeClass = function(element, classname) {
60 element.classList.remove(classname);
65 // Otherwise, implement own
68 addClass = function(element, classname) {
70 // Simply add new class
71 element.className += " " + classname;
75 removeClass = function(element, classname) {
77 // Filter out classes with given name
78 element.className = element.className.replace(/([^ ]+)[ ]*/g,
79 function(match, testClassname, spaces, offset, string) {
81 // If same class, remove
82 if (testClassname == classname)
96 GuacamoleUI.hideStatus = function() {
97 removeClass(document.body, "guac-error");
98 GuacamoleUI.containers.state.style.visibility = "hidden";
99 GuacamoleUI.display.style.opacity = "1";
102 GuacamoleUI.showStatus = function(text) {
103 removeClass(document.body, "guac-error");
104 GuacamoleUI.containers.state.style.visibility = "visible";
105 GuacamoleUI.state.textContent = text;
106 GuacamoleUI.display.style.opacity = "1";
109 GuacamoleUI.showError = function(error) {
110 addClass(document.body, "guac-error");
111 GuacamoleUI.state.textContent = error;
112 GuacamoleUI.display.style.opacity = "0.1";
115 GuacamoleUI.shadeMenu = function() {
119 var step = Math.floor(GuacamoleUI.menu.offsetHeight / 10) + 1;
123 window.clearInterval(show_interval);
124 shade_interval = window.setInterval(function() {
127 GuacamoleUI.menu.style.top = offset + "px";
129 if (offset <= -GuacamoleUI.menu.offsetHeight) {
130 window.clearInterval(shade_interval);
131 GuacamoleUI.menu.style.visiblity = "hidden";
139 GuacamoleUI.showMenu = function() {
143 var step = Math.floor(GuacamoleUI.menu.offsetHeight / 5) + 1;
144 var offset = -GuacamoleUI.menu.offsetHeight;
146 GuacamoleUI.menu.style.visiblity = "";
148 window.clearInterval(shade_interval);
149 show_interval = window.setInterval(function() {
155 window.clearInterval(show_interval);
158 GuacamoleUI.menu.style.top = offset + "px";
165 // Show/Hide clipboard
166 GuacamoleUI.buttons.showClipboard.onclick = function() {
168 var displayed = GuacamoleUI.containers.clipboard.style.display;
169 if (displayed != "block") {
170 GuacamoleUI.containers.clipboard.style.display = "block";
171 GuacamoleUI.buttons.showClipboard.innerHTML = "Hide Clipboard";
174 GuacamoleUI.containers.clipboard.style.display = "none";
175 GuacamoleUI.buttons.showClipboard.innerHTML = "Show Clipboard";
176 GuacamoleUI.clipboard.onchange();
182 * When GuacamoleUI.oskMode == OSK_MODE_NATIVE, "Show Keyboard" tries
183 * to use the native OSK instead of the Guacamole OSK.
185 GuacamoleUI.OSK_MODE_NATIVE = 1;
188 * When GuacamoleUI.oskMode == OSK_MODE_GUAC, "Show Keyboard" uses the
189 * Guacamole OSK, regardless of whether a native OSK is available.
191 GuacamoleUI.OSK_MODE_GUAC = 2;
193 // Assume no native OSK by default
194 GuacamoleUI.oskMode = GuacamoleUI.OSK_MODE_GUAC;
196 // Show/Hide keyboard
197 var keyboardResizeInterval = null;
198 GuacamoleUI.buttons.showKeyboard.onclick = function() {
200 // If Guac OSK shown, hide it.
201 var displayed = GuacamoleUI.containers.keyboard.style.display;
202 if (displayed == "block") {
203 GuacamoleUI.containers.keyboard.style.display = "none";
204 GuacamoleUI.buttons.showKeyboard.textContent = "Show Keyboard";
206 window.onresize = null;
207 window.clearInterval(keyboardResizeInterval);
210 // If not shown ... action depends on OSK mode.
213 // If we think the platform has a native OSK, use the event target to
214 // cause it to display.
215 if (GuacamoleUI.oskMode == GuacamoleUI.OSK_MODE_NATIVE) {
217 // ...but use the Guac OSK if clicked again
218 GuacamoleUI.oskMode = GuacamoleUI.OSK_MODE_GUAC;
220 // Try to show native OSK by focusing eventTarget.
221 GuacamoleUI.eventTarget.focus();
226 // Ensure event target is NOT focused if we are using the Guac OSK.
227 GuacamoleUI.eventTarget.blur();
229 GuacamoleUI.containers.keyboard.style.display = "block";
230 GuacamoleUI.buttons.showKeyboard.textContent = "Hide Keyboard";
232 // Automatically update size
233 window.onresize = updateKeyboardSize;
234 keyboardResizeInterval = window.setInterval(updateKeyboardSize, 30);
236 updateKeyboardSize();
243 GuacamoleUI.buttons.logout.onclick = function() {
244 window.location.href = "logout";
247 // Timeouts for detecting if users wants menu to open or close
248 var detectMenuOpenTimeout = null;
249 var detectMenuCloseTimeout = null;
251 // Clear detection timeouts
252 GuacamoleUI.resetMenuDetect = function() {
254 if (detectMenuOpenTimeout != null) {
255 window.clearTimeout(detectMenuOpenTimeout);
256 detectMenuOpenTimeout = null;
259 if (detectMenuCloseTimeout != null) {
260 window.clearTimeout(detectMenuCloseTimeout);
261 detectMenuCloseTimeout = null;
266 // Initiate detection of menu open action. If not canceled through some
267 // user event, menu will open.
268 GuacamoleUI.startMenuOpenDetect = function() {
270 if (!detectMenuOpenTimeout) {
272 // Clear detection state
273 GuacamoleUI.resetMenuDetect();
275 // Wait and then show menu
276 detectMenuOpenTimeout = window.setTimeout(function() {
278 // If menu opened via mouse, do not show native OSK
279 GuacamoleUI.oskMode = GuacamoleUI.OSK_MODE_GUAC;
281 GuacamoleUI.showMenu();
282 detectMenuOpenTimeout = null;
289 // Initiate detection of menu close action. If not canceled through some
290 // user mouse event, menu will close.
291 GuacamoleUI.startMenuCloseDetect = function() {
293 if (!detectMenuCloseTimeout) {
295 // Clear detection state
296 GuacamoleUI.resetMenuDetect();
298 // Wait and then shade menu
299 detectMenuCloseTimeout = window.setTimeout(function() {
300 GuacamoleUI.shadeMenu();
301 detectMenuCloseTimeout = null;
308 // Show menu if mouseover any part of menu
309 GuacamoleUI.menu.addEventListener('mouseover', GuacamoleUI.showMenu, true);
311 // Stop detecting menu state change intents if mouse is over menu
312 GuacamoleUI.menu.addEventListener('mouseover', GuacamoleUI.resetMenuDetect, true);
314 // When mouse hovers over top of screen, start detection of intent to open menu
315 GuacamoleUI.menuControl.addEventListener('mousemove', GuacamoleUI.startMenuOpenDetect, true);
317 var menuShowLongPressTimeout = null;
319 GuacamoleUI.startLongPressDetect = function() {
321 if (!menuShowLongPressTimeout) {
323 menuShowLongPressTimeout = window.setTimeout(function() {
325 menuShowLongPressTimeout = null;
327 // Assume native OSK if menu shown via long-press
328 GuacamoleUI.oskMode = GuacamoleUI.OSK_MODE_NATIVE;
329 GuacamoleUI.showMenu();
336 GuacamoleUI.stopLongPressDetect = function() {
337 window.clearTimeout(menuShowLongPressTimeout);
338 menuShowLongPressTimeout = null;
341 // Reset event target (add content, reposition cursor in middle.
342 GuacamoleUI.resetEventTarget = function() {
343 GuacamoleUI.eventTarget.value = "GUAC";
344 GuacamoleUI.eventTarget.selectionStart =
345 GuacamoleUI.eventTarget.selectionEnd = 2;
348 // Detect long-press at bottom of screen
349 GuacamoleUI.display.addEventListener('touchstart', GuacamoleUI.startLongPressDetect, true);
352 GuacamoleUI.buttons.reconnect.onclick = function() {
353 window.location.reload();
356 // On-screen keyboard
357 GuacamoleUI.keyboard = new Guacamole.OnScreenKeyboard("layouts/en-us-qwerty-mobile.xml");
358 GuacamoleUI.containers.keyboard.appendChild(GuacamoleUI.keyboard.getElement());
360 // Function for automatically updating keyboard size
361 var lastKeyboardWidth;
362 function updateKeyboardSize() {
363 var currentSize = GuacamoleUI.keyboard.getElement().offsetWidth;
364 if (lastKeyboardWidth != currentSize) {
365 GuacamoleUI.keyboard.resize(currentSize);
366 lastKeyboardWidth = currentSize;
372 // Tie UI events / behavior to a specific Guacamole client
373 GuacamoleUI.attach = function(guac) {
375 var title_prefix = null;
376 var connection_name = null
378 var guac_display = guac.getDisplay();
380 // Set document title appropriately, based on prefix and connection name
381 function updateTitle() {
383 // Use title prefix if present
386 document.title = title_prefix;
388 // Include connection name, if present
390 document.title += " " + connection_name;
394 // Otherwise, just set to connection name
395 else if (connection_name)
396 document.title = connection_name;
400 // When mouse enters display, start detection of intent to close menu
401 guac_display.addEventListener('mouseover', GuacamoleUI.startMenuCloseDetect, true);
403 guac_display.onclick = function(e) {
409 var mouse = new Guacamole.Mouse(guac_display);
410 mouse.onmousedown = mouse.onmouseup = mouse.onmousemove =
411 function(mouseState) {
413 // Determine mouse position within view
414 var mouse_view_x = mouseState.x + guac_display.offsetLeft - window.pageXOffset;
415 var mouse_view_y = mouseState.y + guac_display.offsetTop - window.pageYOffset;
417 // Determine viewport dimensioins
418 var view_width = GuacamoleUI.viewport.offsetWidth;
419 var view_height = GuacamoleUI.viewport.offsetHeight;
421 // Determine scroll amounts based on mouse position relative to document
424 if (mouse_view_x > view_width)
425 scroll_amount_x = mouse_view_x - view_width;
426 else if (mouse_view_x < 0)
427 scroll_amount_x = mouse_view_x;
432 if (mouse_view_y > view_height)
433 scroll_amount_y = mouse_view_y - view_height;
434 else if (mouse_view_y < 0)
435 scroll_amount_y = mouse_view_y;
439 // Scroll (if necessary) to keep mouse on screen.
440 window.scrollBy(scroll_amount_x, scroll_amount_y);
442 // Hide menu on movement
443 GuacamoleUI.startMenuCloseDetect();
445 // Stop detecting long presses if mouse is being used
446 GuacamoleUI.stopLongPressDetect();
449 guac.sendMouseState(mouseState);
454 var keyboard = new Guacamole.Keyboard(document);
456 function disableKeyboard() {
457 keyboard.onkeydown = null;
458 keyboard.onkeyup = null;
461 function enableKeyboard() {
465 // If we're using native OSK, ensure event target is reset
466 // on each key event.
467 if (GuacamoleUI.oskMode == GuacamoleUI.OSK_MODE_NATIVE)
468 GuacamoleUI.resetEventTarget();
470 guac.sendKeyEvent(1, keysym);
475 guac.sendKeyEvent(0, keysym);
479 // Enable keyboard by default
482 // Handle client state change
483 guac.onstatechange = function(clientState) {
485 switch (clientState) {
489 GuacamoleUI.showStatus("Idle.");
490 title_prefix = "[Idle]";
495 GuacamoleUI.shadeMenu();
496 GuacamoleUI.showStatus("Connecting...");
497 title_prefix = "[Connecting...]";
500 // Connected + waiting
502 GuacamoleUI.showStatus("Connected, waiting for first update...");
503 title_prefix = "[Waiting...]";
509 GuacamoleUI.hideStatus();
510 GuacamoleUI.display.className =
511 GuacamoleUI.display.className.replace(/guac-loading/, '');
513 GuacamoleUI.menu.className = "connected";
520 GuacamoleUI.showStatus("Disconnecting...");
521 title_prefix = "[Disconnecting...]";
526 GuacamoleUI.showStatus("Disconnected.");
527 title_prefix = "[Disconnected]";
530 // Unknown status code
532 GuacamoleUI.showStatus("[UNKNOWN STATUS]");
539 // Name instruction handler
540 guac.onname = function(name) {
541 connection_name = name;
546 guac.onerror = function(error) {
548 // Disconnect, if connected
551 // Display error message
552 GuacamoleUI.showError(error);
556 // Disconnect on close
557 window.onunload = function() {
561 // Handle clipboard events
562 GuacamoleUI.clipboard.onchange = function() {
564 var text = GuacamoleUI.clipboard.value;
565 guac.setClipboard(text);
569 // Ignore keypresses when clipboard is focused
570 GuacamoleUI.clipboard.onfocus = function() {
574 // Capture keypresses when clipboard is not focused
575 GuacamoleUI.clipboard.onblur = function() {
579 // Server copy handler
580 guac.onclipboard = function(data) {
581 GuacamoleUI.clipboard.value = data;
584 GuacamoleUI.keyboard.onkeydown = function(keysym) {
585 guac.sendKeyEvent(1, keysym);
588 GuacamoleUI.keyboard.onkeyup = function(keysym) {
589 guac.sendKeyEvent(0, keysym);
592 // Send Ctrl-Alt-Delete
593 GuacamoleUI.buttons.ctrlAltDelete.onclick = function() {
595 var KEYSYM_CTRL = 0xFFE3;
596 var KEYSYM_ALT = 0xFFE9;
597 var KEYSYM_DELETE = 0xFFFF;
599 guac.sendKeyEvent(1, KEYSYM_CTRL);
600 guac.sendKeyEvent(1, KEYSYM_ALT);
601 guac.sendKeyEvent(1, KEYSYM_DELETE);
602 guac.sendKeyEvent(0, KEYSYM_DELETE);
603 guac.sendKeyEvent(0, KEYSYM_ALT);
604 guac.sendKeyEvent(0, KEYSYM_CTRL);