]> git.proxmox.com Git - mirror_novnc.git/blobdiff - app/ui.js
Uncomment ES6 module syntax
[mirror_novnc.git] / app / ui.js
index 1ad5e8fee0348719b292e49dbea3230a1c945071..8009c3825fcdea20305597b7bdcb14f1a0097f11 100644 (file)
--- a/app/ui.js
+++ b/app/ui.js
 /* jslint white: false, browser: true */
 /* global window, document.getElementById, Util, WebUtil, RFB, Display */
 
-/* [module]
- * import Util from "../core/util";
- * import KeyTable from "../core/input/keysym";
- * import RFB from "../core/rfb";
- * import Display from "../core/display";
- * import WebUtil from "./webutil";
- */
+import Util from "../core/util.js";
+import KeyTable from "../core/input/keysym.js";
+import keysyms from "../core/input/keysymdef.js";
+import RFB from "../core/rfb.js";
+import Display from "../core/display.js";
+import WebUtil from "./webutil.js";
 
 var UI;
 
 (function () {
     "use strict";
 
-    /* [begin skip-as-module] */
-    // Load supporting scripts
-    WebUtil.load_scripts(
-        {'core': ["base64.js", "websock.js", "des.js", "input/keysymdef.js",
-                  "input/xtscancodes.js", "input/util.js", "input/devices.js",
-                  "display.js", "inflator.js", "rfb.js", "input/keysym.js"]});
+    // Fallback for all uncought errors
+    window.addEventListener('error', function(event) {
+        try {
+            var msg, div, text;
+
+            msg = document.getElementById('noVNC_fallback_errormsg');
+
+            // Only show the initial error
+            if (msg.hasChildNodes()) {
+                return false;
+            }
+
+            div = document.createElement("div");
+            div.appendChild(document.createTextNode(event.message));
+            msg.appendChild(div);
+
+            div = document.createElement("div");
+            div.className = 'noVNC_location';
+            text = event.filename + ":" + event.lineno + ":" + event.colno;
+            div.appendChild(document.createTextNode(text));
+            msg.appendChild(div);
+
+            if ((event.error !== undefined) &&
+                (event.error.stack !== undefined)) {
+                div = document.createElement("div");
+                div.className = 'noVNC_stack';
+                div.appendChild(document.createTextNode(event.error.stack));
+                msg.appendChild(div);
+            }
 
-    window.onscriptsload = function () { UI.load(); };
-    /* [end skip-as-module] */
+            document.getElementById('noVNC_fallback_error')
+                .classList.add("noVNC_open");
+        } catch (exc) {
+            document.write("noVNC encountered an error.");
+        }
+        // Don't return true since this would prevent the error
+        // from being printed to the browser console.
+        return false;
+    });
+
+    // Set up translations
+    var LINGUAS = ["de", "el", "nl", "sv"];
+    Util.Localisation.setup(LINGUAS);
+    if (Util.Localisation.language !== "en") {
+        WebUtil.load_scripts(
+            {'app': ["locale/" + Util.Localisation.language + ".js"]});
+    }
+
+    var _ = Util.Localisation.get;
 
     UI = {
 
@@ -49,14 +88,16 @@ var UI;
         controlbarDrag: false,
         controlbarMouseDownClientY: 0,
         controlbarMouseDownOffsetY: 0,
-        keyboardVisible: false,
 
-        isTouchDevice: false,
         isSafari: false,
         rememberedClipSetting: null,
         lastKeyboardinput: null,
         defaultKeyboardinputLen: 100,
 
+        inhibit_reconnect: true,
+        reconnect_callback: null,
+        reconnect_password: null,
+
         // Setup rfb object, load settings from browser storage, then call
         // UI.init to setup the UI/menus
         load: function(callback) {
@@ -67,25 +108,30 @@ var UI;
         start: function(callback) {
 
             // Setup global variables first
-            UI.isTouchDevice = 'ontouchstart' in document.documentElement;
             UI.isSafari = (navigator.userAgent.indexOf('Safari') !== -1 &&
                            navigator.userAgent.indexOf('Chrome') === -1);
 
             UI.initSettings();
 
+            // Translate the DOM
+            Util.Localisation.translateDOM();
+
             // Adapt the interface for touch screen devices
-            if (UI.isTouchDevice) {
+            if (Util.isTouchDevice) {
                 document.documentElement.classList.add("noVNC_touch");
                 // Remove the address bar
                 setTimeout(function() { window.scrollTo(0, 1); }, 100);
-                UI.forceSetting('clip', true);
-            } else {
-                UI.initSetting('clip', false);
             }
 
-            // Setup and initialize event handlers
-            UI.setupWindowEvents();
-            UI.setupFullscreen();
+            // Restore control bar position
+            if (WebUtil.readSetting('controlbar_pos') === 'right') {
+                UI.toggleControlbarSide();
+            }
+
+            UI.initFullscreen();
+
+            // Setup event handlers
+            UI.addResizeHandlers();
             UI.addControlbarHandlers();
             UI.addTouchSpecificHandlers();
             UI.addExtraKeysHandlers();
@@ -93,6 +139,10 @@ var UI;
             UI.addConnectionControlHandlers();
             UI.addClipboardHandlers();
             UI.addSettingsHandlers();
+            document.getElementById("noVNC_status")
+                .addEventListener('click', UI.hideStatus);
+
+            UI.openControlbar();
 
             // Show the connect panel on first load unless autoconnecting
             if (!autoconnect) {
@@ -118,14 +168,22 @@ var UI;
             }
         },
 
+        initFullscreen: function() {
+            // Only show the button if fullscreen is properly supported
+            // * Safari doesn't support alphanumerical input while in fullscreen
+            if (!UI.isSafari &&
+                (document.documentElement.requestFullscreen ||
+                 document.documentElement.mozRequestFullScreen ||
+                 document.documentElement.webkitRequestFullscreen ||
+                 document.body.msRequestFullscreen)) {
+                document.getElementById('noVNC_fullscreen_button')
+                    .classList.remove("noVNC_hidden");
+                UI.addFullscreenHandlers();
+            }
+        },
+
         initSettings: function() {
-            // Stylesheet selection dropdown
-            var sheet = WebUtil.selectStylesheet();
-            var sheets = WebUtil.getStylesheets();
             var i;
-            for (i = 0; i < sheets.length; i += 1) {
-                UI.addOption(document.getElementById('noVNC_setting_stylesheet'),sheets[i].title, sheets[i].title);
-            }
 
             // Logging selection dropdown
             var llevels = ['error', 'warn', 'info', 'debug'];
@@ -135,12 +193,7 @@ var UI;
 
             // 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'));
+            UI.updateLogging();
 
             // if port == 80 (or 443) then it won't be present and should be
             // set manually
@@ -157,43 +210,75 @@ var UI;
             /* 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('cursor', !Util.isTouchDevice);
+            UI.initSetting('clip', false);
             UI.initSetting('resize', 'off');
             UI.initSetting('shared', true);
             UI.initSetting('view_only', false);
             UI.initSetting('path', 'websockify');
             UI.initSetting('repeaterID', '');
-            UI.initSetting('token', '');
-        },
+            UI.initSetting('reconnect', false);
+            UI.initSetting('reconnect_delay', 5000);
 
-        setupWindowEvents: function() {
-            window.addEventListener( 'resize', function () {
-                UI.applyResizeMode();
-                UI.updateViewClip();
-                UI.updateViewDrag();
-            } );
+            UI.setupSettingLabels();
+        },
 
-            document.getElementById("noVNC_status")
-                .addEventListener('click', UI.hideStatus);
+        // Adds a link to the label elements on the corresponding input elements
+        setupSettingLabels: function() {
+            var labels = document.getElementsByTagName('LABEL');
+            for (var i = 0; i < labels.length; i++) {
+                var htmlFor = labels[i].htmlFor;
+                if (htmlFor != '') {
+                    var elem = document.getElementById(htmlFor);
+                    if (elem) elem.label = labels[i];
+                } else {
+                    // If 'for' isn't set, use the first input element child
+                    var children = labels[i].children;
+                    for (var j = 0; j < children.length; j++) {
+                        if (children[j].form !== undefined) {
+                            children[j].label = labels[i];
+                            break;
+                        }
+                    }
+                }
+            }
         },
 
-        setupFullscreen: function() {
-            // Only show the button if fullscreen is properly supported
-            // * Safari doesn't support alphanumerical input while in fullscreen
-            if (!UI.isSafari &&
-                (document.documentElement.requestFullscreen ||
-                 document.documentElement.mozRequestFullScreen ||
-                 document.documentElement.webkitRequestFullscreen ||
-                 document.body.msRequestFullscreen)) {
-                document.getElementById('noVNC_fullscreen_button')
-                    .classList.remove("noVNC_hidden");
-                UI.addFullscreenHandlers();
+        initRFB: function() {
+            try {
+                UI.rfb = new RFB({'target': document.getElementById('noVNC_canvas'),
+                                  'onNotification': UI.notification,
+                                  'onUpdateState': UI.updateState,
+                                  'onDisconnected': UI.disconnectFinished,
+                                  'onPasswordRequired': UI.passwordRequired,
+                                  'onXvpInit': UI.updateXvpButton,
+                                  'onClipboard': UI.clipboardReceive,
+                                  'onBell': UI.bell,
+                                  'onFBUComplete': UI.initialResize,
+                                  'onFBResize': UI.updateSessionSize,
+                                  'onDesktopName': UI.updateDesktopName});
+                return true;
+            } catch (exc) {
+                var msg = "Unable to create RFB client -- " + exc;
+                Util.Error(msg);
+                UI.showStatus(msg, 'error');
+                return false;
             }
         },
 
+/* ------^-------
+ *     /INIT
+ * ==============
+ * EVENT HANDLERS
+ * ------v------*/
+
+        addResizeHandlers: function() {
+            window.addEventListener('resize', UI.applyResizeMode);
+            window.addEventListener('resize', UI.updateViewClip);
+        },
+
         addControlbarHandlers: function() {
             document.getElementById("noVNC_control_bar")
                 .addEventListener('mousemove', UI.activateControlbar);
@@ -220,6 +305,11 @@ var UI;
                 .addEventListener('mousemove', UI.dragControlbarHandle);
             // resize events aren't available for elements
             window.addEventListener('resize', UI.updateControlbarHandle);
+
+            var exps = document.getElementsByClassName("noVNC_expander");
+            for (var i = 0;i < exps.length;i++) {
+                exps[i].addEventListener('click', UI.toggleExpander);
+            }
         },
 
         addTouchSpecificHandlers: function() {
@@ -236,11 +326,16 @@ var UI;
 
             document.getElementById("noVNC_keyboardinput")
                 .addEventListener('input', UI.keyInput);
+            document.getElementById("noVNC_keyboardinput")
+                .addEventListener('focus', UI.onfocusVirtualKeyboard);
             document.getElementById("noVNC_keyboardinput")
                 .addEventListener('blur', UI.onblurVirtualKeyboard);
             document.getElementById("noVNC_keyboardinput")
                 .addEventListener('submit', function () { return false; });
 
+            document.documentElement
+                .addEventListener('mousedown', UI.keepVirtualKeyboard, true);
+
             document.getElementById("noVNC_control_bar")
                 .addEventListener('touchstart', UI.activateControlbar);
             document.getElementById("noVNC_control_bar")
@@ -292,12 +387,12 @@ var UI;
         },
 
         addConnectionControlHandlers: function() {
-            document.getElementById("noVNC_connect_controls_button")
-                .addEventListener('click', UI.toggleConnectPanel);
             document.getElementById("noVNC_disconnect_button")
                 .addEventListener('click', UI.disconnect);
             document.getElementById("noVNC_connect_button")
                 .addEventListener('click', UI.connect);
+            document.getElementById("noVNC_cancel_reconnect_button")
+                .addEventListener('click', UI.cancelReconnect);
 
             document.getElementById("noVNC_password_button")
                 .addEventListener('click', UI.setPassword);
@@ -316,14 +411,40 @@ var UI;
                 .addEventListener('click', UI.clipboardClear);
         },
 
+        // Add a call to save settings when the element changes,
+        // unless the optional parameter changeFunc is used instead.
+        addSettingChangeHandler: function(name, changeFunc) {
+            var settingElem = document.getElementById("noVNC_setting_" + name);
+            if (changeFunc === undefined) {
+                changeFunc = function () { UI.saveSetting(name); };
+            }
+            settingElem.addEventListener('change', changeFunc);
+        },
+
         addSettingsHandlers: function() {
             document.getElementById("noVNC_settings_button")
                 .addEventListener('click', UI.toggleSettingsPanel);
-            document.getElementById("noVNC_settings_apply")
-                .addEventListener('click', UI.settingsApply);
 
-            document.getElementById("noVNC_setting_resize")
-                .addEventListener('change', UI.enableDisableViewClip);
+            UI.addSettingChangeHandler('encrypt');
+            UI.addSettingChangeHandler('true_color');
+            UI.addSettingChangeHandler('cursor');
+            UI.addSettingChangeHandler('cursor', UI.updateLocalCursor);
+            UI.addSettingChangeHandler('resize');
+            UI.addSettingChangeHandler('resize', UI.enableDisableViewClip);
+            UI.addSettingChangeHandler('resize', UI.applyResizeMode);
+            UI.addSettingChangeHandler('clip');
+            UI.addSettingChangeHandler('clip', UI.updateViewClip);
+            UI.addSettingChangeHandler('shared');
+            UI.addSettingChangeHandler('view_only');
+            UI.addSettingChangeHandler('view_only', UI.updateViewOnly);
+            UI.addSettingChangeHandler('host');
+            UI.addSettingChangeHandler('port');
+            UI.addSettingChangeHandler('path');
+            UI.addSettingChangeHandler('repeaterID');
+            UI.addSettingChangeHandler('logging');
+            UI.addSettingChangeHandler('logging', UI.updateLogging);
+            UI.addSettingChangeHandler('reconnect');
+            UI.addSettingChangeHandler('reconnect_delay');
         },
 
         addFullscreenHandlers: function() {
@@ -336,56 +457,48 @@ var UI;
             window.addEventListener('msfullscreenchange', UI.updateFullscreenButton);
         },
 
-        initRFB: function() {
-            try {
-                UI.rfb = new RFB({'target': document.getElementById('noVNC_canvas'),
-                                  'onNotification': UI.notification,
-                                  'onUpdateState': UI.updateState,
-                                  'onDisconnected': UI.disconnectFinished,
-                                  'onPasswordRequired': UI.passwordRequired,
-                                  'onXvpInit': UI.updateXvpButton,
-                                  'onClipboard': UI.clipboardReceive,
-                                  'onBell': UI.bell,
-                                  'onFBUComplete': UI.initialResize,
-                                  'onFBResize': UI.updateViewDrag,
-                                  'onDesktopName': UI.updateDesktopName});
-                return true;
-            } catch (exc) {
-                UI.showStatus('Unable to create RFB client -- ' + exc, 'error');
-                return false;
-            }
-        },
-
 /* ------^-------
- *     /INIT
+ * /EVENT HANDLERS
  * ==============
  *     VISUAL
  * ------v------*/
 
         updateState: function(rfb, state, oldstate) {
+            var msg;
+
+            document.documentElement.classList.remove("noVNC_connecting");
+            document.documentElement.classList.remove("noVNC_connected");
+            document.documentElement.classList.remove("noVNC_disconnecting");
+            document.documentElement.classList.remove("noVNC_reconnecting");
+
             switch (state) {
                 case 'connecting':
-                    UI.showStatus("Connecting");
+                    document.getElementById("noVNC_transition_text").textContent = _("Connecting...");
+                    document.documentElement.classList.add("noVNC_connecting");
                     break;
                 case 'connected':
                     UI.connected = true;
+                    UI.inhibit_reconnect = false;
+                    document.documentElement.classList.add("noVNC_connected");
                     if (rfb && rfb.get_encrypt()) {
-                        UI.showStatus("Connected (encrypted) to " +
-                                      UI.desktopName);
+                        msg = _("Connected (encrypted) to ") + UI.desktopName;
                     } else {
-                        UI.showStatus("Connected (unencrypted) to " +
-                                      UI.desktopName);
+                        msg = _("Connected (unencrypted) to ") + UI.desktopName;
                     }
+                    UI.showStatus(msg);
                     break;
                 case 'disconnecting':
-                    UI.showStatus("Disconnecting");
+                    UI.connected = false;
+                    document.getElementById("noVNC_transition_text").textContent = _("Disconnecting...");
+                    document.documentElement.classList.add("noVNC_disconnecting");
                     break;
                 case 'disconnected':
-                    UI.connected = false;
-                    UI.showStatus("Disconnected");
+                    UI.showStatus(_("Disconnected"));
                     break;
                 default:
-                    UI.showStatus("Invalid state", 'error');
+                    msg = "Invalid UI state";
+                    Util.Error(msg);
+                    UI.showStatus(msg, 'error');
                     break;
             }
 
@@ -395,35 +508,53 @@ var UI;
         // Disable/enable controls depending on connection state
         updateVisualState: function() {
             //Util.Debug(">> updateVisualState");
-            document.getElementById('noVNC_setting_encrypt').disabled = UI.connected;
-            document.getElementById('noVNC_setting_true_color').disabled = UI.connected;
-            if (Util.browserSupportsCursorURIs()) {
-                document.getElementById('noVNC_setting_cursor').disabled = UI.connected;
-            } else {
-                UI.updateSetting('cursor', !UI.isTouchDevice);
-                document.getElementById('noVNC_setting_cursor').disabled = true;
-            }
 
             UI.enableDisableViewClip();
-            document.getElementById('noVNC_setting_resize').disabled = UI.connected;
-            document.getElementById('noVNC_setting_shared').disabled = UI.connected;
-            document.getElementById('noVNC_setting_view_only').disabled = UI.connected;
-            document.getElementById('noVNC_setting_path').disabled = UI.connected;
-            document.getElementById('noVNC_setting_repeaterID').disabled = UI.connected;
+
+            if (Util.browserSupportsCursorURIs() && !Util.isTouchDevice) {
+                UI.enableSetting('cursor');
+            } else {
+                UI.disableSetting('cursor');
+            }
 
             if (UI.connected) {
-                document.documentElement.classList.add("noVNC_connected");
+                UI.disableSetting('encrypt');
+                UI.disableSetting('true_color');
+                UI.disableSetting('shared');
+                UI.disableSetting('host');
+                UI.disableSetting('port');
+                UI.disableSetting('path');
+                UI.disableSetting('repeaterID');
                 UI.updateViewClip();
                 UI.setMouseButton(1);
 
                 // Hide the controlbar after 2 seconds
                 UI.closeControlbarTimeout = setTimeout(UI.closeControlbar, 2000);
             } else {
-                document.documentElement.classList.remove("noVNC_connected");
+                UI.enableSetting('encrypt');
+                UI.enableSetting('true_color');
+                UI.enableSetting('shared');
+                UI.enableSetting('host');
+                UI.enableSetting('port');
+                UI.enableSetting('path');
+                UI.enableSetting('repeaterID');
                 UI.updateXvpButton(0);
                 UI.keepControlbar();
             }
 
+            // Hide input related buttons in view only mode
+            if (UI.rfb && UI.rfb.get_view_only()) {
+                document.getElementById('noVNC_keyboard_button')
+                    .classList.add('noVNC_hidden');
+                document.getElementById('noVNC_toggle_extra_keys_button')
+                    .classList.add('noVNC_hidden');
+            } else {
+                document.getElementById('noVNC_keyboard_button')
+                    .classList.remove('noVNC_hidden');
+                document.getElementById('noVNC_toggle_extra_keys_button')
+                    .classList.remove('noVNC_hidden');
+            }
+
             // State change disables viewport dragging.
             // It is enabled (toggled) by direct click on the button
             UI.setViewDrag(false);
@@ -444,9 +575,9 @@ var UI;
                 status_type = 'normal';
             }
 
-            statusElem.classList.remove("noVNC_status_normal",
-                                        "noVNC_status_warn",
-                                        "noVNC_status_error");
+            statusElem.classList.remove("noVNC_status_normal");
+            statusElem.classList.remove("noVNC_status_warn");
+            statusElem.classList.remove("noVNC_status_error");
 
             switch (status_type) {
                 case 'warning':
@@ -463,7 +594,7 @@ var UI;
                     break;
             }
 
-            statusElem.innerHTML = text;
+            statusElem.textContent = text;
             statusElem.classList.add("noVNC_open");
 
             // If no time was specified, show the status for 1.5 seconds
@@ -524,11 +655,41 @@ var UI;
             }
         },
 
+        toggleControlbarSide: function () {
+            // Temporarily disable animation to avoid weird movement
+            var bar = document.getElementById('noVNC_control_bar');
+            bar.style.transitionDuration = '0s';
+            bar.addEventListener('transitionend', function () { this.style.transitionDuration = ""; });
+
+            var anchor = document.getElementById('noVNC_control_bar_anchor');
+            if (anchor.classList.contains("noVNC_right")) {
+                WebUtil.writeSetting('controlbar_pos', 'left');
+                anchor.classList.remove("noVNC_right");
+            } else {
+                WebUtil.writeSetting('controlbar_pos', 'right');
+                anchor.classList.add("noVNC_right");
+            }
+
+            // Consider this a movement of the handle
+            UI.controlbarDrag = true;
+        },
+
         dragControlbarHandle: function (e) {
             if (!UI.controlbarGrabbed) return;
 
             var ptr = Util.getPointerEvent(e);
 
+            var anchor = document.getElementById('noVNC_control_bar_anchor');
+            if (ptr.clientX < (window.innerWidth * 0.1)) {
+                if (anchor.classList.contains("noVNC_right")) {
+                    UI.toggleControlbarSide();
+                }
+            } else if (ptr.clientX > (window.innerWidth * 0.9)) {
+                if (!anchor.classList.contains("noVNC_right")) {
+                    UI.toggleControlbarSide();
+                }
+            }
+
             if (!UI.controlbarDrag) {
                 // The goal is to trigger on a certain physical width, the
                 // devicePixelRatio brings us a bit closer but is not optimal.
@@ -546,6 +707,8 @@ var UI;
 
             e.preventDefault();
             e.stopPropagation();
+            UI.keepControlbar();
+            UI.activateControlbar();
         },
 
         // Move the handle but don't allow any position outside the bounds
@@ -602,6 +765,8 @@ var UI;
                 UI.toggleControlbar();
                 e.preventDefault();
                 e.stopPropagation();
+                UI.keepControlbar();
+                UI.activateControlbar();
             }
             UI.controlbarGrabbed = false;
         },
@@ -614,7 +779,7 @@ var UI;
             var handle = document.getElementById("noVNC_control_bar_handle");
             var bounds = handle.getBoundingClientRect();
 
-            WebUtil.setCapture(handle);
+            Util.setCapture(handle);
             UI.controlbarGrabbed = true;
             UI.controlbarDrag = false;
 
@@ -622,6 +787,16 @@ var UI;
             UI.controlbarMouseDownOffsetY = ptr.clientY - bounds.top;
             e.preventDefault();
             e.stopPropagation();
+            UI.keepControlbar();
+            UI.activateControlbar();
+        },
+
+        toggleExpander: function(e) {
+            if (this.classList.contains("noVNC_open")) {
+                this.classList.remove("noVNC_open");
+            } else {
+                this.classList.add("noVNC_open");
+            }
         },
 
 /* ------^-------
@@ -689,12 +864,6 @@ var UI;
             return val;
         },
 
-        // Force a setting to be a certain value
-        forceSetting: function(name, val) {
-            UI.updateSetting(name, val);
-            return val;
-        },
-
         // Read form control compatible setting from cookie
         getSetting: function(name) {
             var ctrl = document.getElementById('noVNC_setting_' + name);
@@ -709,35 +878,19 @@ var UI;
             return val;
         },
 
-        // 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');
+        // These helpers compensate for the lack of parent-selectors and
+        // previous-sibling-selectors in CSS which are needed when we want to
+        // disable the labels that belong to disabled input elements.
+        disableSetting: function(name) {
+            var ctrl = document.getElementById('noVNC_setting_' + name);
+            ctrl.disabled = true;
+            ctrl.label.classList.add('noVNC_disabled');
+        },
 
-            // Settings with immediate (non-connected related) effect
-            WebUtil.selectStylesheet(UI.getSetting('stylesheet'));
-            WebUtil.init_logging(UI.getSetting('logging'));
-            UI.updateViewClip();
-            UI.updateViewDrag();
-            //Util.Debug("<< settingsApply");
+        enableSetting: function(name) {
+            var ctrl = document.getElementById('noVNC_setting_' + name);
+            ctrl.disabled = false;
+            ctrl.label.classList.remove('noVNC_disabled');
         },
 
 /* ------^-------
@@ -750,7 +903,6 @@ var UI;
             UI.closeSettingsPanel();
             UI.closeXvpPanel();
             UI.closeClipboardPanel();
-            UI.closeConnectPanel();
             UI.closeExtraKeys();
         },
 
@@ -764,13 +916,14 @@ var UI;
             UI.closeAllPanels();
             UI.openControlbar();
 
+            // Refresh UI elements from saved cookies
             UI.updateSetting('encrypt');
             UI.updateSetting('true_color');
             if (Util.browserSupportsCursorURIs()) {
                 UI.updateSetting('cursor');
             } else {
-                UI.updateSetting('cursor', !UI.isTouchDevice);
-                document.getElementById('noVNC_setting_cursor').disabled = true;
+                UI.updateSetting('cursor', !Util.isTouchDevice);
+                UI.disableSetting('cursor');
             }
             UI.updateSetting('clip');
             UI.updateSetting('resize');
@@ -778,8 +931,9 @@ var UI;
             UI.updateSetting('view_only');
             UI.updateSetting('path');
             UI.updateSetting('repeaterID');
-            UI.updateSetting('stylesheet');
             UI.updateSetting('logging');
+            UI.updateSetting('reconnect');
+            UI.updateSetting('reconnect_delay');
 
             document.getElementById('noVNC_settings')
                 .classList.add("noVNC_open");
@@ -794,13 +948,9 @@ var UI;
                 .classList.remove("noVNC_selected");
         },
 
-        // Toggle the settings menu:
-        //   On open, settings are refreshed from saved cookies.
-        //   On close, settings are applied
         toggleSettingsPanel: function() {
             if (document.getElementById('noVNC_settings')
                 .classList.contains("noVNC_open")) {
-                UI.settingsApply();
                 UI.closeSettingsPanel();
             } else {
                 UI.openSettingsPanel();
@@ -841,7 +991,7 @@ var UI;
 
         // Disable/enable XVP button
         updateXvpButton: function(ver) {
-            if (ver >= 1) {
+            if (ver >= 1 && !UI.rfb.get_view_only()) {
                 document.getElementById('noVNC_xvp_button')
                     .classList.remove("noVNC_hidden");
             } else {
@@ -909,66 +1059,48 @@ var UI;
  * ------v------*/
 
         openConnectPanel: function() {
-            UI.closeAllPanels();
-            UI.openControlbar();
-
-            document.getElementById('noVNC_connect_controls')
+            document.getElementById('noVNC_connect_dlg')
                 .classList.add("noVNC_open");
-            document.getElementById('noVNC_connect_controls_button')
-                .classList.add("noVNC_selected");
-
-            document.getElementById('noVNC_setting_host').focus();
         },
 
         closeConnectPanel: function() {
-            document.getElementById('noVNC_connect_controls')
+            document.getElementById('noVNC_connect_dlg')
                 .classList.remove("noVNC_open");
-            document.getElementById('noVNC_connect_controls_button')
-                .classList.remove("noVNC_selected");
-
-            UI.saveSetting('host');
-            UI.saveSetting('port');
-            UI.saveSetting('token');
-            //UI.saveSetting('password');
         },
 
-        toggleConnectPanel: function() {
-            if (document.getElementById('noVNC_connect_controls')
-                .classList.contains("noVNC_open")) {
-                UI.closeConnectPanel();
-            } else {
-                UI.openConnectPanel();
-            }
-        },
+        connect: function(event, password) {
+            var host = UI.getSetting('host');
+            var port = UI.getSetting('port');
+            var path = UI.getSetting('path');
 
-        connect: function() {
-            var host = document.getElementById('noVNC_setting_host').value;
-            var port = document.getElementById('noVNC_setting_port').value;
-            var password = document.getElementById('noVNC_setting_password').value;
-            var token = document.getElementById('noVNC_setting_token').value;
-            var path = document.getElementById('noVNC_setting_path').value;
+            if (typeof password === 'undefined') {
+                password = WebUtil.getConfigVar('password');
+            }
 
-            //if token is in path then ignore the new token variable
-            if (token) {
-                path = WebUtil.injectParamIfMissing(path, "token", token);
+            if (password === null) {
+                password = undefined;
             }
 
             if ((!host) || (!port)) {
-                UI.showStatus("Must set host and port", 'error');
+                var msg = _("Must set host and port");
+                Util.Error(msg);
+                UI.showStatus(msg, 'error');
                 return;
             }
 
             if (!UI.initRFB()) return;
 
             UI.closeAllPanels();
+            UI.closeConnectPanel();
 
             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.updateLocalCursor();
+            UI.updateViewOnly();
+
             UI.rfb.connect(host, port, password, path);
         },
 
@@ -976,16 +1108,50 @@ var UI;
             UI.closeAllPanels();
             UI.rfb.disconnect();
 
+            // Disable automatic reconnecting
+            UI.inhibit_reconnect = true;
+
             // Restore the callback used for initial resize
             UI.rfb.set_onFBUComplete(UI.initialResize);
 
             // Don't display the connection settings until we're actually disconnected
         },
 
+        reconnect: function() {
+            UI.reconnect_callback = null;
+
+            // if reconnect has been disabled in the meantime, do nothing.
+            if (UI.inhibit_reconnect) {
+                return;
+            }
+
+            UI.connect(null, UI.reconnect_password);
+        },
+
         disconnectFinished: function (rfb, reason) {
             if (typeof reason !== 'undefined') {
                 UI.showStatus(reason, 'error');
+            } else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) {
+                document.getElementById("noVNC_transition_text").textContent = _("Reconnecting...");
+                document.documentElement.classList.add("noVNC_reconnecting");
+
+                var delay = parseInt(UI.getSetting('reconnect_delay'));
+                UI.reconnect_callback = setTimeout(UI.reconnect, delay);
+                return;
             }
+
+            UI.openControlbar();
+            UI.openConnectPanel();
+        },
+
+        cancelReconnect: function() {
+            if (UI.reconnect_callback !== null) {
+                clearTimeout(UI.reconnect_callback);
+                UI.reconnect_callback = null;
+            }
+
+            document.documentElement.classList.remove("noVNC_reconnecting");
+            UI.openControlbar();
             UI.openConnectPanel();
         },
 
@@ -1005,16 +1171,20 @@ var UI;
                 }, 100);
 
             if (typeof msg === 'undefined') {
-                msg = "Password is required";
+                msg = _("Password is required");
             }
+            Util.Warn(msg);
             UI.showStatus(msg, "warning");
         },
 
