]> git.proxmox.com Git - mirror_novnc.git/blobdiff - app/ui.js
Merge pull request #996 from PeterDaveHelloKitchen/zh-tw-translation
[mirror_novnc.git] / app / ui.js
index 52fcf1612a5eaea41078a07bf9a0396690c6529d..0991c708ea41006880912a86a0d5ff63a086886f 100644 (file)
--- a/app/ui.js
+++ b/app/ui.js
@@ -12,7 +12,7 @@
 /* global window, document.getElementById, Util, WebUtil, RFB, Display */
 
 import * as Log from '../core/util/logging.js';
-import _, { l10n } from '../core/util/localization.js';
+import _, { l10n } from './localization.js';
 import { isTouchDevice } from '../core/util/browsers.js';
 import { setCapture, getPointerEvent } from '../core/util/events.js';
 import KeyTable from "../core/input/keysym.js";
@@ -27,7 +27,6 @@ var UI = {
     connected: false,
     desktopName: "",
 
-    resizeTimeout: null,
     statusTimeout: null,
     hideKeyboardTimeout: null,
     idleControlbarTimeout: null,
@@ -87,7 +86,6 @@ var UI = {
         UI.initFullscreen();
 
         // Setup event handlers
-        UI.addResizeHandlers();
         UI.addControlbarHandlers();
         UI.addTouchSpecificHandlers();
         UI.addExtraKeysHandlers();
@@ -103,9 +101,7 @@ var UI = {
 
         UI.openControlbar();
 
-        UI.updateViewClip();
-
-        UI.updateVisualState();
+        UI.updateVisualState('init');
 
         document.documentElement.classList.remove("noVNC_loading");
 
@@ -205,11 +201,6 @@ var UI = {
 * 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);
@@ -269,8 +260,6 @@ var UI = {
 
         document.documentElement
             .addEventListener('mousedown', UI.keepVirtualKeyboard, true);
-        document.documentElement
-            .addEventListener('touchstart', UI.keepVirtualKeyboard, true);
 
         document.getElementById("noVNC_control_bar")
             .addEventListener('touchstart', UI.activateControlbar);
@@ -390,54 +379,41 @@ var 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;
-                UI.doneInitialResize = false;
                 document.documentElement.classList.add("noVNC_connected");
-                if (UI.getSetting('encrypt')) {
-                    msg = _("Connected (encrypted) to ") + UI.desktopName;
-                } else {
-                    msg = _("Connected (unencrypted) to ") + UI.desktopName;
-                }
-                UI.showStatus(msg);
-                document.getElementById('noVNC_canvas').focus();
                 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 (UI.connected) {
@@ -447,7 +423,6 @@ var UI = {
             UI.disableSetting('port');
             UI.disableSetting('path');
             UI.disableSetting('repeaterID');
-            UI.updateViewClip();
             UI.setMouseButton(1);
 
             // Hide the controlbar after 2 seconds
@@ -483,8 +458,6 @@ var 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) {
@@ -496,21 +469,40 @@ var 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;
         }
@@ -534,10 +526,6 @@ var 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
@@ -966,9 +954,9 @@ var 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");
     },
 
@@ -1001,6 +989,12 @@ var 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');
@@ -1014,18 +1008,17 @@ var UI = {
             password = undefined;
         }
 
+        UI.hideStatus();
+
         if (!host) {
-            var msg = _("Must set host");
-            Log.Error(msg);
-            UI.showStatus(msg, 'error');
+            Log.Error("Can't connect when host is: " + host);
+            UI.showStatus(_("Must set host"), 'error');
             return;
         }
 
         UI.closeAllPanels();
         UI.closeConnectPanel();
 
-        UI.updateViewOnly();
-
         var url;
 
         url = UI.getSetting('encrypt') ? 'wss' : 'ws';
@@ -1036,28 +1029,39 @@ var UI = {
         }
         url += '/' + path;
 
-        UI.rfb = new RFB(document.getElementById('noVNC_canvas'), url,
+        UI.rfb = new RFB(document.getElementById('noVNC_container'), url,
                          { shared: UI.getSetting('shared'),
                            repeaterID: UI.getSetting('repeaterID'),
                            credentials: { password: password } });
-        UI.rfb.onnotification = UI.notification;
-        UI.rfb.onupdatestate = UI.updateState;
-        UI.rfb.ondisconnected = UI.disconnectFinished;
-        UI.rfb.oncredentialsrequired = UI.credentials;
-        UI.rfb.oncapabilities = function () { UI.updatePowerButton(); UI.initialResize(); };
-        UI.rfb.onclipboard = UI.clipboardReceive;
-        UI.rfb.onbell = UI.bell;
-        UI.rfb.onfbresize = UI.updateSessionSize;
-        UI.rfb.ondesktopname = UI.updateDesktopName;
+        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.updateVisualState('connecting');
+        UI.updateViewOnly();
     },
 
     disconnect: function() {
         UI.closeAllPanels();
         UI.rfb.disconnect();
 
+        UI.connected = false;
+
         // Disable automatic reconnecting
         UI.inhibit_reconnect = true;
 
+        UI.updateVisualState('disconnecting');
+
+        UI.rfb = undefined;
+
         // Don't display the connection settings until we're actually disconnected
     },
 
@@ -1072,31 +1076,79 @@ var 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;
+
+        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');
     },
 
 /* ------^-------
@@ -1105,7 +1157,7 @@ var UI = {
  *   PASSWORD
  * ------v------*/
 
-    credentials: function(rfb, types) {
+    credentials: function(e) {
         // FIXME: handle more types
         document.getElementById('noVNC_password_dlg')
             .classList.add('noVNC_open');
@@ -1114,12 +1166,14 @@ var UI = {
                 document.getElementById('noVNC_password_input').focus();
             }, 100);
 
-        var 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) {
+        // 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
@@ -1128,8 +1182,6 @@ var UI = {
         UI.reconnect_password = password;
         document.getElementById('noVNC_password_dlg')
             .classList.remove('noVNC_open');
-        // Prevent actually submitting the form
-        e.preventDefault();
     },
 
 /* ------^-------
@@ -1190,74 +1242,8 @@ var UI = {
     applyResizeMode: function() {
         if (!UI.rfb) return;
 
-        var screen = UI.screenSize();
-
-        if (screen && UI.connected) {
-
-            var resizeMode = UI.getSetting('resize');
-            UI.rfb.viewportScale = 1.0;
-
-            // 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 {
-                UI.updateScaling();
-            }
-        }
-    },
-
-    // Re-calculate local scaling
-    updateScaling: function() {
-        if (!UI.rfb) return;
-
-        var resizeMode = UI.getSetting('resize');
-        if (resizeMode !== 'scale') {
-            return;
-        }
-
-        var screen = UI.screenSize();
-
-        if (!screen || !UI.connected) {
-            return;
-        }
-
-        UI.rfb.autoscale(screen.w, screen.h);
-        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 we know the capabilities of the server as
-    // some calls later in the chain is dependant on knowing the
-    // server-capabilities.
-    initialResize: function() {
-        if (UI.doneInitialResize) return;
-
-        UI.applyResizeMode();
-        UI.doneInitialResize = true;
+        UI.rfb.scaleViewport = UI.getSetting('resize') === 'scale';
+        UI.rfb.resizeSession = UI.getSetting('resize') === 'remote';
     },
 
 /* ------^-------
@@ -1266,12 +1252,6 @@ var UI = {
  * VIEW CLIPPING
  * ------v------*/
 
-    // Set and configure viewport clipping
-    setViewClip: function(clip) {
-        UI.updateSetting('view_clip', clip);
-        UI.updateViewClip();
-    },
-
     // Update parameters that depend on the viewport clip setting
     updateViewClip: function() {
         if (!UI.rfb) return;
@@ -1279,11 +1259,7 @@ var UI = {
         var cur_clip = UI.rfb.clipViewport;
         var new_clip = UI.getSetting('view_clip');
 
-        var resizeSetting = UI.getSetting('resize');
-        if (resizeSetting === 'scale') {
-            // Disable viewport clipping if we are scaling
-            new_clip = false;
-        } else if (isTouchDevice) {
+        if (isTouchDevice) {
             // Touch devices usually have shit scrollbars
             new_clip = true;
         }
@@ -1292,15 +1268,6 @@ var UI = {
             UI.rfb.clipViewport = 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.
-            UI.rfb.viewportChangeSize(size.w, size.h);
-            UI.fixScrollbars();
-        }
-
         // Changing the viewport may change the state of
         // the dragging button
         UI.updateViewDrag();
@@ -1341,23 +1308,13 @@ var UI = {
     },
 
     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.clipViewport && UI.rfb.isClipped) {
-            clipping = true;
-        }
-
         var viewDragButton = document.getElementById('noVNC_view_drag_button');
 
-        if (!clipping &&
-            UI.rfb.dragViewport) {
-            // 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.
+        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;
         }
 
@@ -1372,7 +1329,7 @@ var UI = {
         if (isTouchDevice) {
             viewDragButton.classList.remove("noVNC_hidden");
 
-            if (clipping) {
+            if (UI.rfb.clipViewport) {
                 viewDragButton.disabled = false;
             } else {
                 viewDragButton.disabled = true;
@@ -1380,7 +1337,7 @@ var UI = {
         } else {
             viewDragButton.disabled = false;
 
-            if (clipping) {
+            if (UI.rfb.clipViewport) {
                 viewDragButton.classList.remove("noVNC_hidden");
             } else {
                 viewDragButton.classList.add("noVNC_hidden");
@@ -1432,11 +1389,17 @@ var 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) {
@@ -1464,14 +1427,7 @@ var UI = {
             }
         }
 
-        // The default action of touchstart is to generate other
-        // events, which other elements might depend on. So we can't
-        // blindly prevent that. Instead restore focus right away.
-        if (event.type === "touchstart") {
-            setTimeout(input.focus.bind(input));
-        } else {
-            event.preventDefault();
-        }
+        event.preventDefault();
     },
 
     keyboardinputReset: function() {
@@ -1656,31 +1612,13 @@ var UI = {
         WebUtil.init_logging(UI.getSetting('logging'));
     },
 
-    updateSessionSize: function(rfb, width, height) {
-        UI.updateViewClip();
-        UI.updateScaling();
-        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') {
             var promise = document.getElementById('noVNC_bell').play();
             // The standards disagree on the return value here
@@ -1713,7 +1651,7 @@ var UI = {
 };
 
 // Set up translations
-var LINGUAS = ["de", "el", "nl", "pl", "sv"];
+var LINGUAS = ["de", "el", "nl", "pl", "sv", "zh"];
 l10n.setup(LINGUAS);
 if (l10n.language !== "en" && l10n.dictionary === undefined) {
     WebUtil.fetchJSON('app/locale/' + l10n.language + '.json', function (translations) {