]> git.proxmox.com Git - mirror_novnc.git/blobdiff - app/ui.js
Add translation in zh_CN
[mirror_novnc.git] / app / ui.js
index 73ad2b447000ea7044c81077db11deb072d0b95b..f3c6d46d416a75dae3d80fe9483b7a6122e3aec1 100644 (file)
--- a/app/ui.js
+++ b/app/ui.js
@@ -8,25 +8,22 @@
  * See README.md for usage and integration instructions.
  */
 
-/* jslint white: false, browser: true */
-/* global window, document.getElementById, Util, WebUtil, RFB, Display */
-
 import * as Log from '../core/util/logging.js';
-import _, { l10n } from '../core/util/localization.js';
-import { isTouchDevice, browserSupportsCursorURIs as cursorURIsSupported } from '../core/util/browsers.js';
+import _, { l10n } from './localization.js';
+import { isTouchDevice } from '../core/util/browser.js';
 import { setCapture, getPointerEvent } from '../core/util/events.js';
 import KeyTable from "../core/input/keysym.js";
 import keysyms from "../core/input/keysymdef.js";
+import Keyboard from "../core/input/keyboard.js";
 import RFB from "../core/rfb.js";
 import Display from "../core/display.js";
 import * as WebUtil from "./webutil.js";
 
