]> git.proxmox.com Git - mirror_novnc.git/blobdiff - app/ui.js
Keep the connect panel when missing host or port
[mirror_novnc.git] / app / ui.js
index 27187fb1fc36e06943f11071deaf8532c89ef495..889ceaa5d19382afabf0234e9e864f8199fd3143 100644 (file)
--- a/app/ui.js
+++ b/app/ui.js
@@ -36,12 +36,19 @@ var UI;
 
     UI = {
 
-        rfb_state: 'loaded',
+        connected: false,
+        desktopName: "",
 
         resizeTimeout: null,
         statusTimeout: null,
         hideKeyboardTimeout: null,
+        idleControlbarTimeout: null,
+        closeControlbarTimeout: null,
 
+        controlbarGrabbed: false,
+        controlbarDrag: false,
+        controlbarMouseDownClientY: 0,
+        controlbarMouseDownOffsetY: 0,
         keyboardVisible: false,
 
         isTouchDevice: false,
@@ -188,10 +195,31 @@ var UI;
         },
 
         addControlbarHandlers: function() {
+            document.getElementById("noVNC_control_bar")
+                .addEventListener('mousemove', UI.activateControlbar);
+            document.getElementById("noVNC_control_bar")
+                .addEventListener('mouseup', UI.activateControlbar);
+            document.getElementById("noVNC_control_bar")
+                .addEventListener('mousedown', UI.activateControlbar);
+            document.getElementById("noVNC_control_bar")
+                .addEventListener('keypress', UI.activateControlbar);
+
+            document.getElementById("noVNC_control_bar")
+                .addEventListener('mousedown', UI.keepControlbar);
+            document.getElementById("noVNC_control_bar")
+                .addEventListener('keypress', UI.keepControlbar);
+
             document.getElementById("noVNC_view_drag_button")
                 .addEventListener('click', UI.toggleViewDrag);
-            document.getElementById("noVNC_send_ctrl_alt_del_button")
-                .addEventListener('click', UI.sendCtrlAltDel);
+
+            document.getElementById("noVNC_control_bar_handle")
+                .addEventListener('mousedown', UI.controlbarHandleMouseDown);
+            document.getElementById("noVNC_control_bar_handle")
+                .addEventListener('mouseup', UI.controlbarHandleMouseUp);
+            document.getElementById("noVNC_control_bar_handle")
+                .addEventListener('mousemove', UI.dragControlbarHandle);
+            // resize events aren't available for elements
+            window.addEventListener('resize', UI.updateControlbarHandle);
         },
 
         addTouchSpecificHandlers: function() {
@@ -204,15 +232,36 @@ var UI;
             document.getElementById("noVNC_mouse_button4")
                 .addEventListener('click', function () { UI.setMouseButton(0); });
             document.getElementById("noVNC_keyboard_button")
-                .addEventListener('click', UI.showKeyboard);
+                .addEventListener('click', UI.toggleVirtualKeyboard);
 
             document.getElementById("noVNC_keyboardinput")
                 .addEventListener('input', UI.keyInput);
             document.getElementById("noVNC_keyboardinput")
-                .addEventListener('blur', UI.hideKeyboard);
+                .addEventListener('blur', UI.onblurVirtualKeyboard);
             document.getElementById("noVNC_keyboardinput")
                 .addEventListener('submit', function () { return false; });
 
+            document.getElementById("noVNC_control_bar")
+                .addEventListener('touchstart', UI.activateControlbar);
+            document.getElementById("noVNC_control_bar")
+                .addEventListener('touchmove', UI.activateControlbar);
+            document.getElementById("noVNC_control_bar")
+                .addEventListener('touchend', UI.activateControlbar);
+            document.getElementById("noVNC_control_bar")
+                .addEventListener('input', UI.activateControlbar);
+
+            document.getElementById("noVNC_control_bar")
+                .addEventListener('touchstart', UI.keepControlbar);
+            document.getElementById("noVNC_control_bar")
+                .addEventListener('input', UI.keepControlbar);
+
+            document.getElementById("noVNC_control_bar_handle")
+                .addEventListener('touchstart', UI.controlbarHandleMouseDown);
+            document.getElementById("noVNC_control_bar_handle")
+                .addEventListener('touchend', UI.controlbarHandleMouseUp);
+            document.getElementById("noVNC_control_bar_handle")
+                .addEventListener('touchmove', UI.dragControlbarHandle);
+
             window.addEventListener('load', UI.keyboardinputReset);
         },
 
