Rename assumeNativeOSK to nativeOSK, automatically reset nativeOSK to false if menu...
[guacamole.git] / src / main / webapp / scripts / interface.js
index 63cc426..9253a8a 100644 (file)
 
-var menu_shaded = false;
+// UI Definition
+var GuacamoleUI = {
 
-var shade_interval = null;
-var show_interval = null;
+    "viewport"    : document.getElementById("viewportClone"),
+    "display"     : document.getElementById("display"),
+    "menu"        : document.getElementById("menu"),
+    "menuControl" : document.getElementById("menuControl"),
+    "touchMenu"   : document.getElementById("touchMenu"),
+    "logo"        : document.getElementById("status-logo"),
+    "eventTarget" : document.getElementById("eventTarget"),
 
-function shadeMenu() {
+    "buttons": {
 
-    if (!menu_shaded) {
+        "showClipboard": document.getElementById("showClipboard"),
+        "showKeyboard" : document.getElementById("showKeyboard"),
+        "ctrlAltDelete": document.getElementById("ctrlAltDelete"),
+        "reconnect"    : document.getElementById("reconnect"),
+        "logout"       : document.getElementById("logout")
 
-        var step = Math.floor(menu.offsetHeight / 5) + 1;
-        var offset = 0;
-        menu_shaded = true;
+    },
 
-        window.clearInterval(show_interval);
-        shade_interval = window.setInterval(function() {
+    "containers": {
+        "state"    : document.getElementById("statusDialog"),
+        "clipboard": document.getElementById("clipboardDiv"),
+        "keyboard" : document.getElementById("keyboardContainer")
+    },
+    
+    "state"     : document.getElementById("statusText"),
+    "clipboard" : document.getElementById("clipboard")
 
-            offset -= step;
-            menu.style.top = offset + "px";
+};
 
-            if (offset <= -menu.offsetHeight) {
-                window.clearInterval(shade_interval);
-                menu.style.visiblity = "hidden";
-            }
+// Constant UI initialization and behavior
+(function() {
 
-        }, 30);
+    var menu_shaded = false;
+
+    var shade_interval = null;
+    var show_interval = null;
+
+    // Cache error image (might not be available when error occurs)
+    var guacErrorImage = new Image();
+    guacErrorImage.src = "images/noguacamole-logo-24.png";
+
+    // Function for adding a class to an element
+    var addClass;
+
+    // Function for removing a class from an element
+    var removeClass;
+
+    // If Node.classList is supported, implement addClass/removeClass using that
+    if (Node.classList) {
+
+        addClass = function(element, classname) {
+            element.classList.add(classname);
+        };
+        
+        removeClass = function(element, classname) {
+            element.classList.remove(classname);
+        };
+        
+    }
+
+    // Otherwise, implement own
+    else {
+
+        addClass = function(element, classname) {
+
+            // Simply add new class
+            element.className += " " + classname;
+
+        };
+        
+        removeClass = function(element, classname) {
+
+            // Filter out classes with given name
+            element.className = element.className.replace(/([^ ]+)[ ]*/g,
+                function(match, testClassname, spaces, offset, string) {
+
+                    // If same class, remove
+                    if (testClassname == classname)
+                        return "";
+
+                    // Otherwise, allow
+                    return match;
+                    
+                }
+            );
+
+        };
+        
     }
 
-}
 
-function showMenu() {
+    GuacamoleUI.hideStatus = function() {
+        removeClass(document.body, "guac-error");
+        GuacamoleUI.containers.state.style.visibility = "hidden";
+        GuacamoleUI.display.style.opacity = "1";
+    };
+    
+    GuacamoleUI.showStatus = function(text) {
+        removeClass(document.body, "guac-error");
+        GuacamoleUI.containers.state.style.visibility = "visible";
+        GuacamoleUI.state.textContent = text;
+        GuacamoleUI.display.style.opacity = "1";
+    };
+    
+    GuacamoleUI.showError = function(error) {
+        addClass(document.body, "guac-error");
+        GuacamoleUI.state.textContent = error;
+        GuacamoleUI.display.style.opacity = "0.1";
+    };
+
+    GuacamoleUI.shadeMenu = function() {
+
+        if (!menu_shaded) {
+
+            var step = Math.floor(GuacamoleUI.menu.offsetHeight / 10) + 1;
+            var offset = 0;
+            menu_shaded = true;
+
+            window.clearInterval(show_interval);
+            shade_interval = window.setInterval(function() {
+
+                offset -= step;
+                GuacamoleUI.menu.style.top = offset + "px";
+
+                if (offset <= -GuacamoleUI.menu.offsetHeight) {
+                    window.clearInterval(shade_interval);
+                    GuacamoleUI.menu.style.visiblity = "hidden";
+                }
+
+            }, 30);
+        }
+
+    };
+
+    GuacamoleUI.showMenu = function() {
+
+        if (menu_shaded) {
+
+            var step = Math.floor(GuacamoleUI.menu.offsetHeight / 5) + 1;
+            var offset = -GuacamoleUI.menu.offsetHeight;
+            menu_shaded = false;
+            GuacamoleUI.menu.style.visiblity = "";
+
+            window.clearInterval(shade_interval);
+            show_interval = window.setInterval(function() {
+
+                offset += step;
+
+                if (offset >= 0) {
+                    offset = 0;
+                    window.clearInterval(show_interval);
+                }
+
+                GuacamoleUI.menu.style.top = offset + "px";
+
+            }, 30);
+        }
+
+    };
+
+    // Show/Hide clipboard
+    GuacamoleUI.buttons.showClipboard.onclick = function() {
+
+        var displayed = GuacamoleUI.containers.clipboard.style.display;
+        if (displayed != "block") {
+            GuacamoleUI.containers.clipboard.style.display = "block";
+            GuacamoleUI.buttons.showClipboard.innerHTML = "Hide Clipboard";
+        }
+        else {
+            GuacamoleUI.containers.clipboard.style.display = "none";
+            GuacamoleUI.buttons.showClipboard.innerHTML = "Show Clipboard";
+            GuacamoleUI.clipboard.onchange();
+        }
+
+    };
+
+    // Assume no native OSK by default
+    GuacamoleUI.nativeOSK = false;
+
+    // Show/Hide keyboard
+    var keyboardResizeInterval = null;
+    GuacamoleUI.buttons.showKeyboard.onclick = function() {
+
+        // If we think the platform has a native OSK, use the event target to
+        // cause it to display.
+        if (GuacamoleUI.nativeOSK) {
+            GuacamoleUI.eventTarget.focus();
+            return;
+        }
+        
+        var displayed = GuacamoleUI.containers.keyboard.style.display;
+        if (displayed != "block") {
+            GuacamoleUI.containers.keyboard.style.display = "block";
+            GuacamoleUI.buttons.showKeyboard.textContent = "Hide Keyboard";
+
+            // Automatically update size
+            window.onresize = updateKeyboardSize;
+            keyboardResizeInterval = window.setInterval(updateKeyboardSize, 30);
+
+            updateKeyboardSize();
+        }
+        else {
+            GuacamoleUI.containers.keyboard.style.display = "none";
+            GuacamoleUI.buttons.showKeyboard.textContent = "Show Keyboard";
+
+            window.onresize = null;
+            window.clearInterval(keyboardResizeInterval);
+        }
+
+    };
+
+    // Logout
+    GuacamoleUI.buttons.logout.onclick = function() {
+        window.location.href = "logout";
+    };
+
+    // Timeouts for detecting if users wants menu to open or close
+    var detectMenuOpenTimeout = null;
+    var detectMenuCloseTimeout = null;
+
+    // Clear detection timeouts
+    GuacamoleUI.resetMenuDetect = function() {
+
+        if (detectMenuOpenTimeout != null) {
+            window.clearTimeout(detectMenuOpenTimeout);
+            detectMenuOpenTimeout = null;
+        }
+
+        if (detectMenuCloseTimeout != null) {
+            window.clearTimeout(detectMenuCloseTimeout);
+            detectMenuCloseTimeout = null;
+        }
+
+    };
+
+    // Initiate detection of menu open action. If not canceled through some
+    // user event, menu will open.
+    GuacamoleUI.startMenuOpenDetect = function() {
+
+        if (!detectMenuOpenTimeout) {
+
+            // Clear detection state
+            GuacamoleUI.resetMenuDetect();
+
+            // Wait and then show menu
+            detectMenuOpenTimeout = window.setTimeout(function() {
+
+                // If menu opened via mouse, do not show native OSK
+                GuacamoleUI.nativeOSK = false;
+
+                GuacamoleUI.showMenu();
+                detectMenuOpenTimeout = null;
+            }, 325);
+
+        }
+
+    };
+
+    // Initiate detection of menu close action. If not canceled through some
+    // user mouse event, menu will close.
+    GuacamoleUI.startMenuCloseDetect = function() {
+
+        if (!detectMenuCloseTimeout) {
 
-    if (menu_shaded) {
+            // Clear detection state
+            GuacamoleUI.resetMenuDetect();
 
-        var step = Math.floor(menu.offsetHeight / 5) + 1;
-        var offset = -menu.offsetHeight;
-        menu_shaded = false;
-        menu.style.visiblity = "";
+            // Wait and then shade menu
+            detectMenuCloseTimeout = window.setTimeout(function() {
+                GuacamoleUI.shadeMenu();
+                detectMenuCloseTimeout = null;
+            }, 500);
 
-        window.clearInterval(shade_interval);
-        show_interval = window.setInterval(function() {
+        }
 
-            offset += step;
+    };
 
-            if (offset >= 0) {
-                offset = 0;
-                window.clearInterval(show_interval);
-            }
+    // Show menu if mouseover any part of menu
+    GuacamoleUI.menu.addEventListener('mouseover', GuacamoleUI.showMenu, true);
 
-            menu.style.top = offset + "px";
+    // Stop detecting menu state change intents if mouse is over menu
+    GuacamoleUI.menu.addEventListener('mouseover', GuacamoleUI.resetMenuDetect, true);
 
-        }, 30);
+    // When mouse hovers over top of screen, start detection of intent to open menu
+    GuacamoleUI.menuControl.addEventListener('mousemove', GuacamoleUI.startMenuOpenDetect, true);
+
+    var menuShowLongPressTimeout = null;
+
+    GuacamoleUI.startLongPressDetect = function() {
+
+        if (!menuShowLongPressTimeout) {
+
+            menuShowLongPressTimeout = window.setTimeout(function() {
+                
+                menuShowLongPressTimeout = null;
+
+                // Assume native OSK if menu shown via long-press
+                GuacamoleUI.nativeOSK = true;
+                GuacamoleUI.showMenu();
+
+            }, 800);
+
+        }
+    };
+
+    GuacamoleUI.stopLongPressDetect = function() {
+        window.clearTimeout(menuShowLongPressTimeout);
+        menuShowLongPressTimeout = null;
+    };
+
+    // Reset event target (add content, reposition cursor in middle.
+    GuacamoleUI.resetEventTarget = function() {
+        GuacamoleUI.eventTarget.value = "GUAC";
+        GuacamoleUI.eventTarget.selectionStart =
+        GuacamoleUI.eventTarget.selectionEnd   = 2;
+    };
+
+    // Detect long-press at bottom of screen
+    document.body.addEventListener('touchstart', GuacamoleUI.startLongPressDetect, true);
+
+    // Reconnect button
+    GuacamoleUI.buttons.reconnect.onclick = function() {
+        window.location.reload();
+    };
+
+    // On-screen keyboard
+    GuacamoleUI.keyboard = new Guacamole.OnScreenKeyboard("layouts/en-us-qwerty-mobile.xml");
+    GuacamoleUI.containers.keyboard.appendChild(GuacamoleUI.keyboard.getElement());
+
+    // Function for automatically updating keyboard size
+    var lastKeyboardWidth;
+    function updateKeyboardSize() {
+        var currentSize = GuacamoleUI.keyboard.getElement().offsetWidth;
+        if (lastKeyboardWidth != currentSize) {
+            GuacamoleUI.keyboard.resize(currentSize);
+            lastKeyboardWidth = currentSize;
+        }
+    };
+
+})();
+
+// Tie UI events / behavior to a specific Guacamole client
+GuacamoleUI.attach = function(guac) {
+
+    var guac_display = guac.getDisplay();
+
+    // When mouse enters display, start detection of intent to close menu
+    guac_display.addEventListener('mouseover', GuacamoleUI.startMenuCloseDetect, true);
+
+    guac_display.onclick = function(e) {
+        e.preventDefault();
+        return false;
+    };
+
+    // Mouse
+    var mouse = new Guacamole.Mouse(guac_display);
+    mouse.onmousedown = mouse.onmouseup = mouse.onmousemove =
+        function(mouseState) {
+       
+            // Determine mouse position within view
+            var mouse_view_x = mouseState.x + guac_display.offsetLeft - window.pageXOffset;
+            var mouse_view_y = mouseState.y + guac_display.offsetTop  - window.pageYOffset;
+
+            // Determine viewport dimensioins
+            var view_width  = GuacamoleUI.viewport.offsetWidth;
+            var view_height = GuacamoleUI.viewport.offsetHeight;
+
+            // Determine scroll amounts based on mouse position relative to document
+
+            var scroll_amount_x;
+            if (mouse_view_x > view_width)
+                scroll_amount_x = mouse_view_x - view_width;
+            else if (mouse_view_x < 0)
+                scroll_amount_x = mouse_view_x;
+            else
+                scroll_amount_x = 0;
+
+            var scroll_amount_y;
+            if (mouse_view_y > view_height)
+                scroll_amount_y = mouse_view_y - view_height;
+            else if (mouse_view_y < 0)
+                scroll_amount_y = mouse_view_y;
+            else
+                scroll_amount_y = 0;
+
+            // Scroll (if necessary) to keep mouse on screen.
+            window.scrollBy(scroll_amount_x, scroll_amount_y);
+       
+            // Hide menu on movement
+            GuacamoleUI.startMenuCloseDetect();
+
+            // Stop detecting long presses if mouse is being used
+            GuacamoleUI.stopLongPressDetect();
+
+            // Send mouse event
+            guac.sendMouseState(mouseState);
+            
+        };
+
+    // Keyboard
+    var keyboard = new Guacamole.Keyboard(document);
+
+    function disableKeyboard() {
+        keyboard.onkeydown = null;
+        keyboard.onkeyup = null;
     }
 
-}
+    function enableKeyboard() {
+        keyboard.onkeydown = 
+            function (keysym) {
+          
+                // If we're using native OSK, ensure event target is reset
+                // on each key event.
+                if (GuacamoleUI.nativeOSK)
+                    GuacamoleUI.resetEventTarget();
+                
+                guac.sendKeyEvent(1, keysym);
+            };
+
+        keyboard.onkeyup = 
+            function (keysym) {
+                guac.sendKeyEvent(0, keysym);
+            };
+    }
+
+    // Enable keyboard by default
+    enableKeyboard();
+
+    // Handle client state change
+    guac.onstatechange = function(clientState) {
+        switch (clientState) {
+
+            // Idle
+            case 0:
+                GuacamoleUI.showStatus("Idle.");
+                break;
+
+            // Connecting
+            case 1:
+                GuacamoleUI.shadeMenu();
+                GuacamoleUI.showStatus("Connecting...");
+                break;
+
+            // Connected + waiting
+            case 2:
+                GuacamoleUI.showStatus("Connected, waiting for first update...");
+                break;
+
+            // Connected
+            case 3:
+                
+                GuacamoleUI.hideStatus();
+                GuacamoleUI.display.className =
+                    GuacamoleUI.display.className.replace(/guac-loading/, '');
+
+                GuacamoleUI.menu.className = "connected";
+                break;
+
+            // Disconnecting
+            case 4:
+                GuacamoleUI.showStatus("Disconnecting...");
+                break;
+
+            // Disconnected
+            case 5:
+                GuacamoleUI.showStatus("Disconnected.");
+                break;
+
+            // Unknown status code
+            default:
+                GuacamoleUI.showStatus("[UNKNOWN STATUS]");
+
+        }
+    };
+
+    // Name instruction handler
+    guac.onname = function(name) {
+        document.title = name;
+    };
+
+    // Error handler
+    guac.onerror = function(error) {
+
+        // Disconnect, if connected
+        guac.disconnect();
+
+        // Display error message
+        GuacamoleUI.showError(error);
+        
+    };
+
+    // Disconnect on close
+    window.onunload = function() {
+        guac.disconnect();
+    };
+
+    // Handle clipboard events
+    GuacamoleUI.clipboard.onchange = function() {
+
+        var text = GuacamoleUI.clipboard.value;
+        guac.setClipboard(text);
+
+    };
+
+    // Ignore keypresses when clipboard is focused
+    GuacamoleUI.clipboard.onfocus = function() {
+        disableKeyboard();
+    };
+
+    // Capture keypresses when clipboard is not focused
+    GuacamoleUI.clipboard.onblur = function() {
+        enableKeyboard();
+    };
+
+    // Server copy handler
+    guac.onclipboard = function(data) {
+        GuacamoleUI.clipboard.value = data;
+    };
+
+    GuacamoleUI.keyboard.onkeydown = function(keysym) {
+        guac.sendKeyEvent(1, keysym);
+    };
+
+    GuacamoleUI.keyboard.onkeyup = function(keysym) {
+        guac.sendKeyEvent(0, keysym);
+    };
+
+    // Send Ctrl-Alt-Delete
+    GuacamoleUI.buttons.ctrlAltDelete.onclick = function() {
+
+        var KEYSYM_CTRL   = 0xFFE3;
+        var KEYSYM_ALT    = 0xFFE9;
+        var KEYSYM_DELETE = 0xFFFF;
+
+        guac.sendKeyEvent(1, KEYSYM_CTRL);
+        guac.sendKeyEvent(1, KEYSYM_ALT);
+        guac.sendKeyEvent(1, KEYSYM_DELETE);
+        guac.sendKeyEvent(0, KEYSYM_DELETE);
+        guac.sendKeyEvent(0, KEYSYM_ALT);
+        guac.sendKeyEvent(0, KEYSYM_CTRL);
+    };
 
+};
\ No newline at end of file