-const UI = {
+var UI = {
 
     connected: false,
     desktopName: "",
 
-    resizeTimeout: null,
     statusTimeout: null,
     hideKeyboardTimeout: null,
     idleControlbarTimeout: null,
@@ -38,7 +35,6 @@ const UI = {
     controlbarMouseDownOffsetY: 0,
 
     isSafari: false,
-    rememberedClipSetting: null,
     lastKeyboardinput: null,
     defaultKeyboardinputLen: 100,
 
@@ -87,11 +83,10 @@ const UI = {
         UI.initFullscreen();
 
         // Setup event handlers
-        UI.addResizeHandlers();
         UI.addControlbarHandlers();
         UI.addTouchSpecificHandlers();
         UI.addExtraKeysHandlers();
-        UI.addXvpHandlers();
+        UI.addMachineHandlers();
         UI.addConnectionControlHandlers();
         UI.addClipboardHandlers();
         UI.addSettingsHandlers();
@@ -103,16 +98,8 @@ const UI = {
 
         UI.openControlbar();
 
-        // Show the connect panel on first load unless autoconnecting
-        if (!autoconnect) {
-            UI.openConnectPanel();
-        }
-
-        UI.updateViewClip();
-
-        UI.updateVisualState();
+        UI.updateVisualState('init');
 
-        document.getElementById('noVNC_setting_host').focus();
         document.documentElement.classList.remove("noVNC_loading");
 
         var autoconnect = WebUtil.getConfigVar('autoconnect', false);
@@ -121,6 +108,8 @@ const UI = {
             UI.connect();
         } else {
             autoconnect = false;
+            // Show the connect panel on first load unless autoconnecting
+            UI.openConnectPanel();
         }
 
         if (typeof callback === "function") {
@@ -171,8 +160,7 @@ const UI = {
         UI.initSetting('host', window.location.hostname);
         UI.initSetting('port', port);
         UI.initSetting('encrypt', (window.location.protocol === "https:"));
-        UI.initSetting('cursor', !isTouchDevice);
-        UI.initSetting('clip', false);
+        UI.initSetting('view_clip', false);
         UI.initSetting('resize', 'off');
         UI.initSetting('shared', true);
         UI.initSetting('view_only', false);
@@ -204,39 +192,12 @@ const UI = {
         }
     },
 
-    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;
-            Log.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);
@@ -245,12 +206,12 @@ const UI = {
         document.getElementById("noVNC_control_bar")
             .addEventListener('mousedown', UI.activateControlbar);
         document.getElementById("noVNC_control_bar")
-            .addEventListener('keypress', UI.activateControlbar);
+            .addEventListener('keydown', UI.activateControlbar);
 
         document.getElementById("noVNC_control_bar")
             .addEventListener('mousedown', UI.keepControlbar);
         document.getElementById("noVNC_control_bar")
-            .addEventListener('keypress', UI.keepControlbar);
+            .addEventListener('keydown', UI.keepControlbar);
 
         document.getElementById("noVNC_view_drag_button")
             .addEventListener('click', UI.toggleViewDrag);
@@ -282,6 +243,9 @@ const UI = {
         document.getElementById("noVNC_keyboard_button")
             .addEventListener('click', UI.toggleVirtualKeyboard);
 
+        UI.touchKeyboard = new Keyboard(document.getElementById('noVNC_keyboardinput'));
+        UI.touchKeyboard.onkeyevent = UI.keyEvent;
+        UI.touchKeyboard.grab();
         document.getElementById("noVNC_keyboardinput")
             .addEventListener('input', UI.keyInput);
         document.getElementById("noVNC_keyboardinput")
@@ -331,15 +295,15 @@ const UI = {
             .addEventListener('click', UI.sendCtrlAltDel);
     },
 
-    addXvpHandlers: function() {
-        document.getElementById("noVNC_xvp_shutdown_button")
-            .addEventListener('click', function() { UI.rfb.xvpShutdown(); });
-        document.getElementById("noVNC_xvp_reboot_button")
-            .addEventListener('click', function() { UI.rfb.xvpReboot(); });
-        document.getElementById("noVNC_xvp_reset_button")
-            .addEventListener('click', function() { UI.rfb.xvpReset(); });
-        document.getElementById("noVNC_xvp_button")
-            .addEventListener('click', UI.toggleXvpPanel);
+    addMachineHandlers: function() {
+        document.getElementById("noVNC_shutdown_button")
+            .addEventListener('click', function() { UI.rfb.machineShutdown(); });
+        document.getElementById("noVNC_reboot_button")
+            .addEventListener('click', function() { UI.rfb.machineReboot(); });
+        document.getElementById("noVNC_reset_button")
+            .addEventListener('click', function() { UI.rfb.machineReset(); });
+        document.getElementById("noVNC_power_button")
+            .addEventListener('click', UI.togglePowerPanel);
     },
 
     addConnectionControlHandlers: function() {
@@ -357,10 +321,6 @@ const UI = {
     addClipboardHandlers: function() {
         document.getElementById("noVNC_clipboard_button")
             .addEventListener('click', UI.toggleClipboardPanel);
-        document.getElementById("noVNC_clipboard_text")
-            .addEventListener('focus', UI.displayBlur);
-        document.getElementById("noVNC_clipboard_text")
-            .addEventListener('blur', UI.displayFocus);
         document.getElementById("noVNC_clipboard_text")
             .addEventListener('change', UI.clipboardSend);
         document.getElementById("noVNC_clipboard_clear_button")
@@ -382,13 +342,11 @@ const UI = {
             .addEventListener('click', UI.toggleSettingsPanel);
 
         UI.addSettingChangeHandler('encrypt');
-        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('view_clip');
+        UI.addSettingChangeHandler('view_clip', UI.updateViewClip);
         UI.addSettingChangeHandler('shared');
         UI.addSettingChangeHandler('view_only');
         UI.addSettingChangeHandler('view_only', UI.updateViewOnly);
@@ -418,60 +376,43 @@ const UI = {
  *     VISUAL
  * ------v------*/
 
-    updateState: function(rfb, state, oldstate) {
-        var msg;
+    // Disable/enable controls depending on connection state
+    updateVisualState: function(state) {
 
         document.documentElement.classList.remove("noVNC_connecting");
         document.documentElement.classList.remove("noVNC_connected");
         document.documentElement.classList.remove("noVNC_disconnecting");
         document.documentElement.classList.remove("noVNC_reconnecting");
 
+        let transition_elem = document.getElementById("noVNC_transition_text");
         switch (state) {
+            case 'init':
+                break;
             case 'connecting':
-                document.getElementById("noVNC_transition_text").textContent = _("Connecting...");
+                transition_elem.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()) {
-                    msg = _("Connected (encrypted) to ") + UI.desktopName;
-                } else {
-                    msg = _("Connected (unencrypted) to ") + UI.desktopName;
-                }
-                UI.showStatus(msg);
                 break;
             case 'disconnecting':
-                UI.connected = false;
-                document.getElementById("noVNC_transition_text").textContent = _("Disconnecting...");
+                transition_elem.textContent = _("Disconnecting...");
                 document.documentElement.classList.add("noVNC_disconnecting");
                 break;
             case 'disconnected':
-                UI.showStatus(_("Disconnected"));
                 break;
-            default:
-                msg = "Invalid UI state";
-                Log.Error(msg);
-                UI.showStatus(msg, 'error');
+            case 'reconnecting':
+                transition_elem.textContent = _("Reconnecting...");
+                document.documentElement.classList.add("noVNC_reconnecting");
                 break;
+            default:
+                Log.Error("Invalid visual state: " + state);
+                UI.showStatus(_("Internal error"), 'error');
+                return;
         }
 
-        UI.updateVisualState();
-    },
-
-    // Disable/enable controls depending on connection state
-    updateVisualState: function() {
-        //Log.Debug(">> updateVisualState");
-
         UI.enableDisableViewClip();
 
-        if (cursorURIsSupported() && !isTouchDevice) {
-            UI.enableSetting('cursor');
-        } else {
-            UI.disableSetting('cursor');
-        }
-
         if (UI.connected) {
             UI.disableSetting('encrypt');
             UI.disableSetting('shared');
@@ -479,7 +420,6 @@ const UI = {
             UI.disableSetting('port');
             UI.disableSetting('path');
             UI.disableSetting('repeaterID');
-            UI.updateViewClip();
             UI.setMouseButton(1);
 
             // Hide the controlbar after 2 seconds
@@ -491,23 +431,10 @@ const UI = {
             UI.enableSetting('port');
             UI.enableSetting('path');
             UI.enableSetting('repeaterID');
-            UI.updateXvpButton(0);
+            UI.updatePowerButton();
             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);
@@ -515,8 +442,6 @@ const UI = {
         // State change also closes the password dialog
         document.getElementById('noVNC_password_dlg')
             .classList.remove('noVNC_open');
-
-        //Log.Debug("<< updateVisualState");
     },
 
     showStatus: function(text, status_type, time) {
@@ -528,21 +453,40 @@ const UI = {
             status_type = 'normal';
         }
 
-        statusElem.classList.remove("noVNC_status_normal");
-        statusElem.classList.remove("noVNC_status_warn");
-        statusElem.classList.remove("noVNC_status_error");
+        // Don't overwrite more severe visible statuses and never
+        // errors. Only shows the first error.
+        let visible_status_type = 'none';
+        if (statusElem.classList.contains("noVNC_open")) {
+            if (statusElem.classList.contains("noVNC_status_error")) {
+                visible_status_type = 'error';
+            } else if (statusElem.classList.contains("noVNC_status_warn")) {
+                visible_status_type = 'warn';
+            } else {
+                visible_status_type = 'normal';
+            }
+        }
+        if (visible_status_type === 'error' ||
+            (visible_status_type === 'warn' && status_type === 'normal')) {
+            return;
+        }
 
         switch (status_type) {
+            case 'error':
+                statusElem.classList.remove("noVNC_status_warn");
+                statusElem.classList.remove("noVNC_status_normal");
+                statusElem.classList.add("noVNC_status_error");
+                break;
             case 'warning':
             case 'warn':
+                statusElem.classList.remove("noVNC_status_error");
+                statusElem.classList.remove("noVNC_status_normal");
                 statusElem.classList.add("noVNC_status_warn");
                 break;
-            case 'error':
-                statusElem.classList.add("noVNC_status_error");
-                break;
             case 'normal':
             case 'info':
             default:
+                statusElem.classList.remove("noVNC_status_error");
+                statusElem.classList.remove("noVNC_status_warn");
                 statusElem.classList.add("noVNC_status_normal");
                 break;
         }
@@ -566,10 +510,6 @@ const UI = {
         document.getElementById('noVNC_status').classList.remove("noVNC_open");
     },
 
-    notification: function (rfb, msg, level, options) {
-        UI.showStatus(msg, level);
-    },
-
     activateControlbar: function(event) {
         clearTimeout(UI.idleControlbarTimeout);
         // We manipulate the anchor instead of the actual control
@@ -609,10 +549,15 @@ const UI = {
     },
 
     toggleControlbarSide: function () {
-        // Temporarily disable animation to avoid weird movement
+        // Temporarily disable animation, if bar is displayed, to avoid weird
+        // movement. The transitionend-event will not fire when display=none.
         var bar = document.getElementById('noVNC_control_bar');
-        bar.style.transitionDuration = '0s';
-        bar.addEventListener('transitionend', function () { this.style.transitionDuration = ""; });
+        var barDisplayStyle = window.getComputedStyle(bar).display;
+        if (barDisplayStyle !== 'none') {
+            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")) {
@@ -627,6 +572,15 @@ const UI = {
         UI.controlbarDrag = true;
     },
 
+    showControlbarHint: function (show) {
+        var hint = document.getElementById('noVNC_control_bar_hint');
+        if (show) {
+            hint.classList.add("noVNC_active");
+        } else {
+            hint.classList.remove("noVNC_active");
+        }
+    },
+
     dragControlbarHandle: function (e) {
         if (!UI.controlbarGrabbed) return;
 
@@ -722,6 +676,7 @@ const UI = {
             UI.activateControlbar();
         }
         UI.controlbarGrabbed = false;
+        UI.showControlbarHint(false);
     },
 
     controlbarHandleMouseDown: function(e) {
@@ -740,6 +695,8 @@ const UI = {
         UI.controlbarGrabbed = true;
         UI.controlbarDrag = false;
 
+        UI.showControlbarHint(true);
+
         UI.controlbarMouseDownClientY = ptr.clientY;
         UI.controlbarMouseDownOffsetY = ptr.clientY - bounds.top;
         e.preventDefault();
@@ -769,21 +726,17 @@ const UI = {
         if (val === null) {
             val = WebUtil.readSetting(name, defVal);
         }
-        UI.updateSetting(name, val);
+        WebUtil.setSetting(name, val);
+        UI.updateSetting(name);
         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);
-        }
+    updateSetting: function(name) {
 
         // Update the settings control
-        value = UI.getSetting(name);
+        var value = UI.getSetting(name);
 
         var ctrl = document.getElementById('noVNC_setting_' + name);
         if (ctrl.type === 'checkbox') {
@@ -858,7 +811,7 @@ const UI = {
 
     closeAllPanels: function() {
         UI.closeSettingsPanel();
-        UI.closeXvpPanel();
+        UI.closePowerPanel();
         UI.closeClipboardPanel();
         UI.closeExtraKeys();
     },
@@ -875,13 +828,7 @@ const UI = {
 
         // Refresh UI elements from saved cookies
         UI.updateSetting('encrypt');
-        if (cursorURIsSupported()) {
-            UI.updateSetting('cursor');
-        } else {
-            UI.updateSetting('cursor', !isTouchDevice);
-            UI.disableSetting('cursor');
-        }
-        UI.updateSetting('clip');
+        UI.updateSetting('view_clip');
         UI.updateSetting('resize');
         UI.updateSetting('shared');
         UI.updateSetting('view_only');
@@ -916,50 +863,52 @@ const UI = {
 /* ------^-------
  *   /SETTINGS
  * ==============
- *      XVP
+ *     POWER
  * ------v------*/
 
-    openXvpPanel: function() {
+    openPowerPanel: function() {
         UI.closeAllPanels();
         UI.openControlbar();
 
-        document.getElementById('noVNC_xvp')
+        document.getElementById('noVNC_power')
             .classList.add("noVNC_open");
-        document.getElementById('noVNC_xvp_button')
+        document.getElementById('noVNC_power_button')
             .classList.add("noVNC_selected");
     },
 
-    closeXvpPanel: function() {
-        document.getElementById('noVNC_xvp')
+    closePowerPanel: function() {
+        document.getElementById('noVNC_power')
             .classList.remove("noVNC_open");
-        document.getElementById('noVNC_xvp_button')
+        document.getElementById('noVNC_power_button')
             .classList.remove("noVNC_selected");
     },
 
-    toggleXvpPanel: function() {
-        if (document.getElementById('noVNC_xvp')
+    togglePowerPanel: function() {
+        if (document.getElementById('noVNC_power')
             .classList.contains("noVNC_open")) {
-            UI.closeXvpPanel();
+            UI.closePowerPanel();
         } else {
-            UI.openXvpPanel();
+            UI.openPowerPanel();
         }
     },
 
-    // Disable/enable XVP button
-    updateXvpButton: function(ver) {
-        if (ver >= 1 && !UI.rfb.get_view_only()) {
-            document.getElementById('noVNC_xvp_button')
+    // Disable/enable power button
+    updatePowerButton: function() {
+        if (UI.connected &&
+            UI.rfb.capabilities.power &&
+            !UI.rfb.viewOnly) {
+            document.getElementById('noVNC_power_button')
                 .classList.remove("noVNC_hidden");
         } else {
-            document.getElementById('noVNC_xvp_button')
+            document.getElementById('noVNC_power_button')
                 .classList.add("noVNC_hidden");
-            // Close XVP panel if open
-            UI.closeXvpPanel();
+            // Close power panel if open
+            UI.closePowerPanel();
         }
     },
 
 /* ------^-------
- *     /XVP
+ *    /POWER
  * ==============
  *   CLIPBOARD
  * ------v------*/
@@ -990,9 +939,9 @@ const UI = {
         }
     },
 
-    clipboardReceive: function(rfb, text) {
-        Log.Debug(">> UI.clipboardReceive: " + text.substr(0,40) + "...");
-        document.getElementById('noVNC_clipboard_text').value = text;
+    clipboardReceive: function(e) {
+        Log.Debug(">> UI.clipboardReceive: " + e.detail.text.substr(0,40) + "...");
+        document.getElementById('noVNC_clipboard_text').value = e.detail.text;
         Log.Debug("<< UI.clipboardReceive");
     },
 
@@ -1025,49 +974,77 @@ const UI = {
     },
 
     connect: function(event, password) {
+
+        // Ignore when rfb already exists
+        if (typeof UI.rfb !== 'undefined') {
+            return;
+        }
+
         var host = UI.getSetting('host');
         var port = UI.getSetting('port');
         var path = UI.getSetting('path');
 
         if (typeof password === 'undefined') {
             password = WebUtil.getConfigVar('password');
+            UI.reconnect_password = password;
         }
 
         if (password === null) {
             password = undefined;
         }
 
-        if ((!host) || (!port)) {
-            var msg = _("Must set host and port");
-            Log.Error(msg);
-            UI.showStatus(msg, 'error');
+        UI.hideStatus();
+
+        if (!host) {
+            Log.Error("Can't connect when host is: " + host);
+            UI.showStatus(_("Must set host"), 'error');
             return;
         }
 
-        if (!UI.initRFB()) return;
-
         UI.closeAllPanels();
         UI.closeConnectPanel();
 
-        UI.rfb.set_encrypt(UI.getSetting('encrypt'));
-        UI.rfb.set_shared(UI.getSetting('shared'));
-        UI.rfb.set_repeaterID(UI.getSetting('repeaterID'));
+        UI.updateVisualState('connecting');
+
+        var url;
 
-        UI.updateLocalCursor();
-        UI.updateViewOnly();
+        url = UI.getSetting('encrypt') ? 'wss' : 'ws';
 
-        UI.rfb.connect(host, port, password, path);
+        url += '://' + host;
+        if(port) {
+            url += ':' + port;
+        }
+        url += '/' + path;
+
+        UI.rfb = new RFB(document.getElementById('noVNC_container'), url,
+                         { shared: UI.getSetting('shared'),
+                           repeaterID: UI.getSetting('repeaterID'),
+                           credentials: { password: password } });
+        UI.rfb.addEventListener("connect", UI.connectFinished);
+        UI.rfb.addEventListener("disconnect", UI.disconnectFinished);
+        UI.rfb.addEventListener("credentialsrequired", UI.credentials);
+        UI.rfb.addEventListener("securityfailure", UI.securityFailed);
+        UI.rfb.addEventListener("capabilities", function () { UI.updatePowerButton(); });
+        UI.rfb.addEventListener("clipboard", UI.clipboardReceive);
+        UI.rfb.addEventListener("bell", UI.bell);
+        UI.rfb.addEventListener("desktopname", UI.updateDesktopName);
+        UI.rfb.clipViewport = UI.getSetting('view_clip');
+        UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
+        UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
+
+        UI.updateViewOnly(); // requires UI.rfb
     },
 
     disconnect: function() {
         UI.closeAllPanels();
         UI.rfb.disconnect();
 
+        UI.connected = false;
+
         // Disable automatic reconnecting
         UI.inhibit_reconnect = true;
 
-        // Restore the callback used for initial resize
-        UI.rfb.set_onFBUComplete(UI.initialResize);
+        UI.updateVisualState('disconnecting');
 
         // Don't display the connection settings until we're actually disconnected
     },
@@ -1083,31 +1060,81 @@ const UI = {
         UI.connect(null, UI.reconnect_password);
     },
 
-    disconnectFinished: function (rfb, reason) {
-        if (typeof reason !== 'undefined') {
-            UI.showStatus(reason, 'error');
+    cancelReconnect: function() {
+        if (UI.reconnect_callback !== null) {
+            clearTimeout(UI.reconnect_callback);
+            UI.reconnect_callback = null;
+        }
+
+        UI.updateVisualState('disconnected');
+
+        UI.openControlbar();
+        UI.openConnectPanel();
+    },
+
+    connectFinished: function (e) {
+        UI.connected = true;
+        UI.inhibit_reconnect = false;
+
+        let msg;
+        if (UI.getSetting('encrypt')) {
+            msg = _("Connected (encrypted) to ") + UI.desktopName;
+        } else {
+            msg = _("Connected (unencrypted) to ") + UI.desktopName;
+        }
+        UI.showStatus(msg);
+        UI.updateVisualState('connected');
+
+        // Do this last because it can only be used on rendered elements
+        UI.rfb.focus();
+    },
+
+    disconnectFinished: function (e) {
+        let wasConnected = UI.connected;
+
+        // This variable is ideally set when disconnection starts, but
+        // when the disconnection isn't clean or if it is initiated by
+        // the server, we need to do it here as well since
+        // UI.disconnect() won't be used in those cases.
+        UI.connected = false;
+
+        UI.rfb = undefined;
+
+        if (!e.detail.clean) {
+            UI.updateVisualState('disconnected');
+            if (wasConnected) {
+                UI.showStatus(_("Something went wrong, connection is closed"),
+                              'error');
+            } else {
+                UI.showStatus(_("Failed to connect to server"), 'error');
+            }
         } else if (UI.getSetting('reconnect', false) === true && !UI.inhibit_reconnect) {
-            document.getElementById("noVNC_transition_text").textContent = _("Reconnecting...");
-            document.documentElement.classList.add("noVNC_reconnecting");
+            UI.updateVisualState('reconnecting');
 
             var delay = parseInt(UI.getSetting('reconnect_delay'));
             UI.reconnect_callback = setTimeout(UI.reconnect, delay);
             return;
+        } else {
+            UI.updateVisualState('disconnected');
+            UI.showStatus(_("Disconnected"), 'normal');
         }
 
         UI.openControlbar();
         UI.openConnectPanel();
     },
 
-    cancelReconnect: function() {
-        if (UI.reconnect_callback !== null) {
-            clearTimeout(UI.reconnect_callback);
-            UI.reconnect_callback = null;
+    securityFailed: function (e) {
+        let msg = "";
+        // On security failures we might get a string with a reason
+        // directly from the server. Note that we can't control if
+        // this string is translated or not.
+        if ('reason' in e.detail) {
+            msg = _("New connection has been rejected with reason: ") +
+                e.detail.reason;
+        } else {
+            msg = _("New connection has been rejected");
         }
-
-        document.documentElement.classList.remove("noVNC_reconnecting");
-        UI.openControlbar();
-        UI.openConnectPanel();
+        UI.showStatus(msg, 'error');
     },
 
 /* ------^-------
@@ -1116,8 +1143,8 @@ const UI = {
  *   PASSWORD
  * ------v------*/
 
-    passwordRequired: function(rfb, msg) {
-
+    credentials: function(e) {
+        // FIXME: handle more types
         document.getElementById('noVNC_password_dlg')
             .classList.add('noVNC_open');
 
@@ -1125,21 +1152,22 @@ const UI = {
                 document.getElementById('noVNC_password_input').focus();
             }, 100);
 
-        if (typeof msg === 'undefined') {
-            msg = _("Password is required");
-        }
-        Log.Warn(msg);
-        UI.showStatus(msg, "warning");
+        Log.Warn("Server asked for a password");
+        UI.showStatus(_("Password is required"), "warning");
     },
 
     setPassword: function(e) {
-        var password = document.getElementById('noVNC_password_input').value;
-        UI.rfb.sendPassword(password);
+        // Prevent actually submitting the form
+        e.preventDefault();
+
+        var inputElem = document.getElementById('noVNC_password_input');
+        var password = inputElem.value;
+        // Clear the input after reading the password
+        inputElem.value = "";
+        UI.rfb.sendCredentials({ password: password });
         UI.reconnect_password = password;
         document.getElementById('noVNC_password_dlg')
             .classList.remove('noVNC_open');
-        // Prevent actually submitting the form
-        e.preventDefault();
     },
 
 /* ------^-------
@@ -1200,99 +1228,30 @@ const UI = {
     applyResizeMode: function() {
         if (!UI.rfb) return;
 
-        var screen = UI.screenSize();
-
-        if (screen && UI.connected && UI.rfb.get_display()) {
-
-            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') {
-
-                // Request changing the resolution of the remote display to
-                // the size of the local browser viewport.
-
-                // In order to not send multiple requests before the browser-resize
-                // is finished we wait 0.5 seconds before sending the request.
-                clearTimeout(UI.resizeTimeout);
-                UI.resizeTimeout = setTimeout(function(){
-                    // Request a remote size covering the viewport
-                    if (UI.rfb.requestDesktopSize(screen.w, screen.h)) {
-                        Log.Debug('Requested new desktop size: ' +
-                                   screen.w + 'x' + screen.h);
-                    }
-                }, 500);
-
-            } else if (resizeMode === 'scale' || resizeMode === 'downscale') {
-                var downscaleOnly = resizeMode === 'downscale';
-                display.autoscale(screen.w, screen.h, downscaleOnly);
-                UI.fixScrollbars();
-            }
-        }
-    },
-
-    // Gets the the size of the available viewport in the browser window
-    screenSize: function() {
-        var screen = document.getElementById('noVNC_screen');
-        return {w: screen.offsetWidth, h: screen.offsetHeight};
-    },
-
-    // Normally we only apply the current resize mode after a window resize
-    // event. This means that when a new connection is opened, there is no
-    // resize mode active.
-    // We have to wait until the first FBU because this is where the client
-    // will find the supported encodings of the server. Some calls later in
-    // the chain is dependant on knowing the server-capabilities.
-    initialResize: function(rfb, fbu) {
-        UI.applyResizeMode();
-        // After doing this once, we remove the callback.
-        UI.rfb.set_onFBUComplete(function() { });
+        UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
+        UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
     },
 
 /* ------^-------
  *    /RESIZE
  * ==============
- *    CLIPPING
+ * VIEW CLIPPING
  * ------v------*/
 
-    // Set and configure viewport clipping
-    setViewClip: function(clip) {
-        UI.updateSetting('clip', clip);
-        UI.updateViewClip();
-    },
-
-    // Update parameters that depend on the clip setting
+    // Update parameters that depend on the viewport clip setting
     updateViewClip: function() {
         if (!UI.rfb) return;
 
-        var display = UI.rfb.get_display();
-        var cur_clip = display.get_viewport();
-        var new_clip = UI.getSetting('clip');
+        var cur_clip = UI.rfb.clipViewport;
+        var new_clip = UI.getSetting('view_clip');
 
-        var resizeSetting = UI.getSetting('resize');
-        if (resizeSetting === 'downscale' || resizeSetting === 'scale') {
-            // Disable clipping if we are scaling
-            new_clip = false;
-        } else if (isTouchDevice) {
+        if (isTouchDevice) {
             // Touch devices usually have shit scrollbars
             new_clip = true;
         }
 
         if (cur_clip !== new_clip) {
-            display.set_viewport(new_clip);
-        }
-
-        var size = UI.screenSize();
-
-        if (new_clip && size) {
-            // When clipping is enabled, the screen is limited to
-            // the size of the browser window.
-            display.viewportChangeSize(size.w, size.h);
-            UI.fixScrollbars();
+            UI.rfb.clipViewport = new_clip;
         }
 
         // Changing the viewport may change the state of
@@ -1300,20 +1259,20 @@ const UI = {
         UI.updateViewDrag();
     },
 
-    // Handle special cases where clipping is forced on/off or locked
+    // Handle special cases where viewport clipping is forced on/off or locked
     enableDisableViewClip: function() {
         var resizeSetting = UI.getSetting('resize');
         // Disable clipping if we are scaling, connected or on touch
-        if (resizeSetting === 'downscale' || resizeSetting === 'scale' ||
+        if (resizeSetting === 'scale' ||
             isTouchDevice) {
-            UI.disableSetting('clip');
+            UI.disableSetting('view_clip');
         } else {
-            UI.enableSetting('clip');
+            UI.enableSetting('view_clip');
         }
     },
 
 /* ------^-------
- *   /CLIPPING
+ * /VIEW CLIPPING
  * ==============
  *    VIEWDRAG
  * ------v------*/
@@ -1321,7 +1280,7 @@ const UI = {
     toggleViewDrag: function() {
         if (!UI.rfb) return;
 
-        var drag = UI.rfb.get_viewportDrag();
+        var drag = UI.rfb.dragViewport;
         UI.setViewDrag(!drag);
      },
 
@@ -1329,34 +1288,23 @@ const UI = {
     setViewDrag: function(drag) {
         if (!UI.rfb) return;
 
-        UI.rfb.set_viewportDrag(drag);
+        UI.rfb.dragViewport = drag;
 
         UI.updateViewDrag();
     },
 
     updateViewDrag: function() {
-        var clipping = false;
-
         if (!UI.connected) return;
 
-        // Check if viewport drag is possible. It is only possible
-        // if the remote display is clipping the client display.
-        if (UI.rfb.get_display().get_viewport() &&
-            UI.rfb.get_display().clippingDisplay()) {
-            clipping = true;
-        }
-
         var viewDragButton = document.getElementById('noVNC_view_drag_button');
 
-        if (!clipping &&
-            UI.rfb.get_viewportDrag()) {
-            // The size of the remote display is the same or smaller
-            // than the client display. Make sure viewport drag isn't
-            // active when it can't be used.
-            UI.rfb.set_viewportDrag(false);
+        if (!UI.rfb.clipViewport && UI.rfb.dragViewport) {
+            // We are no longer clipping the viewport. Make sure
+            // viewport drag isn't active when it can't be used.
+            UI.rfb.dragViewport = false;
         }
 
-        if (UI.rfb.get_viewportDrag()) {
+        if (UI.rfb.dragViewport) {
             viewDragButton.classList.add("noVNC_selected");
         } else {
             viewDragButton.classList.remove("noVNC_selected");
@@ -1367,7 +1315,7 @@ const UI = {
         if (isTouchDevice) {
             viewDragButton.classList.remove("noVNC_hidden");
 
-            if (clipping) {
+            if (UI.rfb.clipViewport) {
                 viewDragButton.disabled = false;
             } else {
                 viewDragButton.disabled = true;
@@ -1375,7 +1323,7 @@ const UI = {
         } else {
             viewDragButton.disabled = false;
 
-            if (clipping) {
+            if (UI.rfb.clipViewport) {
                 viewDragButton.classList.remove("noVNC_hidden");
             } else {
                 viewDragButton.classList.add("noVNC_hidden");
@@ -1427,11 +1375,17 @@ const UI = {
     onfocusVirtualKeyboard: function(event) {
         document.getElementById('noVNC_keyboard_button')
             .classList.add("noVNC_selected");
+        if (UI.rfb) {
+            UI.rfb.focusOnClick = false;
+        }
     },
 
     onblurVirtualKeyboard: function(event) {
         document.getElementById('noVNC_keyboard_button')
             .classList.remove("noVNC_selected");
+        if (UI.rfb) {
+            UI.rfb.focusOnClick = true;
+        }
     },
 
     keepVirtualKeyboard: function(event) {
@@ -1468,6 +1422,12 @@ const UI = {
         UI.lastKeyboardinput = kbi.value;
     },
 
+    keyEvent: function (keysym, code, down) {
+        if (!UI.rfb) return;
+
+        UI.rfb.sendKey(keysym, code, down);
+    },
+
     // 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
@@ -1612,9 +1572,9 @@ const UI = {
  * ------v------*/
 
     setMouseButton: function(num) {
-        var view_only = UI.rfb.get_view_only();
+        var view_only = UI.rfb.viewOnly;
         if (UI.rfb && !view_only) {
-            UI.rfb.get_mouse().set_touchButton(num);
+            UI.rfb.touchButton = num;
         }
 
         var blist = [0, 1,2,4];
@@ -1629,61 +1589,41 @@ const UI = {
         }
     },
 
-    displayBlur: function() {
-        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 && !UI.rfb.get_view_only()) {
-            UI.rfb.get_keyboard().set_focused(true);
-            UI.rfb.get_mouse().set_focused(true);
-        }
-    },
-
-    updateLocalCursor: function() {
-        if (!UI.rfb) return;
-        UI.rfb.set_local_cursor(UI.getSetting('cursor'));
-    },
-
     updateViewOnly: function() {
         if (!UI.rfb) return;
-        UI.rfb.set_view_only(UI.getSetting('view_only'));
+        UI.rfb.viewOnly = UI.getSetting('view_only');
+
+        // Hide input related buttons in view only mode
+        if (UI.rfb.viewOnly) {
+            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');
+        }
+        UI.setMouseButton(1); //has it's own logic for hiding/showing
     },
 
     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 = "";
-    },
-
-    updateDesktopName: function(rfb, name) {
-        UI.desktopName = name;
+    updateDesktopName: function(e) {
+        UI.desktopName = e.detail.name;
         // Display the desktop name in the document title
-        document.title = name + " - noVNC";
+        document.title = e.detail.name + " - noVNC";
     },
 
-    bell: function(rfb) {
+    bell: function(e) {
         if (WebUtil.getConfigVar('bell', 'on') === 'on') {
-            document.getElementById('noVNC_bell').play()
-                .catch(function(e) {
+            var promise = document.getElementById('noVNC_bell').play();
+            // The standards disagree on the return value here
+            if (promise) {
+                promise.catch(function(e) {
                     if (e.name === "NotAllowedError") {
                         // Ignore when the browser doesn't let us play audio.
                         // It is common that the browsers require audio to be
@@ -1692,6 +1632,7 @@ const UI = {
                         Log.Error("Unable to play bell: " + e);
                     }
                 });
+            }
         }
     },
 
@@ -1710,7 +1651,7 @@ const UI = {
 };
 
 // Set up translations
-var LINGUAS = ["de", "el", "nl", "sv"];
+var LINGUAS = ["de", "el", "es", "nl", "pl", "sv", "tr", "zh_CN", "zh_TW"];
 l10n.setup(LINGUAS);
 if (l10n.language !== "en" && l10n.dictionary === undefined) {
     WebUtil.fetchJSON('app/locale/' + l10n.language + '.json', function (translations) {
@@ -1719,7 +1660,8 @@ if (l10n.language !== "en" && l10n.dictionary === undefined) {
         // wait for translations to load before loading the UI
         UI.prime();
     }, function (err) {
-        throw err;
+        Log.Error("Failed to load translations: " + err);
+        UI.prime();
     });
 } else {
     UI.prime();