@@ -227,6 +276,8 @@ var UI;
                 .addEventListener('click', UI.sendTab);
             document.getElementById("noVNC_send_esc_button")
                 .addEventListener('click', UI.sendEsc);
+            document.getElementById("noVNC_send_ctrl_alt_del_button")
+                .addEventListener('click', UI.sendCtrlAltDel);
         },
 
         addXvpHandlers: function() {
@@ -288,15 +339,18 @@ var 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,
                                   'onFBUComplete': UI.initialResize,
                                   'onFBResize': UI.updateViewDrag,
-                                  'onDesktopName': UI.updateDocumentTitle});
+                                  'onDesktopName': UI.updateDesktopName});
                 return true;
             } catch (exc) {
-                UI.updateState(null, 'fatal', null, 'Unable to create RFB client -- ' + exc);
+                UI.showStatus('Unable to create RFB client -- ' + exc, 'error');
                 return false;
             }
         },
@@ -307,35 +361,31 @@ var UI;
  *     VISUAL
  * ------v------*/
 
-        updateState: function(rfb, state, oldstate, msg) {
-            UI.rfb_state = state;
-
-            if (typeof(msg) !== 'undefined') {
-                switch (state) {
-                    case 'failed':
-                    case 'fatal':
-                        // zero means no timeout
-                        UI.showStatus(msg, 'error', 0);
-                        break;
-                    case 'normal':
-                        /* falls through */
-                    case 'disconnected':
-                    case 'loaded':
-                        UI.showStatus(msg, 'normal');
-                        break;
-                    case 'password':
-                        document.getElementById('noVNC_password_dlg')
-                            .classList.add('noVNC_open');
-                        setTimeout(function () {
-                                document.getElementById(('noVNC_password_input').focus());
-                            }, 100);
-
-                        UI.showStatus(msg, 'warn');
-                        break;
-                    default:
-                        UI.showStatus(msg, 'warn');
-                        break;
-                }
+        updateState: function(rfb, state, oldstate) {
+            switch (state) {
+                case 'connecting':
+                    UI.showStatus("Connecting");
+                    break;
+                case 'connected':
+                    UI.connected = true;
+                    if (rfb && rfb.get_encrypt()) {
+                        UI.showStatus("Connected (encrypted) to " +
+                                      UI.desktopName);
+                    } else {
+                        UI.showStatus("Connected (unencrypted) to " +
+                                      UI.desktopName);
+                    }
+                    break;
+                case 'disconnecting':
+                    UI.showStatus("Disconnecting");
+                    break;
+                case 'disconnected':
+                    UI.connected = false;
+                    UI.showStatus("Disconnected");
+                    break;
+                default:
+                    UI.showStatus("Invalid state", 'error');
+                    break;
             }
 
             UI.updateVisualState();
@@ -343,32 +393,34 @@ var UI;
 
         // Disable/enable controls depending on connection state
         updateVisualState: function() {
-            var connected = UI.rfb && UI.rfb_state === 'normal';
-
             //Util.Debug(">> updateVisualState");
-            document.getElementById('noVNC_setting_encrypt').disabled = connected;
-            document.getElementById('noVNC_setting_true_color').disabled = connected;
+            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 = connected;
+                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 = connected;
-            document.getElementById('noVNC_setting_shared').disabled = connected;
-            document.getElementById('noVNC_setting_view_only').disabled = connected;
-            document.getElementById('noVNC_setting_path').disabled = connected;
-            document.getElementById('noVNC_setting_repeaterID').disabled = connected;
+            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 (connected) {
+            if (UI.connected) {
                 document.documentElement.classList.add("noVNC_connected");
                 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.updateXvpButton(0);
+                UI.keepControlbar();
             }
 
             // State change disables viewport dragging.
@@ -379,18 +431,6 @@ var UI;
             document.getElementById('noVNC_password_dlg')
                 .classList.remove('noVNC_open');
 
-            switch (UI.rfb_state) {
-                case 'fatal':
-                case 'failed':
-                case 'disconnected':
-                    UI.openConnectPanel();
-                    break;
-                case 'loaded':
-                    break;
-                default:
-                    break;
-            }
-
             //Util.Debug("<< updateVisualState");
         },
 
@@ -430,8 +470,8 @@ var UI;
                 time = 1500;
             }
 
-            // A specified time of zero means no timeout
-            if (time != 0) {
+            // Error messages do not timeout
+            if (status_type !== 'error') {
                 UI.statusTimeout = window.setTimeout(UI.hideStatus, time);
             }
         },
@@ -441,6 +481,136 @@ 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
+            // bar in order to avoid creating new a stacking group
+            document.getElementById('noVNC_control_bar_anchor')
+                .classList.remove("noVNC_idle");
+            UI.idleControlbarTimeout = window.setTimeout(UI.idleControlbar, 2000);
+        },
+
+        idleControlbar: function() {
+            document.getElementById('noVNC_control_bar_anchor')
+                .classList.add("noVNC_idle");
+        },
+
+        keepControlbar: function() {
+            clearTimeout(UI.closeControlbarTimeout);
+        },
+
+        openControlbar: function() {
+            document.getElementById('noVNC_control_bar')
+                .classList.add("noVNC_open");
+        },
+
+        closeControlbar: function() {
+            UI.closeAllPanels();
+            document.getElementById('noVNC_control_bar')
+                .classList.remove("noVNC_open");
+        },
+
+        toggleControlbar: function() {
+            if (document.getElementById('noVNC_control_bar')
+                .classList.contains("noVNC_open")) {
+                UI.closeControlbar();
+            } else {
+                UI.openControlbar();
+            }
+        },
+
+        dragControlbarHandle: function (e) {
+            if (!UI.controlbarGrabbed) return;
+
+            var ptr = Util.getPointerEvent(e);
+
+            if (!UI.controlbarDrag) {
+                // The goal is to trigger on a certain physical width, the
+                // devicePixelRatio brings us a bit closer but is not optimal.
+                var dragThreshold = 10 * (window.devicePixelRatio || 1);
+                var dragDistance = Math.abs(ptr.clientY - UI.controlbarMouseDownClientY);
+
+                if (dragDistance < dragThreshold) return;
+
+                UI.controlbarDrag = true;
+            }
+
+            var eventY = ptr.clientY - UI.controlbarMouseDownOffsetY;
+
+            UI.moveControlbarHandle(eventY);
+
+            e.preventDefault();
+            e.stopPropagation();
+        },
+
+        // Move the handle but don't allow any position outside the bounds
+        moveControlbarHandle: function (posY) {
+            var handle = document.getElementById("noVNC_control_bar_handle");
+            var handleHeight = Util.getPosition(handle).height;
+            var controlbar = document.getElementById("noVNC_control_bar");
+            var controlbarBounds = Util.getPosition(controlbar);
+            var controlbarTop = controlbarBounds.y;
+            var controlbarBottom = controlbarBounds.y + controlbarBounds.height;
+            var margin = 10;
+
+            var viewportY = posY;
+
+            // Refuse coordinates outside the control bar
+            if (viewportY < controlbarTop + margin) {
+                viewportY = controlbarTop + margin;
+            } else if (viewportY > controlbarBottom - handleHeight - margin) {
+                viewportY = controlbarBottom - handleHeight - margin;
+            }
+
+            // Corner case: control bar too small for stable position
+            if (controlbarBounds.height < (handleHeight + margin * 2)) {
+                viewportY = controlbarTop + (controlbarBounds.height - handleHeight) / 2;
+            }
+
+            var relativeY = viewportY - controlbarTop;
+            handle.style.transform = "translateY(" + relativeY + "px)";
+        },
+
+        updateControlbarHandle: function () {
+            var handle = document.getElementById("noVNC_control_bar_handle");
+            var pos = Util.getPosition(handle);
+            UI.moveControlbarHandle(pos.y);
+        },
+
+        controlbarHandleMouseUp: function(e) {
+            if ((e.type == "mouseup") && (e.button != 0)) return;
+
+            // mouseup and mousedown on the same place toggles the controlbar
+            if (UI.controlbarGrabbed && !UI.controlbarDrag) {
+                UI.toggleControlbar();
+                e.preventDefault();
+                e.stopPropagation();
+            }
+            UI.controlbarGrabbed = false;
+        },
+
+        controlbarHandleMouseDown: function(e) {
+            if ((e.type == "mousedown") && (e.button != 0)) return;
+
+            var ptr = Util.getPointerEvent(e);
+
+            var handle = document.getElementById("noVNC_control_bar_handle");
+            var bounds = handle.getBoundingClientRect();
+
+            WebUtil.setCapture(handle);
+            UI.controlbarGrabbed = true;
+            UI.controlbarDrag = false;
+
+            UI.controlbarMouseDownClientY = ptr.clientY;
+            UI.controlbarMouseDownOffsetY = ptr.clientY - bounds.top;
+            e.preventDefault();
+            e.stopPropagation();
+        },
+
 /* ------^-------
  *    /VISUAL
  * ==============
@@ -579,6 +749,7 @@ var UI;
 
         openSettingsPanel: function() {
             UI.closeAllPanels();
+            UI.openControlbar();
 
             UI.updateSetting('encrypt');
             UI.updateSetting('true_color');
@@ -631,6 +802,7 @@ var UI;
 
         openXvpPanel: function() {
             UI.closeAllPanels();
+            UI.openControlbar();
 
             document.getElementById('noVNC_xvp')
                 .classList.add("noVNC_open");
@@ -675,6 +847,7 @@ var UI;
 
         openClipboardPanel: function() {
             UI.closeAllPanels();
+            UI.openControlbar();
 
             document.getElementById('noVNC_clipboard')
                 .classList.add("noVNC_open");
@@ -724,6 +897,7 @@ var UI;
 
         openConnectPanel: function() {
             UI.closeAllPanels();
+            UI.openControlbar();
 
             document.getElementById('noVNC_connect_controls')
                 .classList.add("noVNC_open");
@@ -755,8 +929,6 @@ var UI;
         },
 
         connect: function() {
-            UI.closeAllPanels();
-
             var host = document.getElementById('noVNC_setting_host').value;
             var port = document.getElementById('noVNC_setting_port').value;
             var password = document.getElementById('noVNC_setting_password').value;
@@ -769,11 +941,14 @@ var UI;
             }
 
             if ((!host) || (!port)) {
-                throw new Error("Must set host and port");
+                UI.showStatus("Must set host and port", 'error');
+                return;
             }
 
             if (!UI.initRFB()) return;
 
+            UI.closeAllPanels();
+
             UI.rfb.set_encrypt(UI.getSetting('encrypt'));
             UI.rfb.set_true_color(UI.getSetting('true_color'));
             UI.rfb.set_local_cursor(UI.getSetting('cursor'));
@@ -794,6 +969,34 @@ var UI;
             // Don't display the connection settings until we're actually disconnected
         },
 
+        disconnectFinished: function (rfb, reason) {
+            if (typeof reason !== 'undefined') {
+                UI.showStatus(reason, 'error');
+            }
+            UI.openConnectPanel();
+        },
+
+/* ------^-------
+ *  /CONNECTION
+ * ==============
+ *   PASSWORD
+ * ------v------*/
+
+        passwordRequired: function(rfb, msg) {
+
+            document.getElementById('noVNC_password_dlg')
+                .classList.add('noVNC_open');
+
+            setTimeout(function () {
+                    document.getElementById('noVNC_password_input').focus();
+                }, 100);
+
+            if (typeof msg === 'undefined') {
+                msg = "Password is required";
+            }
+            UI.showStatus(msg, "warning");
+        },
+
         setPassword: function() {
             UI.rfb.sendPassword(document.getElementById('noVNC_password_input').value);
             document.getElementById('noVNC_password_dlg')
@@ -802,7 +1005,7 @@ var UI;
         },
 
 /* ------^-------
- *  /CONNECTION
+ *  /PASSWORD
  * ==============
  *   FULLSCREEN
  * ------v------*/
@@ -861,7 +1064,7 @@ var UI;
 
             var screen = UI.screenSize();
 
-            if (screen && UI.rfb_state === 'normal' && UI.rfb.get_display()) {
+            if (screen && UI.connected && UI.rfb.get_display()) {
 
                 var display = UI.rfb.get_display();
                 var resizeMode = UI.getSetting('resize');
@@ -944,9 +1147,7 @@ var UI;
         // Update parameters that depend on the clip setting
         updateViewClip: function() {
             var display;
-            if (!UI.rfb) {
-                return;
-            }
+            if (!UI.rfb) return;
 
             var display = UI.rfb.get_display();
             var cur_clip = display.get_viewport();
@@ -988,7 +1189,6 @@ var UI;
         // Handle special cases where clipping is forced on/off or locked
         enableDisableViewClip: function() {
             var resizeSetting = document.getElementById('noVNC_setting_resize');
-            var connected = UI.rfb && UI.rfb_state === 'normal';
 
             if (UI.isSafari) {
                 // Safari auto-hides the scrollbars which makes them
@@ -1010,9 +1210,11 @@ var UI;
             } else if (document.body.msRequestFullscreen && UI.rememberedClip !== null) {
                 // Restore view clip to what it was before fullscreen on IE
                 UI.setViewClip(UI.rememberedClipSetting);
-                document.getElementById('noVNC_setting_clip').disabled = connected || UI.isTouchDevice;
+                document.getElementById('noVNC_setting_clip').disabled =
+                    UI.connected || UI.isTouchDevice;
             } else {
-                document.getElementById('noVNC_setting_clip').disabled = connected || UI.isTouchDevice;
+                document.getElementById('noVNC_setting_clip').disabled =
+                    UI.connected || UI.isTouchDevice;
                 if (UI.isTouchDevice) {
                     UI.setViewClip(true);
                 }
@@ -1044,7 +1246,7 @@ var UI;
         updateViewDrag: function() {
             var clipping = false;
 
-            if (UI.rfb_state !== 'normal') return;
+            if (!UI.connected) return;
 
             // Check if viewport drag is possible. It is only possible
             // if the remote display is clipping the client display.
@@ -1096,45 +1298,60 @@ var UI;
  *    KEYBOARD
  * ------v------*/
 
-        // On touch devices, show the OS keyboard
-        showKeyboard: function() {
-            var kbi = document.getElementById('noVNC_keyboardinput');
-            var skb = document.getElementById('noVNC_keyboard_button');
-            var l = kbi.value.length;
-            if(UI.keyboardVisible === false) {
-                kbi.focus();
-                try { kbi.setSelectionRange(l, l); } // Move the caret to the end
-                catch (err) {} // setSelectionRange is undefined in Google Chrome
-                UI.keyboardVisible = true;
-                skb.classList.add("noVNC_selected");
-            } else if(UI.keyboardVisible === true) {
-                kbi.blur();
-                skb.classList.remove("noVNC_selected");
-                UI.keyboardVisible = false;
+        showVirtualKeyboard: function() {
+            if (!UI.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 {
+                var l = input.value.length;
+                // Move the caret to the end
+                input.setSelectionRange(l, l);
+            } catch (err) {} // setSelectionRange is undefined in Google Chrome
+        },
+
+        hideVirtualKeyboard: function() {
+            if (!UI.isTouchDevice) return;
+
+            var input = document.getElementById('noVNC_keyboardinput');
+
+            if (document.activeElement != input) return;
+
+            input.blur();
+        },
+
+        toggleVirtualKeyboard: function () {
+            if (UI.keyboardVisible) {
+                UI.hideVirtualKeyboard();
+            } else {
+                UI.showVirtualKeyboard();
             }
         },
 
-        hideKeyboard: function() {
-            document.getElementById('noVNC_keyboard_button')
-                .classList.remove("noVNC_selected");
+        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);
         },
 
         keepKeyboard: function() {
             clearTimeout(UI.hideKeyboardTimeout);
             if(UI.keyboardVisible === true) {
-                document.getElementById('noVNC_keyboardinput').focus();
-                document.getElementById('noVNC_keyboard_button')
-                    .classList.add("noVNC_selected");
+                UI.showVirtualKeyboard();
             } else if(UI.keyboardVisible === false) {
-                document.getElementById('noVNC_keyboardinput').blur();
-                document.getElementById('noVNC_keyboard_button')
-                    .classList.remove("noVNC_selected");
+                UI.hideVirtualKeyboard();
             }
         },
 
@@ -1215,8 +1432,15 @@ var UI;
             }
         },
 
+/* ------^-------
+ *   /KEYBOARD
+ * ==============
+ *   EXTRA KEYS
+ * ------v------*/
+
         openExtraKeys: function() {
             UI.closeAllPanels();
+            UI.openControlbar();
 
             document.getElementById('noVNC_modifiers')
                 .classList.add("noVNC_open");
@@ -1276,11 +1500,12 @@ var UI;
         },
 
         sendCtrlAltDel: function() {
+            UI.keepKeyboard();
             UI.rfb.sendCtrlAltDel();
         },
 
 /* ------^-------
- *   /KEYBOARD
+ *   /EXTRA KEYS
  * ==============
  *     MISC
  * ------v------*/
@@ -1315,8 +1540,9 @@ var UI;
             UI.rfb.get_mouse().set_focused(true);
         },
 
-        // Display the desktop name in the document title
-        updateDocumentTitle: function(rfb, name) {
+        updateDesktopName: function(rfb, name) {
+            UI.desktopName = name;
+            // Display the desktop name in the document title
             document.title = name + " - noVNC";
         },