]> git.proxmox.com Git - mirror_novnc.git/blobdiff - include/ui.js
Adapt display to be able to handle a changing clipping-setting while connected
[mirror_novnc.git] / include / ui.js
index 82f7d96350d287d142597d3330aa9c2e8dec7ffc..0d9ad82c6c9449ebbfdbe706dfd980d1ad6bdb65 100644 (file)
 /*
  * noVNC: HTML5 VNC client
- * Copyright (C) 2011 Joel Martin
- * Licensed under LGPL-3 (see LICENSE.txt)
+ * Copyright (C) 2012 Joel Martin
+ * Copyright (C) 2015 Samuel Mannehed for Cendio AB
+ * Licensed under MPL 2.0 (see LICENSE.txt)
  *
  * See README.md for usage and integration instructions.
  */
 
-"use strict";
-/*jslint white: false, browser: true */
-/*global window, $D, Util, WebUtil, RFB, Display */
-
-var UI = {
-
-rfb_state : 'loaded',
-settingsOpen : false,
-connSettingsOpen : true,
-clipboardOpen: false,
-
-// Render default UI and initialize settings menu
-load: function() {
-    var html = '', i, sheet, sheets, llevels;
-
-    // Stylesheet selection dropdown
-    sheet = WebUtil.selectStylesheet();
-    sheets = WebUtil.getStylesheets();
-    for (i = 0; i < sheets.length; i += 1) {
-        UI.addOption($D('noVNC_stylesheet'),sheets[i].title, sheets[i].title);
-    }
-
-    // Logging selection dropdown
-    llevels = ['error', 'warn', 'info', 'debug'];
-    for (i = 0; i < llevels.length; i += 1) {
-        UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]);
-    }
-
-    // Settings with immediate effects
-    UI.initSetting('logging', 'warn');
-    WebUtil.init_logging(UI.getSetting('logging'));
-
-    UI.initSetting('stylesheet', 'default');
-    WebUtil.selectStylesheet(null); 
-    // call twice to get around webkit bug
-    WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
-
-    /* Populate the controls if defaults are provided in the URL */
-    UI.initSetting('host', ''); 
-    UI.initSetting('port', '');
-    UI.initSetting('password', '');
-    UI.initSetting('encrypt', false);
-    UI.initSetting('true_color', true);
-    UI.initSetting('cursor', false);
-    UI.initSetting('shared', true); 
-    UI.initSetting('connectTimeout', 2);
-
-    UI.rfb = RFB({'target': $D('noVNC_canvas'),
-                  'onUpdateState': UI.updateState,
-                  'onClipboard': UI.clipReceive});
-    UI.updateSettingsState(false);
-    // Unfocus clipboard when over the VNC area
-    //$D('VNC_screen').onmousemove = function () {
-    //         var keyboard = UI.rfb.get_keyboard();
-    //        if ((! keyboard) || (! keyboard.get_focused())) {
-    //            $D('VNC_clipboard_text').blur();
-    //         }
-    //    };
-
-    // Show mouse selector buttons on touch screen devices
-    if ('ontouchstart' in document.documentElement) {
-        // Show mobile buttons
-        $D('noVNC_mobile_buttons').style.display = "inline";
-        UI.setMouseButton();
-        // Remove the address bar
-        setTimeout(function() { window.scrollTo(0, 1); }, 100);
-        UI.initSetting('clip', true);
-        $D('noVNC_clip').disabled = true;
-    } else {
-        UI.initSetting('clip', false);
-    }
-
-    //iOS Safari does not support CSS position:fixed. 
-    //This detects iOS devices and enables javascript workaround.  
-    if ((navigator.userAgent.match(/iPhone/i)) ||
-        (navigator.userAgent.match(/iPod/i)) || 
-        (navigator.userAgent.match(/iPad/i))) {
-        //UI.setOnscroll();
-        //UI.setResize(); 
-    }
-    
-    $D('noVNC_host').focus();
-
-    UI.setViewClip();
-    Util.addEvent(window, 'resize', UI.setViewClip);
-
-    Util.addEvent(window, 'beforeunload', function () {
-        if (UI.rfb_state === 'normal') {
-            return "You are currently connected.";
-        }
-    } );
-
-},
-
-// Read form control compatible setting from cookie
-getSetting: function(name) {
-    var val, ctrl = $D('noVNC_' + name);
-    val = WebUtil.readCookie(name);
-    if (ctrl.type === 'checkbox') {
-        if (val.toLowerCase() in {'0':1, 'no':1, 'false':1}) {
-            val = false;
-        } else {
-            val = true;
-        }
-    }
-    return val;
-},
+/* jslint white: false, browser: true */
+/* global window, $D, Util, WebUtil, RFB, Display */
+
+var UI;
+
+(function () {
+    "use strict";
+
+    var resizeTimeout;
+
+    // Load supporting scripts
+    window.onscriptsload = function () { UI.load(); };
+    Util.load_scripts(["webutil.js", "base64.js", "websock.js", "des.js",
+                       "keysymdef.js", "keyboard.js", "input.js", "display.js",
+                       "jsunzip.js", "rfb.js", "keysym.js"]);
+
+    UI = {
+
+        rfb_state : 'loaded',
+        settingsOpen : false,
+        connSettingsOpen : false,
+        popupStatusTimeout: null,
+        clipboardOpen: false,
+        keyboardVisible: false,
+        hideKeyboardTimeout: null,
+        lastKeyboardinput: null,
+        defaultKeyboardinputLen: 100,
+        extraKeysVisible: false,
+        ctrlOn: false,
+        altOn: false,
+        isTouchDevice: false,
+
+        // Setup rfb object, load settings from browser storage, then call
+        // UI.init to setup the UI/menus
+        load: function (callback) {
+            WebUtil.initSettings(UI.start, callback);
+        },
+
+        // Render default UI and initialize settings menu
+        start: function(callback) {
+            UI.isTouchDevice = 'ontouchstart' in document.documentElement;
+
+            // Stylesheet selection dropdown
+            var sheet = WebUtil.selectStylesheet();
+            var sheets = WebUtil.getStylesheets();
+            var i;
+            for (i = 0; i < sheets.length; i += 1) {
+                UI.addOption($D('noVNC_stylesheet'),sheets[i].title, sheets[i].title);
+            }
+
+            // Logging selection dropdown
+            var llevels = ['error', 'warn', 'info', 'debug'];
+            for (i = 0; i < llevels.length; i += 1) {
+                UI.addOption($D('noVNC_logging'),llevels[i], llevels[i]);
+            }
 
-// Update cookie and form control setting. If value is not set, then
-// updates from control to current cookie setting.
-updateSetting: function(name, value) {
+            // Settings with immediate effects
+            UI.initSetting('logging', 'warn');
+            WebUtil.init_logging(UI.getSetting('logging'));
+
+            UI.initSetting('stylesheet', 'default');
+            WebUtil.selectStylesheet(null);
+            // call twice to get around webkit bug
+            WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
+
+            // if port == 80 (or 443) then it won't be present and should be
+            // set manually
+            var port = window.location.port;
+            if (!port) {
+                if (window.location.protocol.substring(0,5) == 'https') {
+                    port = 443;
+                }
+                else if (window.location.protocol.substring(0,4) == 'http') {
+                    port = 80;
+                }
+            }
 
-    var i, ctrl = $D('noVNC_' + name);
-    // Save the cookie for this session
-    if (typeof value !== 'undefined') {
-        WebUtil.createCookie(name, value);
-    }
+            /* Populate the controls if defaults are provided in the URL */
+            UI.initSetting('host', window.location.hostname);
+            UI.initSetting('port', port);
+            UI.initSetting('password', '');
+            UI.initSetting('encrypt', (window.location.protocol === "https:"));
+            UI.initSetting('true_color', true);
+            UI.initSetting('cursor', !UI.isTouchDevice);
+            UI.initSetting('resize', 'off');
+            UI.initSetting('shared', true);
+            UI.initSetting('view_only', false);
+            UI.initSetting('path', 'websockify');
+            UI.initSetting('repeaterID', '');
+
+            var autoconnect = WebUtil.getQueryVar('autoconnect', false);
+            if (autoconnect === 'true' || autoconnect == '1') {
+                autoconnect = true;
+                UI.connect();
+            } else {
+                autoconnect = false;
+            }
 
-    // Update the settings control
-    value = UI.getSetting(name);
+            UI.updateVisualState();
 
-    if (ctrl.type === 'checkbox') {
-        ctrl.checked = value;
+            $D('noVNC_host').focus();
 
-    } else if (typeof ctrl.options !== 'undefined') {
-        for (i = 0; i < ctrl.options.length; i += 1) {
-            if (ctrl.options[i].value === value) {
-                ctrl.selectedIndex = i;
-                break;
+            // Show mouse selector buttons on touch screen devices
+            if (UI.isTouchDevice) {
+                // Show mobile buttons
+                $D('noVNC_mobile_buttons').style.display = "inline";
+                UI.setMouseButton();
+                // Remove the address bar
+                setTimeout(function() { window.scrollTo(0, 1); }, 100);
+                UI.forceSetting('clip', true);
+            } else {
+                UI.initSetting('clip', false);
             }
-        }
-    } else {
-        /*Weird IE9 error leads to 'null' appearring 
-        in textboxes instead of ''.*/
-        if (value === null) {
-            value = "";
-        }
-        ctrl.value = value;
-    }
-},
-
-// Save control setting to cookie
-saveSetting: function(name) {
-    var val, ctrl = $D('noVNC_' + name);
-    if (ctrl.type === 'checkbox') {
-        val = ctrl.checked;
-    } else if (typeof ctrl.options !== 'undefined') {
-        val = ctrl.options[ctrl.selectedIndex].value;
-    } else {
-        val = ctrl.value;
-    }
-    WebUtil.createCookie(name, val);
-    //Util.Debug("Setting saved '" + name + "=" + val + "'");
-    return val;
-},
-
-// Initial page load read/initialization of settings
-initSetting: function(name, defVal) {
-    var val;
-
-    // Check Query string followed by cookie
-    val = WebUtil.getQueryVar(name);
-    if (val === null) {
-        val = WebUtil.readCookie(name, defVal);
-    }
-    UI.updateSetting(name, val);
- //Util.Debug("Setting '" + name + "' initialized to '" + val + "'");
-    return val;
-},
-
-
-// Toggle the settings menu:
-//   On open, settings are refreshed from saved cookies.
-//   On close, settings are applied
-clickSettingsMenu: function() {
-    if (UI.settingsOpen) {
-        UI.settingsApply();
-        UI.closeSettingsMenu();
-    } else {
-        UI.updateSetting('encrypt');
-        UI.updateSetting('true_color');
-        if (UI.rfb.get_display().get_cursor_uri()) {
-            UI.updateSetting('cursor');
-        } else {
-            UI.updateSetting('cursor', false);
-            $D('noVNC_cursor').disabled = true;
-        }
-        UI.updateSetting('clip');
-        UI.updateSetting('shared');
-        UI.updateSetting('connectTimeout');
-        UI.updateSetting('stylesheet');
-        UI.updateSetting('logging');
-
-        UI.openSettingsMenu();
-    }
-},
-
-// Open menu
-openSettingsMenu: function() {
-    if (UI.clipboardOpen == true) {
-        UI.showClipboard(); 
-    } 
-    //Close connection settings if open
-    if (UI.connSettingsOpen == true) {
-        UI.connectPanelbutton();
-    }
-    $D('noVNC_settings').style.display = "block";
-    UI.settingsOpen = true;
-},
-
-// Close menu (without applying settings)
-closeSettingsMenu: function() {
-    $D('noVNC_settings').style.display = "none";
-    UI.settingsOpen = false;
-},
-
-// Save/apply settings when 'Apply' button is pressed
-settingsApply: function() {
-    //Util.Debug(">> settingsApply");
-    UI.saveSetting('encrypt');
-    UI.saveSetting('true_color');
-    if (UI.rfb.get_display().get_cursor_uri()) {
-        UI.saveSetting('cursor');
-    }
-    UI.saveSetting('clip');
-    UI.saveSetting('shared');
-    UI.saveSetting('connectTimeout');
-    UI.saveSetting('stylesheet');
-    UI.saveSetting('logging');
-
-    // Settings with immediate (non-connected related) effect
-    WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
-    WebUtil.init_logging(UI.getSetting('logging'));
-    UI.setViewClip();
-    //Util.Debug("<< settingsApply");
-},
-
-
-
-setPassword: function() {
-    UI.rfb.sendPassword($D('noVNC_password').value);
-    //Reset connect button.
-    $D('noVNC_connect_button').value = "Connect";
-    $D('noVNC_connect_button').onclick = UI.Connect;
-    //Hide connection panel.
-    UI.connectPanelbutton();
-    return false;
-},
-
-sendCtrlAltDel: function() {
-    UI.rfb.sendCtrlAltDel();
-},
-
-setMouseButton: function(num) {
-    var b, blist = [0, 1,2,4], button;
-
-    if (typeof num === 'undefined') {
-        // Disable mouse buttons
-        num = -1;
-    }
-    if (UI.rfb) {
-        UI.rfb.get_mouse().set_touchButton(num);
-    }
-
-    for (b = 0; b < blist.length; b++) {
-        button = $D('noVNC_mouse_button' + blist[b]);
-        if (blist[b] === num) {
-            button.style.display = "";
-        } else {
-            button.style.display = "none";
-            /*
-            button.style.backgroundColor = "black";
-            button.style.color = "lightgray";
-            button.style.backgroundColor = "";
-            button.style.color = "";
-            */
-        }
-    }
-},
-
-updateState: function(rfb, state, oldstate, msg) {
-    var s, sb, c, d, cad, vd, klass;
-    UI.rfb_state = state;
-    s = $D('noVNC_status');
-    sb = $D('noVNC_status_bar');
-    c = $D('connectPanelbutton');
-    d = $D('disconnectButton');
-    switch (state) {
-        case 'failed':
-        case 'fatal':
-            c.style.display = "";
-            d.style.display = "none";
-            UI.updateSettingsState(false);
-            klass = "noVNC_status_error";
-            break;
-        case 'normal':
-            c.style.display = "none";
-            d.style.display = "";
-            UI.updateSettingsState(true);
-            klass = "noVNC_status_normal";
-            break;
-        case 'disconnected':
+
+            UI.setViewClip();
+            UI.setBarPosition();
+
+            Util.addEvent(window, 'resize', function () {
+                UI.onresize();
+                UI.setViewClip();
+                UI.updateViewDrag();
+                UI.setBarPosition();
+            } );
+
+            // Hide the button if fullscreen isn't supported
+            if (!document.documentElement.requestFullscreen &&
+                !document.documentElement.mozRequestFullScreen &&
+                !document.documentElement.webkitRequestFullscreen &&
+                !document.body.msRequestFullscreen) {
+                $D('fullscreenButton').style.display = "none";
+            } else {
+                Util.addEvent(window, 'fullscreenchange', UI.updateFullscreenButton);
+                Util.addEvent(window, 'mozfullscreenchange', UI.updateFullscreenButton);
+                Util.addEvent(window, 'webkitfullscreenchange', UI.updateFullscreenButton);
+                Util.addEvent(window, 'msfullscreenchange', UI.updateFullscreenButton);
+            }
+
+            Util.addEvent(window, 'load', UI.keyboardinputReset);
+
+            Util.addEvent(window, 'beforeunload', function () {
+                if (UI.rfb && UI.rfb_state === 'normal') {
+                    return "You are currently connected.";
+                }
+            } );
+
+            // Show description by default when hosted at for kanaka.github.com
+            if (location.host === "kanaka.github.io") {
+                // Open the description dialog
+                $D('noVNC_description').style.display = "block";
+            } else {
+                // Show the connect panel on first load unless autoconnecting
+                if (autoconnect === UI.connSettingsOpen) {
+                    UI.toggleConnectPanel();
+                }
+            }
+
+            // Add mouse event click/focus/blur event handlers to the UI
+            UI.addMouseHandlers();
+
+            if (typeof callback === "function") {
+                callback(UI.rfb);
+            }
+        },
+
+        initRFB: function () {
+            try {
+                UI.rfb = new RFB({'target': $D('noVNC_canvas'),
+                                  'onUpdateState': UI.updateState,
+                                  'onXvpInit': UI.updateXvpVisualState,
+                                  'onClipboard': UI.clipReceive,
+                                  'onFBUComplete': UI.FBUComplete,
+                                  'onFBResize': UI.updateViewDrag,
+                                  'onDesktopName': UI.updateDocumentTitle});
+                return true;
+            } catch (exc) {
+                UI.updateState(null, 'fatal', null, 'Unable to create RFB client -- ' + exc);
+                return false;
+            }
+        },
+
+        addMouseHandlers: function() {
+            // Setup interface handlers that can't be inline
+            $D("noVNC_view_drag_button").onclick = UI.toggleViewDrag;
+            $D("noVNC_mouse_button0").onclick = function () { UI.setMouseButton(1); };
+            $D("noVNC_mouse_button1").onclick = function () { UI.setMouseButton(2); };
+            $D("noVNC_mouse_button2").onclick = function () { UI.setMouseButton(4); };
+            $D("noVNC_mouse_button4").onclick = function () { UI.setMouseButton(0); };
+            $D("showKeyboard").onclick = UI.showKeyboard;
+
+            $D("keyboardinput").oninput = UI.keyInput;
+            $D("keyboardinput").onblur = UI.keyInputBlur;
+            $D("keyboardinput").onsubmit = function () { return false; };
+
+            $D("showExtraKeysButton").onclick = UI.showExtraKeys;
+            $D("toggleCtrlButton").onclick = UI.toggleCtrl;
+            $D("toggleAltButton").onclick = UI.toggleAlt;
+            $D("sendTabButton").onclick = UI.sendTab;
+            $D("sendEscButton").onclick = UI.sendEsc;
+
+            $D("sendCtrlAltDelButton").onclick = UI.sendCtrlAltDel;
+            $D("xvpShutdownButton").onclick = UI.xvpShutdown;
+            $D("xvpRebootButton").onclick = UI.xvpReboot;
+            $D("xvpResetButton").onclick = UI.xvpReset;
+            $D("noVNC_status").onclick = UI.togglePopupStatus;
+            $D("noVNC_popup_status").onclick = UI.togglePopupStatus;
+            $D("xvpButton").onclick = UI.toggleXvpPanel;
+            $D("clipboardButton").onclick = UI.toggleClipboardPanel;
+            $D("fullscreenButton").onclick = UI.toggleFullscreen;
+            $D("settingsButton").onclick = UI.toggleSettingsPanel;
+            $D("connectButton").onclick = UI.toggleConnectPanel;
+            $D("disconnectButton").onclick = UI.disconnect;
+            $D("descriptionButton").onclick = UI.toggleConnectPanel;
+
+            $D("noVNC_clipboard_text").onfocus = UI.displayBlur;
+            $D("noVNC_clipboard_text").onblur = UI.displayFocus;
+            $D("noVNC_clipboard_text").onchange = UI.clipSend;
+            $D("noVNC_clipboard_clear_button").onclick = UI.clipClear;
+
+            $D("noVNC_settings_menu").onmouseover = UI.displayBlur;
+            $D("noVNC_settings_menu").onmouseover = UI.displayFocus;
+            $D("noVNC_apply").onclick = UI.settingsApply;
+
+            $D("noVNC_connect_button").onclick = UI.connect;
+
+            $D("noVNC_resize").onchange = function () {
+                var connected = UI.rfb && UI.rfb_state === 'normal';
+                UI.enableDisableViewClip(connected);
+            };
+        },
+
+        onresize: function (callback) {
+            if (!UI.rfb) return;
+
+            var size = UI.getCanvasLimit();
+
+            if (size && UI.rfb_state === 'normal' && UI.rfb.get_display()) {
+                var display = UI.rfb.get_display();
+                var scaleType = UI.getSetting('resize');
+                if (scaleType === 'remote') {
+                    // use remote resizing
+
+                    // When the local window has been resized, wait until the size remains
+                    // the same for 0.5 seconds before sending the request for changing
+                    // the resolution of the session
+                    clearTimeout(resizeTimeout);
+                    resizeTimeout = setTimeout(function(){
+                        display.set_maxWidth(size.w);
+                        display.set_maxHeight(size.h);
+                        Util.Debug('Attempting setDesktopSize(' +
+                                   size.w + ', ' + size.h + ')');
+                        UI.rfb.setDesktopSize(size.w, size.h);
+                    }, 500);
+                } else if (scaleType === 'scale' || scaleType === 'downscale') {
+                    // use local scaling
+
+                    var downscaleOnly = scaleType === 'downscale';
+                    var scaleRatio = display.autoscale(size.w, size.h, downscaleOnly);
+                    UI.rfb.get_mouse().set_scale(scaleRatio);
+                    Util.Debug('Scaling by ' + UI.rfb.get_mouse().get_scale());
+                }
+            }
+        },
+
+        getCanvasLimit: function () {
+            var container = $D('noVNC_container');
+
+            // Hide the scrollbars until the size is calculated
+            container.style.overflow = "hidden";
+
+            var pos = Util.getPosition(container);
+            var w = pos.width;
+            var h = pos.height;
+
+            container.style.overflow = "visible";
+
+            if (isNaN(w) || isNaN(h)) {
+                return false;
+            } else {
+                return {w: w, h: h};
+            }
+        },
+
+        // Read form control compatible setting from cookie
+        getSetting: function(name) {
+            var ctrl = $D('noVNC_' + name);
+            var val = WebUtil.readSetting(name);
+            if (typeof val !== 'undefined' && val !== null && ctrl.type === 'checkbox') {
+                if (val.toString().toLowerCase() in {'0':1, 'no':1, 'false':1}) {
+                    val = false;
+                } else {
+                    val = true;
+                }
+            }
+            return val;
+        },
+
+        // Update cookie and form control setting. If value is not set, then
+        // updates from control to current cookie setting.
+        updateSetting: function(name, value) {
+
+            // Save the cookie for this session
+            if (typeof value !== 'undefined') {
+                WebUtil.writeSetting(name, value);
+            }
+
+            // Update the settings control
+            value = UI.getSetting(name);
+
+            var ctrl = $D('noVNC_' + name);
+            if (ctrl.type === 'checkbox') {
+                ctrl.checked = value;
+
+            } else if (typeof ctrl.options !== 'undefined') {
+                for (var i = 0; i < ctrl.options.length; i += 1) {
+                    if (ctrl.options[i].value === value) {
+                        ctrl.selectedIndex = i;
+                        break;
+                    }
+                }
+            } else {
+                /*Weird IE9 error leads to 'null' appearring
+                in textboxes instead of ''.*/
+                if (value === null) {
+                    value = "";
+                }
+                ctrl.value = value;
+            }
+        },
+
+        // Save control setting to cookie
+        saveSetting: function(name) {
+            var val, ctrl = $D('noVNC_' + name);
+            if (ctrl.type === 'checkbox') {
+                val = ctrl.checked;
+            } else if (typeof ctrl.options !== 'undefined') {
+                val = ctrl.options[ctrl.selectedIndex].value;
+            } else {
+                val = ctrl.value;
+            }
+            WebUtil.writeSetting(name, val);
+            //Util.Debug("Setting saved '" + name + "=" + val + "'");
+            return val;
+        },
+
+        // Initial page load read/initialization of settings
+        initSetting: function(name, defVal) {
+            // Check Query string followed by cookie
+            var val = WebUtil.getQueryVar(name);
+            if (val === null) {
+                val = WebUtil.readSetting(name, defVal);
+            }
+            UI.updateSetting(name, val);
+            return val;
+        },
+
+        // Force a setting to be a certain value
+        forceSetting: function(name, val) {
+            UI.updateSetting(name, val);
+            return val;
+        },
+
+
+        // Show the popup status
+        togglePopupStatus: function(text) {
+            var psp = $D('noVNC_popup_status');
+
+            var closePopup = function() { psp.style.display = "none"; };
+
+            if (window.getComputedStyle(psp).display === 'none') {
+                if (typeof text === 'string') {
+                    psp.innerHTML = text;
+                } else {
+                    psp.innerHTML = $D('noVNC_status').innerHTML;
+                }
+                psp.style.display = "block";
+                psp.style.left = window.innerWidth/2 -
+                    parseInt(window.getComputedStyle(psp).width)/2 -30 + "px";
+
+                // Show the popup for a maximum of 1.5 seconds
+                UI.popupStatusTimeout = setTimeout(function() { closePopup(); }, 1500);
+            } else {
+                clearTimeout(UI.popupStatusTimeout);
+                closePopup();
+            }
+        },
+
+        // Show the XVP panel
+        toggleXvpPanel: function() {
+            // Close the description panel
+            $D('noVNC_description').style.display = "none";
+            // Close settings if open
+            if (UI.settingsOpen === true) {
+                UI.settingsApply();
+                UI.closeSettingsMenu();
+            }
+            // Close connection settings if open
+            if (UI.connSettingsOpen === true) {
+                UI.toggleConnectPanel();
+            }
+            // Close clipboard panel if open
+            if (UI.clipboardOpen === true) {
+                UI.toggleClipboardPanel();
+            }
+            // Toggle XVP panel
+            if (UI.xvpOpen === true) {
+                $D('noVNC_xvp').style.display = "none";
+                $D('xvpButton').className = "noVNC_status_button";
+                UI.xvpOpen = false;
+            } else {
+                $D('noVNC_xvp').style.display = "block";
+                $D('xvpButton').className = "noVNC_status_button_selected";
+                UI.xvpOpen = true;
+            }
+        },
+
+        // Show the clipboard panel
+        toggleClipboardPanel: function() {
+            // Close the description panel
+            $D('noVNC_description').style.display = "none";
+            // Close settings if open
+            if (UI.settingsOpen === true) {
+                UI.settingsApply();
+                UI.closeSettingsMenu();
+            }
+            // Close connection settings if open
+            if (UI.connSettingsOpen === true) {
+                UI.toggleConnectPanel();
+            }
+            // Close XVP panel if open
+            if (UI.xvpOpen === true) {
+                UI.toggleXvpPanel();
+            }
+            // Toggle Clipboard Panel
+            if (UI.clipboardOpen === true) {
+                $D('noVNC_clipboard').style.display = "none";
+                $D('clipboardButton').className = "noVNC_status_button";
+                UI.clipboardOpen = false;
+            } else {
+                $D('noVNC_clipboard').style.display = "block";
+                $D('clipboardButton').className = "noVNC_status_button_selected";
+                UI.clipboardOpen = true;
+            }
+        },
+
+        // Toggle fullscreen mode
+        toggleFullscreen: function() {
+            if (document.fullscreenElement || // alternative standard method
+                document.mozFullScreenElement || // currently working methods
+                document.webkitFullscreenElement ||
+                document.msFullscreenElement ) {
+                if (document.exitFullscreen) {
+                    document.exitFullscreen();
+                } else if (document.mozCancelFullScreen) {
+                    document.mozCancelFullScreen();
+                } else if (document.webkitExitFullscreen) {
+                    document.webkitExitFullscreen();
+                } else if (document.msExitFullscreen) {
+                    document.msExitFullscreen();
+                }
+            } else {
+                if (document.documentElement.requestFullscreen) {
+                    document.documentElement.requestFullscreen();
+                } else if (document.documentElement.mozRequestFullScreen) {
+                    document.documentElement.mozRequestFullScreen();
+                } else if (document.documentElement.webkitRequestFullscreen) {
+                    document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
+                } else if (document.body.msRequestFullscreen) {
+                    document.body.msRequestFullscreen();
+                }
+            }
+            UI.updateFullscreenButton();
+        },
+
+        updateFullscreenButton: function() {
+            if (document.fullscreenElement || // alternative standard method
+                document.mozFullScreenElement || // currently working methods
+                document.webkitFullscreenElement ||
+                document.msFullscreenElement ) {
+                $D('fullscreenButton').className = "noVNC_status_button_selected";
+            } else {
+                $D('fullscreenButton').className = "noVNC_status_button";
+            }
+        },
+
+        // Show the connection settings panel/menu
+        toggleConnectPanel: function() {
+            // Close the description panel
+            $D('noVNC_description').style.display = "none";
+            // Close connection settings if open
+            if (UI.settingsOpen === true) {
+                UI.settingsApply();
+                UI.closeSettingsMenu();
+                $D('connectButton').className = "noVNC_status_button";
+            }
+            // Close clipboard panel if open
+            if (UI.clipboardOpen === true) {
+                UI.toggleClipboardPanel();
+            }
+            // Close XVP panel if open
+            if (UI.xvpOpen === true) {
+                UI.toggleXvpPanel();
+            }
+
+            // Toggle Connection Panel
+            if (UI.connSettingsOpen === true) {
+                $D('noVNC_controls').style.display = "none";
+                $D('connectButton').className = "noVNC_status_button";
+                UI.connSettingsOpen = false;
+                UI.saveSetting('host');
+                UI.saveSetting('port');
+                //UI.saveSetting('password');
+            } else {
+                $D('noVNC_controls').style.display = "block";
+                $D('connectButton').className = "noVNC_status_button_selected";
+                UI.connSettingsOpen = true;
+                $D('noVNC_host').focus();
+            }
+        },
+
+        // Toggle the settings menu:
+        //   On open, settings are refreshed from saved cookies.
+        //   On close, settings are applied
+        toggleSettingsPanel: function() {
+            // Close the description panel
+            $D('noVNC_description').style.display = "none";
+            if (UI.settingsOpen) {
+                UI.settingsApply();
+                UI.closeSettingsMenu();
+            } else {
+                UI.updateSetting('encrypt');
+                UI.updateSetting('true_color');
+                if (Util.browserSupportsCursorURIs()) {
+                    UI.updateSetting('cursor');
+                } else {
+                    UI.updateSetting('cursor', !UI.isTouchDevice);
+                    $D('noVNC_cursor').disabled = true;
+                }
+                UI.updateSetting('clip');
+                UI.updateSetting('resize');
+                UI.updateSetting('shared');
+                UI.updateSetting('view_only');
+                UI.updateSetting('path');
+                UI.updateSetting('repeaterID');
+                UI.updateSetting('stylesheet');
+                UI.updateSetting('logging');
+
+                UI.openSettingsMenu();
+            }
+        },
+
+        // Open menu
+        openSettingsMenu: function() {
+            // Close the description panel
+            $D('noVNC_description').style.display = "none";
+            // Close clipboard panel if open
+            if (UI.clipboardOpen === true) {
+                UI.toggleClipboardPanel();
+            }
+            // Close connection settings if open
+            if (UI.connSettingsOpen === true) {
+                UI.toggleConnectPanel();
+            }
+            // Close XVP panel if open
+            if (UI.xvpOpen === true) {
+                UI.toggleXvpPanel();
+            }
+            $D('noVNC_settings').style.display = "block";
+            $D('settingsButton').className = "noVNC_status_button_selected";
+            UI.settingsOpen = true;
+        },
+
+        // Close menu (without applying settings)
+        closeSettingsMenu: function() {
+            $D('noVNC_settings').style.display = "none";
+            $D('settingsButton').className = "noVNC_status_button";
+            UI.settingsOpen = false;
+        },
+
+        // Save/apply settings when 'Apply' button is pressed
+        settingsApply: function() {
+            //Util.Debug(">> settingsApply");
+            UI.saveSetting('encrypt');
+            UI.saveSetting('true_color');
+            if (Util.browserSupportsCursorURIs()) {
+                UI.saveSetting('cursor');
+            }
+
+            UI.saveSetting('resize');
+
+            if (UI.getSetting('resize') === 'downscale' || UI.getSetting('resize') === 'scale') {
+                UI.forceSetting('clip', false);
+            }
+
+            UI.saveSetting('clip');
+            UI.saveSetting('shared');
+            UI.saveSetting('view_only');
+            UI.saveSetting('path');
+            UI.saveSetting('repeaterID');
+            UI.saveSetting('stylesheet');
+            UI.saveSetting('logging');
+
+            // Settings with immediate (non-connected related) effect
+            WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
+            WebUtil.init_logging(UI.getSetting('logging'));
+            UI.setViewClip();
+            UI.updateViewDrag();
+            //Util.Debug("<< settingsApply");
+        },
+
+
+
+        setPassword: function() {
+            UI.rfb.sendPassword($D('noVNC_password').value);
+            //Reset connect button.
+            $D('noVNC_connect_button').value = "Connect";
+            $D('noVNC_connect_button').onclick = UI.Connect;
+            //Hide connection panel.
+            UI.toggleConnectPanel();
+            return false;
+        },
+
+        sendCtrlAltDel: function() {
+            UI.rfb.sendCtrlAltDel();
+        },
+
+        xvpShutdown: function() {
+            UI.rfb.xvpShutdown();
+        },
+
+        xvpReboot: function() {
+            UI.rfb.xvpReboot();
+        },
+
+        xvpReset: function() {
+            UI.rfb.xvpReset();
+        },
+
+        setMouseButton: function(num) {
+            if (typeof num === 'undefined') {
+                // Disable mouse buttons
+                num = -1;
+            }
+            if (UI.rfb) {
+                UI.rfb.get_mouse().set_touchButton(num);
+            }
+
+            var blist = [0, 1,2,4];
+            for (var b = 0; b < blist.length; b++) {
+                var button = $D('noVNC_mouse_button' + blist[b]);
+                if (blist[b] === num) {
+                    button.style.display = "";
+                } else {
+                    button.style.display = "none";
+                }
+            }
+        },
+
+        updateState: function(rfb, state, oldstate, msg) {
+            UI.rfb_state = state;
+            var klass;
+            switch (state) {
+                case 'failed':
+                case 'fatal':
+                    klass = "noVNC_status_error";
+                    break;
+                case 'normal':
+                    klass = "noVNC_status_normal";
+                    break;
+                case 'disconnected':
+                    $D('noVNC_logo').style.display = "block";
+                    $D('noVNC_container').style.display = "none";
+                    /* falls through */
+                case 'loaded':
+                    klass = "noVNC_status_normal";
+                    break;
+                case 'password':
+                    UI.toggleConnectPanel();
+
+                    $D('noVNC_connect_button').value = "Send Password";
+                    $D('noVNC_connect_button').onclick = UI.setPassword;
+                    $D('noVNC_password').focus();
+
+                    klass = "noVNC_status_warn";
+                    break;
+                default:
+                    klass = "noVNC_status_warn";
+                    break;
+            }
+
+            if (typeof(msg) !== 'undefined') {
+                $D('noVNC-control-bar').setAttribute("class", klass);
+                $D('noVNC_status').innerHTML = msg;
+            }
+
+            UI.updateVisualState();
+        },
+
+        // Disable/enable controls depending on connection state
+        updateVisualState: function() {
+            var connected = UI.rfb && UI.rfb_state === 'normal';
+
+            //Util.Debug(">> updateVisualState");
+            $D('noVNC_encrypt').disabled = connected;
+            $D('noVNC_true_color').disabled = connected;
+            if (Util.browserSupportsCursorURIs()) {
+                $D('noVNC_cursor').disabled = connected;
+            } else {
+                UI.updateSetting('cursor', !UI.isTouchDevice);
+                $D('noVNC_cursor').disabled = true;
+            }
+
+            UI.enableDisableViewClip(connected);
+            $D('noVNC_resize').disabled = connected;
+            $D('noVNC_shared').disabled = connected;
+            $D('noVNC_view_only').disabled = connected;
+            $D('noVNC_path').disabled = connected;
+            $D('noVNC_repeaterID').disabled = connected;
+
+            if (connected) {
+                UI.setViewClip();
+                UI.setMouseButton(1);
+                $D('clipboardButton').style.display = "inline";
+                $D('showKeyboard').style.display = "inline";
+                $D('noVNC_extra_keys').style.display = "";
+                $D('sendCtrlAltDelButton').style.display = "inline";
+            } else {
+                UI.setMouseButton();
+                $D('clipboardButton').style.display = "none";
+                $D('showKeyboard').style.display = "none";
+                $D('noVNC_extra_keys').style.display = "none";
+                $D('sendCtrlAltDelButton').style.display = "none";
+                UI.updateXvpVisualState(0);
+            }
+
+            // State change disables viewport dragging.
+            // It is enabled (toggled) by direct click on the button
+            UI.updateViewDrag(false);
+
+            switch (UI.rfb_state) {
+                case 'fatal':
+                case 'failed':
+                case 'disconnected':
+                    $D('connectButton').style.display = "";
+                    $D('disconnectButton').style.display = "none";
+                    UI.connSettingsOpen = false;
+                    UI.toggleConnectPanel();
+                    break;
+                case 'loaded':
+                    $D('connectButton').style.display = "";
+                    $D('disconnectButton').style.display = "none";
+                    break;
+                default:
+                    $D('connectButton').style.display = "none";
+                    $D('disconnectButton').style.display = "";
+                    break;
+            }
+
+            //Util.Debug("<< updateVisualState");
+        },
+
+        // Disable/enable XVP button
+        updateXvpVisualState: function(ver) {
+            if (ver >= 1) {
+                $D('xvpButton').style.display = 'inline';
+            } else {
+                $D('xvpButton').style.display = 'none';
+                // Close XVP panel if open
+                if (UI.xvpOpen === true) {
+                    UI.toggleXvpPanel();
+                }
+            }
+        },
+
+        // This resize can not be done until we know from the first Frame Buffer Update
+        // if it is supported or not.
+        // The resize is needed to make sure the server desktop size is updated to the
+        // corresponding size of the current local window when reconnecting to an
+        // existing session.
+        FBUComplete: function(rfb, fbu) {
+            UI.onresize();
+            UI.rfb.set_onFBUComplete(function() { });
+        },
+
+        // Display the desktop name in the document title
+        updateDocumentTitle: function(rfb, name) {
+            document.title = name + " - noVNC";
+        },
+
+        clipReceive: function(rfb, text) {
+            Util.Debug(">> UI.clipReceive: " + text.substr(0,40) + "...");
+            $D('noVNC_clipboard_text').value = text;
+            Util.Debug("<< UI.clipReceive");
+        },
+
+        connect: function() {
+            UI.closeSettingsMenu();
+            UI.toggleConnectPanel();
+
+            var host = $D('noVNC_host').value;
+            var port = $D('noVNC_port').value;
+            var password = $D('noVNC_password').value;
+            var path = $D('noVNC_path').value;
+            if ((!host) || (!port)) {
+                throw new Error("Must set host and port");
+            }
+
+            if (!UI.initRFB()) return;
+
+            UI.rfb.set_encrypt(UI.getSetting('encrypt'));
+            UI.rfb.set_true_color(UI.getSetting('true_color'));
+            UI.rfb.set_local_cursor(UI.getSetting('cursor'));
+            UI.rfb.set_shared(UI.getSetting('shared'));
+            UI.rfb.set_view_only(UI.getSetting('view_only'));
+            UI.rfb.set_repeaterID(UI.getSetting('repeaterID'));
+
+            UI.rfb.connect(host, port, password, path);
+
+            //Close dialog.
+            setTimeout(UI.setBarPosition, 100);
+            $D('noVNC_logo').style.display = "none";
+            $D('noVNC_container').style.display = "inline";
+        },
+
+        disconnect: function() {
+            UI.closeSettingsMenu();
+            UI.rfb.disconnect();
+
+            // Restore the callback used for initial resize
+            UI.rfb.set_onFBUComplete(UI.FBUComplete);
+
             $D('noVNC_logo').style.display = "block";
-        case 'loaded':
-            //c.value = "Connection";
-            c.style.display = "";
-            d.style.display = "none";
-            UI.updateSettingsState(false);
-            klass = "noVNC_status_normal";
-            break;
-        case 'password':
-            UI.connectPanelbutton();
-
-            $D('noVNC_connect_button').value = "Send Password";
-            $D('noVNC_connect_button').onclick = UI.setPassword;
-            $D('noVNC_password').focus();
-
-            c.style.display = "none";
-            d.style.display = "";
-            UI.updateSettingsState(false);
-            klass = "noVNC_status_warn";
-            break;
-        default:
-            c.style.display = "none";
-            d.style.display = "";
-            UI.updateSettingsState(false);
-            klass = "noVNC_status_warn";
-            break;
-    }
-
-    if (typeof(msg) !== 'undefined') {
-        s.setAttribute("class", klass);
-        sb.setAttribute("class", klass);
-        s.innerHTML = msg;
-    }
-
-},
-
-// Disable/enable controls depending on connection state
-updateSettingsState: function(connected) {
-
-    //Util.Debug(">> updateSettingsState");
-    $D('noVNC_encrypt').disabled = connected;
-    $D('noVNC_true_color').disabled = connected;
-    if (UI.rfb && UI.rfb.get_display() &&
-        UI.rfb.get_display().get_cursor_uri()) {
-        $D('noVNC_cursor').disabled = connected;
-    } else {
-        UI.updateSetting('cursor', false);
-        $D('noVNC_cursor').disabled = true;
-    }
-    $D('noVNC_shared').disabled = connected;
-    $D('noVNC_connectTimeout').disabled = connected;
-
-    if (connected) {
-        UI.setViewClip();
-        UI.setMouseButton(1);
-        $D('sendCtrlAltDelButton').style.display = "inline";
-        $D('noVNC_view_drag_button').style.display = "inline";
-    } else {
-        UI.setMouseButton();
-        $D('sendCtrlAltDelButton').style.display = "none";
-        $D('noVNC_view_drag_button').style.display = "none";
-    }
-
-    // State change disables viewport dragging.
-    // It is enabled (toggled) by direct click on the button
-    UI.setViewDrag(false);
-    //Util.Debug("<< updateSettingsState");
-},
-
-
-clipReceive: function(rfb, text) {
-    Util.Debug(">> UI.clipReceive: " + text.substr(0,40) + "...");
-    $D('noVNC_clipboard_text').value = text;
-    Util.Debug("<< UI.clipReceive");
-},
-
-
-connect: function() {
-    var host, port, password;
-
-    UI.closeSettingsMenu();
-    UI.connectPanelbutton();
-
-    host = $D('noVNC_host').value;
-    port = $D('noVNC_port').value;
-    password = $D('noVNC_password').value;
-    if ((!host) || (!port)) {
-        throw("Must set host and port");
-    }
-
-    UI.rfb.set_encrypt(UI.getSetting('encrypt'));
-    UI.rfb.set_true_color(UI.getSetting('true_color'));
-    UI.rfb.set_local_cursor(UI.getSetting('cursor'));
-    UI.rfb.set_shared(UI.getSetting('shared'));
-    UI.rfb.set_connectTimeout(UI.getSetting('connectTimeout'));
-
-    UI.rfb.connect(host, port, password);
-    //Close dialog.
-    setTimeout(UI.setBarPosition, 100);
-    $D('noVNC_logo').style.display = "none";
-},
-
-disconnect: function() {
-    UI.closeSettingsMenu();
-    UI.rfb.disconnect();
-
-    $D('noVNC_logo').style.display = "block";
-    UI.connSettingsOpen = false;
-    UI.connectPanelbutton();
-},
-
-displayBlur: function() {
-    UI.rfb.get_keyboard().set_focused(false);
-    UI.rfb.get_mouse().set_focused(false);
-},
-
-displayFocus: function() {
-    UI.rfb.get_keyboard().set_focused(true);
-    UI.rfb.get_mouse().set_focused(true);
-},
-
-clipClear: function() {
-    $D('noVNC_clipboard_text').value = "";
-    UI.rfb.clipboardPasteFrom("");
-},
-
-clipSend: function() {
-    var text = $D('noVNC_clipboard_text').value;
-    Util.Debug(">> UI.clipSend: " + text.substr(0,40) + "...");
-    UI.rfb.clipboardPasteFrom(text);
-    Util.Debug("<< UI.clipSend");
-},
-
-showClipboard: function() {
-    //Close settings if open
-    if (UI.settingsOpen == true) {
-        UI.settingsApply();
-        UI.closeSettingsMenu();
-    }
-    //Close connection settings if open
-    if (UI.connSettingsOpen == true) {
-        UI.connectPanelbutton();
-    }
-    //Toggle Connection Panel
-    if (UI.clipboardOpen == true) {
-        $D('noVNC_clipboard').style.display = "none";
-        UI.clipboardOpen = false;
-    } else {
-        $D('noVNC_clipboard').style.display = "block";
-        UI.clipboardOpen = true;
-    }
-},
-
-setViewClip: function(clip) {
-    var display, cur_clip, pos, new_w, new_h;
-
-    if (UI.rfb) {
-        display = UI.rfb.get_display();
-    } else {
-        return;
-    }
-
-    cur_clip = display.get_viewport();
-
-    if (typeof(clip) === 'undefined') {
-        // Nothing
-        clip = UI.getSetting('clip');
-    }
-
-    if (clip && !cur_clip) {
-        // Turn clipping on
-        UI.updateSetting('clip', true);
-    } else if (!clip && cur_clip) {
-        // Turn clipping off
-        UI.updateSetting('clip', false);
-        display.set_viewport(false);
-        $D('noVNC_canvas').style.position = 'static';
-        display.viewportChange();
-    }
-    if (UI.getSetting('clip')) {
-        // If clipping, update clipping settings
-        $D('noVNC_canvas').style.position = 'absolute';
-        pos = Util.getPosition($D('noVNC_canvas'));
-        new_w = window.innerWidth - pos.x;
-        new_h = window.innerHeight - pos.y;
-        display.set_viewport(true);
-        display.viewportChange(0, 0, new_w, new_h);
-    }
-},
-
-setViewDrag: function(drag) {
-    var vmb = $D('noVNC_view_drag_button');
-    if (!UI.rfb) { return; }
-
-    if (typeof(drag) === "undefined") {
-        // If not specified, then toggle
-        drag = !UI.rfb.get_viewportDrag();
-    }
-    if (drag) {
-        vmb.style.backgroundColor = "black";
-        vmb.style.color = "lightgray";
-        UI.rfb.set_viewportDrag(true);
-    } else {
-        vmb.style.backgroundColor = "";
-        vmb.style.color = "";
-        UI.rfb.set_viewportDrag(false);
-    }
-},
-
-showKeyboard: function() {
-    $D('keyboardinput').focus();
-},
-
-
-changeViewportMeta: function (newattributes) {
-
-    // First, get the array of meta-tag elements
-    var metatags = document.getElementsByTagName("meta");
-
-    // Update only the Viewport meta tag
-    for (var cnt = 0; cnt < metatags.length; cnt++)
-    {
-        var name = metatags[cnt].getAttribute("name");
-        var content = metatags[cnt].getAttribute("content");
-
-        // Update the Viewport meta tag
-        if (metatags[cnt].getAttribute("name") == "viewport") {
-            metatags[cnt].setAttribute("content", newattributes);
-        }
-    }
-},
+            $D('noVNC_container').style.display = "none";
+
+            // Don't display the connection settings until we're actually disconnected
+        },
+
+        displayBlur: function() {
+            if (!UI.rfb) return;
+
+            UI.rfb.get_keyboard().set_focused(false);
+            UI.rfb.get_mouse().set_focused(false);
+        },
+
+        displayFocus: function() {
+            if (!UI.rfb) return;
+
+            UI.rfb.get_keyboard().set_focused(true);
+            UI.rfb.get_mouse().set_focused(true);
+        },
+
+        clipClear: function() {
+            $D('noVNC_clipboard_text').value = "";
+            UI.rfb.clipboardPasteFrom("");
+        },
+
+        clipSend: function() {
+            var text = $D('noVNC_clipboard_text').value;
+            Util.Debug(">> UI.clipSend: " + text.substr(0,40) + "...");
+            UI.rfb.clipboardPasteFrom(text);
+            Util.Debug("<< UI.clipSend");
+        },
+
+        // Set and configure viewport clipping
+        setViewClip: function(clip) {
+            var display;
+            if (UI.rfb) {
+                display = UI.rfb.get_display();
+            } else {
+                return;
+            }
 
-//iOS < Version 5 does not support position fixed. Javascript workaround:
-setOnscroll: function() {
-    window.onscroll = function() {
-        UI.setBarPosition();
-    };
-},
+            var cur_clip = display.get_viewport();
 
-setResize: function () {
-    window.onResize = function() {
-        UI.setBarPosition();
-    };
-},
-
-//Helper to add options to dropdown.
-addOption: function(selectbox,text,value )
-{
-    var optn = document.createElement("OPTION");
-    optn.text = text;
-    optn.value = value;
-    selectbox.options.add(optn);
-},
-
-setBarPosition: function() {
-    $D('noVNC-control-bar').style.top = (window.pageYOffset) + 'px';
-    $D('noVNC_mobile_buttons').style.left = (window.pageXOffset) + 'px';
-    
-    var vncwidth = $D('noVNC_screen').style.offsetWidth;
-    $D('noVNC-control-bar').style.width = vncwidth + 'px';
-},
-
-connectPanelbutton: function() {
-    //Close connection settings if open
-    if (UI.settingsOpen == true) {
-        UI.settingsApply();
-        UI.closeSettingsMenu();
-    }
-    if (UI.clipboardOpen == true) {
-        UI.showClipboard(); 
-    } 
-
-    //Toggle Connection Panel
-    if (UI.connSettingsOpen == true) {
-        $D('noVNC_controls').style.display = "none";
-        UI.connSettingsOpen = false;
-    } else {
-        $D('noVNC_controls').style.display = "block";
-        UI.connSettingsOpen = true;
-        $D('noVNC_host').focus();
-    }
-}
-
-};
+            if (typeof(clip) !== 'boolean') {
+                // Use current setting
+                clip = UI.getSetting('clip');
+            }
+
+            if (clip && !cur_clip) {
+                // Turn clipping on
+                UI.updateSetting('clip', true);
+            } else if (!clip && cur_clip) {
+                // Turn clipping off
+                UI.updateSetting('clip', false);
+                display.set_viewport(false);
+                // Disable max dimensions
+                display.set_maxWidth(0);
+                display.set_maxHeight(0);
+                display.viewportChangeSize();
+            }
+            if (UI.getSetting('clip')) {
+                // If clipping, update clipping settings
+                display.set_viewport(true);
+
+                var size = UI.getCanvasLimit();
+                if (size) {
+                    display.set_maxWidth(size.w);
+                    display.set_maxHeight(size.h);
 
+                    // Hide potential scrollbars that can skew the position
+                    $D('noVNC_container').style.overflow = "hidden";
 
+                    // The x position marks the left margin of the canvas,
+                    // remove the margin from both sides to keep it centered
+                    var new_w = size.w - (2 * Util.getPosition($D('noVNC_canvas')).x);
 
+                    $D('noVNC_container').style.overflow = "visible";
 
+                    display.viewportChangeSize(new_w, size.h);
+                }
+            }
+        },
+
+        // Handle special cases where clipping is forced on/off or locked
+        enableDisableViewClip: function (connected) {
+            var resizeElem = $D('noVNC_resize');
+            if (resizeElem.value === 'downscale' || resizeElem.value === 'scale') {
+                UI.forceSetting('clip', false);
+                $D('noVNC_clip').disabled = true;
+            } else {
+                $D('noVNC_clip').disabled = connected || UI.isTouchDevice;
+                if (UI.isTouchDevice) {
+                    UI.forceSetting('clip', true);
+                }
+            }
+        },
+
+        // Update the viewport drag/move button
+        updateViewDrag: function(drag) {
+            if (!UI.rfb) return;
+
+            var vmb = $D('noVNC_view_drag_button');
+
+            // Check if viewport drag is possible
+            if (UI.rfb_state === 'normal' &&
+                UI.rfb.get_display().get_viewport() &&
+                UI.rfb.get_display().clippingDisplay()) {
+
+                // Show and enable the drag button
+                vmb.style.display = "inline";
+                vmb.disabled = false;
+
+            } else {
+                // The VNC content is the same size as
+                // or smaller than the display
+
+                if (UI.rfb.get_viewportDrag) {
+                    // Turn off viewport drag when it's
+                    // active since it can't be used here
+                    vmb.className = "noVNC_status_button";
+                    UI.rfb.set_viewportDrag(false);
+                }
+
+                // Disable or hide the drag button
+                if (UI.rfb_state === 'normal' && UI.isTouchDevice) {
+                    vmb.style.display = "inline";
+                    vmb.disabled = true;
+                } else {
+                    vmb.style.display = "none";
+                }
+                return;
+            }
+
+            if (typeof(drag) !== "undefined" &&
+                typeof(drag) !== "object") {
+                if (drag) {
+                    vmb.className = "noVNC_status_button_selected";
+                    UI.rfb.set_viewportDrag(true);
+                } else {
+                    vmb.className = "noVNC_status_button";
+                    UI.rfb.set_viewportDrag(false);
+                }
+            }
+        },
+
+        toggleViewDrag: function() {
+            if (!UI.rfb) return;
+
+            var vmb = $D('noVNC_view_drag_button');
+            if (UI.rfb.get_viewportDrag()) {
+                vmb.className = "noVNC_status_button";
+                UI.rfb.set_viewportDrag(false);
+            } else {
+                vmb.className = "noVNC_status_button_selected";
+                UI.rfb.set_viewportDrag(true);
+            }
+        },
+
+        // On touch devices, show the OS keyboard
+        showKeyboard: function() {
+            var kbi = $D('keyboardinput');
+            var skb = $D('showKeyboard');
+            var l = kbi.value.length;
+            if(UI.keyboardVisible === false) {
+                kbi.focus();
+                try { kbi.setSelectionRange(l, l); } // Move the caret to the end
+                catch (err) {} // setSelectionRange is undefined in Google Chrome
+                UI.keyboardVisible = true;
+                skb.className = "noVNC_status_button_selected";
+            } else if(UI.keyboardVisible === true) {
+                kbi.blur();
+                skb.className = "noVNC_status_button";
+                UI.keyboardVisible = false;
+            }
+        },
+
+        keepKeyboard: function() {
+            clearTimeout(UI.hideKeyboardTimeout);
+            if(UI.keyboardVisible === true) {
+                $D('keyboardinput').focus();
+                $D('showKeyboard').className = "noVNC_status_button_selected";
+            } else if(UI.keyboardVisible === false) {
+                $D('keyboardinput').blur();
+                $D('showKeyboard').className = "noVNC_status_button";
+            }
+        },
+
+        keyboardinputReset: function() {
+            var kbi = $D('keyboardinput');
+            kbi.value = new Array(UI.defaultKeyboardinputLen).join("_");
+            UI.lastKeyboardinput = kbi.value;
+        },
+
+        // When normal keyboard events are left uncought, use the input events from
+        // the keyboardinput element instead and generate the corresponding key events.
+        // This code is required since some browsers on Android are inconsistent in
+        // sending keyCodes in the normal keyboard events when using on screen keyboards.
+        keyInput: function(event) {
+
+            if (!UI.rfb) return;
+
+            var newValue = event.target.value;
+
+            if (!UI.lastKeyboardinput) {
+                UI.keyboardinputReset();
+            }
+            var oldValue = UI.lastKeyboardinput;
+
+            var newLen;
+            try {
+                // Try to check caret position since whitespace at the end
+                // will not be considered by value.length in some browsers
+                newLen = Math.max(event.target.selectionStart, newValue.length);
+            } catch (err) {
+                // selectionStart is undefined in Google Chrome
+                newLen = newValue.length;
+            }
+            var oldLen = oldValue.length;
+
+            var backspaces;
+            var inputs = newLen - oldLen;
+            if (inputs < 0) {
+                backspaces = -inputs;
+            } else {
+                backspaces = 0;
+            }
+
+            // Compare the old string with the new to account for
+            // text-corrections or other input that modify existing text
+            var i;
+            for (i = 0; i < Math.min(oldLen, newLen); i++) {
+                if (newValue.charAt(i) != oldValue.charAt(i)) {
+                    inputs = newLen - i;
+                    backspaces = oldLen - i;
+                    break;
+                }
+            }
+
+            // Send the key events
+            for (i = 0; i < backspaces; i++) {
+                UI.rfb.sendKey(XK_BackSpace);
+            }
+            for (i = newLen - inputs; i < newLen; i++) {
+                UI.rfb.sendKey(newValue.charCodeAt(i));
+            }
+
+            // Control the text content length in the keyboardinput element
+            if (newLen > 2 * UI.defaultKeyboardinputLen) {
+                UI.keyboardinputReset();
+            } else if (newLen < 1) {
+                // There always have to be some text in the keyboardinput
+                // element with which backspace can interact.
+                UI.keyboardinputReset();
+                // This sometimes causes the keyboard to disappear for a second
+                // but it is required for the android keyboard to recognize that
+                // text has been added to the field
+                event.target.blur();
+                // This has to be ran outside of the input handler in order to work
+                setTimeout(function() { UI.keepKeyboard(); }, 0);
+            } else {
+                UI.lastKeyboardinput = newValue;
+            }
+        },
+
+        keyInputBlur: function() {
+            $D('showKeyboard').className = "noVNC_status_button";
+            //Weird bug in iOS if you change keyboardVisible
+            //here it does not actually occur so next time
+            //you click keyboard icon it doesnt work.
+            UI.hideKeyboardTimeout = setTimeout(function() { UI.setKeyboard(); },100);
+        },
+
+        showExtraKeys: function() {
+            UI.keepKeyboard();
+            if(UI.extraKeysVisible === false) {
+                $D('toggleCtrlButton').style.display = "inline";
+                $D('toggleAltButton').style.display = "inline";
+                $D('sendTabButton').style.display = "inline";
+                $D('sendEscButton').style.display = "inline";
+                $D('showExtraKeysButton').className = "noVNC_status_button_selected";
+                UI.extraKeysVisible = true;
+            } else if(UI.extraKeysVisible === true) {
+                $D('toggleCtrlButton').style.display = "";
+                $D('toggleAltButton').style.display = "";
+                $D('sendTabButton').style.display = "";
+                $D('sendEscButton').style.display = "";
+                $D('showExtraKeysButton').className = "noVNC_status_button";
+                UI.extraKeysVisible = false;
+            }
+        },
+
+        toggleCtrl: function() {
+            UI.keepKeyboard();
+            if(UI.ctrlOn === false) {
+                UI.rfb.sendKey(XK_Control_L, true);
+                $D('toggleCtrlButton').className = "noVNC_status_button_selected";
+                UI.ctrlOn = true;
+            } else if(UI.ctrlOn === true) {
+                UI.rfb.sendKey(XK_Control_L, false);
+                $D('toggleCtrlButton').className = "noVNC_status_button";
+                UI.ctrlOn = false;
+            }
+        },
+
+        toggleAlt: function() {
+            UI.keepKeyboard();
+            if(UI.altOn === false) {
+                UI.rfb.sendKey(XK_Alt_L, true);
+                $D('toggleAltButton').className = "noVNC_status_button_selected";
+                UI.altOn = true;
+            } else if(UI.altOn === true) {
+                UI.rfb.sendKey(XK_Alt_L, false);
+                $D('toggleAltButton').className = "noVNC_status_button";
+                UI.altOn = false;
+            }
+        },
+
+        sendTab: function() {
+            UI.keepKeyboard();
+            UI.rfb.sendKey(XK_Tab);
+        },
+
+        sendEsc: function() {
+            UI.keepKeyboard();
+            UI.rfb.sendKey(XK_Escape);
+        },
+
+        setKeyboard: function() {
+            UI.keyboardVisible = false;
+        },
+
+        //Helper to add options to dropdown.
+        addOption: function(selectbox, text, value) {
+            var optn = document.createElement("OPTION");
+            optn.text = text;
+            optn.value = value;
+            selectbox.options.add(optn);
+        },
+
+        setBarPosition: function() {
+            $D('noVNC-control-bar').style.top = (window.pageYOffset) + 'px';
+            $D('noVNC_mobile_buttons').style.left = (window.pageXOffset) + 'px';
+
+            var vncwidth = $D('noVNC_screen').style.offsetWidth;
+            $D('noVNC-control-bar').style.width = vncwidth + 'px';
+        }
+
+    };
+})();