-        setPassword: function() {
-            UI.rfb.sendPassword(document.getElementById('noVNC_password_input').value);
+        setPassword: function(e) {
+            var password = document.getElementById('noVNC_password_input').value;
+            UI.rfb.sendPassword(password);
+            UI.reconnect_password = password;
             document.getElementById('noVNC_password_dlg')
                 .classList.remove('noVNC_open');
-            return false;
+            // Prevent actually submitting the form
+            e.preventDefault();
         },
 
 /* ------^-------
@@ -1081,6 +1251,10 @@ var UI;
 
                 var display = UI.rfb.get_display();
                 var resizeMode = UI.getSetting('resize');
+                display.set_scale(1);
+
+                // Make sure the viewport is adjusted first
+                UI.updateViewClip();
 
                 if (resizeMode === 'remote') {
 
@@ -1091,46 +1265,25 @@ var UI;
                     // is finished we wait 0.5 seconds before sending the request.
                     clearTimeout(UI.resizeTimeout);
                     UI.resizeTimeout = setTimeout(function(){
-
-                        // Limit the viewport to the size of the browser window
-                        display.set_maxWidth(screen.w);
-                        display.set_maxHeight(screen.h);
-
-                        Util.Debug('Attempting requestDesktopSize(' +
-                                   screen.w + ', ' + screen.h + ')');
-
                         // Request a remote size covering the viewport
-                        UI.rfb.requestDesktopSize(screen.w, screen.h);
+                        if (UI.rfb.requestDesktopSize(screen.w, screen.h)) {
+                            Util.Debug('Requested new desktop size: ' +
+                                       screen.w + 'x' + screen.h);
+                        }
                     }, 500);
 
                 } else if (resizeMode === 'scale' || resizeMode === 'downscale') {
                     var downscaleOnly = resizeMode === 'downscale';
-                    var scaleRatio = display.autoscale(screen.w, screen.h, downscaleOnly);
-                    UI.rfb.get_mouse().set_scale(scaleRatio);
-                    Util.Debug('Scaling by ' + UI.rfb.get_mouse().get_scale());
+                    display.autoscale(screen.w, screen.h, downscaleOnly);
+                    UI.fixScrollbars();
                 }
             }
         },
 
-        // The screen is always the same size as the available viewport
-        // in the browser window minus the height of the control bar
+        // Gets the the size of the available viewport in the browser window
         screenSize: function() {
             var screen = document.getElementById('noVNC_screen');
-
-            // Hide the scrollbars until the size is calculated
-            screen.style.overflow = "hidden";
-
-            var pos = Util.getPosition(screen);
-            var w = pos.width;
-            var h = pos.height;
-
-            screen.style.overflow = "visible";
-
-            if (isNaN(w) || isNaN(h)) {
-                return false;
-            } else {
-                return {w: w, h: h};
-            }
+            return {w: screen.offsetWidth, h: screen.offsetHeight};
         },
 
         // Normally we only apply the current resize mode after a window resize
@@ -1165,6 +1318,15 @@ var UI;
             var cur_clip = display.get_viewport();
             var new_clip = UI.getSetting('clip');
 
+            var resizeSetting = UI.getSetting('resize');
+            if (resizeSetting === 'downscale' || resizeSetting === 'scale') {
+                // Disable clipping if we are scaling
+                new_clip = false;
+            } else if (Util.isTouchDevice) {
+                // Touch devices usually have shit scrollbars
+                new_clip = true;
+            }
+
             if (cur_clip !== new_clip) {
                 display.set_viewport(new_clip);
             }
@@ -1174,63 +1336,24 @@ var UI;
             if (new_clip && size) {
                 // When clipping is enabled, the screen is limited to
                 // the size of the browser window.
-                display.set_maxWidth(size.w);
-                display.set_maxHeight(size.h);
-
-                var screen = document.getElementById('noVNC_screen');
-                var canvas = document.getElementById('noVNC_canvas');
-
-                // Hide potential scrollbars that can skew the position
-                screen.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(canvas).x);
-
-                screen.style.overflow = "visible";
-
-                display.viewportChangeSize(new_w, size.h);
-            } else {
-                // Disable max dimensions
-                display.set_maxWidth(0);
-                display.set_maxHeight(0);
-                display.viewportChangeSize();
+                display.viewportChangeSize(size.w, size.h);
+                UI.fixScrollbars();
             }
+
+            // Changing the viewport may change the state of
+            // the dragging button
+            UI.updateViewDrag();
         },
 
         // Handle special cases where clipping is forced on/off or locked
         enableDisableViewClip: function() {
-            var resizeSetting = document.getElementById('noVNC_setting_resize');
-
-            if (UI.isSafari) {
-                // Safari auto-hides the scrollbars which makes them
-                // impossible to use in most cases
-                UI.setViewClip(true);
-                document.getElementById('noVNC_setting_clip').disabled = true;
-            } else if (resizeSetting.value === 'downscale' || resizeSetting.value === 'scale') {
-                // Disable clipping if we are scaling
-                UI.setViewClip(false);
-                document.getElementById('noVNC_setting_clip').disabled = true;
-            } else if (document.msFullscreenElement) {
-                // The browser is IE and we are in fullscreen mode.
-                // - We need to force clipping while in fullscreen since
-                //   scrollbars doesn't work.
-                UI.showStatus("Forcing clipping mode since scrollbars aren't supported by IE in fullscreen");
-                UI.rememberedClipSetting = UI.getSetting('clip');
-                UI.setViewClip(true);
-                document.getElementById('noVNC_setting_clip').disabled = true;
-            } else if (document.body.msRequestFullscreen &&
-                       UI.rememberedClipSetting !== null) {
-                // Restore view clip to what it was before fullscreen on IE
-                UI.setViewClip(UI.rememberedClipSetting);
-                document.getElementById('noVNC_setting_clip').disabled =
-                    UI.connected || UI.isTouchDevice;
+            var resizeSetting = UI.getSetting('resize');
+            // Disable clipping if we are scaling, connected or on touch
+            if (resizeSetting === 'downscale' || resizeSetting === 'scale' ||
+                Util.isTouchDevice) {
+                UI.disableSetting('clip');
             } else {
-                document.getElementById('noVNC_setting_clip').disabled =
-                    UI.connected || UI.isTouchDevice;
-                if (UI.isTouchDevice) {
-                    UI.setViewClip(true);
-                }
+                UI.enableSetting('clip');
             }
         },
 
@@ -1286,7 +1409,7 @@ var UI;
 
             // Different behaviour for touch vs non-touch
             // The button is disabled instead of hidden on touch devices
-            if (UI.isTouchDevice) {
+            if (Util.isTouchDevice) {
                 viewDragButton.classList.remove("noVNC_hidden");
 
                 if (clipping) {
@@ -1312,15 +1435,12 @@ var UI;
  * ------v------*/
 
         showVirtualKeyboard: function() {
-            if (!UI.isTouchDevice) return;
+            if (!Util.isTouchDevice) return;
 
             var input = document.getElementById('noVNC_keyboardinput');
 
             if (document.activeElement == input) return;
 
-            UI.keyboardVisible = true;
-            document.getElementById('noVNC_keyboard_button')
-                .classList.add("noVNC_selected");
             input.focus();
 
             try {
@@ -1331,7 +1451,7 @@ var UI;
         },
 
         hideVirtualKeyboard: function() {
-            if (!UI.isTouchDevice) return;
+            if (!Util.isTouchDevice) return;
 
             var input = document.getElementById('noVNC_keyboardinput');
 
@@ -1341,31 +1461,50 @@ var UI;
         },
 
         toggleVirtualKeyboard: function () {
-            if (UI.keyboardVisible) {
+            if (document.getElementById('noVNC_keyboard_button')
+                .classList.contains("noVNC_selected")) {
                 UI.hideVirtualKeyboard();
             } else {
                 UI.showVirtualKeyboard();
             }
         },
 
-        onblurVirtualKeyboard: function() {
-            //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.keyboardVisible = false;
-                document.getElementById('noVNC_keyboard_button')
-                       .classList.remove("noVNC_selected");
-            },100);
+        onfocusVirtualKeyboard: function(event) {
+            document.getElementById('noVNC_keyboard_button')
+                .classList.add("noVNC_selected");
         },
 
-        keepKeyboard: function() {
-            clearTimeout(UI.hideKeyboardTimeout);
-            if(UI.keyboardVisible === true) {
-                UI.showVirtualKeyboard();
-            } else if(UI.keyboardVisible === false) {
-                UI.hideVirtualKeyboard();
+        onblurVirtualKeyboard: function(event) {
+            document.getElementById('noVNC_keyboard_button')
+                .classList.remove("noVNC_selected");
+        },
+
+        keepVirtualKeyboard: function(event) {
+            var input = document.getElementById('noVNC_keyboardinput');
+
+            // Only prevent focus change if the virtual keyboard is active
+            if (document.activeElement != input) {
+                return;
             }
+
+            // Only allow focus to move to other elements that need
+            // focus to function properly
+            if (event.target.form !== undefined) {
+                switch (event.target.type) {
+                    case 'text':
+                    case 'email':
+                    case 'search':
+                    case 'password':
+                    case 'tel':
+                    case 'url':
+                    case 'textarea':
+                    case 'select-one':
+                    case 'select-multiple':
+                        return;
+                }
+            }
+
+            event.preventDefault();
         },
 
         keyboardinputReset: function() {
@@ -1424,7 +1563,7 @@ var UI;
                 UI.rfb.sendKey(KeyTable.XK_BackSpace);
             }
             for (i = newLen - inputs; i < newLen; i++) {
-                UI.rfb.sendKey(newValue.charCodeAt(i));
+                UI.rfb.sendKey(keysyms.fromUnicode(newValue.charCodeAt(i)).keysym);
             }
 
             // Control the text content length in the keyboardinput element
@@ -1439,7 +1578,7 @@ var UI;
                 // 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(UI.keepKeyboard, 0);
+                setTimeout(event.target.focus.bind(event.target), 0);
             } else {
                 UI.lastKeyboardinput = newValue;
             }
@@ -1469,7 +1608,6 @@ var UI;
         },
 
         toggleExtraKeys: function() {
-            UI.keepKeyboard();
             if(document.getElementById('noVNC_modifiers')
                 .classList.contains("noVNC_open")) {
                 UI.closeExtraKeys();
@@ -1479,17 +1617,14 @@ var UI;
         },
 
         sendEsc: function() {
-            UI.keepKeyboard();
             UI.rfb.sendKey(KeyTable.XK_Escape);
         },
 
         sendTab: function() {
-            UI.keepKeyboard();
             UI.rfb.sendKey(KeyTable.XK_Tab);
         },
 
         toggleCtrl: function() {
-            UI.keepKeyboard();
             var btn = document.getElementById('noVNC_toggle_ctrl_button');
             if (btn.classList.contains("noVNC_selected")) {
                 UI.rfb.sendKey(KeyTable.XK_Control_L, false);
@@ -1501,7 +1636,6 @@ var UI;
         },
 
         toggleAlt: function() {
-            UI.keepKeyboard();
             var btn = document.getElementById('noVNC_toggle_alt_button');
             if (btn.classList.contains("noVNC_selected")) {
                 UI.rfb.sendKey(KeyTable.XK_Alt_L, false);
@@ -1513,7 +1647,6 @@ var UI;
         },
 
         sendCtrlAltDel: function() {
-            UI.keepKeyboard();
             UI.rfb.sendCtrlAltDel();
         },
 
@@ -1524,14 +1657,16 @@ var UI;
  * ------v------*/
 
         setMouseButton: function(num) {
-            if (UI.rfb) {
+            var view_only = UI.rfb.get_view_only();
+            if (UI.rfb && !view_only) {
                 UI.rfb.get_mouse().set_touchButton(num);
             }
 
             var blist = [0, 1,2,4];
             for (var b = 0; b < blist.length; b++) {
-                var button = document.getElementById('noVNC_mouse_button' + blist[b]);
-                if (blist[b] === num) {
+                var button = document.getElementById('noVNC_mouse_button' +
+                                                     blist[b]);
+                if (blist[b] === num && !view_only) {
                     button.classList.remove("noVNC_hidden");
                 } else {
                     button.classList.add("noVNC_hidden");
@@ -1540,17 +1675,46 @@ var UI;
         },
 
         displayBlur: function() {
-            if (!UI.rfb) return;
-
-            UI.rfb.get_keyboard().set_focused(false);
-            UI.rfb.get_mouse().set_focused(false);
+            if (UI.rfb && !UI.rfb.get_view_only()) {
+                UI.rfb.get_keyboard().set_focused(false);
+                UI.rfb.get_mouse().set_focused(false);
+            }
         },
 
         displayFocus: function() {
-            if (!UI.rfb) return;
+            if (UI.rfb && !UI.rfb.get_view_only()) {
+                UI.rfb.get_keyboard().set_focused(true);
+                UI.rfb.get_mouse().set_focused(true);
+            }
+        },
+
+        updateLocalCursor: function() {
+            UI.rfb.set_local_cursor(UI.getSetting('cursor'));
+        },
+
+        updateViewOnly: function() {
+            UI.rfb.set_view_only(UI.getSetting('view_only'));
+        },
 
-            UI.rfb.get_keyboard().set_focused(true);
-            UI.rfb.get_mouse().set_focused(true);
+        updateLogging: function() {
+            WebUtil.init_logging(UI.getSetting('logging'));
+        },
+
+        updateSessionSize: function(rfb, width, height) {
+            UI.updateViewClip();
+            UI.fixScrollbars();
+        },
+
+        fixScrollbars: function() {
+            // This is a hack because Chrome screws up the calculation
+            // for when scrollbars are needed. So to fix it we temporarily
+            // toggle them off and on.
+            var screen = document.getElementById('noVNC_screen');
+            screen.style.overflow = 'hidden';
+            // Force Chrome to recalculate the layout by asking for
+            // an element's dimensions
+            screen.getBoundingClientRect();
+            screen.style.overflow = null;
         },
 
         updateDesktopName: function(rfb, name) {
@@ -1579,7 +1743,7 @@ var UI;
  */
     };
 
-    /* [module] UI.load(); */
+    UI.load();
 })();
 
-/* [module] export default UI; */
+export default UI;